@classytic/arc 2.19.0 → 2.21.0

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 (139) hide show
  1. package/README.md +1 -1
  2. package/bin/arc.js +37 -0
  3. package/dist/{BaseController-Dy6EQHjM.mjs → BaseController-BnLYgE9A.mjs} +4 -4
  4. package/dist/{EventTransport-IEn7Tn-Q.d.mts → EventTransport-QmY7A258.d.mts} +0 -1
  5. package/dist/{QueryCache-TRfXau3W.d.mts → QueryCache-Z1B38koh.d.mts} +0 -1
  6. package/dist/{ResourceRegistry-5fKZwhK_.mjs → ResourceRegistry-7RnrEWNP.mjs} +1 -1
  7. package/dist/{actionPermissions-D7NGwcAR.mjs → actionPermissions-ze70xXJi.mjs} +1 -1
  8. package/dist/audit/index.d.mts +9 -5
  9. package/dist/audit/index.mjs +1 -1
  10. package/dist/auth/audit.d.mts +7 -4
  11. package/dist/auth/audit.mjs +5 -0
  12. package/dist/auth/index.d.mts +7 -14
  13. package/dist/auth/index.mjs +7 -5
  14. package/dist/auth/redis-session.d.mts +1 -2
  15. package/dist/{buildHandler-B0qvAguf.mjs → buildHandler-D_2ULWxZ.mjs} +3 -3
  16. package/dist/cache/index.d.mts +2 -3
  17. package/dist/cache/index.mjs +2 -2
  18. package/dist/cli/commands/describe.d.mts +1 -2
  19. package/dist/cli/commands/describe.mjs +2 -2
  20. package/dist/cli/commands/docs.mjs +2 -2
  21. package/dist/cli/commands/generate.mjs +1 -1
  22. package/dist/cli/commands/init.mjs +1 -1
  23. package/dist/cli/commands/introspect.mjs +4 -13
  24. package/dist/cli/index.mjs +2 -2
  25. package/dist/{constants-gaEeMlz-.mjs → constants-DNre0wRg.mjs} +1 -1
  26. package/dist/context/index.d.mts +2 -2
  27. package/dist/core/index.d.mts +3 -3
  28. package/dist/core/index.mjs +5 -5
  29. package/dist/{core-Ba-6Sue6.mjs → core-DZ6Xe1jR.mjs} +182 -55
  30. package/dist/{createActionRouter-B_WzBFyf.mjs → createActionRouter-BWMDW-dL.mjs} +4 -4
  31. package/dist/{createAggregationRouter-B_G_nIjm.mjs → createAggregationRouter-l5mNdYrZ.mjs} +5 -4
  32. package/dist/{createApp--703hQE1.mjs → createApp-Bi61XxqU.mjs} +229 -24
  33. package/dist/discovery/index.d.mts +0 -1
  34. package/dist/docs/index.d.mts +1 -2
  35. package/dist/docs/index.mjs +1 -1
  36. package/dist/{elevation-xGUBq6p_.d.mts → elevation-C9s5hFP_.d.mts} +0 -1
  37. package/dist/encryption/index.d.mts +2 -144
  38. package/dist/{errorHandler-BtUUDPEa.d.mts → errorHandler-B0e-bq-O.d.mts} +0 -1
  39. package/dist/{errors-DOHkjQlD.d.mts → errors-DHcHVAgk.d.mts} +0 -1
  40. package/dist/{eventPlugin-DbBFd3x_.d.mts → eventPlugin-A6EbthXR.d.mts} +10 -6
  41. package/dist/events/index.d.mts +9 -6
  42. package/dist/events/index.mjs +9 -6
  43. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  44. package/dist/events/transports/redis.d.mts +1 -2
  45. package/dist/factory/index.d.mts +5 -4
  46. package/dist/factory/index.mjs +3 -3
  47. package/dist/{fastify-augmentation-D-5_yQJi.d.mts → fastify-augmentation-DafigGQp.d.mts} +12 -2
  48. package/dist/{fields-C7ahdGYf.d.mts → fields-BGqrAa_u.d.mts} +1 -2
  49. package/dist/{generate-Brans3fW.mjs → generate-usRSdQmP.mjs} +4 -3
  50. package/dist/hooks/index.d.mts +1 -1
  51. package/dist/idempotency/index.d.mts +4 -3
  52. package/dist/idempotency/index.mjs +1 -1
  53. package/dist/idempotency/redis.d.mts +1 -1
  54. package/dist/{index-CP6A1Ffj.d.mts → index-B5N-NaHs.d.mts} +88 -7
  55. package/dist/{index-CRKiiP5o.d.mts → index-B5PoNbfu.d.mts} +41 -7
  56. package/dist/{index-CUTG2Cuo.d.mts → index-Dtrj6XZk.d.mts} +90 -29
  57. package/dist/index.d.mts +7 -7
  58. package/dist/index.mjs +7 -7
  59. package/dist/{init-Bqpt1Hxf.mjs → init-0YhHkXGk.mjs} +239 -140
  60. package/dist/integrations/event-gateway.d.mts +2 -3
  61. package/dist/integrations/index.d.mts +2 -2
  62. package/dist/integrations/jobs.d.mts +0 -1
  63. package/dist/integrations/mcp/index.d.mts +6 -7
  64. package/dist/integrations/mcp/index.mjs +1 -1
  65. package/dist/integrations/mcp/testing.d.mts +1 -2
  66. package/dist/integrations/mcp/testing.mjs +1 -1
  67. package/dist/integrations/streamline.d.mts +19 -7
  68. package/dist/integrations/streamline.mjs +9 -5
  69. package/dist/integrations/webhooks.d.mts +0 -1
  70. package/dist/integrations/websocket-pushref-redis.d.mts +1 -2
  71. package/dist/integrations/websocket-redis.d.mts +1 -2
  72. package/dist/integrations/websocket.d.mts +2 -2
  73. package/dist/{loadResources-B8UmpITt.mjs → loadResources-DpU8pfjV.mjs} +4 -3
  74. package/dist/{loadResourcesFromEntry-BTlFEx05.mjs → loadResourcesFromEntry-OMCvIEE7.mjs} +1 -1
  75. package/dist/{mcpPlugin-C088Nd-G.mjs → mcpPlugin-B9XjjNVG.mjs} +148 -49
  76. package/dist/middleware/index.d.mts +1 -2
  77. package/dist/middleware/index.mjs +1 -1
  78. package/dist/module-WeuU5jfd.d.mts +429 -0
  79. package/dist/{openapi-DZ-5a5Op.mjs → openapi-Kyod_di6.mjs} +3 -3
  80. package/dist/permissions/index.d.mts +3 -3
  81. package/dist/permissions/index.mjs +2 -2
  82. package/dist/{permissions-BPB1xqGf.mjs → permissions-BJMuKMl7.mjs} +43 -3
  83. package/dist/pipeline/index.d.mts +1 -2
  84. package/dist/plugins/index.d.mts +87 -10
  85. package/dist/plugins/index.mjs +91 -4
  86. package/dist/plugins/response-cache.d.mts +5 -3
  87. package/dist/plugins/tracing-entry.d.mts +1 -1
  88. package/dist/plugins/tracing-entry.mjs +1 -1
  89. package/dist/{pluralize-DccFV9R7.mjs → pluralize-BhrgwZb9.mjs} +3 -3
  90. package/dist/presets/filesUpload.d.mts +4 -5
  91. package/dist/presets/filesUpload.mjs +1 -1
  92. package/dist/presets/index.d.mts +16 -3
  93. package/dist/presets/index.mjs +1 -1
  94. package/dist/presets/multiTenant.d.mts +1 -2
  95. package/dist/presets/multiTenant.mjs +1 -1
  96. package/dist/presets/search.d.mts +2 -3
  97. package/dist/presets/search.mjs +3 -2
  98. package/dist/{presets-BtyFwTO4.mjs → presets-gw0ciIUD.mjs} +1 -1
  99. package/dist/{pushref-registry-DnCqL3aa.d.mts → pushref-registry-BbcnpNTm.d.mts} +4 -2
  100. package/dist/{queryCachePlugin-xzFyKAjT.d.mts → queryCachePlugin-CfYWq-Ak.d.mts} +1 -2
  101. package/dist/{queryCachePlugin-emHb0CuX.mjs → queryCachePlugin-DzqLN904.mjs} +2 -2
  102. package/dist/{redis-C3W0qbz8.d.mts → redis-UDtLaNj6.d.mts} +0 -1
  103. package/dist/{redis-stream-DsP1vqZB.d.mts → redis-stream-iVR2l7aS.d.mts} +1 -2
  104. package/dist/{registerRoute-BdV8DDwU.mjs → registerRoute-oGAl0oA8.mjs} +1 -1
  105. package/dist/registry/index.d.mts +2 -2
  106. package/dist/registry/index.mjs +2 -2
  107. package/dist/{registry-Bm9LjWKs.mjs → registry-vcOCtPij.mjs} +1 -1
  108. package/dist/{schemaIR-D1MJwqmp.mjs → schemaIR-ajetw3Fv.mjs} +1 -42
  109. package/dist/schemas/index.d.mts +0 -1
  110. package/dist/scim/index.d.mts +0 -1
  111. package/dist/scim/index.mjs +2 -2
  112. package/dist/scope/index.d.mts +2 -3
  113. package/dist/{sessionManager-BXG-HGwG.d.mts → sessionManager-BHOM3N_0.d.mts} +10 -6
  114. package/dist/{storage-Co_PQpeQ.d.mts → storage-DLUZ04kK.d.mts} +8 -4
  115. package/dist/sync/changelog.d.mts +2 -0
  116. package/dist/sync/changelog.mjs +2 -0
  117. package/dist/testing/index.d.mts +2 -3
  118. package/dist/testing/index.mjs +6 -3
  119. package/dist/testing/storageContract.d.mts +1 -2
  120. package/dist/{tracing-D8jlGXP7.d.mts → tracing-CsGKQJGg.d.mts} +0 -1
  121. package/dist/types/index.d.mts +4 -4
  122. package/dist/types/storage.d.mts +1 -1
  123. package/dist/types-Cc91nXuA.d.mts +132 -0
  124. package/dist/{types-LNebCoJx.d.mts → types-DH0GupI7.d.mts} +8 -4
  125. package/dist/{types-DpvSF5Hh.d.mts → types-DRj-Ut4n.d.mts} +1 -2
  126. package/dist/{types-9_wtNPsI.d.mts → types-DpxoNX5v.d.mts} +112 -214
  127. package/dist/utils/index.d.mts +182 -128
  128. package/dist/utils/index.mjs +2 -2
  129. package/dist/{utils-C6QQ9-hd.mjs → utils-CO8a3B_1.mjs} +1435 -1391
  130. package/dist/{validate-CRcaWzp2.mjs → validate-C2mGgSXg.mjs} +3 -3
  131. package/dist/{versioning-D_eBCfNr.d.mts → versioning-DQ4mCvg0.d.mts} +1 -2
  132. package/dist/{websocket-MlYlrYq0.d.mts → websocket-uuWqM6UY.d.mts} +5 -4
  133. package/package.json +18 -13
  134. package/skills/arc/SKILL.md +10 -0
  135. package/skills/arc/references/mcp.md +2 -0
  136. package/skills/arc-module-publishing/SKILL.md +242 -0
  137. package/skills/arc-module-publishing/references/authoring-modules.md +272 -0
  138. package/skills/arc-module-publishing/references/package-manifest.md +155 -0
  139. package/skills/arc-module-publishing/references/publishing-workflow.md +182 -0
@@ -0,0 +1,429 @@
1
+ import { n as ErrorMapper } from "./errorHandler-B0e-bq-O.mjs";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/factory/loadResources.d.ts
4
+ /**
5
+ * loadResources — Auto-discover resource files from a directory.
6
+ *
7
+ * Scans for `*.resource.{ts,js,mts,mjs}` files, imports each,
8
+ * and collects their default exports. No barrel file needed.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { createApp, loadResources } from '@classytic/arc/factory';
13
+ *
14
+ * // Recommended: import.meta.url — works in both src/ (dev) and dist/ (prod)
15
+ * const app = await createApp({
16
+ * resources: await loadResources(import.meta.url),
17
+ * auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } },
18
+ * });
19
+ *
20
+ * // Or explicit path (must match runtime layout)
21
+ * const app2 = await createApp({
22
+ * resources: await loadResources('./src/resources'),
23
+ * });
24
+ * ```
25
+ *
26
+ * File convention:
27
+ * ```
28
+ * src/resources/
29
+ * product/product.resource.ts → export default defineResource({ name: 'product', ... })
30
+ * order/order.resource.ts → export default defineResource({ name: 'order', ... })
31
+ * ```
32
+ */
33
+ /**
34
+ * Resource interface — the contract between `loadResources`/`createApp` and resource definitions.
35
+ *
36
+ * Matches the shape of `ResourceDefinition` from `defineResource()` without requiring
37
+ * the import. All properties except `toPlugin` are optional so plain objects work too:
38
+ *
39
+ * ```typescript
40
+ * // Full resource (from defineResource)
41
+ * const product = defineResource({ name: 'product', ... }); // satisfies ResourceLike
42
+ *
43
+ * // Minimal resource (plain object)
44
+ * const simple: ResourceLike = { name: 'ping', toPlugin: () => () => {} };
45
+ * ```
46
+ *
47
+ * **DO NOT add an index signature** (`[key: string]: unknown`) to this interface.
48
+ * Class instances (like `ResourceDefinition`) don't implicitly carry index signatures,
49
+ * so adding one here makes `ResourceDefinition` *unassignable* to `ResourceLike` —
50
+ * the exact opposite of the intent. TypeScript's structural typing already allows
51
+ * classes with extra properties to satisfy this interface without an index signature.
52
+ */
53
+ interface ResourceLike {
54
+ /** Plugin factory — called by createApp to register routes */
55
+ toPlugin: () => unknown;
56
+ /** Resource name (used for route generation, logging, duplicate detection) */
57
+ name?: string;
58
+ /** Route prefix (default: `/${name}s`) */
59
+ prefix?: string;
60
+ /** Skip the global `resourcePrefix` from createApp — register at root */
61
+ skipGlobalPrefix?: boolean;
62
+ /** Display name for docs/OpenAPI */
63
+ displayName?: string;
64
+ /** Applied preset names */
65
+ _appliedPresets?: string[];
66
+ }
67
+ /**
68
+ * Resource module — what a `.resource.ts` file's default (or named) export
69
+ * may resolve to.
70
+ *
71
+ * Two shapes accepted:
72
+ * 1. **Plain `ResourceLike`** — the result of `defineResource({...})`.
73
+ * Used as-is; no engine wiring needed.
74
+ * 2. **Factory `(ctx: TContext) => ResourceLike | Promise<ResourceLike>`**
75
+ * — a function arc calls with the `context` from `LoadResourcesOptions`.
76
+ * Eliminates the parallel `createXResource(engine)` factory files +
77
+ * `exclude: [...]` bookkeeping that engine-bound resources used to need.
78
+ *
79
+ * Detection is by `typeof === 'function'`: `ResourceDefinition` instances
80
+ * (returned by `defineResource()`) are class instances (`typeof === 'object'`),
81
+ * so the two shapes are unambiguous in practice.
82
+ */
83
+ type ResourceModule<TContext = unknown> = ResourceLike | ((ctx: TContext) => ResourceLike | Promise<ResourceLike>);
84
+ interface LoadResourcesOptions<TContext = unknown> {
85
+ /** File pattern suffix (default: '.resource'). Matches `*.resource.{ts,js,mts,mjs}`. */
86
+ suffix?: string;
87
+ /** Recurse into subdirectories (default: true) */
88
+ recursive?: boolean;
89
+ /**
90
+ * Context passed to factory-style default exports. Resources whose default
91
+ * export is a function `(ctx) => ResourceLike` are called with this value;
92
+ * plain `ResourceLike` exports are returned unchanged.
93
+ *
94
+ * Use this to thread engine handles into engine-bound resources without
95
+ * creating parallel factory files outside `loadResources`'s sweep:
96
+ *
97
+ * ```ts
98
+ * // category.resource.ts
99
+ * import type { AppContext } from '#core/app/context.js';
100
+ * export default (ctx: AppContext) =>
101
+ * defineResource({
102
+ * name: 'category',
103
+ * adapter: createMongooseAdapter(
104
+ * ctx.catalog.models.Category,
105
+ * ctx.catalog.repositories.category,
106
+ * ),
107
+ * });
108
+ *
109
+ * // create-arc-app-options.ts
110
+ * resources: async () => {
111
+ * const [catalog, flow] = await Promise.all([
112
+ * ensureCatalogEngine(),
113
+ * ensureFlowEngine(),
114
+ * ]);
115
+ * return loadResources(import.meta.url, { context: { catalog, flow } });
116
+ * }
117
+ * ```
118
+ *
119
+ * Backwards compatible — pre-2.11.1 callers omit `context`, plain exports
120
+ * keep working unchanged. Factories that need a context but receive
121
+ * `undefined` should narrow defensively.
122
+ */
123
+ context?: TContext;
124
+ /**
125
+ * Resource names to exclude. Matched against the resource's `.name` property
126
+ * after import, so you use the resource name (not the filename).
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * await loadResources('./src/resources', { exclude: ['debug', 'legacy-report'] })
131
+ * ```
132
+ */
133
+ exclude?: string[];
134
+ /**
135
+ * Resource names to include. When set, only matching resources are returned.
136
+ * Takes priority over `exclude`.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * // Only load these two resources (useful for testing or microservice splits)
141
+ * await loadResources('./src/resources', { include: ['product', 'order'] })
142
+ * ```
143
+ */
144
+ include?: string[];
145
+ /**
146
+ * Path-level gate, called for every discovered file BEFORE it is imported.
147
+ * Return `false` to skip the file entirely — its module never evaluates,
148
+ * so top-level side effects (engine construction, model registration) of
149
+ * gated-off features never fire. That's the difference from
150
+ * `include`/`exclude`, which match resource NAMES after import.
151
+ *
152
+ * The feature-flag → resource-dir manifest every tiered SaaS host carries
153
+ * becomes one callback:
154
+ *
155
+ * ```ts
156
+ * await loadResources(import.meta.url, {
157
+ * filter: (path) => {
158
+ * if (path.includes(`${sep}loyalty${sep}`)) return isFeatureEnabled('loyalty');
159
+ * if (path.includes(`${sep}pos${sep}`)) return isFeatureEnabled('pos');
160
+ * return true;
161
+ * },
162
+ * });
163
+ * ```
164
+ *
165
+ * Receives the ABSOLUTE file path. Gating everything is legitimate — the
166
+ * result is an empty array (interacts with `strictResourceDir` like any
167
+ * other empty discovery).
168
+ */
169
+ filter?: (absolutePath: string) => boolean;
170
+ /**
171
+ * Optional logger override for diagnostics. When omitted, warnings flow
172
+ * through arc's standard logger (`arcLog('loadResources')`) — same path
173
+ * as every other arc-internal warning, controlled by `ARC_SUPPRESS_WARNINGS=1`
174
+ * and routable via `configureArcLogger({ writer })`.
175
+ *
176
+ * Pass any object with `warn(msg)` to override (e.g. `fastify.log` —
177
+ * which is what `registerResources` passes automatically when arc's
178
+ * factory triggers auto-discovery via `resourceDir`).
179
+ *
180
+ * Pre-2.11.1 had a `silent: boolean` flag and "silent by default" semantics;
181
+ * both removed. Migration:
182
+ *
183
+ * ```ts
184
+ * // Pre-2.11.1 // 2.11.1+
185
+ * loadResources(url, { silent: true }); // ARC_SUPPRESS_WARNINGS=1 (env)
186
+ * // OR configureArcLogger({ writer })
187
+ * loadResources(url, { silent: !isDev }); // ARC_SUPPRESS_WARNINGS=1 in prod
188
+ * loadResources(url, { logger: pinoAdapter }); // unchanged — still works
189
+ * ```
190
+ */
191
+ logger?: {
192
+ warn: (msg: string) => void;
193
+ };
194
+ }
195
+ /**
196
+ * Scan a directory for resource files and import their default exports.
197
+ *
198
+ * Accepts a directory path OR `import.meta.url` (file:// URL).
199
+ * When given a URL, resolves to the directory containing that file —
200
+ * so `loadResources(import.meta.url)` works in both dev (`src/`) and
201
+ * production (`dist/`) without path gymnastics.
202
+ *
203
+ * @param dir - Directory path, or `import.meta.url` (file:// URL resolved to its dirname)
204
+ * @param options - Pattern and recursion options
205
+ * @returns Array of resource definitions (anything with `.toPlugin()`)
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * // Works from both src/ and dist/ — resolves relative to the calling file
210
+ * await loadResources(import.meta.url);
211
+ *
212
+ * // Subdirectory relative to the calling file
213
+ * await loadResources(import.meta.url, { suffix: '.resource' });
214
+ *
215
+ * // Explicit path (must match runtime layout)
216
+ * await loadResources('./src/resources');
217
+ * ```
218
+ */
219
+ declare function loadResources<TContext = unknown>(dir: string, options?: LoadResourcesOptions<TContext>): Promise<ResourceLike[]>;
220
+ //#endregion
221
+ //#region src/factory/module.d.ts
222
+ interface ArcModule<TExports = unknown> {
223
+ /** Stable identifier — appears in boot logs and duplicate-module detection. */
224
+ readonly name: string;
225
+ /**
226
+ * Names of modules this one must be composed AFTER. arc topologically orders
227
+ * the `modules` array from these edges before ANY phase runs, so a
228
+ * dependency's `bootstrap` (and thus its `fastify.arc.modules[dep]` export)
229
+ * is always live before this module's bootstrap, resources, or
230
+ * afterResources. Teardown (`onClose`) runs in the reverse of that order.
231
+ *
232
+ * Use this instead of relying on list position when a module reads another's
233
+ * public export — e.g. `@classytic/arc-reservation` wiring off
234
+ * `arc.modules.order`:
235
+ *
236
+ * ```ts
237
+ * defineModule({ name: "reservation", dependsOn: ["order"], bootstrap: (f) => {
238
+ * const order = getModuleExports<OrderEngine>(f, "order"); // guaranteed live
239
+ * …
240
+ * }});
241
+ * ```
242
+ *
243
+ * The order is a STABLE topological sort: modules with no `dependsOn` (and
244
+ * any two modules with no edge between them) keep their original list order,
245
+ * so adding `dependsOn` never silently reorders an unrelated module. All
246
+ * failure modes are fail-fast at boot: a name not in the composed set, a
247
+ * self-reference, and dependency CYCLES each throw with the offending path.
248
+ *
249
+ * **`dependsOn` is COMPOSITION order, not lazy loading.** Every module input
250
+ * (including dynamic-import thunks) is resolved — imported — up front via
251
+ * `Promise.all` BEFORE the sort; `dependsOn` then orders the *composition*
252
+ * (which module's `plugins`/`bootstrap` runs first), it does NOT delay
253
+ * *importing* a dependent until its dependency is ready. Use a thunk to gate
254
+ * whether a module is imported at all (region/tier packs); use `dependsOn`
255
+ * to order modules that are already selected.
256
+ */
257
+ readonly dependsOn?: readonly string[];
258
+ /**
259
+ * Fastify plugins / decorators this module needs — the module-level analog
260
+ * of the app's `plugins()` slot. Runs in the plugins phase: AFTER the app's
261
+ * own `plugins()` (so module infra can build on app foundations like a DB
262
+ * connection) and in `dependsOn` order, but BEFORE any module `bootstrap` —
263
+ * so engines initialised in `bootstrap` can rely on what's registered here.
264
+ *
265
+ * Separating this from `bootstrap` keeps lifecycle intent explicit for
266
+ * published ecosystem packages: `plugins` = "register infra"; `bootstrap` =
267
+ * "initialise engines (and return the public export)". Registering a plugin
268
+ * inside `bootstrap` still works — this slot just makes the two distinct.
269
+ */
270
+ plugins?: (fastify: FastifyInstance) => void | Promise<void>;
271
+ /**
272
+ * Domain error mappers this module ships (see `defineErrorMapper`). Merged
273
+ * into the app's error handler at boot, AFTER any host-declared mappers —
274
+ * host config keeps priority; module mappers follow in composition order.
275
+ *
276
+ * Closes the "composition root must know every domain's error classes"
277
+ * coupling: before this slot, adding `@classytic/arc-shipping` meant
278
+ * remembering to also register its `ShippingError` mapper in the host's
279
+ * `errorHandler.errorMappers` — forget it and the domain error crossed the
280
+ * wire as an opaque 500. Now the module carries its own mapper:
281
+ *
282
+ * ```ts
283
+ * defineModule({
284
+ * name: 'shipping',
285
+ * errorMappers: [shippingErrorMapper],
286
+ * ...
287
+ * })
288
+ * ```
289
+ */
290
+ readonly errorMappers?: readonly ErrorMapper[];
291
+ /**
292
+ * Domain init — runs in the `bootstrap` phase (after all `plugins`, before
293
+ * resources). Initialize engines / singletons / event subscriptions here so
294
+ * they are live when this module's `resources` factory runs.
295
+ *
296
+ * A returned value becomes the module's PUBLIC EXPORT, recorded at
297
+ * `fastify.arc.modules[name]` (wiki/modules.md — "no container", layer 2).
298
+ * Later modules' bootstraps can read it (list order = init order), which is
299
+ * how cross-module ports are wired without a DI container. Declare the
300
+ * export shape via `defineModule<TExports>` and read it back with the typed
301
+ * accessor — no cast at the consuming end:
302
+ *
303
+ * ```ts
304
+ * // arc-invoice bootstrap, composed AFTER arc-accounting:
305
+ * const acct = getModuleExports<AccountingEngine>(fastify, "accounting");
306
+ * ```
307
+ */
308
+ bootstrap?: (fastify: FastifyInstance) => TExports | Promise<TExports>;
309
+ /**
310
+ * The module's resources. Array or factory — the factory form runs AFTER all
311
+ * bootstraps, so `defineResource(...)` receives fully-booted engines. Flows
312
+ * through arc's normal registration (prefix, dedup, docs), identical to a
313
+ * top-level `resources` entry.
314
+ */
315
+ resources?: ReadonlyArray<ResourceLike> | ((fastify: FastifyInstance) => ReadonlyArray<ResourceLike> | Promise<ReadonlyArray<ResourceLike>>);
316
+ /**
317
+ * Post-registration wiring — runs in the `afterResources` phase, once this
318
+ * module's routes are mounted. Use for cross-resource event subscriptions the
319
+ * module owns.
320
+ */
321
+ afterResources?: (fastify: FastifyInstance) => void | Promise<void>;
322
+ /**
323
+ * Teardown — registered as a Fastify `onClose` hook. Destroy engines, stop
324
+ * timers, flush the module's outbox. Modules close in REVERSE list order
325
+ * (last composed, first closed), mirroring init.
326
+ */
327
+ onClose?: (fastify: FastifyInstance) => void | Promise<void>;
328
+ }
329
+ /**
330
+ * A module in any of the forms `createApp({ modules })` accepts:
331
+ *
332
+ * - `ArcModule` — eager (already imported)
333
+ * - `Promise<ArcModule>` — an in-flight import
334
+ * - `() => ArcModule | Promise<…>` — a thunk, resolved at boot
335
+ *
336
+ * The **thunk of a dynamic import** is the point of this union — it lets a host
337
+ * compose modules *logically* and load them *lazily / conditionally*, the
338
+ * `next/dynamic` idea applied to backend domains. The module's code (and its
339
+ * heavy deps) is only pulled in when the thunk runs, so region- or tier-gated
340
+ * packs never enter the bundle path they aren't selected for:
341
+ *
342
+ * ```ts
343
+ * const taxPack =
344
+ * region === "BD"
345
+ * ? () => import("@classytic/bd-tax").then((m) => m.createBdTaxModule(deps))
346
+ * : () => import("@classytic/us-tax").then((m) => m.createUsTaxModule(deps));
347
+ *
348
+ * await createApp({ modules: [coreModule(deps), taxPack] });
349
+ * // only the selected tax package is imported + composed
350
+ * ```
351
+ */
352
+ type ArcModuleInput<TExports = unknown> = ArcModule<TExports> | Promise<ArcModule<TExports>> | (() => ArcModule<TExports> | Promise<ArcModule<TExports>>);
353
+ /**
354
+ * Resolve any module-input form to a concrete `ArcModule`. A thunk is invoked
355
+ * (this is where a dynamic `import()` fires); a promise (or plain module) is
356
+ * awaited. Called once per module at the start of the bootstrap phase.
357
+ */
358
+ declare function resolveModule(input: ArcModuleInput): Promise<ArcModule>;
359
+ /**
360
+ * Order modules for composition by their `dependsOn` edges — a STABLE
361
+ * topological sort. Called once at the start of the bootstrap phase; every
362
+ * subsequent phase (bootstrap, resources, afterResources, and reverse-order
363
+ * onClose) iterates the returned list, so a module's declared dependencies are
364
+ * always composed before it.
365
+ *
366
+ * "Stable" = modules with no edge between them keep their original list order,
367
+ * so declaring `dependsOn` on one module never silently reorders an unrelated
368
+ * one. Backward compatible: a `modules` array with NO `dependsOn` anywhere is
369
+ * returned unchanged.
370
+ *
371
+ * Fail-fast — throws (never reorders past a broken contract) on:
372
+ * - duplicate module names (the name is the graph key)
373
+ * - a `dependsOn` name not present in the composed set
374
+ * - a self-reference (`dependsOn` includes the module's own name)
375
+ * - a dependency cycle (reports the concrete `a → b → … → a` path)
376
+ */
377
+ declare function orderModules(modules: readonly ArcModule[]): ArcModule[];
378
+ /**
379
+ * Typed module registry — hosts augment this via declaration merging so
380
+ * `getModuleExports(f, "order")` infers the export type without a manual type
381
+ * argument (the fastify / awilix pattern):
382
+ *
383
+ * ```ts
384
+ * declare module "@classytic/arc/factory" {
385
+ * interface ArcModuleRegistry {
386
+ * order: OrderEngine;
387
+ * accounting: AccountingEngine;
388
+ * }
389
+ * }
390
+ * ```
391
+ *
392
+ * Left empty here so the fallback `getModuleExports<T>(f, name: string)` stays
393
+ * available for apps that don't augment it.
394
+ */
395
+ interface ArcModuleRegistry {}
396
+ /**
397
+ * Identity helper for authoring a typed module — mirrors `defineResource`.
398
+ * Gives inference + a single obvious construction site; does no work.
399
+ *
400
+ * `TExports` is inferred from the `bootstrap` return value, so a module
401
+ * author gets a typed public export for free:
402
+ *
403
+ * ```ts
404
+ * export const accountingModule = (deps: Deps) =>
405
+ * defineModule({
406
+ * name: "accounting",
407
+ * bootstrap: async () => createAccountingEngine(deps), // TExports inferred
408
+ * });
409
+ * ```
410
+ */
411
+ declare function defineModule<TExports = unknown>(module: ArcModule<TExports>): ArcModule<TExports>;
412
+ /**
413
+ * Typed accessor for a module's public export (its `bootstrap` return value),
414
+ * recorded at `fastify.arc.modules[name]`.
415
+ *
416
+ * Throwing, in line with the fail-fast contract (and `requireOrgId`-style
417
+ * accessors): a missing entry means the module either isn't composed, is
418
+ * composed AFTER the caller (list order = init order), or returned nothing —
419
+ * all wiring bugs that must surface at boot, not as `undefined` downstream.
420
+ *
421
+ * The type parameter is an assertion, not a proof. Two ways to type it:
422
+ * - augment `ArcModuleRegistry` once — then the name alone infers the type:
423
+ * `getModuleExports(f, "accounting")` → `AccountingEngine`.
424
+ * - or pass it inline: `getModuleExports<AccountingEngine>(f, "accounting")`.
425
+ */
426
+ declare function getModuleExports<K extends keyof ArcModuleRegistry>(fastify: FastifyInstance, name: K): ArcModuleRegistry[K];
427
+ declare function getModuleExports<TExports = unknown>(fastify: FastifyInstance, name: string): TExports;
428
+ //#endregion
429
+ export { getModuleExports as a, LoadResourcesOptions as c, loadResources as d, defineModule as i, ResourceLike as l, ArcModuleInput as n, orderModules as o, ArcModuleRegistry as r, resolveModule as s, ArcModule as t, ResourceModule as u };
@@ -1,8 +1,8 @@
1
1
  import { t as getUserRoles } from "./types-XTApa8b6.mjs";
2
2
  import { n as convertRouteSchema } from "./schemaConverter-U0MnO9vd.mjs";
3
- import { t as resolveActionPermission } from "./actionPermissions-D7NGwcAR.mjs";
4
- import { t as pluralize } from "./pluralize-DccFV9R7.mjs";
5
- import { t as buildActionBodySchema } from "./createActionRouter-B_WzBFyf.mjs";
3
+ import { t as resolveActionPermission } from "./actionPermissions-ze70xXJi.mjs";
4
+ import { t as pluralize } from "./pluralize-BhrgwZb9.mjs";
5
+ import { t as buildActionBodySchema } from "./createActionRouter-BWMDW-dL.mjs";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/docs/openapi/canonical-schemas.ts
8
8
  /**
@@ -1,3 +1,3 @@
1
- import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-C7ahdGYf.mjs";
2
- import { A as requireRoles, C as allOf, D as not, E as denyAll, F as RequireAgentScopeOptions, I as RequireMandateOptions, L as requireAgentScope, M as when, N as applyPermissionResult, O as requireAuth, P as normalizePermissionResult, R as requireDPoP, S as createOrgPermissions, T as anyOf, _ as ConnectEventsOptions, a as presets_d_exports, b as PermissionEventBus, c as readOnly, d as requireOrgRole, f as requireScopeContext, g as createRoleHierarchy, h as RoleHierarchy, i as ownerWithAdminBypass, j as roles, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig, z as requireMandate } from "../index-CRKiiP5o.mjs";
3
- export { type ConnectEventsOptions, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type FieldPermission, type FieldPermissionMap, type FieldPermissionType, type PermissionCheck, type PermissionContext, type PermissionEventBus, type PermissionResult, type RequireAgentScopeOptions, type RequireMandateOptions, type RoleHierarchy, type UserBase, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
1
+ import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-BGqrAa_u.mjs";
2
+ import { A as requireOwnership, B as requireMandate, C as createOrgPermissions, D as denyAll, E as anyOf, F as normalizePermissionResult, I as RequireAgentScopeOptions, L as RequireMandateOptions, M as roles, N as when, O as not, P as applyPermissionResult, R as requireAgentScope, S as createDynamicPermissionMatrix, T as allowPublic, _ as createRoleHierarchy, a as permissionMatrix, b as DynamicPermissionMatrixConfig, c as publicReadAdminWrite, d as requireOrgMembership, f as requireOrgRole, g as RoleHierarchy, h as requireTeamMembership, i as ownerWithAdminBypass, j as requireRoles, k as requireAuth, l as readOnly, m as requireServiceScope, n as authenticated, o as presets_d_exports, p as requireScopeContext, r as fullPublic, s as publicRead, t as adminOnly, u as requireOrgInScope, v as ConnectEventsOptions, w as allOf, x as PermissionEventBus, y as DynamicPermissionMatrix, z as requireDPoP } from "../index-B5PoNbfu.mjs";
3
+ export { type ConnectEventsOptions, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type FieldPermission, type FieldPermissionMap, type FieldPermissionType, type PermissionCheck, type PermissionContext, type PermissionEventBus, type PermissionResult, type RequireAgentScopeOptions, type RequireMandateOptions, type RoleHierarchy, type UserBase, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, permissionMatrix, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -1,3 +1,3 @@
1
- import { A as requireMandate, C as requireAuth, D as when, E as roles, F as applyFieldWritePermissions, I as fields, L as resolveEffectiveRoles, N as normalizePermissionResult, O as requireAgentScope, P as applyFieldReadPermissions, S as not, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, j as applyPermissionResult, k as requireDPoP, l as createRoleHierarchy, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "../permissions-BPB1xqGf.mjs";
1
+ import { A as requireDPoP, C as not, D as roles, E as requireRoles, F as applyFieldReadPermissions, I as applyFieldWritePermissions, L as fields, M as applyPermissionResult, O as when, P as normalizePermissionResult, R as resolveEffectiveRoles, S as denyAll, T as requireOwnership, _ as requireServiceScope, a as permissionMatrix, b as allowPublic, c as publicReadAdminWrite, d as createDynamicPermissionMatrix, f as createOrgPermissions, g as requireScopeContext, h as requireOrgRole, i as ownerWithAdminBypass, j as requireMandate, k as requireAgentScope, l as readOnly, m as requireOrgMembership, n as authenticated, o as presets_exports, p as requireOrgInScope, r as fullPublic, s as publicRead, t as adminOnly, u as createRoleHierarchy, v as requireTeamMembership, w as requireAuth, x as anyOf, y as allOf } from "../permissions-BJMuKMl7.mjs";
2
2
  import { n as normalizeRoles, t as getUserRoles } from "../types-XTApa8b6.mjs";
3
- export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
3
+ export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, permissionMatrix, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -167,7 +167,7 @@ function applyFieldWritePermissions(body, fieldPermissions, userRoles) {
167
167
  function resolveEffectiveRoles(userRoles, orgRoles) {
168
168
  if (orgRoles.length === 0) return [...userRoles];
169
169
  if (userRoles.length === 0) return [...orgRoles];
170
- return [...new Set([...userRoles, ...orgRoles])];
170
+ return [.../* @__PURE__ */ new Set([...userRoles, ...orgRoles])];
171
171
  }
172
172
  //#endregion
173
173
  //#region src/permissions/applyPermissionResult.ts
@@ -1382,6 +1382,7 @@ var presets_exports = /* @__PURE__ */ __exportAll({
1382
1382
  authenticated: () => authenticated,
1383
1383
  fullPublic: () => fullPublic,
1384
1384
  ownerWithAdminBypass: () => ownerWithAdminBypass,
1385
+ permissionMatrix: () => permissionMatrix,
1385
1386
  publicRead: () => publicRead,
1386
1387
  publicReadAdminWrite: () => publicReadAdminWrite,
1387
1388
  readOnly: () => readOnly
@@ -1450,6 +1451,37 @@ function adminOnly(roles = ["admin"], overrides) {
1450
1451
  }, overrides);
1451
1452
  }
1452
1453
  /**
1454
+ * Arbitrary-gate CRUD matrix — the generalization every host and arc-*
1455
+ * module hand-rolls as `{ list: view, get: view, create: manage, ... }`.
1456
+ *
1457
+ * Unlike the fixed presets above, the gates are caller-supplied
1458
+ * `PermissionCheck`s, so ANY combinator plugs in — org roles, ownership,
1459
+ * `anyOf(...)`, service scopes, custom async checks. `delete` defaults to
1460
+ * the `write` gate.
1461
+ *
1462
+ * @example
1463
+ * ```typescript
1464
+ * // Two-gate (view/manage) — the arc-* module convention:
1465
+ * permissionMatrix({ read: requireAuth(), write: requireOrgRole('manager') })
1466
+ *
1467
+ * // Three-gate with a stricter delete:
1468
+ * permissionMatrix({
1469
+ * read: allowPublic(),
1470
+ * write: requireOrgRole('manager'),
1471
+ * delete: requireOrgRole('owner'),
1472
+ * })
1473
+ * ```
1474
+ */
1475
+ function permissionMatrix(gates, overrides) {
1476
+ return withOverrides({
1477
+ list: gates.read,
1478
+ get: gates.read,
1479
+ create: gates.write,
1480
+ update: gates.write,
1481
+ delete: gates.delete ?? gates.write
1482
+ }, overrides);
1483
+ }
1484
+ /**
1453
1485
  * Owner-scoped with admin bypass.
1454
1486
  * list = auth (scoped to owner), get = auth, create = auth,
1455
1487
  * update + delete = ownership check with admin bypass.
@@ -1482,12 +1514,20 @@ function fullPublic(overrides) {
1482
1514
  /**
1483
1515
  * Read-only: list + get authenticated, write operations denied.
1484
1516
  * Useful for computed/derived resources.
1517
+ *
1518
+ * Writes carry an explicit `denyAll` check — an omitted CRUD permission
1519
+ * mounts the route with NO gate (public-by-omission), so leaving
1520
+ * create/update/delete undefined here would expose unauthenticated writes.
1521
+ * Hosts that disable write routes entirely (`disabledRoutes`) are unaffected.
1485
1522
  */
1486
1523
  function readOnly(overrides) {
1487
1524
  return withOverrides({
1488
1525
  list: requireAuth(),
1489
- get: requireAuth()
1526
+ get: requireAuth(),
1527
+ create: denyAll("Write operations are disabled on this read-only resource"),
1528
+ update: denyAll("Write operations are disabled on this read-only resource"),
1529
+ delete: denyAll("Write operations are disabled on this read-only resource")
1490
1530
  }, overrides);
1491
1531
  }
1492
1532
  //#endregion
1493
- export { requireMandate as A, requireAuth as C, when as D, roles as E, applyFieldWritePermissions as F, fields as I, resolveEffectiveRoles as L, evaluateAndApplyPermission as M, normalizePermissionResult as N, requireAgentScope as O, applyFieldReadPermissions as P, not as S, requireRoles as T, requireTeamMembership as _, presets_exports as a, anyOf as b, readOnly as c, createOrgPermissions as d, requireOrgInScope as f, requireServiceScope as g, requireScopeContext as h, ownerWithAdminBypass as i, applyPermissionResult as j, requireDPoP as k, createRoleHierarchy as l, requireOrgRole as m, authenticated as n, publicRead as o, requireOrgMembership as p, fullPublic as r, publicReadAdminWrite as s, adminOnly as t, createDynamicPermissionMatrix as u, allOf as v, requireOwnership as w, denyAll as x, allowPublic as y };
1533
+ export { requireDPoP as A, not as C, roles as D, requireRoles as E, applyFieldReadPermissions as F, applyFieldWritePermissions as I, fields as L, applyPermissionResult as M, evaluateAndApplyPermission as N, when as O, normalizePermissionResult as P, resolveEffectiveRoles as R, denyAll as S, requireOwnership as T, requireServiceScope as _, permissionMatrix as a, allowPublic as b, publicReadAdminWrite as c, createDynamicPermissionMatrix as d, createOrgPermissions as f, requireScopeContext as g, requireOrgRole as h, ownerWithAdminBypass as i, requireMandate as j, requireAgentScope as k, readOnly as l, requireOrgMembership as m, authenticated as n, presets_exports as o, requireOrgInScope as p, fullPublic as r, publicRead as s, adminOnly as t, createRoleHierarchy as u, requireTeamMembership as v, requireAuth as w, anyOf as x, allOf as y };
@@ -1,5 +1,4 @@
1
- import { Bt as Transform, Ft as NextFunction, It as OperationFilter, Lt as PipelineConfig, Nt as Guard, Pt as Interceptor, Rt as PipelineContext, bt as IControllerResponse, zt as PipelineStep } from "../index-CUTG2Cuo.mjs";
2
-
1
+ import { Bt as Transform, Ft as NextFunction, It as OperationFilter, Lt as PipelineConfig, Nt as Guard, Pt as Interceptor, Rt as PipelineContext, bt as IControllerResponse, zt as PipelineStep } from "../index-Dtrj6XZk.mjs";
3
2
  //#region src/pipeline/guard.d.ts
4
3
  interface GuardOptions {
5
4
  operations?: OperationFilter;