From fcb6fe378114145aed5a728e34543e80668a5d78 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 25 Sep 2024 17:20:00 +0200 Subject: [PATCH 1/2] Update to platformativ 2 Signed-off-by: Matteo Collina --- config.d.ts | 32 +- index.js | 9 +- lib/schema.js | 3 +- package.json | 13 +- schema.json | 2534 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2580 insertions(+), 11 deletions(-) create mode 100644 schema.json diff --git a/config.d.ts b/config.d.ts index a7667e4..a7548f5 100644 --- a/config.d.ts +++ b/config.d.ts @@ -48,7 +48,13 @@ export interface PlatformaticPgHooksConfig { logger?: | boolean | { - level?: string; + level: ( + | ("fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent") + | { + [k: string]: unknown; + } + ) & + string; transport?: | { target?: string; @@ -78,6 +84,9 @@ export interface PlatformaticPgHooksConfig { }; [k: string]: unknown; }; + loggerInstance?: { + [k: string]: unknown; + }; serializerOpts?: { schema?: { [k: string]: unknown; @@ -97,7 +106,9 @@ export interface PlatformaticPgHooksConfig { requestIdLogLabel?: string; jsonShorthand?: boolean; trustProxy?: boolean | string | string[] | number; + http2?: boolean; https?: { + allowHTTP1?: boolean; key: | string | { @@ -226,6 +237,10 @@ export interface PlatformaticPgHooksConfig { [k: string]: boolean; }; }; + ignoreRoutes?: { + method: string; + path: string; + }[]; enabled?: boolean | string; /** * Base URL for generated Platformatic DB routes @@ -261,6 +276,10 @@ export interface PlatformaticPgHooksConfig { * The user metadata key to store user roles */ roleKey?: string; + /** + * The user metadata path to store user roles + */ + rolePath?: string; /** * The role name for anonymous users */ @@ -335,11 +354,17 @@ export interface PlatformaticPgHooksConfig { port?: number | string; hostname?: string; endpoint?: string; - server?: "own" | "parent"; + server?: "own" | "parent" | "hide"; + defaultMetrics?: { + enabled: boolean; + }; auth?: { username: string; password: string; }; + labels?: { + [k: string]: string; + }; }; plugins?: { [k: string]: unknown; @@ -352,6 +377,9 @@ export interface PlatformaticPgHooksConfig { path?: string; schema?: string; url?: string; + fullResponse?: boolean; + fullRequest?: boolean; + validateResponse?: boolean; }[]; watch?: | { diff --git a/index.js b/index.js index 0fc8569..0156094 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ 'use strict' -const { platformaticDB } = require('@platformatic/db') -const { buildServer } = require('@platformatic/service') +const { platformaticDB, DbStackable } = require('@platformatic/db') +const { buildServer, buildStackable } = require('@platformatic/service') const { schema } = require('./lib/schema') const { Generator } = require('./lib/generator') const { join } = require('path') @@ -40,6 +40,10 @@ function _buildServer (opts) { return buildServer(opts, stackable) } +async function buildPgHooksStackable (opts) { + return buildStackable(opts, stackable, DbStackable) +} + // break Fastify encapsulation stackable[Symbol.for('skip-override')] = true @@ -47,3 +51,4 @@ module.exports = stackable module.exports.schema = schema module.exports.Generator = Generator module.exports.buildServer = _buildServer +module.exports.buildStackable = buildPgHooksStackable diff --git a/lib/schema.js b/lib/schema.js index 30cce12..b1a5eec 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -1,10 +1,11 @@ 'use strict' const { schema } = require('@platformatic/db') +const { version } = require('../package.json') const platformaticPgHooksSchema = { ...schema.schema, - $id: 'platformatic-pg-hooks', + $id: `https://schemas.platformatic.dev/@platformatic/pg-hooks/${version}.json`, title: 'Platformatic pg-hooks Config', properties: { ...schema.properties, diff --git a/package.json b/package.json index 2bde5b5..be71cd0 100644 --- a/package.json +++ b/package.json @@ -7,24 +7,25 @@ "start-platformatic-pg-hooks": "./cli/start.js" }, "scripts": { - "build:config": "node lib/schema.js | json2ts > config.d.ts", + "build:config": "node lib/schema.js > schema.json && json2ts > config.d.ts < schema.json", + "build": "npm run build:config", "test": "standard && borp --concurrency 1" }, "devDependencies": { "@matteo.collina/tspl": "^0.1.1", "borp": "^0.17.0", - "fastify": "^4.26.0", - "platformatic": "^1.23.0", + "fastify": "^5.0.0", + "platformatic": "^2.0.0", "standard": "^17.1.0" }, "dependencies": { - "@platformatic/config": "^1.21.1", - "@platformatic/db": "^1.21.1", + "@platformatic/config": "^2.1.0", + "@platformatic/db": "^2.1.0", "cron-parser": "^4.9.0", "json-schema-to-typescript": "^15.0.0", "minimist": "^1.2.0" }, "engines": { - "node": "^18.8.0 || >=20.6.0" + "node": ">= 20.16.0" } } diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..73ad2e3 --- /dev/null +++ b/schema.json @@ -0,0 +1,2534 @@ +{ + "$id": "https://schemas.platformatic.dev/@platformatic/pg-hooks/0.2.0.json", + "title": "Platformatic pg-hooks Config", + "properties": { + "server": { + "type": "object", + "properties": { + "hostname": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "pluginTimeout": { + "type": "integer", + "default": 60000 + }, + "healthCheck": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "interval": { + "type": "integer" + } + }, + "additionalProperties": true + } + ] + }, + "ignoreTrailingSlash": { + "type": "boolean" + }, + "ignoreDuplicateSlashes": { + "type": "boolean" + }, + "connectionTimeout": { + "type": "integer" + }, + "keepAliveTimeout": { + "type": "integer", + "default": 5000 + }, + "maxRequestsPerSocket": { + "type": "integer" + }, + "forceCloseConnections": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^idle$" + } + ] + }, + "requestTimeout": { + "type": "integer" + }, + "bodyLimit": { + "type": "integer" + }, + "maxParamLength": { + "type": "integer" + }, + "disableRequestLogging": { + "type": "boolean" + }, + "exposeHeadRoutes": { + "type": "boolean" + }, + "logger": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "level": { + "type": "string", + "default": "info", + "oneOf": [ + { + "enum": [ + "fatal", + "error", + "warn", + "info", + "debug", + "trace", + "silent" + ] + }, + { + "pattern": "^\\{.+\\}$" + } + ] + }, + "transport": { + "anyOf": [ + { + "type": "object", + "properties": { + "target": { + "type": "string", + "resolveModule": true + }, + "options": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "targets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "target": { + "type": "string", + "resolveModule": true + }, + "options": { + "type": "object" + }, + "level": { + "type": "string" + }, + "additionalProperties": false + } + } + }, + "options": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "pipeline": { + "type": "object", + "properties": { + "target": { + "type": "string", + "resolveModule": true + }, + "options": { + "type": "object" + } + }, + "additionalProperties": false + } + }, + "required": [ + "level" + ], + "default": {}, + "additionalProperties": true + } + ] + }, + "loggerInstance": { + "type": "object" + }, + "serializerOpts": { + "type": "object", + "properties": { + "schema": { + "type": "object" + }, + "ajv": { + "type": "object" + }, + "rounding": { + "type": "string", + "enum": [ + "floor", + "ceil", + "round", + "trunc" + ], + "default": "trunc" + }, + "debugMode": { + "type": "boolean" + }, + "mode": { + "type": "string", + "enum": [ + "debug", + "standalone" + ] + }, + "largeArraySize": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "default": 20000 + }, + "largeArrayMechanism": { + "type": "string", + "enum": [ + "default", + "json-stringify" + ], + "default": "default" + } + } + }, + "caseSensitive": { + "type": "boolean" + }, + "requestIdHeader": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean", + "const": false + } + ] + }, + "requestIdLogLabel": { + "type": "string" + }, + "jsonShorthand": { + "type": "boolean" + }, + "trustProxy": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "integer" + } + ] + }, + "http2": { + "type": "boolean" + }, + "https": { + "type": "object", + "properties": { + "allowHTTP1": { + "type": "boolean" + }, + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + "resolvePath": true + } + }, + "additionalProperties": false + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + "resolvePath": true + } + }, + "additionalProperties": false + } + ] + } + } + ] + }, + "cert": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + "resolvePath": true + } + }, + "additionalProperties": false + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + "resolvePath": true + } + }, + "additionalProperties": false + } + ] + } + } + ] + }, + "requestCert": { + "type": "boolean" + }, + "rejectUnauthorized": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "key", + "cert" + ] + }, + "cors": { + "type": "object", + "$comment": "See https://github.com/fastify/fastify-cors", + "properties": { + "origin": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "regexp": { + "type": "string" + } + }, + "required": [ + "regexp" + ] + } + ] + } + }, + { + "type": "object", + "properties": { + "regexp": { + "type": "string" + } + }, + "required": [ + "regexp" + ] + } + ] + }, + "methods": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedHeaders": { + "type": "string", + "description": "Comma separated string of allowed headers." + }, + "exposedHeaders": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Comma separated string of exposed headers." + } + ] + }, + "credentials": { + "type": "boolean" + }, + "maxAge": { + "type": "integer" + }, + "preflightContinue": { + "type": "boolean", + "default": false + }, + "optionsSuccessStatus": { + "type": "integer", + "default": 204 + }, + "preflight": { + "type": "boolean", + "default": true + }, + "strictPreflight": { + "type": "boolean", + "default": true + }, + "hideOptionsRoute": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "db": { + "type": "object", + "properties": { + "connectionString": { + "type": "string" + }, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "schemalock": { + "oneOf": [ + { + "type": "boolean", + "default": false + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + "resolvePath": true + } + } + } + ] + }, + "poolSize": { + "type": "integer" + }, + "idleTimeoutMilliseconds": { + "type": "integer" + }, + "queueTimeoutMilliseconds": { + "type": "integer" + }, + "acquireLockTimeoutMilliseconds": { + "type": "integer" + }, + "autoTimestamp": { + "oneOf": [ + { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "default": "created_at" + }, + "updatedAt": { + "type": "string", + "default": "updated_at" + } + } + }, + { + "type": "boolean" + } + ] + }, + "graphql": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "graphiql": { + "type": "boolean" + }, + "include": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "ignore": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + ] + } + }, + "subscriptionIgnore": { + "type": "array", + "items": { + "type": "string" + } + }, + "schema": { + "type": "string" + }, + "schemaPath": { + "type": "string", + "resolvePath": true + }, + "enabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + } + } + } + ] + }, + "openapi": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "info": { + "$ref": "#/$defs/info" + }, + "jsonSchemaDialect": { + "type": "string", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + }, + "default": [ + { + "url": "/" + } + ] + }, + "paths": { + "$ref": "#/$defs/paths" + }, + "webhooks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "components": { + "$ref": "#/$defs/components" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/$defs/tag" + } + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + }, + "swaggerPrefix": { + "type": "string", + "description": "Base URL for the OpenAPI Swagger Documentation" + }, + "path": { + "type": "string", + "description": "Path to an OpenAPI spec file", + "resolvePath": true + }, + "allowPrimaryKeysInInput": { + "type": "boolean", + "default": true + }, + "include": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "ignore": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + ] + } + }, + "ignoreRoutes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "method", + "path" + ], + "additionalProperties": false + } + }, + "enabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "prefix": { + "type": "string", + "description": "Base URL for generated Platformatic DB routes" + } + }, + "additionalProperties": false + } + ] + }, + "include": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "ignore": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "limit": { + "type": "object", + "properties": { + "default": { + "type": "integer", + "default": 10 + }, + "max": { + "type": "integer", + "default": 100 + } + } + }, + "events": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "connectionString": { + "type": "string" + }, + "enabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + } + }, + "additionalProperties": false + } + ] + }, + "cache": { + "type": "boolean" + } + }, + "required": [ + "connectionString" + ] + }, + "authorization": { + "type": "object", + "properties": { + "adminSecret": { + "type": "string", + "description": "The password should be used to access routes under /_admin prefix and for admin access to REST and GraphQL endpoints with X-PLATFORMATIC-ADMIN-SECRET header." + }, + "roleKey": { + "type": "string", + "description": "The user metadata key to store user roles", + "default": "X-PLATFORMATIC-ROLE" + }, + "rolePath": { + "type": "string", + "description": "The user metadata path to store user roles" + }, + "anonymousRole": { + "type": "string", + "description": "The role name for anonymous users", + "default": "anonymous" + }, + "jwt": { + "type": "object", + "additionalProperties": true, + "properties": { + "secret": { + "oneOf": [ + { + "type": "string", + "description": "the shared secret for JWT" + }, + { + "type": "object", + "description": "the JWT secret configuration (see: https://github.com/fastify/fastify-jwt#secret-required)", + "additionalProperties": true + } + ] + }, + "namespace": { + "type": "string", + "description": "the namespace for JWT custom claims" + }, + "jwks": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "additionalProperties": true + } + ] + } + } + }, + "webhook": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "the webhook url" + } + }, + "additionalProperties": false + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "entity": { + "type": "string", + "description": "the DB entity type to which the rule applies" + }, + "role": { + "type": "string", + "description": "the role name to match the rule" + }, + "defaults": { + "type": "object", + "description": "defaults for entity creation", + "additionalProperties": { + "type": "string" + } + }, + "find": { + "$ref": "#/$defs/crud-operation-auth" + }, + "save": { + "$ref": "#/$defs/crud-operation-auth" + }, + "delete": { + "$ref": "#/$defs/crud-operation-auth" + } + }, + "required": [ + "role" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "entities": { + "type": "array", + "description": "the DB entity types to which the rule applies", + "items": { + "type": "string" + } + }, + "role": { + "type": "string", + "description": "the role name to match the rule" + }, + "defaults": { + "type": "object", + "description": "defaults for entity creation", + "additionalProperties": { + "type": "string" + } + }, + "find": { + "$ref": "#/$defs/crud-operation-auth" + }, + "save": { + "$ref": "#/$defs/crud-operation-auth" + }, + "delete": { + "$ref": "#/$defs/crud-operation-auth" + } + }, + "required": [ + "role" + ], + "additionalProperties": false + } + ] + } + } + }, + "additionalProperties": false + }, + "metrics": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "hostname": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "server": { + "type": "string", + "enum": [ + "own", + "parent", + "hide" + ] + }, + "defaultMetrics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false + }, + "auth": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "username", + "password" + ] + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "plugins": { + "type": "object", + "properties": { + "packages": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "options": { + "type": "object", + "additionalProperties": true + } + }, + "required": [ + "name" + ] + } + ] + } + }, + "paths": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "resolvePath": true + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + "resolvePath": true + }, + "encapsulate": { + "type": "boolean", + "default": true + }, + "maxDepth": { + "type": "integer" + }, + "autoHooks": { + "type": "boolean" + }, + "autoHooksPattern": { + "type": "string" + }, + "cascadeHooks": { + "type": "boolean" + }, + "overwriteHooks": { + "type": "boolean" + }, + "routeParams": { + "type": "boolean" + }, + "forceESM": { + "type": "boolean" + }, + "ignoreFilter": { + "type": "string" + }, + "matchFilter": { + "type": "string" + }, + "ignorePattern": { + "type": "string" + }, + "scriptPattern": { + "type": "string" + }, + "indexPattern": { + "type": "string" + }, + "options": { + "type": "object", + "additionalProperties": true + } + } + } + ] + } + }, + "typescript": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "tsConfig": { + "type": "string", + "resolvePath": true + }, + "outDir": { + "type": "string", + "resolvePath": true + }, + "flags": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "boolean" + }, + { + "type": "string" + } + ] + } + }, + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "paths" + ] + }, + { + "required": [ + "packages" + ] + } + ] + }, + "telemetry": { + "$id": "/OpenTelemetry", + "type": "object", + "properties": { + "serviceName": { + "type": "string", + "description": "The name of the service. Defaults to the folder name if not specified." + }, + "version": { + "type": "string", + "description": "The version of the service (optional)" + }, + "skip": { + "type": "array", + "description": "An array of paths to skip when creating spans. Useful for health checks and other endpoints that do not need to be traced.", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path to skip. Can be a string or a regex." + }, + "method": { + "description": "HTTP method to skip", + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "DELETE", + "PATCH", + "HEAD", + "OPTIONS" + ] + } + } + } + }, + "exporter": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "console", + "otlp", + "zipkin", + "memory" + ], + "default": "console" + }, + "options": { + "type": "object", + "description": "Options for the exporter. These are passed directly to the exporter.", + "properties": { + "url": { + "type": "string", + "description": "The URL to send the traces to. Not used for console or memory exporters." + }, + "headers": { + "type": "object", + "description": "Headers to send to the exporter. Not used for console or memory exporters." + } + } + }, + "additionalProperties": false + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "console", + "otlp", + "zipkin", + "memory" + ], + "default": "console" + }, + "options": { + "type": "object", + "description": "Options for the exporter. These are passed directly to the exporter.", + "properties": { + "url": { + "type": "string", + "description": "The URL to send the traces to. Not used for console or memory exporters." + }, + "headers": { + "type": "object", + "description": "Headers to send to the exporter. Not used for console or memory exporters." + } + } + }, + "additionalProperties": false + } + } + ] + } + }, + "required": [ + "serviceName" + ], + "additionalProperties": false + }, + "clients": { + "type": "array", + "items": { + "type": "object", + "properties": { + "serviceId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "openapi", + "graphql" + ] + }, + "path": { + "type": "string", + "resolvePath": true + }, + "schema": { + "type": "string", + "resolvePath": true + }, + "url": { + "type": "string" + }, + "fullResponse": { + "type": "boolean" + }, + "fullRequest": { + "type": "boolean" + }, + "validateResponse": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "watch": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "allow": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "nullable": true, + "default": null + }, + "ignore": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "default": null + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "$schema": { + "type": "string" + }, + "module": { + "type": "string" + }, + "hooks": { + "type": "object", + "properties": { + "lock": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "leaderPoll": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ] + } + } + } + }, + "$defs": { + "info": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string" + }, + "contact": { + "$ref": "#/$defs/contact" + }, + "license": { + "$ref": "#/$defs/license" + }, + "version": { + "type": "string" + } + }, + "required": [ + "title", + "version" + ], + "$ref": "#/$defs/specification-extensions" + }, + "contact": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "$ref": "#/$defs/specification-extensions" + }, + "license": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions" + }, + "server": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object", + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server-variable" + } + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions" + }, + "server-variable": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object", + "type": "object", + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "default" + ], + "$ref": "#/$defs/specification-extensions" + }, + "components": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object", + "type": "object", + "properties": { + "schemas": { + "type": "object" + }, + "responses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/response-or-reference" + } + }, + "parameters": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + }, + "requestBodies": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/request-body-or-reference" + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "securitySchemes": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/security-scheme-or-reference" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "pathItems": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + } + }, + "$ref": "#/$defs/specification-extensions" + }, + "paths": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object", + "type": "object", + "patternProperties": { + "^/": { + "$ref": "#/$defs/path-item" + } + }, + "$ref": "#/$defs/specification-extensions" + }, + "path-item": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "get": { + "$ref": "#/$defs/operation" + }, + "put": { + "$ref": "#/$defs/operation" + }, + "post": { + "$ref": "#/$defs/operation" + }, + "delete": { + "$ref": "#/$defs/operation" + }, + "options": { + "$ref": "#/$defs/operation" + }, + "head": { + "$ref": "#/$defs/operation" + }, + "patch": { + "$ref": "#/$defs/operation" + }, + "trace": { + "$ref": "#/$defs/operation" + } + }, + "$ref": "#/$defs/specification-extensions" + }, + "path-item-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/path-item" + } + }, + "operation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object", + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "requestBody": { + "$ref": "#/$defs/request-body-or-reference" + }, + "responses": { + "$ref": "#/$defs/responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + } + }, + "$ref": "#/$defs/specification-extensions" + }, + "external-documentation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions" + }, + "parameter": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "path", + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "content": { + "type": "object", + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "required": [ + "name", + "in" + ], + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "if": { + "type": "object", + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "type": "object", + "properties": { + "allowEmptyValue": { + "default": false, + "type": "boolean" + } + } + }, + "$ref": "#/$defs/specification-extensions" + }, + "parameter-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/parameter" + } + }, + "request-body": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "content": { + "$ref": "#/$defs/content" + }, + "required": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ], + "$ref": "#/$defs/specification-extensions" + }, + "request-body-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/request-body" + } + }, + "content": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + } + }, + "media-type": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object", + "type": "object", + "properties": { + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/encoding" + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/examples" + } + ] + }, + "encoding": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object", + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/encoding/$defs/explode-default" + } + ], + "$defs": { + "explode-default": { + "if": { + "type": "object", + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "type": "object", + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "type": "object", + "properties": { + "explode": { + "default": false + } + } + } + } + } + }, + "responses": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/response-or-reference" + }, + "minProperties": 1, + "$ref": "#/$defs/specification-extensions" + }, + "response": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "content": { + "$ref": "#/$defs/content" + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + } + }, + "required": [ + "description" + ], + "$ref": "#/$defs/specification-extensions" + }, + "response-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/response" + } + }, + "callbacks": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object", + "type": "object", + "$ref": "#/$defs/specification-extensions", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "callbacks-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/callbacks" + } + }, + "example": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": true, + "externalValue": { + "type": "string" + } + }, + "not": { + "required": [ + "value", + "externalValue" + ] + }, + "$ref": "#/$defs/specification-extensions" + }, + "example-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/example" + } + }, + "link": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object", + "type": "object", + "properties": { + "operationRef": { + "type": "string" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "$ref": "#/$defs/map-of-strings" + }, + "requestBody": true, + "description": { + "type": "string" + }, + "body": { + "$ref": "#/$defs/server" + } + }, + "oneOf": [ + { + "required": [ + "operationRef" + ] + }, + { + "required": [ + "operationId" + ] + } + ], + "$ref": "#/$defs/specification-extensions" + }, + "link-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/link" + } + }, + "header": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "content": { + "type": "object", + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "$ref": "#/$defs/specification-extensions" + }, + "header-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/header" + } + }, + "tag": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions" + }, + "reference": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object", + "type": "object", + "properties": { + "$ref": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "schema": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object", + "type": [ + "object", + "boolean" + ] + }, + "security-scheme": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object", + "type": "object", + "properties": { + "type": { + "enum": [ + "apiKey", + "http", + "mutualTLS", + "oauth2", + "openIdConnect" + ] + }, + "description": { + "type": "string" + } + }, + "required": [ + "type" + ], + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-apikey" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http-bearer" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oauth2" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oidc" + } + ], + "$defs": { + "type-apikey": { + "if": { + "type": "object", + "properties": { + "type": { + "const": "apiKey" + } + }, + "required": [ + "type" + ] + }, + "then": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "cookie" + ] + } + }, + "required": [ + "name", + "in" + ] + } + }, + "type-http": { + "if": { + "type": "object", + "properties": { + "type": { + "const": "http" + } + }, + "required": [ + "type" + ] + }, + "then": { + "type": "object", + "properties": { + "scheme": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + } + }, + "type-http-bearer": { + "if": { + "type": "object", + "properties": { + "type": { + "const": "http" + }, + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + }, + "required": [ + "type", + "scheme" + ] + }, + "then": { + "type": "object", + "properties": { + "bearerFormat": { + "type": "string" + } + } + } + }, + "type-oauth2": { + "if": { + "type": "object", + "properties": { + "type": { + "const": "oauth2" + } + }, + "required": [ + "type" + ] + }, + "then": { + "type": "object", + "properties": { + "flows": { + "$ref": "#/$defs/oauth-flows" + } + }, + "required": [ + "flows" + ] + } + }, + "type-oidc": { + "if": { + "type": "object", + "properties": { + "type": { + "const": "openIdConnect" + } + }, + "required": [ + "type" + ] + }, + "then": { + "type": "object", + "properties": { + "openIdConnectUrl": { + "type": "string" + } + }, + "required": [ + "openIdConnectUrl" + ] + } + } + } + }, + "security-scheme-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/security-scheme" + } + }, + "oauth-flows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/$defs/oauth-flows/$defs/implicit" + }, + "password": { + "$ref": "#/$defs/oauth-flows/$defs/password" + }, + "clientCredentials": { + "$ref": "#/$defs/oauth-flows/$defs/client-credentials" + }, + "authorizationCode": { + "$ref": "#/$defs/oauth-flows/$defs/authorization-code" + } + }, + "$ref": "#/$defs/specification-extensions", + "$defs": { + "implicit": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions" + }, + "password": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions" + }, + "client-credentials": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions" + }, + "authorization-code": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + }, + "refreshUrl": { + "type": "string" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions" + } + } + }, + "security-requirement": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "specification-extensions": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions", + "type": "object", + "patternProperties": { + "^x-": true + } + }, + "examples": { + "type": "object", + "properties": { + "example": true, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + } + } + }, + "map-of-strings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "crud-operation-auth": { + "oneOf": [ + { + "type": "object", + "description": "CRUD operation authorization config", + "properties": { + "checks": { + "description": "checks for the operation", + "type": "object", + "additionalProperties": { + "if": { + "type": "object" + }, + "then": { + "type": "object", + "properties": { + "eq": { + "type": "string" + }, + "in": { + "type": "string" + }, + "nin": { + "type": "string" + }, + "nen": { + "type": "string" + }, + "gt": { + "type": "string" + }, + "gte": { + "type": "string" + }, + "lt": { + "type": "string" + }, + "lte": { + "type": "string" + } + }, + "additionalProperties": false + }, + "else": { + "type": "string" + } + } + }, + "fields": { + "type": "array", + "description": "array of enabled field for the operation", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + { + "type": "boolean", + "description": "true if enabled (with not authorization constraints enabled)" + } + ] + } + } +} From f612c05f4b62720b917aeb9c76bff3b96c9ad481 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 25 Sep 2024 19:06:19 +0200 Subject: [PATCH 2/2] Add buildStackable test Signed-off-by: Matteo Collina --- test/queue.test.js | 92 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/test/queue.test.js b/test/queue.test.js index ed3153e..dc1df7b 100644 --- a/test/queue.test.js +++ b/test/queue.test.js @@ -1,6 +1,7 @@ 'use strict' -const { buildServer, adminSecret } = require('./helper') +const { buildServer, adminSecret, getConfig } = require('./helper') +const { buildStackable } = require('..') const { test } = require('node:test') const { once, EventEmitter } = require('events') const tspl = require('@matteo.collina/tspl') @@ -394,3 +395,92 @@ test('`text plain` content type header in the Queue', async (t) => { await p }) + +test('happy path w buildStackable', async (t) => { + const plan = tspl(t, { plan: 5 }) + const ee = new EventEmitter() + + const { config } = await getConfig() + const server = await buildStackable({ config }) + + await server.start() + t.after(() => server.stop()) + + const target = Fastify() + target.post('/', async (req, reply) => { + plan.deepStrictEqual(req.body, { message: 'HELLO FOLKS!' }, 'message is plan.strictEqual') + ee.emit('called') + return { ok: true } + }) + + t.after(() => target.close()) + await target.listen({ port: 0 }) + const targetUrl = `http://localhost:${target.server.address().port}` + + let queueId + { + const res = await server.inject({ + method: 'POST', + url: '/graphql', + headers: { + 'X-PLATFORMATIC-ADMIN-SECRET': adminSecret + }, + payload: { + query: ` + mutation($callbackUrl: String!) { + saveQueue(input: { name: "test", callbackUrl: $callbackUrl, method: "POST" }) { + id + } + } + `, + variables: { + callbackUrl: targetUrl + } + } + }) + plan.strictEqual(res.statusCode, 200) + const body = JSON.parse(res.body) + const { data } = body + queueId = data.saveQueue.id + plan.strictEqual(queueId, '1') + } + + const p = once(ee, 'called') + { + const msg = JSON.stringify({ + message: 'HELLO FOLKS!' + }) + const now = Date.now() + const query = ` + mutation($body: String!, $queueId: ID) { + saveMessage(input: { queueId: $queueId, headers: "{ \\"content-type\\": \\"application/json\\" }", body: $body }) { + id + when + } + } + ` + + const res = await server.inject({ + method: 'POST', + url: '/graphql', + headers: { + 'X-PLATFORMATIC-ADMIN-SECRET': adminSecret + }, + payload: { + query, + variables: { + body: msg, + queueId + } + } + }) + const body = JSON.parse(res.body) + plan.strictEqual(res.statusCode, 200) + + const { data } = body + const when = new Date(data.saveMessage.when) + plan.strictEqual(when.getTime() - now >= 0, true) + } + + await p +})