@classytic/arc 1.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
- package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
- package/dist/HookSystem-BsGV-j2l.mjs +405 -0
- package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +82 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +276 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-C3T5DTUx.mjs +141 -0
- package/dist/audited-C3T5DTUx.mjs.map +1 -0
- package/dist/auth/index.d.mts +189 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +1102 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth/redis-session.d.mts +44 -0
- package/dist/auth/redis-session.d.mts.map +1 -0
- package/dist/auth/redis-session.mjs +76 -0
- package/dist/auth/redis-session.mjs.map +1 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
- package/dist/cache/index.d.mts +146 -0
- package/dist/cache/index.d.mts.map +1 -0
- package/dist/cache/index.mjs +92 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/caching-Bl28lYsR.mjs +94 -0
- package/dist/caching-Bl28lYsR.mjs.map +1 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
- package/dist/cli/commands/describe.d.mts +19 -0
- package/dist/cli/commands/describe.d.mts.map +1 -0
- package/dist/cli/commands/describe.mjs +239 -0
- package/dist/cli/commands/describe.mjs.map +1 -0
- package/dist/cli/commands/docs.d.mts +14 -0
- package/dist/cli/commands/docs.d.mts.map +1 -0
- package/dist/cli/commands/docs.mjs +53 -0
- package/dist/cli/commands/docs.mjs.map +1 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
- package/dist/cli/commands/generate.d.mts.map +1 -0
- package/dist/cli/commands/generate.mjs +358 -0
- package/dist/cli/commands/generate.mjs.map +1 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
- package/dist/cli/commands/init.d.mts.map +1 -0
- package/dist/cli/commands/{init.js → init.mjs} +807 -616
- package/dist/cli/commands/init.mjs.map +1 -0
- package/dist/cli/commands/introspect.d.mts +11 -0
- package/dist/cli/commands/introspect.d.mts.map +1 -0
- package/dist/cli/commands/introspect.mjs +76 -0
- package/dist/cli/commands/introspect.mjs.map +1 -0
- package/dist/cli/index.d.mts +17 -0
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/index.mjs +157 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/constants-DdXFXQtN.mjs +85 -0
- package/dist/constants-DdXFXQtN.mjs.map +1 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-CUgNqegw.mjs +560 -0
- package/dist/createApp-CUgNqegw.mjs.map +1 -0
- package/dist/defineResource-k0_BDn8v.mjs +2197 -0
- package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
- package/dist/discovery/index.d.mts +47 -0
- package/dist/discovery/index.d.mts.map +1 -0
- package/dist/discovery/index.mjs +110 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/docs/index.d.mts +163 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +73 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/elevation-BRy3yFWT.mjs +113 -0
- package/dist/elevation-BRy3yFWT.mjs.map +1 -0
- package/dist/elevation-B_2dRLVP.d.mts +88 -0
- package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
- package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
- package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
- package/dist/errorHandler-C1okiriz.mjs +109 -0
- package/dist/errorHandler-C1okiriz.mjs.map +1 -0
- package/dist/errors-B9bZok84.mjs +212 -0
- package/dist/errors-B9bZok84.mjs.map +1 -0
- package/dist/errors-ChKiFz62.d.mts +125 -0
- package/dist/errors-ChKiFz62.d.mts.map +1 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
- package/dist/eventPlugin-DGR_B2on.mjs +230 -0
- package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
- package/dist/events/index.d.mts +54 -0
- package/dist/events/index.d.mts.map +1 -0
- package/dist/events/index.mjs +52 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +178 -0
- package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
- package/dist/events/transports/redis.d.mts +77 -0
- package/dist/events/transports/redis.d.mts.map +1 -0
- package/dist/events/transports/redis.mjs +125 -0
- package/dist/events/transports/redis.mjs.map +1 -0
- package/dist/externalPaths-DlINfKbP.d.mts +51 -0
- package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
- package/dist/factory/index.d.mts +64 -0
- package/dist/factory/index.d.mts.map +1 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
- package/dist/fields-DyaDVX4J.d.mts +110 -0
- package/dist/fields-DyaDVX4J.d.mts.map +1 -0
- package/dist/fields-iagOozy0.mjs +115 -0
- package/dist/fields-iagOozy0.mjs.map +1 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +97 -0
- package/dist/idempotency/index.d.mts.map +1 -0
- package/dist/idempotency/index.mjs +320 -0
- package/dist/idempotency/index.mjs.map +1 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +115 -0
- package/dist/idempotency/mongodb.mjs.map +1 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +104 -0
- package/dist/idempotency/redis.mjs.map +1 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +105 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/event-gateway.d.mts +47 -0
- package/dist/integrations/event-gateway.d.mts.map +1 -0
- package/dist/integrations/event-gateway.mjs +44 -0
- package/dist/integrations/event-gateway.mjs.map +1 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +104 -0
- package/dist/integrations/jobs.d.mts.map +1 -0
- package/dist/integrations/jobs.mjs +124 -0
- package/dist/integrations/jobs.mjs.map +1 -0
- package/dist/integrations/streamline.d.mts +61 -0
- package/dist/integrations/streamline.d.mts.map +1 -0
- package/dist/integrations/streamline.mjs +126 -0
- package/dist/integrations/streamline.mjs.map +1 -0
- package/dist/integrations/websocket.d.mts +83 -0
- package/dist/integrations/websocket.d.mts.map +1 -0
- package/dist/integrations/websocket.mjs +289 -0
- package/dist/integrations/websocket.mjs.map +1 -0
- package/dist/interface-B01JvPVc.d.mts +78 -0
- package/dist/interface-B01JvPVc.d.mts.map +1 -0
- package/dist/interface-CZe8IkMf.d.mts +55 -0
- package/dist/interface-CZe8IkMf.d.mts.map +1 -0
- package/dist/interface-Ch8HU9uM.d.mts +1098 -0
- package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
- package/dist/keys-BqNejWup.mjs +43 -0
- package/dist/keys-BqNejWup.mjs.map +1 -0
- package/dist/logger-Df2O2WsW.mjs +79 -0
- package/dist/logger-Df2O2WsW.mjs.map +1 -0
- package/dist/memory-cQgelFOj.mjs +144 -0
- package/dist/memory-cQgelFOj.mjs.map +1 -0
- package/dist/migrations/index.d.mts +157 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +261 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/mongodb-BfJVlUJH.mjs +94 -0
- package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
- package/dist/mongodb-CGzRbfAK.d.mts +119 -0
- package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
- package/dist/mongodb-JN-9JA7K.d.mts +72 -0
- package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
- package/dist/openapi-G3Cw7XuM.mjs +524 -0
- package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
- package/dist/org/index.d.mts +69 -0
- package/dist/org/index.d.mts.map +1 -0
- package/dist/org/index.mjs +514 -0
- package/dist/org/index.mjs.map +1 -0
- package/dist/org/types.d.mts +83 -0
- package/dist/org/types.d.mts.map +1 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +279 -0
- package/dist/permissions/index.d.mts.map +1 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +173 -0
- package/dist/plugins/index.d.mts.map +1 -0
- package/dist/plugins/index.mjs +523 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/plugins/response-cache.d.mts +88 -0
- package/dist/plugins/response-cache.d.mts.map +1 -0
- package/dist/plugins/response-cache.mjs +284 -0
- package/dist/plugins/response-cache.mjs.map +1 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +186 -0
- package/dist/plugins/tracing-entry.mjs.map +1 -0
- package/dist/pluralize-CEweyOEm.mjs +87 -0
- package/dist/pluralize-CEweyOEm.mjs.map +1 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -169
- package/dist/policies/index.d.mts.map +1 -0
- package/dist/policies/index.mjs +322 -0
- package/dist/policies/index.mjs.map +1 -0
- package/dist/presets/{index.d.ts → index.d.mts} +63 -131
- package/dist/presets/index.d.mts.map +1 -0
- package/dist/presets/index.mjs +144 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/presets/multiTenant.d.mts +25 -0
- package/dist/presets/multiTenant.d.mts.map +1 -0
- package/dist/presets/multiTenant.mjs +114 -0
- package/dist/presets/multiTenant.mjs.map +1 -0
- package/dist/presets-BITljm96.mjs +120 -0
- package/dist/presets-BITljm96.mjs.map +1 -0
- package/dist/presets-DzSMwlKj.d.mts +58 -0
- package/dist/presets-DzSMwlKj.d.mts.map +1 -0
- package/dist/prisma-DJbMt3yf.mjs +628 -0
- package/dist/prisma-DJbMt3yf.mjs.map +1 -0
- package/dist/prisma-Dg9GoVdj.d.mts +275 -0
- package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
- package/dist/redis-D-JAeLtm.d.mts +50 -0
- package/dist/redis-D-JAeLtm.d.mts.map +1 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
- package/dist/registry/index.d.mts +12 -0
- package/dist/registry/index.d.mts.map +1 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-QQD6ROJc.mjs +56 -0
- package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
- package/dist/schemaConverter-BwrmWroW.mjs +99 -0
- package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
- package/dist/schemas/index.d.mts +64 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +83 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/scope/index.d.mts +22 -0
- package/dist/scope/index.d.mts.map +1 -0
- package/dist/scope/index.mjs +66 -0
- package/dist/scope/index.mjs.map +1 -0
- package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
- package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
- package/dist/sse-B3c3_yZp.mjs +124 -0
- package/dist/sse-B3c3_yZp.mjs.map +1 -0
- package/dist/testing/index.d.mts +908 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +1977 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/tracing-Cc7vVQPp.d.mts +71 -0
- package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
- package/dist/typeGuards-DhMNLuvU.mjs +10 -0
- package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
- package/dist/types/index.d.mts +947 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +15 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-Beqn1Un7.mjs +39 -0
- package/dist/types-Beqn1Un7.mjs.map +1 -0
- package/dist/types-CIgB7UUl.d.mts +446 -0
- package/dist/types-CIgB7UUl.d.mts.map +1 -0
- package/dist/types-aYB4V7uN.d.mts +87 -0
- package/dist/types-aYB4V7uN.d.mts.map +1 -0
- package/dist/utils/index.d.mts +748 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/discovery/index.ts"],"sourcesContent":["/**\n * Arc Auto-Discovery Plugin\n *\n * Automatically discovers and registers resource files matching a glob pattern.\n * Eliminates manual resource registration boilerplate.\n *\n * This is a SEPARATE subpath import — only loaded when explicitly used:\n * import { discoveryPlugin, discoverResources } from '@classytic/arc/discovery';\n *\n * Serverless-safe: only runs at startup, no persistent process needed.\n *\n * @example\n * ```typescript\n * import { discoveryPlugin } from '@classytic/arc/discovery';\n *\n * // Auto-discovers all *.resource.ts files\n * await fastify.register(discoveryPlugin, {\n * paths: ['./src/modules'],\n * pattern: '**\\/*.resource.{ts,js}',\n * });\n *\n * // Or use the helper directly\n * import { discoverResources } from '@classytic/arc/discovery';\n *\n * const resources = await discoverResources({\n * paths: ['./src/modules'],\n * pattern: '**\\/*.resource.{ts,js}',\n * });\n *\n * for (const resource of resources) {\n * await fastify.register(resource.toPlugin());\n * }\n * ```\n */\nimport type { FastifyInstance, FastifyPluginAsync } from 'fastify';\nimport { readdir, stat } from 'node:fs/promises';\nimport { join, resolve, extname } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A discovered resource — must have toPlugin() method */\nexport interface DiscoverableResource {\n toPlugin(): FastifyPluginAsync;\n name?: string;\n definition?: { name: string };\n}\n\nexport interface DiscoveryOptions {\n /** Directories to scan (relative to cwd or absolute) */\n paths: string[];\n /**\n * File name pattern to match.\n * Supports simple globs: *.resource.ts, *.resource.js\n * Default: '*.resource.{ts,js}'\n */\n pattern?: string;\n /** Export name to look for in each file (default: 'default' then first ResourceDefinition) */\n exportName?: string;\n /** Filter function to include/exclude discovered resources */\n filter?: (resource: DiscoverableResource, filePath: string) => boolean;\n /** Called for each discovered resource (for logging) */\n onDiscover?: (name: string, filePath: string) => void;\n /** Whether to scan recursively (default: true) */\n recursive?: boolean;\n}\n\nexport interface DiscoveryPluginOptions extends DiscoveryOptions {\n /** URL prefix applied to all discovered resources */\n prefix?: string;\n}\n\n// ============================================================================\n// File Discovery (pure filesystem, no glob library needed)\n// ============================================================================\n\n/**\n * Match a filename against a simple pattern.\n * Supports: *.resource.ts, *.resource.js, *.resource.{ts,js}\n */\nfunction matchPattern(filename: string, pattern: string): boolean {\n // Expand {ts,js} brace patterns\n if (pattern.includes('{') && pattern.includes('}')) {\n const match = pattern.match(/\\{([^}]+)\\}/);\n if (match) {\n const alternatives = match[1]!.split(',').map((s) => s.trim());\n return alternatives.some((alt) => {\n const expanded = pattern.replace(match[0], alt);\n return matchPattern(filename, expanded);\n });\n }\n }\n\n // Simple wildcard: *.resource.ts → any file ending with .resource.ts\n if (pattern.startsWith('*')) {\n const suffix = pattern.slice(1);\n return filename.endsWith(suffix);\n }\n\n // Exact match\n return filename === pattern;\n}\n\n/**\n * Recursively scan directories for files matching a pattern.\n */\nasync function scanDirectory(\n dir: string,\n pattern: string,\n recursive: boolean\n): Promise<string[]> {\n const results: string[] = [];\n const resolvedDir = resolve(dir);\n\n const entries = await readdir(resolvedDir, { withFileTypes: true }).catch(\n () => [] as { name: string; isFile(): boolean; isDirectory(): boolean }[],\n );\n\n for (const entry of entries) {\n const fullPath = join(resolvedDir, String(entry.name));\n\n if (entry.isDirectory() && recursive) {\n const nested = await scanDirectory(fullPath, pattern, recursive);\n results.push(...nested);\n } else if (entry.isFile()) {\n // Strip any path prefix from pattern (e.g., **/*.resource.ts → *.resource.ts)\n const filePattern = pattern.replace(/^\\*\\*\\//, '');\n if (matchPattern(String(entry.name), filePattern)) {\n results.push(fullPath);\n }\n }\n }\n\n return results;\n}\n\n// ============================================================================\n// Resource Discovery\n// ============================================================================\n\n/**\n * Discover and import resource files.\n *\n * @returns Array of discovered resources with their file paths\n */\nexport async function discoverResources(\n options: DiscoveryOptions\n): Promise<Array<{ resource: DiscoverableResource; filePath: string }>> {\n const {\n paths,\n pattern = '*.resource.{ts,js}',\n exportName,\n filter,\n onDiscover,\n recursive = true,\n } = options;\n\n const discovered: Array<{ resource: DiscoverableResource; filePath: string }> = [];\n\n // Scan all directories\n const allFiles: string[] = [];\n for (const dir of paths) {\n const files = await scanDirectory(dir, pattern, recursive);\n allFiles.push(...files);\n }\n\n // Sort for deterministic registration order\n allFiles.sort();\n\n // Import each file and extract the resource\n for (const filePath of allFiles) {\n const ext = extname(filePath);\n // Only import .js and .mjs files (compiled output). .ts files need a loader.\n // In development with tsx/ts-node, .ts files work too.\n const fileUrl = pathToFileURL(filePath).href;\n\n let module: Record<string, unknown>;\n try {\n module = await import(fileUrl);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to import resource file: ${filePath}\\n${message}`);\n }\n\n // Find the resource export\n let resource: DiscoverableResource | null = null;\n\n // 1. Check specific export name\n if (exportName && module[exportName]) {\n resource = module[exportName] as DiscoverableResource;\n }\n\n // 2. Check default export\n if (!resource && module.default && typeof (module.default as Record<string, unknown>).toPlugin === 'function') {\n resource = module.default as DiscoverableResource;\n }\n\n // 3. Search for first export with toPlugin()\n if (!resource) {\n for (const value of Object.values(module)) {\n if (value && typeof value === 'object' && typeof (value as Record<string, unknown>).toPlugin === 'function') {\n resource = value as DiscoverableResource;\n break;\n }\n }\n }\n\n if (!resource) {\n throw new Error(\n `No resource found in: ${filePath}\\n` +\n 'Resource files must export an object with a toPlugin() method.\\n' +\n 'Use defineResource() or export the resource as default.'\n );\n }\n\n // Apply filter\n if (filter && !filter(resource, filePath)) {\n continue;\n }\n\n const name = resource.definition?.name ?? resource.name ?? filePath;\n onDiscover?.(name, filePath);\n discovered.push({ resource, filePath });\n }\n\n return discovered;\n}\n\n// ============================================================================\n// Fastify Plugin\n// ============================================================================\n\nconst discoveryPluginImpl: FastifyPluginAsync<DiscoveryPluginOptions> = async (\n fastify: FastifyInstance,\n options: DiscoveryPluginOptions\n) => {\n const { prefix, ...discoveryOptions } = options;\n\n const discovered = await discoverResources({\n ...discoveryOptions,\n onDiscover: discoveryOptions.onDiscover ?? ((name, filePath) => {\n fastify.log.debug({ resource: name, file: filePath }, 'Auto-discovered resource');\n }),\n });\n\n // Register all discovered resources\n for (const { resource } of discovered) {\n const plugin = resource.toPlugin();\n if (prefix) {\n await fastify.register(plugin, { prefix });\n } else {\n await fastify.register(plugin);\n }\n }\n\n fastify.log.debug(\n `Auto-discovery: registered ${discovered.length} resource(s)`\n );\n};\n\n/** Auto-discovery plugin for Arc resources */\nexport const discoveryPlugin: FastifyPluginAsync<DiscoveryPluginOptions> = discoveryPluginImpl;\nexport default discoveryPlugin;\n"],"mappings":";;;;;;;;;AAkFA,SAAS,aAAa,UAAkB,SAA0B;AAEhE,KAAI,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,IAAI,EAAE;EAClD,MAAM,QAAQ,QAAQ,MAAM,cAAc;AAC1C,MAAI,MAEF,QADqB,MAAM,GAAI,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAC1C,MAAM,QAAQ;AAEhC,UAAO,aAAa,UADH,QAAQ,QAAQ,MAAM,IAAI,IAAI,CACR;IACvC;;AAKN,KAAI,QAAQ,WAAW,IAAI,EAAE;EAC3B,MAAM,SAAS,QAAQ,MAAM,EAAE;AAC/B,SAAO,SAAS,SAAS,OAAO;;AAIlC,QAAO,aAAa;;;;;AAMtB,eAAe,cACb,KACA,SACA,WACmB;CACnB,MAAM,UAAoB,EAAE;CAC5B,MAAM,cAAc,QAAQ,IAAI;CAEhC,MAAM,UAAU,MAAM,QAAQ,aAAa,EAAE,eAAe,MAAM,CAAC,CAAC,YAC5D,EAAE,CACT;AAED,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,aAAa,OAAO,MAAM,KAAK,CAAC;AAEtD,MAAI,MAAM,aAAa,IAAI,WAAW;GACpC,MAAM,SAAS,MAAM,cAAc,UAAU,SAAS,UAAU;AAChE,WAAQ,KAAK,GAAG,OAAO;aACd,MAAM,QAAQ,EAAE;GAEzB,MAAM,cAAc,QAAQ,QAAQ,WAAW,GAAG;AAClD,OAAI,aAAa,OAAO,MAAM,KAAK,EAAE,YAAY,CAC/C,SAAQ,KAAK,SAAS;;;AAK5B,QAAO;;;;;;;AAYT,eAAsB,kBACpB,SACsE;CACtE,MAAM,EACJ,OACA,UAAU,sBACV,YACA,QACA,YACA,YAAY,SACV;CAEJ,MAAM,aAA0E,EAAE;CAGlF,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,OAAO,OAAO;EACvB,MAAM,QAAQ,MAAM,cAAc,KAAK,SAAS,UAAU;AAC1D,WAAS,KAAK,GAAG,MAAM;;AAIzB,UAAS,MAAM;AAGf,MAAK,MAAM,YAAY,UAAU;AACnB,UAAQ,SAAS;EAG7B,MAAM,UAAU,cAAc,SAAS,CAAC;EAExC,IAAI;AACJ,MAAI;AACF,YAAS,MAAM,OAAO;WACf,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAM,IAAI,MAAM,mCAAmC,SAAS,IAAI,UAAU;;EAI5E,IAAI,WAAwC;AAG5C,MAAI,cAAc,OAAO,YACvB,YAAW,OAAO;AAIpB,MAAI,CAAC,YAAY,OAAO,WAAW,OAAQ,OAAO,QAAoC,aAAa,WACjG,YAAW,OAAO;AAIpB,MAAI,CAAC,UACH;QAAK,MAAM,SAAS,OAAO,OAAO,OAAO,CACvC,KAAI,SAAS,OAAO,UAAU,YAAY,OAAQ,MAAkC,aAAa,YAAY;AAC3G,eAAW;AACX;;;AAKN,MAAI,CAAC,SACH,OAAM,IAAI,MACR,yBAAyB,SAAS;yDAGnC;AAIH,MAAI,UAAU,CAAC,OAAO,UAAU,SAAS,CACvC;EAGF,MAAM,OAAO,SAAS,YAAY,QAAQ,SAAS,QAAQ;AAC3D,eAAa,MAAM,SAAS;AAC5B,aAAW,KAAK;GAAE;GAAU;GAAU,CAAC;;AAGzC,QAAO;;AAOT,MAAM,sBAAkE,OACtE,SACA,YACG;CACH,MAAM,EAAE,QAAQ,GAAG,qBAAqB;CAExC,MAAM,aAAa,MAAM,kBAAkB;EACzC,GAAG;EACH,YAAY,iBAAiB,gBAAgB,MAAM,aAAa;AAC9D,WAAQ,IAAI,MAAM;IAAE,UAAU;IAAM,MAAM;IAAU,EAAE,2BAA2B;;EAEpF,CAAC;AAGF,MAAK,MAAM,EAAE,cAAc,YAAY;EACrC,MAAM,SAAS,SAAS,UAAU;AAClC,MAAI,OACF,OAAM,QAAQ,SAAS,QAAQ,EAAE,QAAQ,CAAC;MAE1C,OAAM,QAAQ,SAAS,OAAO;;AAIlC,SAAQ,IAAI,MACV,8BAA8B,WAAW,OAAO,cACjD;;;AAIH,MAAa,kBAA8D"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import "../elevation-B_2dRLVP.mjs";
|
|
2
|
+
import "../interface-Ch8HU9uM.mjs";
|
|
3
|
+
import "../types-aYB4V7uN.mjs";
|
|
4
|
+
import { RegistryEntry } from "../types/index.mjs";
|
|
5
|
+
import { t as ExternalOpenApiPaths } from "../externalPaths-DlINfKbP.mjs";
|
|
6
|
+
import { FastifyPluginAsync } from "fastify";
|
|
7
|
+
|
|
8
|
+
//#region src/docs/openapi.d.ts
|
|
9
|
+
interface OpenApiOptions {
|
|
10
|
+
/** API title */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** API version */
|
|
13
|
+
version?: string;
|
|
14
|
+
/** API description */
|
|
15
|
+
description?: string;
|
|
16
|
+
/** Server URL */
|
|
17
|
+
serverUrl?: string;
|
|
18
|
+
/** Route prefix for spec endpoint (default: '/_docs') */
|
|
19
|
+
prefix?: string;
|
|
20
|
+
/** API prefix for all resource paths (e.g., '/api/v1') */
|
|
21
|
+
apiPrefix?: string;
|
|
22
|
+
/** Auth roles required to access spec (default: [] = public) */
|
|
23
|
+
authRoles?: string[];
|
|
24
|
+
/** Include internal routes (default: false) */
|
|
25
|
+
includeInternal?: boolean;
|
|
26
|
+
/** Custom OpenAPI extensions */
|
|
27
|
+
extensions?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
interface OpenApiSpec {
|
|
30
|
+
openapi: string;
|
|
31
|
+
info: {
|
|
32
|
+
title: string;
|
|
33
|
+
version: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
};
|
|
36
|
+
servers?: Array<{
|
|
37
|
+
url: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
}>;
|
|
40
|
+
paths: Record<string, PathItem>;
|
|
41
|
+
components: {
|
|
42
|
+
schemas: Record<string, SchemaObject>;
|
|
43
|
+
securitySchemes?: Record<string, SecurityScheme>;
|
|
44
|
+
};
|
|
45
|
+
tags: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
}>;
|
|
49
|
+
security?: Array<Record<string, string[]>>;
|
|
50
|
+
}
|
|
51
|
+
interface OpenApiBuildOptions {
|
|
52
|
+
title?: string;
|
|
53
|
+
version?: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
serverUrl?: string;
|
|
56
|
+
apiPrefix?: string;
|
|
57
|
+
}
|
|
58
|
+
interface PathItem {
|
|
59
|
+
get?: Operation;
|
|
60
|
+
post?: Operation;
|
|
61
|
+
put?: Operation;
|
|
62
|
+
patch?: Operation;
|
|
63
|
+
delete?: Operation;
|
|
64
|
+
options?: Operation;
|
|
65
|
+
head?: Operation;
|
|
66
|
+
}
|
|
67
|
+
interface Operation {
|
|
68
|
+
tags: string[];
|
|
69
|
+
summary: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
operationId: string;
|
|
72
|
+
parameters?: Parameter[];
|
|
73
|
+
requestBody?: RequestBody;
|
|
74
|
+
responses: Record<string, Response>;
|
|
75
|
+
security?: Array<Record<string, string[]>>;
|
|
76
|
+
/** Arc permission metadata (OpenAPI extension) */
|
|
77
|
+
'x-arc-permission'?: {
|
|
78
|
+
type: string;
|
|
79
|
+
roles?: readonly string[];
|
|
80
|
+
};
|
|
81
|
+
/** Arc pipeline steps (OpenAPI extension) */
|
|
82
|
+
'x-arc-pipeline'?: Array<{
|
|
83
|
+
type: string;
|
|
84
|
+
name: string;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
interface Parameter {
|
|
88
|
+
name: string;
|
|
89
|
+
in: 'path' | 'query' | 'header';
|
|
90
|
+
required?: boolean;
|
|
91
|
+
schema: SchemaObject;
|
|
92
|
+
description?: string;
|
|
93
|
+
}
|
|
94
|
+
interface RequestBody {
|
|
95
|
+
required?: boolean;
|
|
96
|
+
content: Record<string, {
|
|
97
|
+
schema: SchemaObject;
|
|
98
|
+
}>;
|
|
99
|
+
}
|
|
100
|
+
interface Response {
|
|
101
|
+
description: string;
|
|
102
|
+
content?: Record<string, {
|
|
103
|
+
schema: SchemaObject;
|
|
104
|
+
}>;
|
|
105
|
+
}
|
|
106
|
+
interface SchemaObject {
|
|
107
|
+
type?: string;
|
|
108
|
+
format?: string;
|
|
109
|
+
properties?: Record<string, SchemaObject>;
|
|
110
|
+
items?: SchemaObject;
|
|
111
|
+
required?: string[];
|
|
112
|
+
$ref?: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
example?: unknown;
|
|
115
|
+
additionalProperties?: boolean | SchemaObject;
|
|
116
|
+
enum?: string[];
|
|
117
|
+
minimum?: number;
|
|
118
|
+
maximum?: number;
|
|
119
|
+
minLength?: number;
|
|
120
|
+
maxLength?: number;
|
|
121
|
+
pattern?: string;
|
|
122
|
+
}
|
|
123
|
+
interface SecurityScheme {
|
|
124
|
+
type: string;
|
|
125
|
+
scheme?: string;
|
|
126
|
+
bearerFormat?: string;
|
|
127
|
+
in?: string;
|
|
128
|
+
name?: string;
|
|
129
|
+
}
|
|
130
|
+
declare const openApiPlugin: FastifyPluginAsync<OpenApiOptions>;
|
|
131
|
+
/**
|
|
132
|
+
* Build OpenAPI spec from registry resources.
|
|
133
|
+
* Shared by HTTP docs endpoint and CLI export command.
|
|
134
|
+
*/
|
|
135
|
+
declare function buildOpenApiSpec(resources: RegistryEntry[], options?: OpenApiBuildOptions, externalPaths?: ExternalOpenApiPaths[]): OpenApiSpec;
|
|
136
|
+
declare const _default: FastifyPluginAsync<OpenApiOptions>;
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/docs/scalar.d.ts
|
|
139
|
+
interface ScalarOptions {
|
|
140
|
+
/** Route prefix for UI (default: '/docs') */
|
|
141
|
+
routePrefix?: string;
|
|
142
|
+
/** OpenAPI spec URL (default: '/_docs/openapi.json') */
|
|
143
|
+
specUrl?: string;
|
|
144
|
+
/** Page title */
|
|
145
|
+
title?: string;
|
|
146
|
+
/** Theme (default: 'default') */
|
|
147
|
+
theme?: 'default' | 'alternate' | 'moon' | 'purple' | 'solarized' | 'bluePlanet' | 'saturn' | 'kepler' | 'mars' | 'deepSpace';
|
|
148
|
+
/** Show sidebar (default: true) */
|
|
149
|
+
showSidebar?: boolean;
|
|
150
|
+
/** Dark mode (default: false) */
|
|
151
|
+
darkMode?: boolean;
|
|
152
|
+
/** Auth roles required to access docs */
|
|
153
|
+
authRoles?: string[];
|
|
154
|
+
/** Custom CSS */
|
|
155
|
+
customCss?: string;
|
|
156
|
+
/** Favicon URL */
|
|
157
|
+
favicon?: string;
|
|
158
|
+
}
|
|
159
|
+
declare const scalarPlugin: FastifyPluginAsync<ScalarOptions>;
|
|
160
|
+
declare const _default$1: FastifyPluginAsync<ScalarOptions>;
|
|
161
|
+
//#endregion
|
|
162
|
+
export { type ExternalOpenApiPaths, type OpenApiBuildOptions, type OpenApiOptions, type OpenApiSpec, type ScalarOptions, buildOpenApiSpec, _default as openApiPlugin, openApiPlugin as openApiPluginFn, _default$1 as scalarPlugin, scalarPlugin as scalarPluginFn };
|
|
163
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/docs/openapi.ts","../../src/docs/scalar.ts"],"mappings":";;;;;;;;UAuBiB,cAAA;EAgBf;EAdA,KAAA;EAgBa;EAdb,OAAA;EAcmB;EAZnB,WAAA;EAe0B;EAb1B,SAAA;EAoBU;EAlBV,MAAA;EAmBO;EAjBP,SAAA;EAmBW;EAjBX,SAAA;EAkBoB;EAhBpB,eAAA;EAmBiB;EAjBjB,UAAA,GAAa,MAAA;AAAA;AAAA,UAGE,WAAA;EACf,OAAA;EACA,IAAA;IACE,KAAA;IACA,OAAA;IACA,WAAA;EAAA;EAEF,OAAA,GAAU,KAAA;IAAQ,GAAA;IAAa,WAAA;EAAA;EAC/B,KAAA,EAAO,MAAA,SAAe,QAAA;EACtB,UAAA;IACE,OAAA,EAAS,MAAA,SAAe,YAAA;IACxB,eAAA,GAAkB,MAAA,SAAe,cAAA;EAAA;EAEnC,IAAA,EAAM,KAAA;IAAQ,IAAA;IAAc,WAAA;EAAA;EAC5B,QAAA,GAAW,KAAA,CAAM,MAAA;AAAA;AAAA,UAGF,mBAAA;EACf,KAAA;EACA,OAAA;EACA,WAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,UAGQ,QAAA;EACR,GAAA,GAAM,SAAA;EACN,IAAA,GAAO,SAAA;EACP,GAAA,GAAM,SAAA;EACN,KAAA,GAAQ,SAAA;EACR,MAAA,GAAS,SAAA;EACT,OAAA,GAAU,SAAA;EACV,IAAA,GAAO,SAAA;AAAA;AAAA,UAGC,SAAA;EACR,IAAA;EACA,OAAA;EACA,WAAA;EACA,WAAA;EACA,UAAA,GAAa,SAAA;EACb,WAAA,GAAc,WAAA;EACd,SAAA,EAAW,MAAA,SAAe,QAAA;EAC1B,QAAA,GAAW,KAAA,CAAM,MAAA;EAbR;EAeT,kBAAA;IAAuB,IAAA;IAAc,KAAA;EAAA;EAnBrC;EAqBA,gBAAA,GAAmB,KAAA;IAAQ,IAAA;IAAc,IAAA;EAAA;AAAA;AAAA,UAGjC,SAAA;EACR,IAAA;EACA,EAAA;EACA,QAAA;EACA,MAAA,EAAQ,YAAA;EACR,WAAA;AAAA;AAAA,UAGQ,WAAA;EACR,QAAA;EACA,OAAA,EAAS,MAAA;IAAiB,MAAA,EAAQ,YAAA;EAAA;AAAA;AAAA,UAG1B,QAAA;EACR,WAAA;EACA,OAAA,GAAU,MAAA;IAAiB,MAAA,EAAQ,YAAA;EAAA;AAAA;AAAA,UAG3B,YAAA;EACR,IAAA;EACA,MAAA;EACA,UAAA,GAAa,MAAA,SAAe,YAAA;EAC5B,KAAA,GAAQ,YAAA;EACR,QAAA;EACA,IAAA;EACA,WAAA;EACA,OAAA;EACA,oBAAA,aAAiC,YAAA;EACjC,IAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,UAGQ,cAAA;EACR,IAAA;EACA,MAAA;EACA,YAAA;EACA,EAAA;EACA,IAAA;AAAA;AAAA,cAGI,aAAA,EAAe,kBAAA,CAAmB,cAAA;;AA/CO;;;iBAmG/B,gBAAA,CACd,SAAA,EAAW,aAAA,IACX,OAAA,GAAS,mBAAA,EACT,aAAA,GAAgB,oBAAA,KACf,WAAA;AAAA,cAkGF,QAAA;;;UChRgB,aAAA;EDiBf;ECfA,WAAA;EDmBA;ECjBA,OAAA;EDiBmB;ECfnB,KAAA;EDkBe;EChBf,KAAA;;EAEA,WAAA;EDsBsB;ECpBtB,QAAA;EDsB0B;ECpB1B,SAAA;EDqBmC;ECnBnC,SAAA;EDqBM;ECnBN,OAAA;AAAA;AAAA,cAGI,YAAA,EAAc,kBAAA,CAAmB,aAAA;AAAA,cAAa,UAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-G3Cw7XuM.mjs";
|
|
2
|
+
import fp from "fastify-plugin";
|
|
3
|
+
|
|
4
|
+
//#region src/docs/scalar.ts
|
|
5
|
+
/**
|
|
6
|
+
* Scalar API Reference Plugin
|
|
7
|
+
*
|
|
8
|
+
* Beautiful, modern API documentation UI.
|
|
9
|
+
* Lighter and more modern than Swagger UI.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { scalarPlugin } from '@classytic/arc/docs';
|
|
13
|
+
*
|
|
14
|
+
* await fastify.register(scalarPlugin, {
|
|
15
|
+
* routePrefix: '/docs',
|
|
16
|
+
* specUrl: '/_docs/openapi.json',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // UI available at /docs
|
|
20
|
+
*/
|
|
21
|
+
const scalarPlugin = async (fastify, opts = {}) => {
|
|
22
|
+
const { routePrefix = "/docs", specUrl = "/_docs/openapi.json", title = "API Documentation", theme = "default", showSidebar = true, darkMode = false, authRoles = [], customCss = "", favicon } = opts;
|
|
23
|
+
const scalarConfig = JSON.stringify({
|
|
24
|
+
spec: { url: specUrl },
|
|
25
|
+
theme,
|
|
26
|
+
showSidebar,
|
|
27
|
+
darkMode,
|
|
28
|
+
...favicon && { favicon }
|
|
29
|
+
});
|
|
30
|
+
const html = `<!DOCTYPE html>
|
|
31
|
+
<html>
|
|
32
|
+
<head>
|
|
33
|
+
<meta charset="utf-8">
|
|
34
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
35
|
+
<title>${title}</title>
|
|
36
|
+
${favicon ? `<link rel="icon" href="${favicon}">` : ""}
|
|
37
|
+
<style>
|
|
38
|
+
body { margin: 0; padding: 0; }
|
|
39
|
+
${customCss}
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<script id="api-reference" data-url="${specUrl}"><\/script>
|
|
44
|
+
<script>
|
|
45
|
+
var configuration = ${scalarConfig};
|
|
46
|
+
document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration);
|
|
47
|
+
<\/script>
|
|
48
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
49
|
+
</body>
|
|
50
|
+
</html>`;
|
|
51
|
+
fastify.get(routePrefix, async (request, reply) => {
|
|
52
|
+
if (authRoles.length > 0) {
|
|
53
|
+
const user = request.user;
|
|
54
|
+
if (!authRoles.some((role) => user?.roles?.includes(role)) && !user?.roles?.includes("superadmin")) {
|
|
55
|
+
reply.code(403).send({ error: "Access denied" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
reply.type("text/html").send(html);
|
|
60
|
+
});
|
|
61
|
+
if (!routePrefix.endsWith("/")) fastify.get(`${routePrefix}/`, async (_, reply) => {
|
|
62
|
+
reply.redirect(routePrefix);
|
|
63
|
+
});
|
|
64
|
+
fastify.log?.debug?.(`Scalar API docs available at ${routePrefix}`);
|
|
65
|
+
};
|
|
66
|
+
var scalar_default = fp(scalarPlugin, {
|
|
67
|
+
name: "arc-scalar",
|
|
68
|
+
fastify: "5.x"
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { buildOpenApiSpec, openapi_default as openApiPlugin, openApiPlugin as openApiPluginFn, scalar_default as scalarPlugin, scalarPlugin as scalarPluginFn };
|
|
73
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/docs/scalar.ts"],"sourcesContent":["/**\n * Scalar API Reference Plugin\n *\n * Beautiful, modern API documentation UI.\n * Lighter and more modern than Swagger UI.\n *\n * @example\n * import { scalarPlugin } from '@classytic/arc/docs';\n *\n * await fastify.register(scalarPlugin, {\n * routePrefix: '/docs',\n * specUrl: '/_docs/openapi.json',\n * });\n *\n * // UI available at /docs\n */\n\nimport fp from 'fastify-plugin';\nimport type { FastifyInstance, FastifyPluginAsync } from 'fastify';\n\nexport interface ScalarOptions {\n /** Route prefix for UI (default: '/docs') */\n routePrefix?: string;\n /** OpenAPI spec URL (default: '/_docs/openapi.json') */\n specUrl?: string;\n /** Page title */\n title?: string;\n /** Theme (default: 'default') */\n theme?: 'default' | 'alternate' | 'moon' | 'purple' | 'solarized' | 'bluePlanet' | 'saturn' | 'kepler' | 'mars' | 'deepSpace';\n /** Show sidebar (default: true) */\n showSidebar?: boolean;\n /** Dark mode (default: false) */\n darkMode?: boolean;\n /** Auth roles required to access docs */\n authRoles?: string[];\n /** Custom CSS */\n customCss?: string;\n /** Favicon URL */\n favicon?: string;\n}\n\nconst scalarPlugin: FastifyPluginAsync<ScalarOptions> = async (\n fastify: FastifyInstance,\n opts: ScalarOptions = {}\n) => {\n const {\n routePrefix = '/docs',\n specUrl = '/_docs/openapi.json',\n title = 'API Documentation',\n theme = 'default',\n showSidebar = true,\n darkMode = false,\n authRoles = [],\n customCss = '',\n favicon,\n } = opts;\n\n // Scalar configuration\n const scalarConfig = JSON.stringify({\n spec: { url: specUrl },\n theme,\n showSidebar,\n darkMode,\n ...(favicon && { favicon }),\n });\n\n // HTML template for Scalar\n const html = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>${title}</title>\n ${favicon ? `<link rel=\"icon\" href=\"${favicon}\">` : ''}\n <style>\n body { margin: 0; padding: 0; }\n ${customCss}\n </style>\n</head>\n<body>\n <script id=\"api-reference\" data-url=\"${specUrl}\"></script>\n <script>\n var configuration = ${scalarConfig};\n document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration);\n </script>\n <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n</body>\n</html>`;\n\n // Serve UI (not included in OpenAPI spec)\n fastify.get(routePrefix, async (request, reply) => {\n // Check auth if required\n if (authRoles.length > 0) {\n const user = (request as { user?: { roles?: string[] } }).user;\n const hasRole = authRoles.some((role) => user?.roles?.includes(role));\n if (!hasRole && !user?.roles?.includes('superadmin')) {\n reply.code(403).send({ error: 'Access denied' });\n return;\n }\n }\n\n reply.type('text/html').send(html);\n });\n\n // Redirect /docs/ to /docs\n if (!routePrefix.endsWith('/')) {\n fastify.get(`${routePrefix}/`, async (_, reply) => {\n reply.redirect(routePrefix);\n });\n }\n\n fastify.log?.debug?.(`Scalar API docs available at ${routePrefix}`);\n};\n\nexport default fp(scalarPlugin, {\n name: 'arc-scalar',\n fastify: '5.x',\n});\n\nexport { scalarPlugin };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyCA,MAAM,eAAkD,OACtD,SACA,OAAsB,EAAE,KACrB;CACH,MAAM,EACJ,cAAc,SACd,UAAU,uBACV,QAAQ,qBACR,QAAQ,WACR,cAAc,MACd,WAAW,OACX,YAAY,EAAE,EACd,YAAY,IACZ,YACE;CAGJ,MAAM,eAAe,KAAK,UAAU;EAClC,MAAM,EAAE,KAAK,SAAS;EACtB;EACA;EACA;EACA,GAAI,WAAW,EAAE,SAAS;EAC3B,CAAC;CAGF,MAAM,OAAO;;;;;WAKJ,MAAM;IACb,UAAU,0BAA0B,QAAQ,MAAM,GAAG;;;MAGnD,UAAU;;;;yCAIyB,QAAQ;;0BAEvB,aAAa;;;;;;AAQrC,SAAQ,IAAI,aAAa,OAAO,SAAS,UAAU;AAEjD,MAAI,UAAU,SAAS,GAAG;GACxB,MAAM,OAAQ,QAA4C;AAE1D,OAAI,CADY,UAAU,MAAM,SAAS,MAAM,OAAO,SAAS,KAAK,CAAC,IACrD,CAAC,MAAM,OAAO,SAAS,aAAa,EAAE;AACpD,UAAM,KAAK,IAAI,CAAC,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAChD;;;AAIJ,QAAM,KAAK,YAAY,CAAC,KAAK,KAAK;GAClC;AAGF,KAAI,CAAC,YAAY,SAAS,IAAI,CAC5B,SAAQ,IAAI,GAAG,YAAY,IAAI,OAAO,GAAG,UAAU;AACjD,QAAM,SAAS,YAAY;GAC3B;AAGJ,SAAQ,KAAK,QAAQ,gCAAgC,cAAc;;AAGrE,qBAAe,GAAG,cAAc;CAC9B,MAAM;CACN,SAAS;CACV,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
|
|
2
|
+
import { t as arcLog } from "./logger-Df2O2WsW.mjs";
|
|
3
|
+
import fp from "fastify-plugin";
|
|
4
|
+
|
|
5
|
+
//#region src/scope/elevation.ts
|
|
6
|
+
/**
|
|
7
|
+
* Elevation Plugin — Explicit Platform Admin Access
|
|
8
|
+
*
|
|
9
|
+
* Opt-in Fastify plugin that allows platform admins to explicitly
|
|
10
|
+
* elevate their scope via the `x-arc-scope: platform` header.
|
|
11
|
+
*
|
|
12
|
+
* Without this header, a superadmin is treated as a normal user.
|
|
13
|
+
* This prevents implicit bypass and enables audit logging.
|
|
14
|
+
*
|
|
15
|
+
* ## Lifecycle
|
|
16
|
+
*
|
|
17
|
+
* Elevation wraps `fastify.authenticate` so it always runs AFTER
|
|
18
|
+
* authentication has set `request.user`. This avoids the `onRequest`
|
|
19
|
+
* timing issue where `request.user` doesn't exist yet.
|
|
20
|
+
*
|
|
21
|
+
* Flow: `authenticate()` → user is set → `elevation check` → scope is set
|
|
22
|
+
*
|
|
23
|
+
* Inspired by Stripe Connect's `Stripe-Account` header.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const app = await createApp({
|
|
28
|
+
* auth: { type: 'betterAuth', betterAuth: adapter },
|
|
29
|
+
* elevation: {
|
|
30
|
+
* platformRoles: ['superadmin'],
|
|
31
|
+
* onElevation: (event) => auditLog.write(event),
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
var elevation_exports = /* @__PURE__ */ __exportAll({
|
|
37
|
+
default: () => elevation_default,
|
|
38
|
+
elevationPlugin: () => elevationPlugin
|
|
39
|
+
});
|
|
40
|
+
const log = arcLog("elevation");
|
|
41
|
+
const elevationPlugin = async (fastify, opts = {}) => {
|
|
42
|
+
const { platformRoles = ["superadmin"], scopeHeader = "x-arc-scope", orgHeader = "x-organization-id", onElevation } = opts;
|
|
43
|
+
if (!fastify.hasDecorator("authenticate")) {
|
|
44
|
+
log.warn("authenticate decorator not found. Register auth before elevation. Elevation will not function.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const originalAuthenticate = fastify.authenticate;
|
|
48
|
+
const authenticateWithElevation = async (request, reply) => {
|
|
49
|
+
await originalAuthenticate.call(fastify, request, reply);
|
|
50
|
+
if (reply.sent) return;
|
|
51
|
+
if (request.headers[scopeHeader] !== "platform") return;
|
|
52
|
+
const user = request.user;
|
|
53
|
+
if (!user) {
|
|
54
|
+
log.debug("Elevation requested but no user after auth");
|
|
55
|
+
reply.code(401).send({
|
|
56
|
+
success: false,
|
|
57
|
+
error: "Unauthorized",
|
|
58
|
+
message: "Authentication required for platform elevation",
|
|
59
|
+
code: "ELEVATION_AUTH_REQUIRED"
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const userRoles = user.roles ?? [];
|
|
64
|
+
if (!platformRoles.some((r) => userRoles.includes(r))) {
|
|
65
|
+
log.debug("Elevation rejected — insufficient roles", {
|
|
66
|
+
userId: user.id ?? user._id,
|
|
67
|
+
userRoles,
|
|
68
|
+
required: platformRoles
|
|
69
|
+
});
|
|
70
|
+
reply.code(403).send({
|
|
71
|
+
success: false,
|
|
72
|
+
error: "Forbidden",
|
|
73
|
+
message: "Insufficient privileges for platform elevation",
|
|
74
|
+
code: "ELEVATION_FORBIDDEN"
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const orgId = request.headers[orgHeader];
|
|
79
|
+
const userId = String(user.id ?? user._id ?? "unknown");
|
|
80
|
+
request.scope = {
|
|
81
|
+
kind: "elevated",
|
|
82
|
+
organizationId: orgId || void 0,
|
|
83
|
+
elevatedBy: userId
|
|
84
|
+
};
|
|
85
|
+
log.debug("Scope elevated", {
|
|
86
|
+
userId,
|
|
87
|
+
organizationId: orgId
|
|
88
|
+
});
|
|
89
|
+
if (onElevation) try {
|
|
90
|
+
await onElevation({
|
|
91
|
+
userId,
|
|
92
|
+
organizationId: orgId || void 0,
|
|
93
|
+
request,
|
|
94
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
log.warn("onElevation callback threw — continuing request");
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
fastify.authenticate = authenticateWithElevation;
|
|
101
|
+
log.debug("Plugin registered", {
|
|
102
|
+
platformRoles,
|
|
103
|
+
scopeHeader
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
var elevation_default = fp(elevationPlugin, {
|
|
107
|
+
name: "arc-elevation",
|
|
108
|
+
fastify: "5.x"
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { elevation_default as n, elevation_exports as r, elevationPlugin as t };
|
|
113
|
+
//# sourceMappingURL=elevation-BRy3yFWT.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elevation-BRy3yFWT.mjs","names":[],"sources":["../src/scope/elevation.ts"],"sourcesContent":["/**\n * Elevation Plugin — Explicit Platform Admin Access\n *\n * Opt-in Fastify plugin that allows platform admins to explicitly\n * elevate their scope via the `x-arc-scope: platform` header.\n *\n * Without this header, a superadmin is treated as a normal user.\n * This prevents implicit bypass and enables audit logging.\n *\n * ## Lifecycle\n *\n * Elevation wraps `fastify.authenticate` so it always runs AFTER\n * authentication has set `request.user`. This avoids the `onRequest`\n * timing issue where `request.user` doesn't exist yet.\n *\n * Flow: `authenticate()` → user is set → `elevation check` → scope is set\n *\n * Inspired by Stripe Connect's `Stripe-Account` header.\n *\n * @example\n * ```typescript\n * const app = await createApp({\n * auth: { type: 'betterAuth', betterAuth: adapter },\n * elevation: {\n * platformRoles: ['superadmin'],\n * onElevation: (event) => auditLog.write(event),\n * },\n * });\n * ```\n */\n\nimport fp from 'fastify-plugin';\nimport type { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify';\nimport type { RequestScope } from './types.js';\nimport { arcLog } from '../logger/index.js';\n\nconst log = arcLog('elevation');\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ElevationOptions {\n /** Roles that can use elevation (default: ['superadmin']) */\n platformRoles?: string[];\n /** Header name for scope declaration (default: 'x-arc-scope') */\n scopeHeader?: string;\n /** Header name for target organization (default: 'x-organization-id') */\n orgHeader?: string;\n /** Called when elevation happens — use for audit logging */\n onElevation?: (event: ElevationEvent) => void | Promise<void>;\n}\n\nexport interface ElevationEvent {\n userId: string;\n organizationId?: string;\n request: FastifyRequest;\n timestamp: Date;\n}\n\n// ============================================================================\n// Plugin\n// ============================================================================\n\nconst elevationPlugin: FastifyPluginAsync<ElevationOptions> = async (\n fastify: FastifyInstance,\n opts: ElevationOptions = {},\n) => {\n const {\n platformRoles = ['superadmin'],\n scopeHeader = 'x-arc-scope',\n orgHeader = 'x-organization-id',\n onElevation,\n } = opts;\n\n // Elevation requires auth — wrap the authenticate decorator\n if (!fastify.hasDecorator('authenticate')) {\n log.warn(\n 'authenticate decorator not found. ' +\n 'Register auth before elevation. Elevation will not function.',\n );\n return;\n }\n\n const originalAuthenticate = fastify.authenticate;\n\n // Replace authenticate with elevation-aware version\n const authenticateWithElevation = async (\n request: FastifyRequest,\n reply: FastifyReply,\n ): Promise<void> => {\n // Step 1: Run original auth (sets request.user + default scope)\n await originalAuthenticate.call(fastify, request, reply);\n\n // If auth failed and sent a reply, stop\n if (reply.sent) return;\n\n // Step 2: Check elevation header\n const headerValue = request.headers[scopeHeader] as string | undefined;\n if (headerValue !== 'platform') return;\n\n // Step 3: Validate user for elevation\n const user = request.user;\n if (!user) {\n log.debug('Elevation requested but no user after auth');\n reply.code(401).send({\n success: false,\n error: 'Unauthorized',\n message: 'Authentication required for platform elevation',\n code: 'ELEVATION_AUTH_REQUIRED',\n });\n return;\n }\n\n const userRoles = (user.roles ?? []) as string[];\n if (!platformRoles.some((r) => userRoles.includes(r))) {\n log.debug('Elevation rejected — insufficient roles', {\n userId: user.id ?? user._id,\n userRoles,\n required: platformRoles,\n });\n reply.code(403).send({\n success: false,\n error: 'Forbidden',\n message: 'Insufficient privileges for platform elevation',\n code: 'ELEVATION_FORBIDDEN',\n });\n return;\n }\n\n // Step 4: Build elevated scope\n const orgId = request.headers[orgHeader] as string | undefined;\n const userId = String(user.id ?? user._id ?? 'unknown');\n\n const scope: RequestScope = {\n kind: 'elevated',\n organizationId: orgId || undefined,\n elevatedBy: userId,\n };\n\n request.scope = scope;\n log.debug('Scope elevated', { userId, organizationId: orgId });\n\n // Step 5: Fire audit callback\n if (onElevation) {\n try {\n await onElevation({\n userId,\n organizationId: orgId || undefined,\n request,\n timestamp: new Date(),\n });\n } catch {\n // Don't fail the request if audit logging fails\n log.warn('onElevation callback threw — continuing request');\n }\n }\n };\n\n // Overwrite the authenticate decorator\n fastify.authenticate = authenticateWithElevation;\n\n log.debug('Plugin registered', { platformRoles, scopeHeader });\n};\n\nexport default fp(elevationPlugin, {\n name: 'arc-elevation',\n fastify: '5.x',\n});\n\nexport { elevationPlugin };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAM,MAAM,OAAO,YAAY;AA4B/B,MAAM,kBAAwD,OAC5D,SACA,OAAyB,EAAE,KACxB;CACH,MAAM,EACJ,gBAAgB,CAAC,aAAa,EAC9B,cAAc,eACd,YAAY,qBACZ,gBACE;AAGJ,KAAI,CAAC,QAAQ,aAAa,eAAe,EAAE;AACzC,MAAI,KACF,iGAED;AACD;;CAGF,MAAM,uBAAuB,QAAQ;CAGrC,MAAM,4BAA4B,OAChC,SACA,UACkB;AAElB,QAAM,qBAAqB,KAAK,SAAS,SAAS,MAAM;AAGxD,MAAI,MAAM,KAAM;AAIhB,MADoB,QAAQ,QAAQ,iBAChB,WAAY;EAGhC,MAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,MAAM;AACT,OAAI,MAAM,6CAA6C;AACvD,SAAM,KAAK,IAAI,CAAC,KAAK;IACnB,SAAS;IACT,OAAO;IACP,SAAS;IACT,MAAM;IACP,CAAC;AACF;;EAGF,MAAM,YAAa,KAAK,SAAS,EAAE;AACnC,MAAI,CAAC,cAAc,MAAM,MAAM,UAAU,SAAS,EAAE,CAAC,EAAE;AACrD,OAAI,MAAM,2CAA2C;IACnD,QAAQ,KAAK,MAAM,KAAK;IACxB;IACA,UAAU;IACX,CAAC;AACF,SAAM,KAAK,IAAI,CAAC,KAAK;IACnB,SAAS;IACT,OAAO;IACP,SAAS;IACT,MAAM;IACP,CAAC;AACF;;EAIF,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,MAAM,SAAS,OAAO,KAAK,MAAM,KAAK,OAAO,UAAU;AAQvD,UAAQ,QANoB;GAC1B,MAAM;GACN,gBAAgB,SAAS;GACzB,YAAY;GACb;AAGD,MAAI,MAAM,kBAAkB;GAAE;GAAQ,gBAAgB;GAAO,CAAC;AAG9D,MAAI,YACF,KAAI;AACF,SAAM,YAAY;IAChB;IACA,gBAAgB,SAAS;IACzB;IACA,2BAAW,IAAI,MAAM;IACtB,CAAC;UACI;AAEN,OAAI,KAAK,kDAAkD;;;AAMjE,SAAQ,eAAe;AAEvB,KAAI,MAAM,qBAAqB;EAAE;EAAe;EAAa,CAAC;;AAGhE,wBAAe,GAAG,iBAAiB;CACjC,MAAM;CACN,SAAS;CACV,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
2
|
+
|
|
3
|
+
//#region src/scope/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Request Scope — The One Standard
|
|
6
|
+
*
|
|
7
|
+
* Discriminated union representing the access context of every request.
|
|
8
|
+
* Replaces scattered orgScope/orgRoles/organizationId/bypassRoles.
|
|
9
|
+
*
|
|
10
|
+
* Set once by auth adapters, read everywhere by permissions/presets/guards.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // In a permission check
|
|
15
|
+
* const scope = request.scope;
|
|
16
|
+
* if (isElevated(scope)) return true;
|
|
17
|
+
* if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Request scope — 4 kinds, 4 states, no ambiguity.
|
|
22
|
+
*
|
|
23
|
+
* | Kind | Meaning |
|
|
24
|
+
* |---------------|-----------------------------------|
|
|
25
|
+
* | public | No authentication |
|
|
26
|
+
* | authenticated | Logged in, no org context |
|
|
27
|
+
* | member | In an org with specific roles |
|
|
28
|
+
* | elevated | Platform admin, explicit elevation |
|
|
29
|
+
*/
|
|
30
|
+
type RequestScope = {
|
|
31
|
+
kind: 'public';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'authenticated';
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'member';
|
|
36
|
+
organizationId: string;
|
|
37
|
+
orgRoles: string[];
|
|
38
|
+
teamId?: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'elevated';
|
|
41
|
+
organizationId?: string;
|
|
42
|
+
elevatedBy: string;
|
|
43
|
+
};
|
|
44
|
+
/** Check if scope is `member` kind */
|
|
45
|
+
declare function isMember(scope: RequestScope): scope is Extract<RequestScope, {
|
|
46
|
+
kind: 'member';
|
|
47
|
+
}>;
|
|
48
|
+
/** Check if scope is `elevated` kind */
|
|
49
|
+
declare function isElevated(scope: RequestScope): scope is Extract<RequestScope, {
|
|
50
|
+
kind: 'elevated';
|
|
51
|
+
}>;
|
|
52
|
+
/** Check if scope has org access (member OR elevated) */
|
|
53
|
+
declare function hasOrgAccess(scope: RequestScope): boolean;
|
|
54
|
+
/** Check if request is authenticated (any kind except public) */
|
|
55
|
+
declare function isAuthenticated(scope: RequestScope): boolean;
|
|
56
|
+
/** Get organizationId from scope (if present) */
|
|
57
|
+
declare function getOrgId(scope: RequestScope): string | undefined;
|
|
58
|
+
/** Get org roles from scope (empty array if not a member) */
|
|
59
|
+
declare function getOrgRoles(scope: RequestScope): string[];
|
|
60
|
+
/** Get team ID from scope (only available on member kind) */
|
|
61
|
+
declare function getTeamId(scope: RequestScope): string | undefined;
|
|
62
|
+
/** Default public scope — used as initial decoration value */
|
|
63
|
+
declare const PUBLIC_SCOPE: Readonly<RequestScope>;
|
|
64
|
+
/** Default authenticated scope — used when user is logged in but no org */
|
|
65
|
+
declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/scope/elevation.d.ts
|
|
68
|
+
interface ElevationOptions {
|
|
69
|
+
/** Roles that can use elevation (default: ['superadmin']) */
|
|
70
|
+
platformRoles?: string[];
|
|
71
|
+
/** Header name for scope declaration (default: 'x-arc-scope') */
|
|
72
|
+
scopeHeader?: string;
|
|
73
|
+
/** Header name for target organization (default: 'x-organization-id') */
|
|
74
|
+
orgHeader?: string;
|
|
75
|
+
/** Called when elevation happens — use for audit logging */
|
|
76
|
+
onElevation?: (event: ElevationEvent) => void | Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
interface ElevationEvent {
|
|
79
|
+
userId: string;
|
|
80
|
+
organizationId?: string;
|
|
81
|
+
request: FastifyRequest;
|
|
82
|
+
timestamp: Date;
|
|
83
|
+
}
|
|
84
|
+
declare const elevationPlugin: FastifyPluginAsync<ElevationOptions>;
|
|
85
|
+
declare const _default: FastifyPluginAsync<ElevationOptions>;
|
|
86
|
+
//#endregion
|
|
87
|
+
export { AUTHENTICATED_SCOPE as a, getOrgId as c, hasOrgAccess as d, isAuthenticated as f, elevationPlugin as i, getOrgRoles as l, isMember as m, ElevationOptions as n, PUBLIC_SCOPE as o, isElevated as p, _default as r, RequestScope as s, ElevationEvent as t, getTeamId as u };
|
|
88
|
+
//# sourceMappingURL=elevation-B_2dRLVP.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elevation-B_2dRLVP.d.mts","names":[],"sources":["../src/scope/types.ts","../src/scope/elevation.ts"],"mappings":";;;;;;AA+BA;;;;;;;;;;;;;;;AAWA;;;;;;;;KAXY,YAAA;EACN,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;EAAgB,cAAA;EAAwB,QAAA;EAAoB,MAAA;AAAA;EAC5D,IAAA;EAAkB,cAAA;EAAyB,UAAA;AAAA;;iBAOjC,QAAA,CAAS,KAAA,EAAO,YAAA,GAAe,KAAA,IAAS,OAAA,CAAQ,YAAA;EAAgB,IAAA;AAAA;;iBAKhE,UAAA,CAAW,KAAA,EAAO,YAAA,GAAe,KAAA,IAAS,OAAA,CAAQ,YAAA;EAAgB,IAAA;AAAA;;iBAKlE,YAAA,CAAa,KAAA,EAAO,YAAA;AAApC;AAAA,iBAKgB,eAAA,CAAgB,KAAA,EAAO,YAAA;;iBASvB,QAAA,CAAS,KAAA,EAAO,YAAA;;iBAOhB,WAAA,CAAY,KAAA,EAAO,YAAA;;iBAMnB,SAAA,CAAU,KAAA,EAAO,YAAA;;cAUpB,YAAA,EAAc,QAAA,CAAS,YAAA;AAvBpC;AAAA,cA0Ba,mBAAA,EAAqB,QAAA,CAAS,YAAA;;;UClD1B,gBAAA;EDAmE;ECElF,aAAA;EDGc;ECDd,WAAA;;EAEA,SAAA;EDDgE;ECGhE,WAAA,IAAe,KAAA,EAAO,cAAA,YAA0B,OAAA;AAAA;AAAA,UAGjC,cAAA;EACf,MAAA;EACA,cAAA;EACA,OAAA,EAAS,cAAA;EACT,SAAA,EAAW,IAAA;AAAA;AAAA,cAOP,eAAA,EAAiB,kBAAA,CAAmB,gBAAA;AAAA,cAAgB,QAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { t as DomainEvent } from "./EventTransport-BD2U0BTc.mjs";
|
|
2
|
+
import { FastifyInstance, FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
3
|
+
|
|
4
|
+
//#region src/plugins/caching.d.ts
|
|
5
|
+
interface CachingRule {
|
|
6
|
+
/** Path prefix to match (e.g., '/api/products') */
|
|
7
|
+
match: string;
|
|
8
|
+
/** Cache-Control max-age in seconds */
|
|
9
|
+
maxAge: number;
|
|
10
|
+
/** Cache-Control: private vs public (default: public) */
|
|
11
|
+
private?: boolean;
|
|
12
|
+
/** stale-while-revalidate directive in seconds */
|
|
13
|
+
staleWhileRevalidate?: number;
|
|
14
|
+
}
|
|
15
|
+
interface CachingOptions {
|
|
16
|
+
/** Default max-age in seconds for Cache-Control (default: 0 = no-cache) */
|
|
17
|
+
maxAge?: number;
|
|
18
|
+
/** Enable ETag generation (default: true) */
|
|
19
|
+
etag?: boolean;
|
|
20
|
+
/** Enable conditional requests — 304 Not Modified (default: true) */
|
|
21
|
+
conditional?: boolean;
|
|
22
|
+
/** HTTP methods to cache (default: ['GET', 'HEAD']) */
|
|
23
|
+
methods?: string[];
|
|
24
|
+
/** Paths to exclude from caching (prefix match) */
|
|
25
|
+
exclude?: string[];
|
|
26
|
+
/** Custom cache rules per path prefix */
|
|
27
|
+
rules?: CachingRule[];
|
|
28
|
+
}
|
|
29
|
+
declare const cachingPlugin: FastifyPluginAsync<CachingOptions>;
|
|
30
|
+
declare const _default$1: FastifyPluginAsync<CachingOptions>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/plugins/sse.d.ts
|
|
33
|
+
interface SSEOptions {
|
|
34
|
+
/** SSE endpoint path (default: '/events/stream') */
|
|
35
|
+
path?: string;
|
|
36
|
+
/** Require authentication (default: true) */
|
|
37
|
+
requireAuth?: boolean;
|
|
38
|
+
/** Event patterns to stream (default: ['*'] = all) */
|
|
39
|
+
patterns?: string[];
|
|
40
|
+
/** Heartbeat interval in ms (default: 30000) */
|
|
41
|
+
heartbeat?: number;
|
|
42
|
+
/** Filter events by organizationId from request.scope (default: false) */
|
|
43
|
+
orgScoped?: boolean;
|
|
44
|
+
/** Custom event filter function */
|
|
45
|
+
filter?: (event: DomainEvent<unknown>, request: FastifyRequest) => boolean;
|
|
46
|
+
}
|
|
47
|
+
declare const ssePlugin: FastifyPluginAsync<SSEOptions>;
|
|
48
|
+
declare const _default: FastifyPluginAsync<SSEOptions>;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/plugins/errorHandler.d.ts
|
|
51
|
+
interface ErrorHandlerOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Include stack trace in error responses (default: false in production)
|
|
54
|
+
*/
|
|
55
|
+
includeStack?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Custom error callback for logging to external services
|
|
58
|
+
*/
|
|
59
|
+
onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Map specific error types to custom responses
|
|
62
|
+
*/
|
|
63
|
+
errorMap?: Record<string, {
|
|
64
|
+
statusCode: number;
|
|
65
|
+
code: string;
|
|
66
|
+
message?: string;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
declare function errorHandlerPluginFn(fastify: FastifyInstance, options?: ErrorHandlerOptions): Promise<void>;
|
|
70
|
+
declare const errorHandlerPlugin: typeof errorHandlerPluginFn;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { ssePlugin as a, _default$1 as c, _default as i, cachingPlugin as l, errorHandlerPlugin as n, CachingOptions as o, SSEOptions as r, CachingRule as s, ErrorHandlerOptions as t };
|
|
73
|
+
//# sourceMappingURL=errorHandler-BbcgBmIH.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler-BbcgBmIH.d.mts","names":[],"sources":["../src/plugins/caching.ts","../src/plugins/sse.ts","../src/plugins/errorHandler.ts"],"mappings":";;;;UAwBiB,WAAA;EAqBf;EAnBA,KAAA;EAqBQ;EAnBR,MAAA;EAmBmB;EAjBnB,OAAA;EAgID;EA9HC,oBAAA;AAAA;AAAA,UAGe,cAAA;EAoCqC;EAlCpD,MAAA;;EAEA,IAAA;;EAEA,WAAA;;EAEA,OAAA;ECTe;EDWf,OAAA;;EAEA,KAAA,GAAQ,WAAA;AAAA;AAAA,cAwBJ,aAAA,EAAe,kBAAA,CAAmB,cAAA;AAAA,cAAc,UAAA;;;UCrCrC,UAAA;EDWf;ECTA,IAAA;EDWQ;ECTR,WAAA;EDSmB;ECPnB,QAAA;EDsHD;ECpHC,SAAA;ED6BmB;EC3BnB,SAAA;ED2BoD;ECzBpD,MAAA,IAAU,KAAA,EAAO,WAAA,WAAsB,OAAA,EAAS,cAAA;AAAA;AAAA,cAO5C,SAAA,EAAW,kBAAA,CAAmB,UAAA;AAAA,cAAU,QAAA;;;UC/B7B,mBAAA;EFiBf;;;EEbA,YAAA;EFqBA;;;EEhBA,OAAA,IAAW,KAAA,EAAO,KAAA,EAAO,OAAA,EAAS,cAAA,YAA0B,OAAA;EFwCxD;;;EEnCJ,QAAA,GAAW,MAAA;IACT,UAAA;IACA,IAAA;IACA,OAAA;EAAA;AAAA;AAAA,iBAcW,oBAAA,CACb,OAAA,EAAS,eAAA,EACT,OAAA,GAAS,mBAAA,GACR,OAAA;AAAA,cAoJU,kBAAA,SAAkB,oBAAA"}
|