@classytic/arc 2.3.0 → 2.4.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.
Files changed (175) hide show
  1. package/README.md +187 -18
  2. package/bin/arc.js +11 -3
  3. package/dist/BaseController-CkM5dUh_.mjs +1031 -0
  4. package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
  5. package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
  6. package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
  7. package/dist/adapters/index.d.mts +3 -5
  8. package/dist/adapters/index.mjs +2 -3
  9. package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
  10. package/dist/audit/index.d.mts +4 -7
  11. package/dist/audit/index.mjs +2 -29
  12. package/dist/audit/mongodb.d.mts +1 -4
  13. package/dist/audit/mongodb.mjs +2 -3
  14. package/dist/auth/index.d.mts +7 -9
  15. package/dist/auth/index.mjs +65 -63
  16. package/dist/auth/redis-session.d.mts +1 -1
  17. package/dist/auth/redis-session.mjs +1 -2
  18. package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
  19. package/dist/cache/index.d.mts +23 -23
  20. package/dist/cache/index.mjs +4 -6
  21. package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
  22. package/dist/chunk-BpYLSNr0.mjs +14 -0
  23. package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
  24. package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
  25. package/dist/cli/commands/describe.mjs +24 -7
  26. package/dist/cli/commands/docs.mjs +6 -7
  27. package/dist/cli/commands/doctor.d.mts +10 -0
  28. package/dist/cli/commands/doctor.mjs +156 -0
  29. package/dist/cli/commands/generate.mjs +66 -17
  30. package/dist/cli/commands/init.mjs +315 -45
  31. package/dist/cli/commands/introspect.mjs +2 -4
  32. package/dist/cli/index.d.mts +1 -10
  33. package/dist/cli/index.mjs +4 -153
  34. package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
  35. package/dist/core/index.d.mts +3 -5
  36. package/dist/core/index.mjs +5 -4
  37. package/dist/core-C1XCMtqM.mjs +185 -0
  38. package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
  39. package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
  40. package/dist/discovery/index.mjs +37 -5
  41. package/dist/docs/index.d.mts +6 -9
  42. package/dist/docs/index.mjs +3 -21
  43. package/dist/dynamic/index.d.mts +93 -0
  44. package/dist/dynamic/index.mjs +122 -0
  45. package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
  46. package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
  47. package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
  48. package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
  49. package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
  50. package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
  51. package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
  52. package/dist/events/index.d.mts +72 -7
  53. package/dist/events/index.mjs +216 -4
  54. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  55. package/dist/events/transports/redis-stream-entry.mjs +19 -7
  56. package/dist/events/transports/redis.d.mts +1 -1
  57. package/dist/events/transports/redis.mjs +3 -4
  58. package/dist/factory/index.d.mts +23 -9
  59. package/dist/factory/index.mjs +48 -3
  60. package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
  61. package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
  62. package/dist/hooks/index.d.mts +1 -3
  63. package/dist/hooks/index.mjs +2 -3
  64. package/dist/idempotency/index.d.mts +5 -5
  65. package/dist/idempotency/index.mjs +3 -7
  66. package/dist/idempotency/mongodb.d.mts +1 -1
  67. package/dist/idempotency/mongodb.mjs +4 -5
  68. package/dist/idempotency/redis.d.mts +1 -1
  69. package/dist/idempotency/redis.mjs +2 -5
  70. package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
  73. package/dist/index.d.mts +100 -105
  74. package/dist/index.mjs +85 -58
  75. package/dist/integrations/event-gateway.d.mts +1 -1
  76. package/dist/integrations/event-gateway.mjs +8 -4
  77. package/dist/integrations/index.d.mts +4 -2
  78. package/dist/integrations/index.mjs +1 -1
  79. package/dist/integrations/jobs.d.mts +2 -2
  80. package/dist/integrations/jobs.mjs +63 -14
  81. package/dist/integrations/mcp/index.d.mts +219 -0
  82. package/dist/integrations/mcp/index.mjs +572 -0
  83. package/dist/integrations/mcp/testing.d.mts +53 -0
  84. package/dist/integrations/mcp/testing.mjs +104 -0
  85. package/dist/integrations/streamline.mjs +39 -19
  86. package/dist/integrations/webhooks.d.mts +56 -0
  87. package/dist/integrations/webhooks.mjs +139 -0
  88. package/dist/integrations/websocket-redis.d.mts +46 -0
  89. package/dist/integrations/websocket-redis.mjs +50 -0
  90. package/dist/integrations/websocket.d.mts +68 -2
  91. package/dist/integrations/websocket.mjs +96 -13
  92. package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
  93. package/dist/interface-DGmPxakH.d.mts +2213 -0
  94. package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
  95. package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
  96. package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
  97. package/dist/metrics-Csh4nsvv.mjs +224 -0
  98. package/dist/migrations/index.d.mts +113 -44
  99. package/dist/migrations/index.mjs +84 -102
  100. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  101. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  102. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  103. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  104. package/dist/org/index.d.mts +12 -14
  105. package/dist/org/index.mjs +92 -119
  106. package/dist/org/types.d.mts +2 -2
  107. package/dist/org/types.mjs +1 -1
  108. package/dist/permissions/index.d.mts +4 -278
  109. package/dist/permissions/index.mjs +4 -579
  110. package/dist/permissions-CA5zg0yK.mjs +751 -0
  111. package/dist/plugins/index.d.mts +104 -107
  112. package/dist/plugins/index.mjs +203 -313
  113. package/dist/plugins/response-cache.mjs +4 -69
  114. package/dist/plugins/tracing-entry.d.mts +1 -1
  115. package/dist/plugins/tracing-entry.mjs +24 -11
  116. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  117. package/dist/policies/index.d.mts +2 -2
  118. package/dist/policies/index.mjs +80 -83
  119. package/dist/presets/index.d.mts +26 -19
  120. package/dist/presets/index.mjs +2 -142
  121. package/dist/presets/multiTenant.d.mts +1 -4
  122. package/dist/presets/multiTenant.mjs +4 -6
  123. package/dist/presets-C9QXJV1u.mjs +422 -0
  124. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  125. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  126. package/dist/queryParser-CgCtsjti.mjs +352 -0
  127. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  128. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  129. package/dist/registry/index.d.mts +1 -4
  130. package/dist/registry/index.mjs +3 -4
  131. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  132. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  133. package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
  134. package/dist/rpc/index.d.mts +90 -0
  135. package/dist/rpc/index.mjs +248 -0
  136. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  137. package/dist/schemas/index.d.mts +30 -30
  138. package/dist/schemas/index.mjs +2 -4
  139. package/dist/scope/index.d.mts +13 -2
  140. package/dist/scope/index.mjs +18 -5
  141. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  142. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  143. package/dist/testing/index.d.mts +551 -567
  144. package/dist/testing/index.mjs +1744 -1799
  145. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  146. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  147. package/dist/types/index.d.mts +4 -946
  148. package/dist/types/index.mjs +2 -4
  149. package/dist/types-BJmgxNbF.d.mts +275 -0
  150. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  151. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  152. package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
  153. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  154. package/dist/utils/index.d.mts +254 -351
  155. package/dist/utils/index.mjs +7 -6
  156. package/dist/utils-Dc0WhlIl.mjs +594 -0
  157. package/dist/versioning-BzfeHmhj.mjs +37 -0
  158. package/package.json +44 -10
  159. package/skills/arc/SKILL.md +518 -0
  160. package/skills/arc/references/auth.md +250 -0
  161. package/skills/arc/references/events.md +272 -0
  162. package/skills/arc/references/integrations.md +385 -0
  163. package/skills/arc/references/mcp.md +431 -0
  164. package/skills/arc/references/production.md +610 -0
  165. package/skills/arc/references/testing.md +183 -0
  166. package/dist/audited-CGdLiSlE.mjs +0 -140
  167. package/dist/chunk-C7Uep-_p.mjs +0 -20
  168. package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
  169. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  170. package/dist/interface-BtdYtQUA.d.mts +0 -1114
  171. package/dist/presets-BTeYbw7h.d.mts +0 -57
  172. package/dist/presets-CeFtfDR8.mjs +0 -119
  173. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  174. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  175. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
@@ -1,16 +1,49 @@
1
1
  import { extname, join, resolve } from "node:path";
2
- import { readdir } from "node:fs/promises";
3
2
  import { pathToFileURL } from "node:url";
4
-
3
+ import { readdir } from "node:fs/promises";
5
4
  //#region src/discovery/index.ts
6
5
  /**
6
+ * Arc Auto-Discovery Plugin
7
+ *
8
+ * Automatically discovers and registers resource files matching a glob pattern.
9
+ * Eliminates manual resource registration boilerplate.
10
+ *
11
+ * This is a SEPARATE subpath import — only loaded when explicitly used:
12
+ * import { discoveryPlugin, discoverResources } from '@classytic/arc/discovery';
13
+ *
14
+ * Serverless-safe: only runs at startup, no persistent process needed.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { discoveryPlugin } from '@classytic/arc/discovery';
19
+ *
20
+ * // Auto-discovers all *.resource.ts files
21
+ * await fastify.register(discoveryPlugin, {
22
+ * paths: ['./src/modules'],
23
+ * pattern: '**\/*.resource.{ts,js}',
24
+ * });
25
+ *
26
+ * // Or use the helper directly
27
+ * import { discoverResources } from '@classytic/arc/discovery';
28
+ *
29
+ * const resources = await discoverResources({
30
+ * paths: ['./src/modules'],
31
+ * pattern: '**\/*.resource.{ts,js}',
32
+ * });
33
+ *
34
+ * for (const resource of resources) {
35
+ * await fastify.register(resource.toPlugin());
36
+ * }
37
+ * ```
38
+ */
39
+ /**
7
40
  * Match a filename against a simple pattern.
8
41
  * Supports: *.resource.ts, *.resource.js, *.resource.{ts,js}
9
42
  */
10
43
  function matchPattern(filename, pattern) {
11
44
  if (pattern.includes("{") && pattern.includes("}")) {
12
45
  const match = pattern.match(/\{([^}]+)\}/);
13
- if (match) return match[1].split(",").map((s) => s.trim()).some((alt) => {
46
+ if (match) return (match[1]?.split(",").map((s) => s.trim()) ?? []).some((alt) => {
14
47
  return matchPattern(filename, pattern.replace(match[0], alt));
15
48
  });
16
49
  }
@@ -104,6 +137,5 @@ const discoveryPluginImpl = async (fastify, options) => {
104
137
  };
105
138
  /** Auto-discovery plugin for Arc resources */
106
139
  const discoveryPlugin = discoveryPluginImpl;
107
-
108
140
  //#endregion
109
- export { discoveryPlugin as default, discoveryPlugin, discoverResources };
141
+ export { discoveryPlugin as default, discoveryPlugin, discoverResources };
@@ -1,8 +1,5 @@
1
- import "../elevation-DGo5shaX.mjs";
2
- import "../interface-BtdYtQUA.mjs";
3
- import "../types-RLkFVgaw.mjs";
4
- import { RegistryEntry } from "../types/index.mjs";
5
- import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
1
+ import { Q as RegistryEntry } from "../interface-DGmPxakH.mjs";
2
+ import { t as ExternalOpenApiPaths } from "../externalPaths-DpO-s7r8.mjs";
6
3
  import { FastifyPluginAsync } from "fastify";
7
4
 
8
5
  //#region src/docs/openapi.d.ts
@@ -74,19 +71,19 @@ interface Operation {
74
71
  responses: Record<string, Response>;
75
72
  security?: Array<Record<string, string[]>>;
76
73
  /** Arc permission metadata (OpenAPI extension) */
77
- 'x-arc-permission'?: {
74
+ "x-arc-permission"?: {
78
75
  type: string;
79
76
  roles?: readonly string[];
80
77
  };
81
78
  /** Arc pipeline steps (OpenAPI extension) */
82
- 'x-arc-pipeline'?: Array<{
79
+ "x-arc-pipeline"?: Array<{
83
80
  type: string;
84
81
  name: string;
85
82
  }>;
86
83
  }
87
84
  interface Parameter {
88
85
  name: string;
89
- in: 'path' | 'query' | 'header';
86
+ in: "path" | "query" | "header";
90
87
  required?: boolean;
91
88
  schema: SchemaObject;
92
89
  description?: string;
@@ -144,7 +141,7 @@ interface ScalarOptions {
144
141
  /** Page title */
145
142
  title?: string;
146
143
  /** Theme (default: 'default') */
147
- theme?: 'default' | 'alternate' | 'moon' | 'purple' | 'solarized' | 'bluePlanet' | 'saturn' | 'kepler' | 'mars' | 'deepSpace';
144
+ theme?: "default" | "alternate" | "moon" | "purple" | "solarized" | "bluePlanet" | "saturn" | "kepler" | "mars" | "deepSpace";
148
145
  /** Show sidebar (default: true) */
149
146
  showSidebar?: boolean;
150
147
  /** Dark mode (default: false) */
@@ -1,24 +1,7 @@
1
- import { t as getUserRoles } from "../types-DelU6kln.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-9nB_kiuR.mjs";
1
+ import { t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-CBmZ6EQN.mjs";
3
3
  import fp from "fastify-plugin";
4
-
5
4
  //#region src/docs/scalar.ts
6
- /**
7
- * Scalar API Reference Plugin
8
- *
9
- * Beautiful, modern API documentation UI.
10
- * Lighter and more modern than Swagger UI.
11
- *
12
- * @example
13
- * import { scalarPlugin } from '@classytic/arc/docs';
14
- *
15
- * await fastify.register(scalarPlugin, {
16
- * routePrefix: '/docs',
17
- * specUrl: '/_docs/openapi.json',
18
- * });
19
- *
20
- * // UI available at /docs
21
- */
22
5
  const scalarPlugin = async (fastify, opts = {}) => {
23
6
  const { routePrefix = "/docs", specUrl = "/_docs/openapi.json", title = "API Documentation", theme = "default", showSidebar = true, darkMode = false, authRoles = [], customCss = "", favicon } = opts;
24
7
  const scalarConfig = JSON.stringify({
@@ -69,6 +52,5 @@ var scalar_default = fp(scalarPlugin, {
69
52
  name: "arc-scalar",
70
53
  fastify: "5.x"
71
54
  });
72
-
73
55
  //#endregion
74
- export { buildOpenApiSpec, openapi_default as openApiPlugin, openApiPlugin as openApiPluginFn, scalar_default as scalarPlugin, scalarPlugin as scalarPluginFn };
56
+ export { buildOpenApiSpec, openapi_default as openApiPlugin, openApiPlugin as openApiPluginFn, scalar_default as scalarPlugin, scalarPlugin as scalarPluginFn };
@@ -0,0 +1,93 @@
1
+ import { Lt as ResourceDefinition, n as DataAdapter } from "../interface-DGmPxakH.mjs";
2
+ import { t as PermissionCheck } from "../types-BNUccdcf.mjs";
3
+
4
+ //#region src/dynamic/ArcDynamicLoader.d.ts
5
+ interface ArcArchitectureSchema {
6
+ /** Application name */
7
+ app: string;
8
+ /** Resources to provision */
9
+ resources: ArcResourceSchema[];
10
+ }
11
+ /** Field type — maps to JSON Schema / Zod types */
12
+ type ArcFieldType = "string" | "number" | "boolean" | "date" | "object" | "array";
13
+ /** Per-field definition — matches Arc's FieldRuleEntry for MCP compatibility */
14
+ interface ArcFieldSchema {
15
+ type: ArcFieldType;
16
+ required?: boolean;
17
+ description?: string;
18
+ enum?: string[];
19
+ min?: number;
20
+ max?: number;
21
+ minLength?: number;
22
+ maxLength?: number;
23
+ /** System-managed fields (createdAt, updatedAt) — excluded from create/update schemas */
24
+ systemManaged?: boolean;
25
+ /** Immutable after creation (e.g. slug, organizationId) */
26
+ immutable?: boolean;
27
+ }
28
+ /** Permission preset name — matches Arc's built-in presets */
29
+ type ArcPermissionPreset = "publicRead" | "publicReadAdminWrite" | "authenticated" | "adminOnly" | "ownerWithAdminBypass" | "fullPublic" | "readOnly";
30
+ /** Fine-grained per-operation permission */
31
+ interface ArcPermissionMap {
32
+ list?: "public" | "auth" | "admin";
33
+ get?: "public" | "auth" | "admin";
34
+ create?: "auth" | "admin";
35
+ update?: "auth" | "admin" | "owner";
36
+ delete?: "auth" | "admin" | "owner";
37
+ }
38
+ interface ArcResourceSchema {
39
+ /** Resource name (e.g., 'product', 'user') — used for URL prefix and tool names */
40
+ name: string;
41
+ /** Display name for docs and MCP descriptions (defaults to capitalized name) */
42
+ displayName?: string;
43
+ /** Custom URL prefix (defaults to `/${name}s`) */
44
+ prefix?: string;
45
+ /** Adapter resolution key — passed to adapterResolver */
46
+ adapterPattern?: string;
47
+ /** Permission preset name or fine-grained per-operation map */
48
+ permissions: ArcPermissionPreset | ArcPermissionMap;
49
+ /** Presets to apply (e.g., 'softDelete', 'slugLookup', 'bulk') */
50
+ presets?: string[];
51
+ /** Field definitions — drives schemaOptions.fieldRules for validation and MCP tool schemas */
52
+ fields?: Record<string, ArcFieldSchema | ArcFieldType>;
53
+ /** Fields allowed for filtering in list operations (drives queryParser + MCP) */
54
+ filterable?: string[];
55
+ /** Fields allowed for sorting (drives queryParser + MCP) */
56
+ sortable?: string[];
57
+ /** CRUD operations to disable (e.g., ['delete'] for append-only resources) */
58
+ disabledRoutes?: string[];
59
+ /** Tenant field name for multi-tenant resources */
60
+ tenantField?: string;
61
+ }
62
+ interface DynamicLoaderContext {
63
+ /** Resolve a data adapter for a resource — receives name and optional pattern key */
64
+ adapterResolver: (resourceName: string, pattern?: string) => DataAdapter;
65
+ /** Resolve custom permission checks beyond built-in presets */
66
+ permissionResolver?: (policy: string) => PermissionCheck;
67
+ }
68
+ /**
69
+ * Load an Arc Architecture Schema (JSON) and produce fully configured ResourceDefinitions.
70
+ *
71
+ * Each resource gets:
72
+ * - Adapter from the resolver
73
+ * - Permissions from presets or fine-grained map
74
+ * - schemaOptions.fieldRules for validation and MCP tool schemas
75
+ * - ArcQueryParser with allowedFilterFields/allowedSortFields for MCP auto-derive
76
+ * - Presets applied
77
+ */
78
+ declare class ArcDynamicLoader {
79
+ private context;
80
+ constructor(context: DynamicLoaderContext);
81
+ /**
82
+ * Load an AAS definition and return fully constructed ResourceDefinitions.
83
+ * Validates the schema before processing — throws on malformed input.
84
+ */
85
+ load(schema: ArcArchitectureSchema): ResourceDefinition<unknown>[];
86
+ private buildFieldRules;
87
+ private buildQueryParser;
88
+ private resolvePermissions;
89
+ private resolvePreset;
90
+ private resolveFinGrained;
91
+ }
92
+ //#endregion
93
+ export { ArcArchitectureSchema, ArcDynamicLoader, ArcFieldSchema, ArcFieldType, ArcPermissionMap, ArcPermissionPreset, ArcResourceSchema, DynamicLoaderContext };
@@ -0,0 +1,122 @@
1
+ import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
+ import { n as defineResource } from "../defineResource-D9aY5Cy6.mjs";
3
+ import { _ as ownerWithAdminBypass, b as publicReadAdminWrite, g as fullPublic, h as authenticated, m as adminOnly, x as readOnly, y as publicRead } from "../permissions-CA5zg0yK.mjs";
4
+ //#region src/dynamic/ArcDynamicLoader.ts
5
+ const VALID_FIELD_TYPES = new Set([
6
+ "string",
7
+ "number",
8
+ "boolean",
9
+ "date",
10
+ "object",
11
+ "array"
12
+ ]);
13
+ function validateSchema(schema) {
14
+ if (!schema.app || typeof schema.app !== "string") throw new Error("AAS: 'app' name is required");
15
+ if (!Array.isArray(schema.resources) || schema.resources.length === 0) throw new Error("AAS: 'resources' must be a non-empty array");
16
+ for (const r of schema.resources) {
17
+ if (!r.name || typeof r.name !== "string") throw new Error("AAS: each resource must have a 'name' string");
18
+ if (!r.permissions) throw new Error(`AAS: resource "${r.name}" must have 'permissions'`);
19
+ if (r.fields) for (const [fieldName, fieldDef] of Object.entries(r.fields)) {
20
+ const type = typeof fieldDef === "string" ? fieldDef : fieldDef.type;
21
+ if (!VALID_FIELD_TYPES.has(type)) throw new Error(`AAS: resource "${r.name}" field "${fieldName}" has invalid type "${type}". Valid types: ${[...VALID_FIELD_TYPES].join(", ")}`);
22
+ }
23
+ }
24
+ }
25
+ /**
26
+ * Load an Arc Architecture Schema (JSON) and produce fully configured ResourceDefinitions.
27
+ *
28
+ * Each resource gets:
29
+ * - Adapter from the resolver
30
+ * - Permissions from presets or fine-grained map
31
+ * - schemaOptions.fieldRules for validation and MCP tool schemas
32
+ * - ArcQueryParser with allowedFilterFields/allowedSortFields for MCP auto-derive
33
+ * - Presets applied
34
+ */
35
+ var ArcDynamicLoader = class {
36
+ context;
37
+ constructor(context) {
38
+ this.context = context;
39
+ }
40
+ /**
41
+ * Load an AAS definition and return fully constructed ResourceDefinitions.
42
+ * Validates the schema before processing — throws on malformed input.
43
+ */
44
+ load(schema) {
45
+ validateSchema(schema);
46
+ return schema.resources.map((r) => {
47
+ const adapter = this.context.adapterResolver(r.name, r.adapterPattern);
48
+ const fieldRules = this.buildFieldRules(r.fields);
49
+ const queryParser = this.buildQueryParser(r);
50
+ return defineResource({
51
+ name: r.name,
52
+ displayName: r.displayName,
53
+ prefix: r.prefix,
54
+ adapter,
55
+ queryParser,
56
+ presets: r.presets,
57
+ permissions: this.resolvePermissions(r.permissions),
58
+ disabledRoutes: r.disabledRoutes,
59
+ tenantField: r.tenantField,
60
+ schemaOptions: fieldRules ? {
61
+ fieldRules,
62
+ filterableFields: r.filterable
63
+ } : void 0
64
+ });
65
+ });
66
+ }
67
+ buildFieldRules(fields) {
68
+ if (!fields) return void 0;
69
+ const rules = {};
70
+ for (const [name, def] of Object.entries(fields)) rules[name] = typeof def === "string" ? { type: def } : def;
71
+ return rules;
72
+ }
73
+ buildQueryParser(r) {
74
+ if (!r.filterable && !r.sortable) return void 0;
75
+ return new ArcQueryParser({
76
+ allowedFilterFields: r.filterable,
77
+ allowedSortFields: r.sortable
78
+ });
79
+ }
80
+ resolvePermissions(policy) {
81
+ if (typeof policy === "string") return this.resolvePreset(policy);
82
+ return this.resolveFinGrained(policy);
83
+ }
84
+ resolvePreset(preset) {
85
+ switch (preset) {
86
+ case "publicRead": return publicRead();
87
+ case "publicReadAdminWrite": return publicReadAdminWrite();
88
+ case "authenticated": return authenticated();
89
+ case "adminOnly": return adminOnly();
90
+ case "ownerWithAdminBypass": return ownerWithAdminBypass();
91
+ case "fullPublic": return fullPublic();
92
+ case "readOnly": return readOnly();
93
+ default:
94
+ if (this.context.permissionResolver) {
95
+ const resolved = this.context.permissionResolver(preset);
96
+ if (resolved) return resolved;
97
+ }
98
+ throw new Error(`Unknown permission preset: "${preset}"`);
99
+ }
100
+ }
101
+ resolveFinGrained(policy) {
102
+ const pick = (preset, op) => preset[op] ?? authenticated()[op];
103
+ const map = {
104
+ public: (op) => pick(publicRead(), op),
105
+ auth: (op) => pick(authenticated(), op),
106
+ admin: (op) => pick(adminOnly(), op),
107
+ owner: (op) => pick(ownerWithAdminBypass(), op)
108
+ };
109
+ const permissions = {};
110
+ const ops = {
111
+ list: policy.list,
112
+ get: policy.get,
113
+ create: policy.create,
114
+ update: policy.update,
115
+ delete: policy.delete
116
+ };
117
+ for (const [op, level] of Object.entries(ops)) if (level && map[level]) permissions[op] = map[level](op);
118
+ return permissions;
119
+ }
120
+ };
121
+ //#endregion
122
+ export { ArcDynamicLoader };
@@ -1,39 +1,8 @@
1
- import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
2
- import { t as getUserRoles } from "./types-DelU6kln.mjs";
3
- import { t as arcLog } from "./logger-ByrvQWZO.mjs";
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
+ import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
3
+ import { t as arcLog } from "./logger-Dz3j1ItV.mjs";
4
4
  import fp from "fastify-plugin";
5
-
6
5
  //#region src/scope/elevation.ts
7
- /**
8
- * Elevation Plugin — Explicit Platform Admin Access
9
- *
10
- * Opt-in Fastify plugin that allows platform admins to explicitly
11
- * elevate their scope via the `x-arc-scope: platform` header.
12
- *
13
- * Without this header, a superadmin is treated as a normal user.
14
- * This prevents implicit bypass and enables audit logging.
15
- *
16
- * ## Lifecycle
17
- *
18
- * Elevation wraps `fastify.authenticate` so it always runs AFTER
19
- * authentication has set `request.user`. This avoids the `onRequest`
20
- * timing issue where `request.user` doesn't exist yet.
21
- *
22
- * Flow: `authenticate()` → user is set → `elevation check` → scope is set
23
- *
24
- * Inspired by Stripe Connect's `Stripe-Account` header.
25
- *
26
- * @example
27
- * ```typescript
28
- * const app = await createApp({
29
- * auth: { type: 'betterAuth', betterAuth: adapter },
30
- * elevation: {
31
- * platformRoles: ['superadmin'],
32
- * onElevation: (event) => auditLog.write(event),
33
- * },
34
- * });
35
- * ```
36
- */
37
6
  var elevation_exports = /* @__PURE__ */ __exportAll({
38
7
  default: () => elevation_default,
39
8
  elevationPlugin: () => elevationPlugin
@@ -80,6 +49,7 @@ const elevationPlugin = async (fastify, opts = {}) => {
80
49
  const userId = String(user.id ?? user._id ?? "unknown");
81
50
  request.scope = {
82
51
  kind: "elevated",
52
+ userId,
83
53
  organizationId: orgId || void 0,
84
54
  elevatedBy: userId
85
55
  };
@@ -108,6 +78,5 @@ var elevation_default = fp(elevationPlugin, {
108
78
  name: "arc-elevation",
109
79
  fastify: "5.x"
110
80
  });
111
-
112
81
  //#endregion
113
- export { elevation_default as n, elevation_exports as r, elevationPlugin as t };
82
+ export { elevation_default as n, elevation_exports as r, elevationPlugin as t };
@@ -15,6 +15,10 @@ import { FastifyPluginAsync, FastifyRequest } from "fastify";
15
15
  * const scope = request.scope;
16
16
  * if (isElevated(scope)) return true;
17
17
  * if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
18
+ *
19
+ * // Get user identity from scope
20
+ * const userId = getUserId(scope);
21
+ * const globalRoles = getUserRoles(scope);
18
22
  * ```
19
23
  */
20
24
  /**
@@ -26,28 +30,36 @@ import { FastifyPluginAsync, FastifyRequest } from "fastify";
26
30
  * | authenticated | Logged in, no org context |
27
31
  * | member | In an org with specific roles |
28
32
  * | elevated | Platform admin, explicit elevation |
33
+ *
34
+ * `userId` and `userRoles` are available on all authenticated variants.
35
+ * `orgRoles` are org-level roles (from membership); `userRoles` are global roles (from user document).
29
36
  */
30
37
  type RequestScope = {
31
- kind: 'public';
38
+ kind: "public";
32
39
  } | {
33
- kind: 'authenticated';
40
+ kind: "authenticated";
41
+ userId?: string;
42
+ userRoles?: string[];
34
43
  } | {
35
- kind: 'member';
44
+ kind: "member";
45
+ userId?: string;
46
+ userRoles: string[];
36
47
  organizationId: string;
37
48
  orgRoles: string[];
38
49
  teamId?: string;
39
50
  } | {
40
- kind: 'elevated';
51
+ kind: "elevated";
52
+ userId?: string;
41
53
  organizationId?: string;
42
54
  elevatedBy: string;
43
55
  };
44
56
  /** Check if scope is `member` kind */
45
57
  declare function isMember(scope: RequestScope): scope is Extract<RequestScope, {
46
- kind: 'member';
58
+ kind: "member";
47
59
  }>;
48
60
  /** Check if scope is `elevated` kind */
49
61
  declare function isElevated(scope: RequestScope): scope is Extract<RequestScope, {
50
- kind: 'elevated';
62
+ kind: "elevated";
51
63
  }>;
52
64
  /** Check if scope has org access (member OR elevated) */
53
65
  declare function hasOrgAccess(scope: RequestScope): boolean;
@@ -59,6 +71,28 @@ declare function getOrgId(scope: RequestScope): string | undefined;
59
71
  declare function getOrgRoles(scope: RequestScope): string[];
60
72
  /** Get team ID from scope (only available on member kind) */
61
73
  declare function getTeamId(scope: RequestScope): string | undefined;
74
+ /**
75
+ * Get userId from scope (available on authenticated, member, elevated).
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * import { getUserId } from '@classytic/arc/scope';
80
+ * const userId = getUserId(request.scope);
81
+ * ```
82
+ */
83
+ declare function getUserId(scope: RequestScope): string | undefined;
84
+ /**
85
+ * Get global user roles from scope (available on authenticated and member).
86
+ * These are user-level roles (e.g. superadmin, finance-admin) distinct from
87
+ * org-level roles (scope.orgRoles).
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * import { getUserRoles } from '@classytic/arc/scope';
92
+ * const globalRoles = getUserRoles(request.scope);
93
+ * ```
94
+ */
95
+ declare function getUserRoles(scope: RequestScope): string[];
62
96
  /** Default public scope — used as initial decoration value */
63
97
  declare const PUBLIC_SCOPE: Readonly<RequestScope>;
64
98
  /** Default authenticated scope — used when user is logged in but no org */
@@ -84,4 +118,4 @@ interface ElevationEvent {
84
118
  declare const elevationPlugin: FastifyPluginAsync<ElevationOptions>;
85
119
  declare const _default: FastifyPluginAsync<ElevationOptions>;
86
120
  //#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 };
121
+ export { AUTHENTICATED_SCOPE as a, getOrgId as c, getUserId as d, getUserRoles as f, isMember as g, isElevated as h, elevationPlugin as i, getOrgRoles as l, isAuthenticated as m, ElevationOptions as n, PUBLIC_SCOPE as o, hasOrgAccess as p, _default as r, RequestScope as s, ElevationEvent as t, getTeamId as u };
@@ -1,7 +1,6 @@
1
- import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
2
- import { f as isArcError } from "./errors-DBANPbGr.mjs";
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
+ import { f as isArcError } from "./errors-rxhfP7Hf.mjs";
3
3
  import fp from "fastify-plugin";
4
-
5
4
  //#region src/plugins/errorHandler.ts
6
5
  var errorHandler_exports = /* @__PURE__ */ __exportAll({
7
6
  default: () => errorHandlerPlugin,
@@ -103,6 +102,5 @@ const errorHandlerPlugin = fp(errorHandlerPluginFn, {
103
102
  name: "arc-error-handler",
104
103
  fastify: "5.x"
105
104
  });
106
-
107
105
  //#endregion
108
- export { errorHandler_exports as n, errorHandlerPlugin as t };
106
+ export { errorHandler_exports as n, errorHandlerPlugin as t };
@@ -0,0 +1,139 @@
1
+ import { t as DomainEvent } from "./EventTransport-wc5hSLik.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$3: 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$2: FastifyPluginAsync<SSEOptions>;
49
+ //#endregion
50
+ //#region src/plugins/metrics.d.ts
51
+ interface MetricsOptions {
52
+ /** Endpoint path (default: '/_metrics') */
53
+ path?: string;
54
+ /** Prefix for all metric names (default: 'arc') */
55
+ prefix?: string;
56
+ /** Called after metrics are collected (for OTLP push, etc.) */
57
+ onCollect?: (metrics: MetricEntry[]) => void;
58
+ }
59
+ interface MetricEntry {
60
+ name: string;
61
+ type: "counter" | "histogram" | "gauge";
62
+ help: string;
63
+ values: Array<{
64
+ labels: Record<string, string>;
65
+ value: number;
66
+ }>;
67
+ }
68
+ interface MetricsCollector {
69
+ /** Get all metrics as structured data */
70
+ collect(): MetricEntry[];
71
+ /** Reset all metrics */
72
+ reset(): void;
73
+ /** Record a CRUD operation */
74
+ recordOperation(resource: string, operation: string, status: number, durationMs: number): void;
75
+ /** Record a cache hit */
76
+ recordCacheHit(resource: string): void;
77
+ /** Record a cache miss */
78
+ recordCacheMiss(resource: string): void;
79
+ /** Record an event publish */
80
+ recordEventPublish(eventType: string): void;
81
+ /** Record an event consume */
82
+ recordEventConsume(eventType: string): void;
83
+ /** Record a circuit breaker state change */
84
+ recordCircuitBreakerState(service: string, state: string): void;
85
+ }
86
+ declare module "fastify" {
87
+ interface FastifyInstance {
88
+ metrics: MetricsCollector;
89
+ }
90
+ }
91
+ declare const metricsPlugin: FastifyPluginAsync<MetricsOptions>;
92
+ declare const _default$1: FastifyPluginAsync<MetricsOptions>;
93
+ //#endregion
94
+ //#region src/plugins/versioning.d.ts
95
+ interface VersioningOptions {
96
+ /** Versioning strategy */
97
+ type: "header" | "prefix";
98
+ /** Default version when none specified (default: '1') */
99
+ defaultVersion?: string;
100
+ /** Header name to read (default: 'accept-version') */
101
+ headerName?: string;
102
+ /** Response header name (default: 'x-api-version') */
103
+ responseHeader?: string;
104
+ /** Deprecated versions — adds Deprecation + Sunset headers */
105
+ deprecated?: string[];
106
+ /** Sunset date for deprecated versions (ISO 8601) */
107
+ sunset?: string;
108
+ }
109
+ declare module "fastify" {
110
+ interface FastifyRequest {
111
+ apiVersion: string;
112
+ }
113
+ }
114
+ declare const versioningPlugin: FastifyPluginAsync<VersioningOptions>;
115
+ declare const _default: FastifyPluginAsync<VersioningOptions>;
116
+ //#endregion
117
+ //#region src/plugins/errorHandler.d.ts
118
+ interface ErrorHandlerOptions {
119
+ /**
120
+ * Include stack trace in error responses (default: false in production)
121
+ */
122
+ includeStack?: boolean;
123
+ /**
124
+ * Custom error callback for logging to external services
125
+ */
126
+ onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
127
+ /**
128
+ * Map specific error types to custom responses
129
+ */
130
+ errorMap?: Record<string, {
131
+ statusCode: number;
132
+ code: string;
133
+ message?: string;
134
+ }>;
135
+ }
136
+ declare function errorHandlerPluginFn(fastify: FastifyInstance, options?: ErrorHandlerOptions): Promise<void>;
137
+ declare const errorHandlerPlugin: typeof errorHandlerPluginFn;
138
+ //#endregion
139
+ export { cachingPlugin as _, versioningPlugin as a, MetricsOptions as c, SSEOptions as d, _default$2 as f, _default$3 as g, CachingRule as h, _default as i, _default$1 as l, CachingOptions as m, errorHandlerPlugin as n, MetricEntry as o, ssePlugin as p, VersioningOptions as r, MetricsCollector as s, ErrorHandlerOptions as t, metricsPlugin as u };
@@ -206,6 +206,5 @@ function createError(statusCode, message, details) {
206
206
  function isArcError(error) {
207
207
  return error instanceof ArcError;
208
208
  }
209
-
210
209
  //#endregion
211
- export { OrgAccessDeniedError as a, ServiceUnavailableError as c, createError as d, isArcError as f, NotFoundError as i, UnauthorizedError as l, ConflictError as n, OrgRequiredError as o, ForbiddenError as r, RateLimitError as s, ArcError as t, ValidationError as u };
210
+ export { OrgAccessDeniedError as a, ServiceUnavailableError as c, createError as d, isArcError as f, NotFoundError as i, UnauthorizedError as l, ConflictError as n, OrgRequiredError as o, ForbiddenError as r, RateLimitError as s, ArcError as t, ValidationError as u };