@classytic/arc 2.21.0 → 2.22.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 (94) hide show
  1. package/dist/{BaseController-BnLYgE9A.mjs → BaseController-C6FlRWxF.mjs} +1 -1
  2. package/dist/audit/index.d.mts +2 -2
  3. package/dist/auth/index.d.mts +30 -2
  4. package/dist/auth/index.mjs +5 -2
  5. package/dist/cli/commands/describe.d.mts +1 -1
  6. package/dist/cli/commands/docs.mjs +1 -1
  7. package/dist/cli/commands/init.mjs +1 -1
  8. package/dist/cli/index.mjs +1 -1
  9. package/dist/core/index.d.mts +2 -2
  10. package/dist/core/index.mjs +2 -2
  11. package/dist/{core-DZ6Xe1jR.mjs → core-b6fgFgnd.mjs} +123 -66
  12. package/dist/{createActionRouter-BWMDW-dL.mjs → createActionRouter-h1cEEaEX.mjs} +9 -5
  13. package/dist/{createAggregationRouter-l5mNdYrZ.mjs → createAggregationRouter-B1C0kvTL.mjs} +3 -3
  14. package/dist/{createApp-Bi61XxqU.mjs → createApp-Ci7yG5gv.mjs} +242 -56
  15. package/dist/docs/index.d.mts +1 -1
  16. package/dist/docs/index.mjs +1 -1
  17. package/dist/encryption/index.mjs +9 -5
  18. package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-BTzXSwIg.mjs} +24 -3
  19. package/dist/{errorHandler-B0e-bq-O.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -0
  20. package/dist/factory/index.d.mts +29 -8
  21. package/dist/factory/index.mjs +34 -3
  22. package/dist/{fastify-augmentation-DafigGQp.d.mts → fastify-augmentation-9_fuetB4.d.mts} +2 -2
  23. package/dist/fields-D3lhgzhD.d.mts +121 -0
  24. package/dist/health-ScyjIVo5.mjs +202 -0
  25. package/dist/hooks/index.d.mts +1 -1
  26. package/dist/idempotency/index.mjs +2 -0
  27. package/dist/{types-DpxoNX5v.d.mts → index-BEu4wjbL.d.mts} +187 -66
  28. package/dist/{index-B5N-NaHs.d.mts → index-D8MKYGDa.d.mts} +3 -3
  29. package/dist/{index-Dtrj6XZk.d.mts → index-DbtgCOCj.d.mts} +1287 -1225
  30. package/dist/{index-B5PoNbfu.d.mts → index-ogIHvOjo.d.mts} +1 -1
  31. package/dist/index.d.mts +6 -5
  32. package/dist/index.mjs +3 -3
  33. package/dist/{init-0YhHkXGk.mjs → init-DleK3p9f.mjs} +118 -2
  34. package/dist/integrations/index.d.mts +1 -1
  35. package/dist/integrations/mcp/index.d.mts +3 -3
  36. package/dist/integrations/mcp/index.mjs +1 -1
  37. package/dist/integrations/mcp/testing.d.mts +1 -1
  38. package/dist/integrations/mcp/testing.mjs +1 -1
  39. package/dist/integrations/webhooks.d.mts +8 -4
  40. package/dist/integrations/webhooks.mjs +9 -5
  41. package/dist/jsonBody-BlzAoGCe.mjs +43 -0
  42. package/dist/logger/index.d.mts +26 -1
  43. package/dist/logger/index.mjs +39 -1
  44. package/dist/{mcpPlugin-B9XjjNVG.mjs → mcpPlugin-ByA2tf6D.mjs} +6 -2
  45. package/dist/middleware/index.d.mts +1 -1
  46. package/dist/{module-WeuU5jfd.d.mts → module-B4u10iOJ.d.mts} +26 -1
  47. package/dist/{openapi-Kyod_di6.mjs → openapi-BRpMZD7C.mjs} +2 -2
  48. package/dist/permissions/index.d.mts +3 -2
  49. package/dist/pipeline/index.d.mts +1 -1
  50. package/dist/plugins/index.d.mts +22 -5
  51. package/dist/plugins/index.mjs +11 -280
  52. package/dist/plugins/response-cache.mjs +1 -0
  53. package/dist/plugins/tracing-entry.mjs +7 -4
  54. package/dist/presets/filesUpload.d.mts +2 -2
  55. package/dist/presets/index.d.mts +1 -1
  56. package/dist/presets/multiTenant.d.mts +1 -1
  57. package/dist/presets/search.d.mts +2 -2
  58. package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
  59. package/dist/{registerRoute-oGAl0oA8.mjs → registerRoute-xHc42fwB.mjs} +37 -17
  60. package/dist/registry/index.d.mts +2 -2
  61. package/dist/requestId-5BhTVzBv.mjs +113 -0
  62. package/dist/schemaConverter-Ds5reSGs.mjs +168 -0
  63. package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-B-6SPe7g.mjs} +2 -2
  64. package/dist/scim/index.mjs +6 -7
  65. package/dist/scope/index.mjs +2 -76
  66. package/dist/scope-k15RXfcq.mjs +64 -0
  67. package/dist/testing/index.d.mts +83 -3
  68. package/dist/testing/index.mjs +70 -3
  69. package/dist/types/index.d.mts +2 -2
  70. package/dist/{types-DRj-Ut4n.d.mts → types-Bc42CUyk.d.mts} +1 -1
  71. package/dist/{fields-BGqrAa_u.d.mts → types-Csxga7d3.d.mts} +1 -121
  72. package/dist/usage/index.d.mts +142 -0
  73. package/dist/usage/index.mjs +0 -0
  74. package/dist/utils/index.d.mts +40 -18
  75. package/dist/utils/index.mjs +2 -2
  76. package/dist/{utils-CO8a3B_1.mjs → utils-FhJ88TJE.mjs} +59 -13
  77. package/dist/{versioning-DQ4mCvg0.d.mts → versioning-DLwklPlG.d.mts} +0 -5
  78. package/llms.txt +39 -0
  79. package/package.json +453 -447
  80. package/skills/arc/SKILL.md +14 -8
  81. package/skills/arc/references/api-reference.md +34 -0
  82. package/skills/arc/references/auth.md +21 -4
  83. package/skills/arc/references/mcp.md +1 -1
  84. package/skills/arc/references/production.md +44 -2
  85. package/skills/arc-code-review/SKILL.md +1 -1
  86. package/skills/arc-code-review/references/anti-patterns.md +71 -18
  87. package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
  88. package/skills/arc-code-review/references/migration-recipes.md +7 -7
  89. package/skills/arc-code-review/references/mongokit-migration.md +4 -3
  90. package/skills/arc-module-publishing/SKILL.md +2 -1
  91. package/skills/arc-module-publishing/references/authoring-modules.md +55 -0
  92. package/skills/arc-module-publishing/references/package-manifest.md +4 -2
  93. package/dist/preloadResources-CHw3_oA_.mjs +0 -45
  94. package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
@@ -1,6 +1,6 @@
1
- import { bn as HookSystem, z as ResourceRegistry } from "./index-Dtrj6XZk.mjs";
1
+ import { bn as HookSystem, z as ResourceRegistry } from "./index-DbtgCOCj.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
3
- import { r as ArcModuleRegistry } from "./module-WeuU5jfd.mjs";
3
+ import { r as ArcModuleRegistry } from "./module-B4u10iOJ.mjs";
4
4
  import { FastifyPluginAsync } from "fastify";
5
5
  //#region src/core/arcCorePlugin.d.ts
6
6
  interface ArcCorePluginOptions {
@@ -0,0 +1,121 @@
1
+ //#region src/permissions/fields.d.ts
2
+ /**
3
+ * Field-Level Permissions
4
+ *
5
+ * Control field visibility and writability per role.
6
+ * Integrated into the response path (read) and sanitization path (write).
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { fields, defineResource } from '@classytic/arc';
11
+ *
12
+ * const userResource = defineResource({
13
+ * name: 'user',
14
+ * adapter: userAdapter,
15
+ * fields: {
16
+ * salary: fields.visibleTo(['admin', 'hr']),
17
+ * internalNotes: fields.writableBy(['admin']),
18
+ * email: fields.redactFor(['viewer']),
19
+ * password: fields.hidden(),
20
+ * },
21
+ * });
22
+ * ```
23
+ */
24
+ type FieldPermissionType = "hidden" | "visibleTo" | "writableBy" | "redactFor";
25
+ interface FieldPermission {
26
+ readonly _type: FieldPermissionType;
27
+ readonly roles?: readonly string[];
28
+ readonly redactValue?: unknown;
29
+ }
30
+ type FieldPermissionMap = Record<string, FieldPermission>;
31
+ declare const fields: {
32
+ /**
33
+ * Field is never included in responses. Not writable via API.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * fields: { password: fields.hidden() }
38
+ * ```
39
+ */
40
+ hidden(): FieldPermission;
41
+ /**
42
+ * Field is only visible to users with specified roles.
43
+ * Other users don't see the field at all.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * fields: { salary: fields.visibleTo(['admin', 'hr']) }
48
+ * ```
49
+ */
50
+ visibleTo(roles: readonly string[]): FieldPermission;
51
+ /**
52
+ * Field is only writable by users with specified roles.
53
+ * All users can still read the field. Users without the role
54
+ * have the field silently stripped from write operations.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * fields: { role: fields.writableBy(['admin']) }
59
+ * ```
60
+ */
61
+ writableBy(roles: readonly string[]): FieldPermission;
62
+ /**
63
+ * Field is redacted (replaced with a placeholder) for specified roles.
64
+ * Other users see the real value.
65
+ *
66
+ * @param roles - Roles that see the redacted value
67
+ * @param redactValue - Replacement value (default: '***')
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * fields: {
72
+ * email: fields.redactFor(['viewer']),
73
+ * ssn: fields.redactFor(['basic'], '***-**-****'),
74
+ * }
75
+ * ```
76
+ */
77
+ redactFor(roles: readonly string[], redactValue?: unknown): FieldPermission;
78
+ };
79
+ /**
80
+ * Apply field-level READ permissions to a response object.
81
+ * Strips hidden fields, enforces visibility, and applies redaction.
82
+ *
83
+ * @param data - The response object (mutated in place for performance)
84
+ * @param fieldPermissions - Field permission map from resource config
85
+ * @param userRoles - Current user's roles (empty array for unauthenticated)
86
+ * @returns The filtered object
87
+ */
88
+ declare function applyFieldReadPermissions<T extends Record<string, unknown>>(data: T, fieldPermissions: FieldPermissionMap, userRoles: readonly string[]): T;
89
+ /**
90
+ * Result of applying write permissions — includes both the filtered body
91
+ * and the list of fields that were stripped so callers can decide whether
92
+ * to reject the request (secure default) or silently strip (legacy).
93
+ */
94
+ interface FieldWritePermissionResult<T extends Record<string, unknown>> {
95
+ readonly body: T;
96
+ readonly deniedFields: readonly string[];
97
+ }
98
+ /**
99
+ * Apply field-level WRITE permissions to request body.
100
+ *
101
+ * Returns both the filtered body and the list of denied fields. Callers are
102
+ * expected to reject the request when `deniedFields.length > 0` — silently
103
+ * stripping fields hides misconfigurations and real attacks. See
104
+ * `BodySanitizer` for the default policy.
105
+ *
106
+ * @param body - The request body (returns a new filtered copy)
107
+ * @param fieldPermissions - Field permission map from resource config
108
+ * @param userRoles - Current user's roles
109
+ */
110
+ declare function applyFieldWritePermissions<T extends Record<string, unknown>>(body: T, fieldPermissions: FieldPermissionMap, userRoles: readonly string[]): FieldWritePermissionResult<T>;
111
+ /**
112
+ * Resolve effective roles by merging global user roles with org-level roles.
113
+ *
114
+ * Global roles come from `req.user.role` (normalized via getUserRoles()).
115
+ * Org roles come from `req.context.orgRoles` (set by BA adapter's org bridge).
116
+ *
117
+ * When no org context exists, returns global roles only — backward compatible.
118
+ */
119
+ declare function resolveEffectiveRoles(userRoles: readonly string[], orgRoles: readonly string[]): string[];
120
+ //#endregion
121
+ export { applyFieldWritePermissions as a, applyFieldReadPermissions as i, FieldPermissionMap as n, fields as o, FieldPermissionType as r, resolveEffectiveRoles as s, FieldPermission as t };
@@ -0,0 +1,202 @@
1
+ import fp from "fastify-plugin";
2
+ //#region src/plugins/health.ts
3
+ function createHttpMetrics() {
4
+ return {
5
+ requestsTotal: {},
6
+ requestDurations: [],
7
+ _ringIndex: 0,
8
+ startTime: Date.now()
9
+ };
10
+ }
11
+ const healthPlugin = async (fastify, opts = {}) => {
12
+ const { prefix = "/_health", checks = [], metrics = false, metricsCollector, version, collectHttpMetrics = metrics } = opts;
13
+ const httpMetrics = createHttpMetrics();
14
+ fastify.get(prefix, { schema: {
15
+ tags: ["Health"],
16
+ summary: "Liveness probe (alias for /live)",
17
+ description: "Returns 200 if the process is alive",
18
+ response: { 200: {
19
+ type: "object",
20
+ properties: {
21
+ status: {
22
+ type: "string",
23
+ enum: ["ok"]
24
+ },
25
+ timestamp: { type: "string" },
26
+ version: { type: "string" }
27
+ }
28
+ } }
29
+ } }, async () => ({
30
+ status: "ok",
31
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
32
+ ...version ? { version } : {}
33
+ }));
34
+ fastify.get(`${prefix}/live`, { schema: {
35
+ tags: ["Health"],
36
+ summary: "Liveness probe",
37
+ description: "Returns 200 if the process is alive",
38
+ response: { 200: {
39
+ type: "object",
40
+ properties: {
41
+ status: {
42
+ type: "string",
43
+ enum: ["ok"]
44
+ },
45
+ timestamp: { type: "string" },
46
+ version: { type: "string" }
47
+ }
48
+ } }
49
+ } }, async () => {
50
+ return {
51
+ status: "ok",
52
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
53
+ ...version ? { version } : {}
54
+ };
55
+ });
56
+ fastify.get(`${prefix}/ready`, { schema: {
57
+ tags: ["Health"],
58
+ summary: "Readiness probe",
59
+ description: "Returns 200 if all dependencies are healthy",
60
+ response: {
61
+ 200: {
62
+ type: "object",
63
+ properties: {
64
+ status: {
65
+ type: "string",
66
+ enum: ["ready", "not_ready"]
67
+ },
68
+ timestamp: { type: "string" },
69
+ checks: {
70
+ type: "array",
71
+ items: {
72
+ type: "object",
73
+ properties: {
74
+ name: { type: "string" },
75
+ healthy: { type: "boolean" },
76
+ duration: { type: "number" },
77
+ error: { type: "string" }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ },
83
+ 503: {
84
+ type: "object",
85
+ properties: {
86
+ status: {
87
+ type: "string",
88
+ enum: ["not_ready"]
89
+ },
90
+ timestamp: { type: "string" },
91
+ checks: { type: "array" }
92
+ }
93
+ }
94
+ }
95
+ } }, async (_, reply) => {
96
+ const results = await runChecks(checks);
97
+ const criticalFailed = results.some((r) => !r.healthy && (checks.find((c) => c.name === r.name)?.critical ?? true));
98
+ const response = {
99
+ status: criticalFailed ? "not_ready" : "ready",
100
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
101
+ checks: results
102
+ };
103
+ if (criticalFailed) reply.code(503);
104
+ return response;
105
+ });
106
+ if (metrics) fastify.get(`${prefix}/metrics`, async (_, reply) => {
107
+ reply.type("text/plain; charset=utf-8");
108
+ if (metricsCollector) return await metricsCollector();
109
+ const uptime = process.uptime();
110
+ const memory = process.memoryUsage();
111
+ const cpu = process.cpuUsage();
112
+ const lines = [
113
+ "# HELP process_uptime_seconds Process uptime in seconds",
114
+ "# TYPE process_uptime_seconds gauge",
115
+ `process_uptime_seconds ${uptime.toFixed(2)}`,
116
+ "",
117
+ "# HELP process_memory_heap_bytes Heap memory usage in bytes",
118
+ "# TYPE process_memory_heap_bytes gauge",
119
+ `process_memory_heap_bytes{type="used"} ${memory.heapUsed}`,
120
+ `process_memory_heap_bytes{type="total"} ${memory.heapTotal}`,
121
+ "",
122
+ "# HELP process_memory_rss_bytes RSS memory in bytes",
123
+ "# TYPE process_memory_rss_bytes gauge",
124
+ `process_memory_rss_bytes ${memory.rss}`,
125
+ "",
126
+ "# HELP process_memory_external_bytes External memory in bytes",
127
+ "# TYPE process_memory_external_bytes gauge",
128
+ `process_memory_external_bytes ${memory.external}`,
129
+ "",
130
+ "# HELP process_cpu_user_microseconds User CPU time in microseconds",
131
+ "# TYPE process_cpu_user_microseconds counter",
132
+ `process_cpu_user_microseconds ${cpu.user}`,
133
+ "",
134
+ "# HELP process_cpu_system_microseconds System CPU time in microseconds",
135
+ "# TYPE process_cpu_system_microseconds counter",
136
+ `process_cpu_system_microseconds ${cpu.system}`,
137
+ ""
138
+ ];
139
+ if (collectHttpMetrics && Object.keys(httpMetrics.requestsTotal).length > 0) {
140
+ lines.push("# HELP http_requests_total Total HTTP requests by status code", "# TYPE http_requests_total counter");
141
+ for (const [status, count] of Object.entries(httpMetrics.requestsTotal)) lines.push(`http_requests_total{status="${status}"} ${count}`);
142
+ lines.push("");
143
+ if (httpMetrics.requestDurations.length > 0) {
144
+ const sorted = [...httpMetrics.requestDurations].sort((a, b) => a - b);
145
+ const p50 = sorted[Math.floor(sorted.length * .5)] || 0;
146
+ const p95 = sorted[Math.floor(sorted.length * .95)] || 0;
147
+ const p99 = sorted[Math.floor(sorted.length * .99)] || 0;
148
+ const sum = sorted.reduce((a, b) => a + b, 0);
149
+ lines.push("# HELP http_request_duration_milliseconds HTTP request duration", "# TYPE http_request_duration_milliseconds summary", `http_request_duration_milliseconds{quantile="0.5"} ${p50.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.95"} ${p95.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.99"} ${p99.toFixed(2)}`, `http_request_duration_milliseconds_sum ${sum.toFixed(2)}`, `http_request_duration_milliseconds_count ${sorted.length}`, "");
150
+ }
151
+ }
152
+ return lines.join("\n");
153
+ });
154
+ if (collectHttpMetrics) fastify.addHook("onResponse", async (_request, reply) => {
155
+ const duration = reply.elapsedTime;
156
+ const statusBucket = `${Math.floor(reply.statusCode / 100)}xx`;
157
+ httpMetrics.requestsTotal[statusBucket] = (httpMetrics.requestsTotal[statusBucket] || 0) + 1;
158
+ if (httpMetrics.requestDurations.length < 1e4) httpMetrics.requestDurations.push(duration);
159
+ else httpMetrics.requestDurations[httpMetrics._ringIndex % 1e4] = duration;
160
+ httpMetrics._ringIndex = httpMetrics._ringIndex + 1;
161
+ });
162
+ fastify.log?.debug?.(`Health plugin registered at ${prefix} (alias), ${prefix}/live, ${prefix}/ready${metrics ? `, ${prefix}/metrics` : ""}`);
163
+ };
164
+ /**
165
+ * Run all health checks with timeout
166
+ */
167
+ async function runChecks(checks) {
168
+ const results = [];
169
+ for (const check of checks) {
170
+ const start = Date.now();
171
+ const timeout = check.timeout ?? 5e3;
172
+ let timer;
173
+ try {
174
+ const checkPromise = Promise.resolve(check.check());
175
+ const timeoutPromise = new Promise((_, reject) => {
176
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Health check timeout")), timeout);
177
+ });
178
+ const healthy = await Promise.race([checkPromise, timeoutPromise]);
179
+ results.push({
180
+ name: check.name,
181
+ healthy: Boolean(healthy),
182
+ duration: Date.now() - start
183
+ });
184
+ } catch (err) {
185
+ results.push({
186
+ name: check.name,
187
+ healthy: false,
188
+ duration: Date.now() - start,
189
+ error: err.message
190
+ });
191
+ } finally {
192
+ if (timer) clearTimeout(timer);
193
+ }
194
+ }
195
+ return results;
196
+ }
197
+ var health_default = fp(healthPlugin, {
198
+ name: "arc-health",
199
+ fastify: "5.x"
200
+ });
201
+ //#endregion
202
+ export { health_default as n, healthPlugin as t };
@@ -1,2 +1,2 @@
1
- import { Cn as afterDelete, Dn as beforeUpdate, En as beforeDelete, On as createHookSystem, Sn as afterCreate, Tn as beforeCreate, _n as HookOperation, bn as HookSystem, gn as HookHandler, hn as HookContext, kn as defineHook, mn as DefineHookOptions, vn as HookPhase, wn as afterUpdate, xn as HookSystemOptions, yn as HookRegistration } from "../index-Dtrj6XZk.mjs";
1
+ import { Cn as afterDelete, Dn as beforeUpdate, En as beforeDelete, On as createHookSystem, Sn as afterCreate, Tn as beforeCreate, _n as HookOperation, bn as HookSystem, gn as HookHandler, hn as HookContext, kn as defineHook, mn as DefineHookOptions, vn as HookPhase, wn as afterUpdate, xn as HookSystemOptions, yn as HookRegistration } from "../index-DbtgCOCj.mjs";
2
2
  export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -287,12 +287,14 @@ const idempotencyPlugin = async (fastify, opts = {}) => {
287
287
  });
288
288
  fastify.decorateRequest("idempotencyKey", void 0);
289
289
  fastify.decorateRequest("idempotencyReplayed", false);
290
+ fastify.decorateRequest("_idempotencyFullKey", void 0);
290
291
  fastify.log?.debug?.("Idempotency plugin disabled");
291
292
  return;
292
293
  }
293
294
  const methodSet = new Set(methods.map((m) => m.toUpperCase()));
294
295
  fastify.decorateRequest("idempotencyKey", void 0);
295
296
  fastify.decorateRequest("idempotencyReplayed", false);
297
+ fastify.decorateRequest("_idempotencyFullKey", void 0);
296
298
  /**
297
299
  * Check if this request should use idempotency
298
300
  */
@@ -1,13 +1,13 @@
1
- import { Ut as Authenticator } from "./index-Dtrj6XZk.mjs";
1
+ import { Ut as Authenticator } from "./index-DbtgCOCj.mjs";
2
2
  import { r as CacheStore } from "./interface-C1_WLTMK.mjs";
3
3
  import { n as ElevationOptions } from "./elevation-C9s5hFP_.mjs";
4
4
  import { a as EventTransport } from "./EventTransport-QmY7A258.mjs";
5
5
  import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
6
6
  import { r as QueryCachePluginOptions } from "./queryCachePlugin-CfYWq-Ak.mjs";
7
- import { t as ErrorHandlerOptions } from "./errorHandler-B0e-bq-O.mjs";
8
- import { l as ResourceLike, n as ArcModuleInput } from "./module-WeuU5jfd.mjs";
7
+ import { t as ErrorHandlerOptions } from "./errorHandler-C6yv4ZTW.mjs";
8
+ import { l as ResourceLike, n as ArcModuleInput } from "./module-B4u10iOJ.mjs";
9
9
  import { t as EventPluginOptions } from "./eventPlugin-A6EbthXR.mjs";
10
- import { _ as HealthOptions, f as CachingOptions, l as SSEOptions, o as MetricsOptions, t as VersioningOptions } from "./versioning-DQ4mCvg0.mjs";
10
+ import { _ as HealthOptions, f as CachingOptions, l as SSEOptions, o as MetricsOptions, t as VersioningOptions } from "./versioning-DLwklPlG.mjs";
11
11
  import { r as IdempotencyStore } from "./interface-Uk8ONJv7.mjs";
12
12
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
13
13
  //#region src/testing/preloadResources.d.ts
@@ -33,30 +33,7 @@ declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
33
33
  */
34
34
  declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
35
35
  //#endregion
36
- //#region src/factory/types.d.ts
37
- type CorsOptions = Record<string, unknown> & {
38
- origin?: unknown;
39
- credentials?: boolean;
40
- methods?: string[];
41
- allowedHeaders?: string[];
42
- };
43
- type HelmetOptions = Record<string, unknown>;
44
- type RateLimitOpts = Record<string, unknown> & {
45
- max?: number;
46
- timeWindow?: string | number;
47
- /**
48
- * Path patterns to exempt from rate limiting. Supports exact match
49
- * (`/health`) or prefix match with trailing `*` (`/api/auth/*`).
50
- *
51
- * Implemented by synthesising a `@fastify/rate-limit` `allowList`
52
- * function. When combined with a user-supplied `allowList`, both
53
- * are OR-ed together (path OR ip/custom match skips the limit).
54
- *
55
- * Use this for endpoints that are hit frequently but independently
56
- * of user traffic — session heartbeat, webhooks, health probes.
57
- */
58
- skipPaths?: string[];
59
- };
36
+ //#region src/factory/types/auth.d.ts
60
37
  /**
61
38
  * Arc's built-in JWT auth
62
39
  *
@@ -161,13 +138,29 @@ interface JwtAuthOption {
161
138
  * });
162
139
  * ```
163
140
  */
141
+ /** The shape `createBetterAuthAdapter()` returns. */
142
+ interface BetterAuthAdapterResult {
143
+ plugin: FastifyPluginAsync;
144
+ openapi?: ExternalOpenApiPaths;
145
+ }
164
146
  interface BetterAuthOption {
165
147
  type: "betterAuth";
166
- /** Better Auth adapter — pass the result of createBetterAuthAdapter() */
167
- betterAuth: {
168
- plugin: FastifyPluginAsync;
169
- openapi?: ExternalOpenApiPaths;
170
- };
148
+ /**
149
+ * Better Auth adapter — the result of `createBetterAuthAdapter()`, or a
150
+ * sync/async THUNK returning it (2.22). The thunk resolves during auth
151
+ * registration — AFTER `beforeBoot` — for adapters whose construction
152
+ * needs a live DB (Better Auth's `mongodbAdapter(mongoose.connection
153
+ * .getClient().db())` requires an OPEN connection, so an eager value
154
+ * forces hosts back into connect-before-createApp ordering dances):
155
+ *
156
+ * ```typescript
157
+ * createApp({
158
+ * beforeBoot: () => connectDatabase(),
159
+ * auth: { type: 'betterAuth', betterAuth: () => createBetterAuthAdapter({ auth: getAuth() }) },
160
+ * });
161
+ * ```
162
+ */
163
+ betterAuth: BetterAuthAdapterResult | (() => BetterAuthAdapterResult | Promise<BetterAuthAdapterResult>);
171
164
  }
172
165
  /**
173
166
  * Custom auth plugin — full control over authentication setup
@@ -234,6 +227,112 @@ interface CustomAuthenticatorOption {
234
227
  * - `CustomAuthenticatorOption` — A bare authenticate function (`type: 'authenticator'`)
235
228
  */
236
229
  type AuthOption = false | JwtAuthOption | BetterAuthOption | CustomPluginAuthOption | CustomAuthenticatorOption;
230
+ //#endregion
231
+ //#region src/factory/types/plugin-options.d.ts
232
+ /**
233
+ * Plugin-specific option shapes for `createApp()` — under-pressure,
234
+ * multipart, and raw-body plugin configuration.
235
+ */
236
+ interface UnderPressureOptions {
237
+ /** Expose `/_status` route for health checks (default: false) */
238
+ exposeStatusRoute?: boolean;
239
+ /** Event loop lag threshold in ms — requests rejected above this (default: 1000) */
240
+ maxEventLoopDelay?: number;
241
+ /** V8 heap usage threshold in bytes — requests rejected above this */
242
+ maxHeapUsedBytes?: number;
243
+ /** RSS memory threshold in bytes — requests rejected above this */
244
+ maxRssBytes?: number;
245
+ }
246
+ interface MultipartOptions {
247
+ limits?: {
248
+ /** Max file size in bytes (default: Fastify default ~1MB) */
249
+ fileSize?: number;
250
+ /** Max number of files per request */
251
+ files?: number;
252
+ };
253
+ }
254
+ interface RawBodyOptions {
255
+ /** Body field name to store raw body on (default: 'rawBody') */
256
+ field?: string;
257
+ /** Apply to all routes globally (default: false) */
258
+ global?: boolean;
259
+ /** Encoding for raw body string (default: 'utf8') */
260
+ encoding?: string;
261
+ /** Parse raw body before other parsers (default: false) */
262
+ runFirst?: boolean;
263
+ }
264
+ //#endregion
265
+ //#region src/factory/types/security.d.ts
266
+ /**
267
+ * Security-plugin option shapes — CORS / Helmet / rate-limit config for
268
+ * `createApp()`, including plan-aware rate limiting (2.22).
269
+ */
270
+ type CorsOptions = Record<string, unknown> & {
271
+ origin?: unknown;
272
+ credentials?: boolean;
273
+ methods?: string[];
274
+ allowedHeaders?: string[];
275
+ };
276
+ type HelmetOptions = Record<string, unknown>;
277
+ type RateLimitOpts = Record<string, unknown> & {
278
+ max?: number;
279
+ timeWindow?: string | number;
280
+ /**
281
+ * Path patterns to exempt from rate limiting. Supports exact match
282
+ * (`/health`) or prefix match with trailing `*` (`/api/auth/*`).
283
+ *
284
+ * Implemented by synthesising a `@fastify/rate-limit` `allowList`
285
+ * function. When combined with a user-supplied `allowList`, both
286
+ * are OR-ed together (path OR ip/custom match skips the limit).
287
+ *
288
+ * Use this for endpoints that are hit frequently but independently
289
+ * of user traffic — session heartbeat, webhooks, health probes.
290
+ */
291
+ skipPaths?: string[];
292
+ /**
293
+ * Plan-aware limits (2.22) — resolve the caller's plan per request and
294
+ * apply that plan's ceiling instead of the global `max`:
295
+ *
296
+ * ```typescript
297
+ * rateLimit: {
298
+ * timeWindow: '1 minute',
299
+ * plan: {
300
+ * resolve: (req) => lookupPlan(req), // 'free' | 'pro' | ...
301
+ * limits: { free: { max: 60 }, pro: { max: 1000 }, enterprise: false },
302
+ * default: 'free',
303
+ * },
304
+ * }
305
+ * ```
306
+ *
307
+ * `false` = effectively unlimited. When `plan` is set and no
308
+ * `keyGenerator` is supplied, arc defaults to the tenant key chain
309
+ * (org → user → client → IP) so buckets follow actors, not addresses.
310
+ *
311
+ * Timing caveat: the limiter runs before route-level auth, so
312
+ * `req.scope` may be unresolved for token-authenticated calls —
313
+ * `resolve` receives the RAW request and may need to derive the plan
314
+ * from its own header/token/session lookup. Same caveat as
315
+ * `createTenantKeyGenerator`'s IP fallback (documented there).
316
+ */
317
+ plan?: RateLimitPlanConfig;
318
+ };
319
+ /** Plan-aware rate limiting (2.22) — see `rateLimit.plan` on CreateAppOptions. */
320
+ interface RateLimitPlanConfig {
321
+ /**
322
+ * Name the caller's plan from the raw request. Return `undefined` to
323
+ * fall back to `default`. Sync or async; a throwing resolver also
324
+ * falls back (limits must fail-safe, not fail-open to unlimited).
325
+ */
326
+ resolve: (req: import("fastify").FastifyRequest) => string | undefined | Promise<string | undefined>;
327
+ /** Per-plan ceilings within the shared `timeWindow`. `false` = effectively unlimited. */
328
+ limits: Record<string, {
329
+ max: number;
330
+ } | false>;
331
+ /** Plan used when `resolve` returns undefined/unknown or throws. Default: fall back to the global `max`. */
332
+ default?: string;
333
+ }
334
+ //#endregion
335
+ //#region src/factory/types/app-options.d.ts
237
336
  /**
238
337
  * CreateApp Options
239
338
  *
@@ -276,8 +375,18 @@ type AuthOption = false | JwtAuthOption | BetterAuthOption | CustomPluginAuthOpt
276
375
  * ```
277
376
  */
278
377
  interface CreateAppOptions {
279
- /** Environment preset: 'production', 'development', 'testing', or 'edge' */
280
- preset?: "production" | "development" | "testing" | "edge";
378
+ /** Environment preset: 'production', 'development', 'testing', 'edge', or 'worker' */
379
+ preset?: "production" | "development" | "testing" | "edge" | "worker";
380
+ /**
381
+ * Route-mounting switch (2.23) — the worker-role primitive. `false`
382
+ * registers every resource's shared runtime state (registry metadata +
383
+ * adapter, hooks, cache-invalidation rules) WITHOUT mounting any routes,
384
+ * so headless processes keep tenant-purge cascade, per-resource audit,
385
+ * and events metadata intact. Set by `preset: 'worker'` /
386
+ * `createWorker()`; hosts composing a custom worker shape may set it
387
+ * directly. Default: true (routes mount).
388
+ */
389
+ mountRoutes?: boolean;
281
390
  /**
282
391
  * Runtime profile for store backends.
283
392
  * - 'memory' (default): Uses in-memory stores. Suitable for single-instance deployments.
@@ -324,10 +433,50 @@ interface CreateAppOptions {
324
433
  * ```
325
434
  */
326
435
  debug?: boolean | string;
327
- /** Trust proxy headers (X-Forwarded-For, etc.) */
328
- trustProxy?: boolean;
436
+ /**
437
+ * Trust proxy headers (X-Forwarded-For, etc.). Pass-through to Fastify's
438
+ * `trustProxy` server option — all its forms are supported, not just the
439
+ * boolean:
440
+ *
441
+ * - `true` — trust ALL proxies (only safe when the app is never directly
442
+ * reachable; a spoofed `X-Forwarded-For` otherwise forges `request.ip`,
443
+ * which feeds rate-limit keys and audit logs)
444
+ * - `1` — trust exactly one hop (typical single LB / reverse proxy)
445
+ * - `'10.0.0.0/8'` / `['10.0.0.0/8', '172.16.0.0/12']` — trust only these
446
+ * CIDR ranges
447
+ *
448
+ * @example
449
+ * ```ts
450
+ * createApp({ preset: 'production', trustProxy: 1 }); // one LB hop
451
+ * createApp({ preset: 'production', trustProxy: '10.0.0.0/8' }); // VPC proxies
452
+ * ```
453
+ */
454
+ trustProxy?: boolean | string | string[] | number;
329
455
  /** Fastify plugin/onReady timeout in ms (default: 10_000). Raise for slow boot work (index materialisation, WAL replay, external warm-up). */
330
456
  pluginTimeout?: number;
457
+ /**
458
+ * Max time in ms the server waits to RECEIVE an entire request
459
+ * (headers + body). Pass-through to Fastify's `requestTimeout` (Node's
460
+ * `server.requestTimeout`). Default `0` defers to Node's own 300s bound
461
+ * — slow-loris requests are already cut off there, so arc doesn't
462
+ * override; tighten (e.g. `30_000`) for APIs that never accept slow
463
+ * uploads. Response duration (SSE, streaming) is NOT affected.
464
+ */
465
+ requestTimeout?: number;
466
+ /**
467
+ * Max time in ms to establish a connection before the socket is
468
+ * destroyed. Pass-through to Fastify's `connectionTimeout` (Node's
469
+ * `server.timeout`). Default `0` = no limit (Node's `headersTimeout`
470
+ * ~60s still bounds idle pre-request sockets).
471
+ */
472
+ connectionTimeout?: number;
473
+ /**
474
+ * Keep-alive idle timeout in ms (Node's `server.keepAliveTimeout`,
475
+ * default 72_000). MUST be longer than the idle timeout of any load
476
+ * balancer in front of the app (ALB default 60s) or the LB reuses
477
+ * sockets the server just closed → intermittent 502s.
478
+ */
479
+ keepAliveTimeout?: number;
331
480
  /**
332
481
  * Maximum JSON body size in bytes. Pass-through to Fastify's
333
482
  * server-level `bodyLimit` option; default is Fastify's 1 MiB
@@ -933,33 +1082,5 @@ interface CreateAppOptions {
933
1082
  /** Hook called when the app is shutting down */
934
1083
  onClose?: (fastify: FastifyInstance) => void | Promise<void>;
935
1084
  }
936
- interface UnderPressureOptions {
937
- /** Expose `/_status` route for health checks (default: false) */
938
- exposeStatusRoute?: boolean;
939
- /** Event loop lag threshold in ms — requests rejected above this (default: 1000) */
940
- maxEventLoopDelay?: number;
941
- /** V8 heap usage threshold in bytes — requests rejected above this */
942
- maxHeapUsedBytes?: number;
943
- /** RSS memory threshold in bytes — requests rejected above this */
944
- maxRssBytes?: number;
945
- }
946
- interface MultipartOptions {
947
- limits?: {
948
- /** Max file size in bytes (default: Fastify default ~1MB) */
949
- fileSize?: number;
950
- /** Max number of files per request */
951
- files?: number;
952
- };
953
- }
954
- interface RawBodyOptions {
955
- /** Body field name to store raw body on (default: 'rawBody') */
956
- field?: string;
957
- /** Apply to all routes globally (default: false) */
958
- global?: boolean;
959
- /** Encoding for raw body string (default: 'utf8') */
960
- encoding?: string;
961
- /** Parse raw body before other parsers (default: false) */
962
- runFirst?: boolean;
963
- }
964
1085
  //#endregion
965
- export { CustomPluginAuthOption as a, RawBodyOptions as c, preloadResourcesAsync as d, CustomAuthenticatorOption as i, UnderPressureOptions as l, BetterAuthOption as n, JwtAuthOption as o, CreateAppOptions as r, MultipartOptions as s, AuthOption as t, preloadResources as u };
1086
+ export { AuthOption as a, CustomPluginAuthOption as c, preloadResourcesAsync as d, UnderPressureOptions as i, JwtAuthOption as l, MultipartOptions as n, BetterAuthOption as o, RawBodyOptions as r, CustomAuthenticatorOption as s, CreateAppOptions as t, preloadResources as u };
@@ -1,7 +1,7 @@
1
- import { C as RequestContext, Et as AggregationConfig, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, L as RequestWithExtras, T as CrudRouterOptions, V as ResourceDefinition, bt as IControllerResponse, ht as RouteSchemaOptions, pt as RouteMcpConfig, q as CrudController, qt as AnyRecord, st as ResourceConfig, xt as IRequestContext, yt as IController } from "./index-Dtrj6XZk.mjs";
1
+ import { C as RequestContext, Et as ActionDefinition, F as FastifyWithDecorators, H as CrudController, L as RequestWithExtras, Lt as IController, Rt as IControllerResponse, T as CrudRouterOptions, U as ResourceConfig, V as ResourceDefinition, et as RouteSchemaOptions, jt as RouteMcpConfig, qt as AnyRecord, tt as ArcFieldRule, ut as AggregationConfig, zt as IRequestContext } from "./index-DbtgCOCj.mjs";
2
2
  import { i as RequestScope } from "./types-DH0GupI7.mjs";
3
- import { c as PermissionCheck } from "./fields-BGqrAa_u.mjs";
4
- import "./fastify-augmentation-DafigGQp.mjs";
3
+ import { t as PermissionCheck } from "./types-Csxga7d3.mjs";
4
+ import "./fastify-augmentation-9_fuetB4.mjs";
5
5
  import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
6
6
  import { z } from "zod";
7
7
  import { DataAdapter } from "@classytic/repo-core/adapter";