@classytic/arc 2.25.0 → 2.28.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 (48) hide show
  1. package/README.md +2 -0
  2. package/dist/{BaseController--M8EMzt0.mjs → BaseController-B65zbz9m.mjs} +4 -2
  3. package/dist/{applyPermissionResult-Tw4iXtlf.mjs → applyPermissionResult-BnyVE9eD.mjs} +10 -0
  4. package/dist/cleanup/index.d.mts +313 -0
  5. package/dist/cleanup/index.mjs +1267 -0
  6. package/dist/cli/commands/docs.mjs +1 -1
  7. package/dist/core/index.d.mts +1 -1
  8. package/dist/core/index.mjs +4 -3
  9. package/dist/core-BGa8TXJR.mjs +221 -0
  10. package/dist/{createActionRouter-B6GvAuAX.mjs → createActionRouter-C-N7z2EL.mjs} +2 -2
  11. package/dist/{preloadResources-DfyngIN9.mjs → createApp-Du5G54TM.mjs} +2 -74
  12. package/dist/{core-EUNu0u07.mjs → defineResource-2zvVVpxg.mjs} +6 -224
  13. package/dist/docs/index.mjs +1 -1
  14. package/dist/events/index.d.mts +28 -2
  15. package/dist/events/index.mjs +20 -4
  16. package/dist/factory/index.d.mts +4 -3
  17. package/dist/factory/index.mjs +3 -1
  18. package/dist/{fastify-augmentation-dPNBVeIj.d.mts → fastify-augmentation-OzYGIiU1.d.mts} +2 -1
  19. package/dist/index-CZY4EGga.d.mts +107 -0
  20. package/dist/{index-f4PTrk3B.d.mts → index-CjVg-Epg.d.mts} +1 -1
  21. package/dist/index.d.mts +2 -2
  22. package/dist/index.mjs +6 -5
  23. package/dist/integrations/mcp/index.mjs +1 -1
  24. package/dist/integrations/mcp/testing.mjs +1 -1
  25. package/dist/{mcpPlugin-Cb7olw0j.mjs → mcpPlugin-Cs6nRGCt.mjs} +3 -3
  26. package/dist/module-_-M7C5ES.mjs +29 -0
  27. package/dist/{openapi-DrLtog_B.mjs → openapi-BLV4OMXb.mjs} +1 -1
  28. package/dist/permissions/index.mjs +1 -1
  29. package/dist/{pipeline-BN5G05rd.mjs → pipeline-BGRnV-To.mjs} +1 -1
  30. package/dist/plugins/index.d.mts +2 -2
  31. package/dist/plugins/index.mjs +1 -1
  32. package/dist/plugins/tracing-entry.mjs +1 -1
  33. package/dist/{preloadResources-0oe-JSSS.d.mts → preloadResources-CM4uUaFv.d.mts} +2 -1
  34. package/dist/preloadResources-DSY5HGpS.mjs +45 -0
  35. package/dist/{realtime-Cuw_deA-.mjs → realtime-DEuDu9io.mjs} +1 -1
  36. package/dist/{registerRoute-v0Wb6Plt.mjs → registerRoute-DTp18Ah_.mjs} +1 -1
  37. package/dist/registry/index.d.mts +1 -1
  38. package/dist/registry/index.mjs +1 -1
  39. package/dist/{registry-vcOCtPij.mjs → registry-9km_w1aP.mjs} +15 -0
  40. package/dist/testing/cleanupStoreContract.d.mts +10 -0
  41. package/dist/testing/cleanupStoreContract.mjs +375 -0
  42. package/dist/testing/index.d.mts +3 -2
  43. package/dist/testing/index.mjs +4 -2
  44. package/dist/testing/outboxStoreContract.d.mts +13 -0
  45. package/dist/testing/outboxStoreContract.mjs +247 -0
  46. package/dist/types-C_ZZRcD6.d.mts +450 -0
  47. package/dist/{index-KUoxjIq1.d.mts → types-Cu6vVJmh.d.mts} +1 -105
  48. package/package.json +27 -12
@@ -1,4 +1,4 @@
1
- import { t as buildOpenApiSpec } from "../../openapi-DrLtog_B.mjs";
1
+ import { t as buildOpenApiSpec } from "../../openapi-BLV4OMXb.mjs";
2
2
  import { t as loadResourcesFromEntry } from "../../loadResourcesFromEntry-kvGT6EcO.mjs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { mkdirSync, writeFileSync } from "node:fs";
@@ -1,3 +1,3 @@
1
1
  import { $t as SlugExt, An as ControllerConfigurableOptions, B as defineResource, Jt as BaseController, Mn as ListResult, Nn as BodySanitizer, Pn as BodySanitizerConfig, Qt as TreeMixin, V as ResourceDefinition, Xt as SoftDeleteMixin, Yt as SoftDeleteExt, Zt as TreeExt, an as QueryResolverConfig, ct as AggMeasureShorthand, dt as AggregationDateRangeRequirement, en as SlugMixin, ft as AggregationIndexHint, gt as AggregationsMap, ht as AggregationRateLimit, in as QueryResolver, jn as ControllerConstructionOptions, kn as BaseControllerOptions, lt as AggregationCacheConfig, mt as AggregationMaterializedResult, nn as BulkMixin, on as AccessControl, pt as AggregationMaterializedContext, rn as BaseCrudController, sn as AccessControlConfig, st as AggMeasureInput, tn as BulkExt, ut as AggregationConfig } from "../index-DPRH0Cme.mjs";
2
- import { A as DEFAULT_ID_FIELD, B as MAX_FILTER_DEPTH, C as DefineActionConfig, D as defineAggregation, E as createPermissionMiddleware, F as DEFAULT_UPDATE_METHOD, G as RESERVED_QUERY_PARAMS, H as MAX_SEARCH_LENGTH, I as HOOK_OPERATIONS, K as SYSTEM_FIELDS, L as HOOK_PHASES, M as DEFAULT_MAX_LIMIT, N as DEFAULT_SORT, O as CRUD_OPERATIONS, P as DEFAULT_TENANT_FIELD, R as HookOperation, S as mergeResourceConfig, T as createCrudRouter, U as MUTATION_OPERATIONS, V as MAX_REGEX_LENGTH, W as MutationOperation, _ as getEntityQuery, a as formatValidationErrors, b as ResourceSeams, c as isFieldReadable, d as createRequestContext, f as getControllerContext, g as getEntityIdField, h as getEntityId, i as assertValidConfig, j as DEFAULT_LIMIT, k as CrudOperation, l as createCrudHandlers, m as sendControllerResponse, n as ValidateOptions, o as validateResourceConfig, p as getControllerScope, r as ValidationResult, s as collectReadBlockedFields, t as ConfigError, u as createFastifyHandler, v as defineResourceVariants, w as defineAction, x as SeamedResourceConfig, y as AdapterLike, z as HookPhase } from "../index-f4PTrk3B.mjs";
2
+ import { A as DEFAULT_ID_FIELD, B as MAX_FILTER_DEPTH, C as DefineActionConfig, D as defineAggregation, E as createPermissionMiddleware, F as DEFAULT_UPDATE_METHOD, G as RESERVED_QUERY_PARAMS, H as MAX_SEARCH_LENGTH, I as HOOK_OPERATIONS, K as SYSTEM_FIELDS, L as HOOK_PHASES, M as DEFAULT_MAX_LIMIT, N as DEFAULT_SORT, O as CRUD_OPERATIONS, P as DEFAULT_TENANT_FIELD, R as HookOperation, S as mergeResourceConfig, T as createCrudRouter, U as MUTATION_OPERATIONS, V as MAX_REGEX_LENGTH, W as MutationOperation, _ as getEntityQuery, a as formatValidationErrors, b as ResourceSeams, c as isFieldReadable, d as createRequestContext, f as getControllerContext, g as getEntityIdField, h as getEntityId, i as assertValidConfig, j as DEFAULT_LIMIT, k as CrudOperation, l as createCrudHandlers, m as sendControllerResponse, n as ValidateOptions, o as validateResourceConfig, p as getControllerScope, r as ValidationResult, s as collectReadBlockedFields, t as ConfigError, u as createFastifyHandler, v as defineResourceVariants, w as defineAction, x as SeamedResourceConfig, y as AdapterLike, z as HookPhase } from "../index-CjVg-Epg.mjs";
3
3
  export { AccessControl, type AccessControlConfig, type AdapterLike, type AggMeasureInput, type AggMeasureShorthand, type AggregationCacheConfig, type AggregationConfig, type AggregationDateRangeRequirement, type AggregationIndexHint, type AggregationMaterializedContext, type AggregationMaterializedResult, type AggregationRateLimit, type AggregationsMap, BaseController, type BaseControllerOptions, BaseCrudController, BodySanitizer, type BodySanitizerConfig, type BulkExt, BulkMixin, CRUD_OPERATIONS, type ConfigError, type ControllerConfigurableOptions, type ControllerConstructionOptions, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, type DefineActionConfig, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, type ListResult, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, type QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, type ResourceSeams, SYSTEM_FIELDS, type SeamedResourceConfig, type SlugExt, SlugMixin, type SoftDeleteExt, SoftDeleteMixin, type TreeExt, TreeMixin, type ValidateOptions, type ValidationResult, assertValidConfig, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, formatValidationErrors, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, mergeResourceConfig, sendControllerResponse, validateResourceConfig };
@@ -1,5 +1,6 @@
1
1
  import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "../constants-DNre0wRg.mjs";
2
- import { a as BulkMixin, c as collectReadBlockedFields, f as AccessControl, i as SlugMixin, l as isFieldReadable, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController, u as BodySanitizer } from "../BaseController--M8EMzt0.mjs";
3
- import { a as defineResource, c as validateResourceConfig, d as defineAction, f as createCrudRouter, i as defineResourceVariants, l as ResourceDefinition, m as defineAggregation, n as getEntityIdField, o as assertValidConfig, p as createPermissionMiddleware, r as getEntityQuery, s as formatValidationErrors, t as getEntityId, u as mergeResourceConfig } from "../core-EUNu0u07.mjs";
4
- import { a as createFastifyHandler, c as getControllerScope, i as createCrudHandlers, l as sendControllerResponse, o as createRequestContext, s as getControllerContext } from "../pipeline-BN5G05rd.mjs";
2
+ import { a as BulkMixin, c as collectReadBlockedFields, f as AccessControl, i as SlugMixin, l as isFieldReadable, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController, u as BodySanitizer } from "../BaseController-B65zbz9m.mjs";
3
+ import { a as ResourceDefinition, i as validateResourceConfig, n as assertValidConfig, o as createCrudRouter, r as formatValidationErrors, s as createPermissionMiddleware, t as defineResource } from "../defineResource-2zvVVpxg.mjs";
4
+ import { a as createFastifyHandler, c as getControllerScope, i as createCrudHandlers, l as sendControllerResponse, o as createRequestContext, s as getControllerContext } from "../pipeline-BGRnV-To.mjs";
5
+ import { a as mergeResourceConfig, i as defineResourceVariants, n as getEntityIdField, o as defineAction, r as getEntityQuery, s as defineAggregation, t as getEntityId } from "../core-BGa8TXJR.mjs";
5
6
  export { AccessControl, BaseController, BaseCrudController, BodySanitizer, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, assertValidConfig, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, formatValidationErrors, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, mergeResourceConfig, sendControllerResponse, validateResourceConfig };
@@ -0,0 +1,221 @@
1
+ import "./constants-DNre0wRg.mjs";
2
+ import { t as defineResource } from "./defineResource-2zvVVpxg.mjs";
3
+ //#region src/core/aggregation/defineAggregation.ts
4
+ /**
5
+ * Declare a single resource aggregation. Exported configs flow into
6
+ * `defineResource({ aggregations: { ... } })` either as named keys or
7
+ * via auto-discovery patterns (loadAggregations — future).
8
+ *
9
+ * @example Inline
10
+ * ```ts
11
+ * defineResource({
12
+ * name: 'order',
13
+ * aggregations: {
14
+ * revenueByStatus: defineAggregation({
15
+ * groupBy: 'status',
16
+ * measures: { count: 'count', revenue: 'sum:totalPrice' },
17
+ * permissions: requireRoles(['admin']),
18
+ * }),
19
+ * },
20
+ * });
21
+ * ```
22
+ *
23
+ * @example Multi-file
24
+ * ```ts
25
+ * // orders/aggregations/revenue-by-status.ts
26
+ * import { defineAggregation } from '@classytic/arc';
27
+ *
28
+ * export const revenueByStatus = defineAggregation({
29
+ * groupBy: 'status',
30
+ * measures: { count: 'count', revenue: 'sum:totalPrice' },
31
+ * permissions: requireRoles(['admin']),
32
+ * timeout: 5000,
33
+ * cache: { staleTime: 60 },
34
+ * });
35
+ *
36
+ * // orders/order.resource.ts
37
+ * import * as aggregations from './aggregations/index.js';
38
+ *
39
+ * defineResource({
40
+ * name: 'order',
41
+ * aggregations, // 30+ entries flow in
42
+ * });
43
+ * ```
44
+ */
45
+ function defineAggregation(config) {
46
+ return config;
47
+ }
48
+ //#endregion
49
+ //#region src/core/defineAction.ts
50
+ /**
51
+ * Build an `ActionDefinition` with a typed handler. The literal schema
52
+ * type captured here flows into `data`, so `defineAction({ schema:
53
+ * z.object({...}), handler })` produces fully-typed code with no
54
+ * `as MyShape` cast.
55
+ *
56
+ * Behaviorally identical to a bare `ActionDefinition` object — same
57
+ * validation path (AJV), same permission resolution, same MCP wiring.
58
+ * The runtime shape is unchanged; only the type-level inference is new.
59
+ */
60
+ function defineAction(config) {
61
+ return {
62
+ handler: config.handler,
63
+ permissions: config.permissions,
64
+ schema: config.schema,
65
+ description: config.description,
66
+ id: config.id,
67
+ mcp: config.mcp
68
+ };
69
+ }
70
+ //#endregion
71
+ //#region src/core/defineResource/seams.ts
72
+ /** Plain object test — literals only; class instances must replace, not merge. */
73
+ function isPlainObject(value) {
74
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
75
+ const proto = Object.getPrototypeOf(value);
76
+ return proto === Object.prototype || proto === null;
77
+ }
78
+ /** Recursive plain-object merge; nested arrays REPLACE (they're value lists). */
79
+ function mergePlain(base, next) {
80
+ const out = { ...base };
81
+ for (const [key, value] of Object.entries(next)) {
82
+ if (value === void 0) continue;
83
+ const existing = out[key];
84
+ out[key] = isPlainObject(existing) && isPlainObject(value) ? mergePlain(existing, value) : value;
85
+ }
86
+ return out;
87
+ }
88
+ /**
89
+ * Canonical slot-aware merge of a base resource config with host seams.
90
+ * Later seams win over earlier ones; `undefined` values never clobber.
91
+ *
92
+ * Merge semantics (value-driven — no per-slot registry to drift):
93
+ *
94
+ * - **Top-level arrays CONCAT** — `routes`, `middlewares`, `presets`,
95
+ * `routeGuards`, `disabledRoutes`: additive collections, base first.
96
+ * - **Plain objects merge recursively** — `permissions`, `actions`,
97
+ * `schemaOptions` (incl. `fieldRules`), `aggregations`, `customSchemas`,
98
+ * `fields`, `cache`, `events`: per-key composition. Arrays NESTED inside
99
+ * them replace (they're value lists — enums, sort specs), not concat.
100
+ * - **Everything else last-wins** — class instances (`adapter`,
101
+ * `controller`, `queryParser`), functions, scalars.
102
+ *
103
+ * The single `as` at the return is the one documented erasure for the
104
+ * {@link AdapterLike}-widened seam slots — inside the helper so no module
105
+ * author ever writes one (mirrors mongokit's one-cast-at-the-boundary rule).
106
+ */
107
+ function mergeResourceConfig(base, ...seams) {
108
+ const out = { ...base };
109
+ for (const seam of seams) {
110
+ if (!seam) continue;
111
+ for (const [key, value] of Object.entries(seam)) {
112
+ if (value === void 0) continue;
113
+ const existing = out[key];
114
+ if (Array.isArray(existing) && Array.isArray(value)) out[key] = [...existing, ...value];
115
+ else if (isPlainObject(existing) && isPlainObject(value)) out[key] = mergePlain(existing, value);
116
+ else if (Array.isArray(value)) out[key] = [...value];
117
+ else out[key] = value;
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+ //#endregion
123
+ //#region src/core/defineResourceVariants.ts
124
+ /**
125
+ * Define multiple resources from a shared base config and per-variant overrides.
126
+ *
127
+ * Each variant is independently passed through `defineResource()` — the
128
+ * returned `ResourceDefinition`s are real, fully-registered resources.
129
+ * Register each one's plugin in your app:
130
+ *
131
+ * ```typescript
132
+ * await app.register(articlePublic.toPlugin());
133
+ * await app.register(articleAdmin.toPlugin());
134
+ * ```
135
+ *
136
+ * @param base Shared config — adapter, queryParser, schemaOptions, hooks, etc.
137
+ * Must NOT include `name` or `prefix` (those are per-variant).
138
+ * @param variants Map of variant key → override. Each variant must declare
139
+ * its own `name` and `prefix`. Other fields override the base.
140
+ * @returns A record where each key from `variants` maps to a real
141
+ * `ResourceDefinition` ready for `.toPlugin()` registration.
142
+ */
143
+ function defineResourceVariants(base, variants) {
144
+ const out = {};
145
+ for (const key of Object.keys(variants)) {
146
+ const override = variants[key];
147
+ out[key] = defineResource({
148
+ ...base,
149
+ ...override
150
+ });
151
+ }
152
+ return out;
153
+ }
154
+ //#endregion
155
+ //#region src/core/entityHelpers.ts
156
+ /**
157
+ * Per-request entity helpers — read the resource binding (idField + the
158
+ * URL `:id` value) off `req.arc`.
159
+ *
160
+ * Action handlers receive their `id` argument as the raw URL `:id` value.
161
+ * When the resource declares a custom `idField` (`slug`, `reportId`, …)
162
+ * that's NOT the document `_id`, a naive `Model.findById(id)` silently
163
+ * returns null. The historical footgun was a `findById(id)` typo where
164
+ * the handler author hadn't realised that `:id` resolves to the friendly
165
+ * handle.
166
+ *
167
+ * `getEntityQuery(req)` produces the canonical filter shape so handlers
168
+ * compose lookups with no resource-config recall:
169
+ *
170
+ * ```ts
171
+ * actions: {
172
+ * archive: {
173
+ * handler: async (id, data, req) => {
174
+ * const doc = await Model.findOne(getEntityQuery(req));
175
+ * if (!doc) throw new NotFoundError("Order");
176
+ * // ...
177
+ * },
178
+ * },
179
+ * }
180
+ * ```
181
+ *
182
+ * The router populates `req.arc.idField` and `req.arc.entityId` before
183
+ * invoking the handler — these helpers are zero-cost reads.
184
+ */
185
+ /**
186
+ * Read the resource's configured `idField` for the current request.
187
+ * Falls back to the framework default (`_id`) when the route hasn't
188
+ * bound an idField — keeps handlers safe to author without checking
189
+ * the resource config.
190
+ */
191
+ function getEntityIdField(req) {
192
+ return req.arc?.idField ?? "_id";
193
+ }
194
+ /**
195
+ * Read the URL `:id` path param value as the resource handle.
196
+ * Returns `undefined` when the route has no `:id` segment (collection
197
+ * routes) — handlers that need the entity must be on row routes.
198
+ */
199
+ function getEntityId(req) {
200
+ if (req.arc?.entityId !== void 0) return req.arc.entityId;
201
+ return req.params?.id;
202
+ }
203
+ /**
204
+ * Compose a `findOne` filter that resolves the current request's
205
+ * entity, regardless of whether the resource binds `_id` or a custom
206
+ * field. Idiomatic shape for arc action handlers.
207
+ *
208
+ * ```ts
209
+ * const doc = await Model.findOne(getEntityQuery(req));
210
+ * ```
211
+ *
212
+ * Returns `{}` when the route has no entity context (collection routes
213
+ * or tests bypassing the router) — caller decides what that means.
214
+ */
215
+ function getEntityQuery(req) {
216
+ const id = getEntityId(req);
217
+ if (id === void 0) return {};
218
+ return { [getEntityIdField(req)]: id };
219
+ }
220
+ //#endregion
221
+ export { mergeResourceConfig as a, defineResourceVariants as i, getEntityIdField as n, defineAction as o, getEntityQuery as r, defineAggregation as s, getEntityId as t };
@@ -2,8 +2,8 @@ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import "./constants-DNre0wRg.mjs";
3
3
  import { f as createError } from "./errors-DkO8Nk6-.mjs";
4
4
  import { a as buildRouteHooks, i as selectPluginMw, l as buildAuthMiddlewareForPermissions, n as buildRouteConfig, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-DrED-C4v.mjs";
5
- import { n as buildActionPermissionMw, t as tryRegisterRoute } from "./registerRoute-v0Wb6Plt.mjs";
6
- import { l as sendControllerResponse, r as resolvePipelineSteps, t as buildActionPipelineHandler } from "./pipeline-BN5G05rd.mjs";
5
+ import { n as buildActionPermissionMw, t as tryRegisterRoute } from "./registerRoute-DTp18Ah_.mjs";
6
+ import { l as sendControllerResponse, r as resolvePipelineSteps, t as buildActionPipelineHandler } from "./pipeline-BGRnV-To.mjs";
7
7
  import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-Ca7ySNPx.mjs";
8
8
  //#region src/core/createActionRouter.ts
9
9
  var createActionRouter_exports = /* @__PURE__ */ __exportAll({
@@ -214,34 +214,6 @@ function describeModuleCycle(remaining, byName) {
214
214
  return `[arc] module dependency cycle: ${cycle ? cycle.join(" → ") : [...remaining].join(", ")}. Modules cannot dependsOn each other circularly — break it with a shared module both point at, or wire the softer direction through an event/port instead of a hard dependsOn.`;
215
215
  }
216
216
  //#endregion
217
- //#region src/factory/module/index.ts
218
- /**
219
- * Identity helper for authoring a typed module — mirrors `defineResource`.
220
- * Gives inference + a single obvious construction site; does no work.
221
- *
222
- * `TExports` is inferred from the `bootstrap` return value, so a module
223
- * author gets a typed public export for free:
224
- *
225
- * ```ts
226
- * export const accountingModule = (deps: Deps) =>
227
- * defineModule({
228
- * name: "accounting",
229
- * bootstrap: async () => createAccountingEngine(deps), // TExports inferred
230
- * });
231
- * ```
232
- */
233
- function defineModule(module) {
234
- return module;
235
- }
236
- function getModuleExports(fastify, name) {
237
- const modules = fastify.arc?.modules;
238
- if (!modules || !Object.hasOwn(modules, name)) {
239
- const available = modules ? Object.keys(modules) : [];
240
- throw new Error(`[arc] no public export recorded for module "${name}".\nA module's export is its bootstrap() return value; it is readable only AFTER that module's bootstrap ran (dependsOn / list order = init order).\nModules with recorded exports: ${available.length ? available.join(", ") : "(none)"}.`);
241
- }
242
- return modules[name];
243
- }
244
- //#endregion
245
217
  //#region src/factory/presets.ts
246
218
  /**
247
219
  * Production preset - strict security, performance optimized
@@ -539,7 +511,7 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
539
511
  }
540
512
  if (config.arcPlugins?.realtime) if (config.arcPlugins?.events === false) fastify.log.warn("Realtime plugin requires events plugin (arcPlugins.events). Realtime disabled.");
541
513
  else {
542
- const { default: realtimePlugin } = await import("./realtime-Cuw_deA-.mjs").then((n) => n.r);
514
+ const { default: realtimePlugin } = await import("./realtime-DEuDu9io.mjs").then((n) => n.r);
543
515
  const opts = config.arcPlugins.realtime === true ? {} : config.arcPlugins.realtime;
544
516
  await fastify.register(realtimePlugin, opts);
545
517
  trackPlugin("arc-realtime", opts);
@@ -1466,48 +1438,4 @@ const ArcFactory = {
1466
1438
  }
1467
1439
  };
1468
1440
  //#endregion
1469
- //#region src/factory/preloadResources.ts
1470
- /**
1471
- * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
1472
- *
1473
- * Accepts either:
1474
- * - `{ import: 'default' }` form: values are the resource directly
1475
- * - default form: values are the full module — picks first export with `toPlugin()`
1476
- *
1477
- * Throws if any module doesn't yield a valid `ResourceLike`.
1478
- */
1479
- function preloadResources(globResult) {
1480
- const resources = [];
1481
- for (const [path, value] of Object.entries(globResult)) {
1482
- const resource = pickResource(value);
1483
- if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
1484
- resources.push(resource);
1485
- }
1486
- return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
1487
- }
1488
- /**
1489
- * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
1490
- *
1491
- * Use this when resources depend on prior bootstrap (e.g., engine init) and
1492
- * cannot be evaluated at import time of the preload file.
1493
- */
1494
- async function preloadResourcesAsync(globResult) {
1495
- return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
1496
- const resource = pickResource(await loader());
1497
- if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
1498
- return resource;
1499
- }))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
1500
- }
1501
- function pickResource(value) {
1502
- if (!value || typeof value !== "object") return void 0;
1503
- if (typeof value.toPlugin === "function") return value;
1504
- const mod = value;
1505
- const candidates = [
1506
- mod.default,
1507
- mod.resource,
1508
- ...Object.values(mod)
1509
- ];
1510
- for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
1511
- }
1512
- //#endregion
1513
- export { collectModuleScheduledJobs as _, createApp_exports as a, resolveModule as b, getPreset as c, workerPreset as d, defineModule as f, collectModuleHealthChecks as g, subscribeModuleEventHandlers as h, createApp as i, productionPreset as l, orderModules as m, preloadResourcesAsync as n, developmentPreset as o, getModuleExports as p, ArcFactory as r, edgePreset as s, preloadResources as t, testingPreset as u, collectModuleWorkflows as v, resolveContribution as y };
1441
+ export { edgePreset as a, testingPreset as c, subscribeModuleEventHandlers as d, collectModuleHealthChecks as f, resolveModule as g, resolveContribution as h, developmentPreset as i, workerPreset as l, collectModuleWorkflows as m, createApp as n, getPreset as o, collectModuleScheduledJobs as p, createApp_exports as r, productionPreset as s, ArcFactory as t, orderModules as u };
@@ -3,15 +3,15 @@ import { arcLog } from "./logger/index.mjs";
3
3
  import { r as ForbiddenError } from "./errors-DkO8Nk6-.mjs";
4
4
  import { n as getUserRoles } from "./userHelpers-Cg_y3fBz.mjs";
5
5
  import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-CSyCxl2V.mjs";
6
- import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController--M8EMzt0.mjs";
6
+ import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController-B65zbz9m.mjs";
7
7
  import { a as buildRouteHooks, c as buildAuthMiddleware, i as selectPluginMw, n as buildRouteConfig, o as resolveRoutePreHandlers, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-DrED-C4v.mjs";
8
- import { a as applyFieldWritePermissions, s as resolveEffectiveRoles } from "./applyPermissionResult-Tw4iXtlf.mjs";
9
- import { r as buildCrudPermissionMw, t as tryRegisterRoute } from "./registerRoute-v0Wb6Plt.mjs";
8
+ import { a as applyFieldWritePermissions, s as resolveEffectiveRoles } from "./applyPermissionResult-BnyVE9eD.mjs";
9
+ import { r as buildCrudPermissionMw, t as tryRegisterRoute } from "./registerRoute-DTp18Ah_.mjs";
10
10
  import { c as getDefaultCrudSchemas } from "./utils-taUVD4p3.mjs";
11
11
  import { n as convertRouteSchema, r as ensureSchemaConverter, t as convertOpenApiSchemas } from "./schemaConverter-DJvodiHj.mjs";
12
12
  import { i as pipeUIMessageStreamToReply, r as isReadableStream } from "./streaming-BQcW6_Kq.mjs";
13
13
  import { t as hasEvents } from "./typeGuards-C9KXUly6.mjs";
14
- import { a as createFastifyHandler, i as createCrudHandlers, n as buildPipelineHandler, o as createRequestContext, r as resolvePipelineSteps, u as buildRequestScopeProjection } from "./pipeline-BN5G05rd.mjs";
14
+ import { a as createFastifyHandler, i as createCrudHandlers, n as buildPipelineHandler, o as createRequestContext, r as resolvePipelineSteps, u as buildRequestScopeProjection } from "./pipeline-BGRnV-To.mjs";
15
15
  import { A as requireAuth } from "./permissions-BrtiBKGs.mjs";
16
16
  import { r as getAvailablePresets, t as applyPresets } from "./presets-DwFDVKP0.mjs";
17
17
  import { t as pluralize } from "./pluralize-BhrgwZb9.mjs";
@@ -55,52 +55,6 @@ function methodCarriesBody(method) {
55
55
  return m === "POST" || m === "PUT" || m === "PATCH";
56
56
  }
57
57
  //#endregion
58
- //#region src/core/aggregation/defineAggregation.ts
59
- /**
60
- * Declare a single resource aggregation. Exported configs flow into
61
- * `defineResource({ aggregations: { ... } })` either as named keys or
62
- * via auto-discovery patterns (loadAggregations — future).
63
- *
64
- * @example Inline
65
- * ```ts
66
- * defineResource({
67
- * name: 'order',
68
- * aggregations: {
69
- * revenueByStatus: defineAggregation({
70
- * groupBy: 'status',
71
- * measures: { count: 'count', revenue: 'sum:totalPrice' },
72
- * permissions: requireRoles(['admin']),
73
- * }),
74
- * },
75
- * });
76
- * ```
77
- *
78
- * @example Multi-file
79
- * ```ts
80
- * // orders/aggregations/revenue-by-status.ts
81
- * import { defineAggregation } from '@classytic/arc';
82
- *
83
- * export const revenueByStatus = defineAggregation({
84
- * groupBy: 'status',
85
- * measures: { count: 'count', revenue: 'sum:totalPrice' },
86
- * permissions: requireRoles(['admin']),
87
- * timeout: 5000,
88
- * cache: { staleTime: 60 },
89
- * });
90
- *
91
- * // orders/order.resource.ts
92
- * import * as aggregations from './aggregations/index.js';
93
- *
94
- * defineResource({
95
- * name: 'order',
96
- * aggregations, // 30+ entries flow in
97
- * });
98
- * ```
99
- */
100
- function defineAggregation(config) {
101
- return config;
102
- }
103
- //#endregion
104
58
  //#region src/core/createCrudRouter.ts
105
59
  /**
106
60
  * Mount custom routes (from presets or user-defined `routes`) on Fastify.
@@ -340,80 +294,6 @@ function createPermissionMiddleware(permission, resourceName, action) {
340
294
  return buildCrudPermissionMw(permission, resourceName, action);
341
295
  }
342
296
  //#endregion
343
- //#region src/core/defineAction.ts
344
- /**
345
- * Build an `ActionDefinition` with a typed handler. The literal schema
346
- * type captured here flows into `data`, so `defineAction({ schema:
347
- * z.object({...}), handler })` produces fully-typed code with no
348
- * `as MyShape` cast.
349
- *
350
- * Behaviorally identical to a bare `ActionDefinition` object — same
351
- * validation path (AJV), same permission resolution, same MCP wiring.
352
- * The runtime shape is unchanged; only the type-level inference is new.
353
- */
354
- function defineAction(config) {
355
- return {
356
- handler: config.handler,
357
- permissions: config.permissions,
358
- schema: config.schema,
359
- description: config.description,
360
- id: config.id,
361
- mcp: config.mcp
362
- };
363
- }
364
- //#endregion
365
- //#region src/core/defineResource/seams.ts
366
- /** Plain object test — literals only; class instances must replace, not merge. */
367
- function isPlainObject(value) {
368
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
369
- const proto = Object.getPrototypeOf(value);
370
- return proto === Object.prototype || proto === null;
371
- }
372
- /** Recursive plain-object merge; nested arrays REPLACE (they're value lists). */
373
- function mergePlain(base, next) {
374
- const out = { ...base };
375
- for (const [key, value] of Object.entries(next)) {
376
- if (value === void 0) continue;
377
- const existing = out[key];
378
- out[key] = isPlainObject(existing) && isPlainObject(value) ? mergePlain(existing, value) : value;
379
- }
380
- return out;
381
- }
382
- /**
383
- * Canonical slot-aware merge of a base resource config with host seams.
384
- * Later seams win over earlier ones; `undefined` values never clobber.
385
- *
386
- * Merge semantics (value-driven — no per-slot registry to drift):
387
- *
388
- * - **Top-level arrays CONCAT** — `routes`, `middlewares`, `presets`,
389
- * `routeGuards`, `disabledRoutes`: additive collections, base first.
390
- * - **Plain objects merge recursively** — `permissions`, `actions`,
391
- * `schemaOptions` (incl. `fieldRules`), `aggregations`, `customSchemas`,
392
- * `fields`, `cache`, `events`: per-key composition. Arrays NESTED inside
393
- * them replace (they're value lists — enums, sort specs), not concat.
394
- * - **Everything else last-wins** — class instances (`adapter`,
395
- * `controller`, `queryParser`), functions, scalars.
396
- *
397
- * The single `as` at the return is the one documented erasure for the
398
- * {@link AdapterLike}-widened seam slots — inside the helper so no module
399
- * author ever writes one (mirrors mongokit's one-cast-at-the-boundary rule).
400
- */
401
- function mergeResourceConfig(base, ...seams) {
402
- const out = { ...base };
403
- for (const seam of seams) {
404
- if (!seam) continue;
405
- for (const [key, value] of Object.entries(seam)) {
406
- if (value === void 0) continue;
407
- const existing = out[key];
408
- if (Array.isArray(existing) && Array.isArray(value)) out[key] = [...existing, ...value];
409
- else if (isPlainObject(existing) && isPlainObject(value)) out[key] = mergePlain(existing, value);
410
- else if (Array.isArray(value)) out[key] = [...value];
411
- else out[key] = value;
412
- }
413
- }
414
- return out;
415
- }
416
- //#endregion
417
297
  //#region src/core/defineResource/controller.ts
418
298
  /**
419
299
  * Phase 4 — pick (or auto-create) the resource's controller.
@@ -1398,7 +1278,7 @@ function buildResourcePlugin(resource) {
1398
1278
  extensions: resource.extensions
1399
1279
  });
1400
1280
  if (resource.actions && Object.keys(resource.actions).length > 0) {
1401
- const { createActionRouter } = await import("./createActionRouter-B6GvAuAX.mjs").then((n) => n.n);
1281
+ const { createActionRouter } = await import("./createActionRouter-C-N7z2EL.mjs").then((n) => n.n);
1402
1282
  createActionRouter(typedInstance, {
1403
1283
  ...normalizeActionsToRouterConfig(resource.actions, resource.actionPermissions, resource.tag, resource.permissions, resource.name, typedInstance.log),
1404
1284
  resourceName: resource.name,
@@ -2308,102 +2188,4 @@ function defineResource(config) {
2308
2188
  return resource;
2309
2189
  }
2310
2190
  //#endregion
2311
- //#region src/core/defineResourceVariants.ts
2312
- /**
2313
- * Define multiple resources from a shared base config and per-variant overrides.
2314
- *
2315
- * Each variant is independently passed through `defineResource()` — the
2316
- * returned `ResourceDefinition`s are real, fully-registered resources.
2317
- * Register each one's plugin in your app:
2318
- *
2319
- * ```typescript
2320
- * await app.register(articlePublic.toPlugin());
2321
- * await app.register(articleAdmin.toPlugin());
2322
- * ```
2323
- *
2324
- * @param base Shared config — adapter, queryParser, schemaOptions, hooks, etc.
2325
- * Must NOT include `name` or `prefix` (those are per-variant).
2326
- * @param variants Map of variant key → override. Each variant must declare
2327
- * its own `name` and `prefix`. Other fields override the base.
2328
- * @returns A record where each key from `variants` maps to a real
2329
- * `ResourceDefinition` ready for `.toPlugin()` registration.
2330
- */
2331
- function defineResourceVariants(base, variants) {
2332
- const out = {};
2333
- for (const key of Object.keys(variants)) {
2334
- const override = variants[key];
2335
- out[key] = defineResource({
2336
- ...base,
2337
- ...override
2338
- });
2339
- }
2340
- return out;
2341
- }
2342
- //#endregion
2343
- //#region src/core/entityHelpers.ts
2344
- /**
2345
- * Per-request entity helpers — read the resource binding (idField + the
2346
- * URL `:id` value) off `req.arc`.
2347
- *
2348
- * Action handlers receive their `id` argument as the raw URL `:id` value.
2349
- * When the resource declares a custom `idField` (`slug`, `reportId`, …)
2350
- * that's NOT the document `_id`, a naive `Model.findById(id)` silently
2351
- * returns null. The historical footgun was a `findById(id)` typo where
2352
- * the handler author hadn't realised that `:id` resolves to the friendly
2353
- * handle.
2354
- *
2355
- * `getEntityQuery(req)` produces the canonical filter shape so handlers
2356
- * compose lookups with no resource-config recall:
2357
- *
2358
- * ```ts
2359
- * actions: {
2360
- * archive: {
2361
- * handler: async (id, data, req) => {
2362
- * const doc = await Model.findOne(getEntityQuery(req));
2363
- * if (!doc) throw new NotFoundError("Order");
2364
- * // ...
2365
- * },
2366
- * },
2367
- * }
2368
- * ```
2369
- *
2370
- * The router populates `req.arc.idField` and `req.arc.entityId` before
2371
- * invoking the handler — these helpers are zero-cost reads.
2372
- */
2373
- /**
2374
- * Read the resource's configured `idField` for the current request.
2375
- * Falls back to the framework default (`_id`) when the route hasn't
2376
- * bound an idField — keeps handlers safe to author without checking
2377
- * the resource config.
2378
- */
2379
- function getEntityIdField(req) {
2380
- return req.arc?.idField ?? "_id";
2381
- }
2382
- /**
2383
- * Read the URL `:id` path param value as the resource handle.
2384
- * Returns `undefined` when the route has no `:id` segment (collection
2385
- * routes) — handlers that need the entity must be on row routes.
2386
- */
2387
- function getEntityId(req) {
2388
- if (req.arc?.entityId !== void 0) return req.arc.entityId;
2389
- return req.params?.id;
2390
- }
2391
- /**
2392
- * Compose a `findOne` filter that resolves the current request's
2393
- * entity, regardless of whether the resource binds `_id` or a custom
2394
- * field. Idiomatic shape for arc action handlers.
2395
- *
2396
- * ```ts
2397
- * const doc = await Model.findOne(getEntityQuery(req));
2398
- * ```
2399
- *
2400
- * Returns `{}` when the route has no entity context (collection routes
2401
- * or tests bypassing the router) — caller decides what that means.
2402
- */
2403
- function getEntityQuery(req) {
2404
- const id = getEntityId(req);
2405
- if (id === void 0) return {};
2406
- return { [getEntityIdField(req)]: id };
2407
- }
2408
- //#endregion
2409
- export { defineResource as a, validateResourceConfig as c, defineAction as d, createCrudRouter as f, defineResourceVariants as i, ResourceDefinition as l, defineAggregation as m, getEntityIdField as n, assertValidConfig as o, createPermissionMiddleware as p, getEntityQuery as r, formatValidationErrors as s, getEntityId as t, mergeResourceConfig as u };
2191
+ export { ResourceDefinition as a, validateResourceConfig as i, assertValidConfig as n, createCrudRouter as o, formatValidationErrors as r, createPermissionMiddleware as s, defineResource as t };
@@ -1,5 +1,5 @@
1
1
  import { n as getUserRoles } from "../userHelpers-Cg_y3fBz.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-DrLtog_B.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-BLV4OMXb.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {