@classytic/arc 2.23.0 → 2.25.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.
- package/dist/{BaseController-BQ8KNvWC.mjs → BaseController--M8EMzt0.mjs} +90 -21
- package/dist/QueryCache-DOMT_iq7.d.mts +72 -0
- package/dist/actionPermissions-CAphcmUG.mjs +24 -0
- package/dist/audit/index.d.mts +32 -3
- package/dist/audit/index.mjs +7 -4
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +263 -259
- package/dist/{buildHandler-D_2ULWxZ.mjs → buildHandler-Dtwd8fdz.mjs} +1 -1
- package/dist/cache/index.d.mts +41 -3
- package/dist/cache/index.mjs +33 -4
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/index.mjs +2 -2
- package/dist/context/index.mjs +2 -64
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-DQ6rDTyH.mjs → core-EUNu0u07.mjs} +296 -11
- package/dist/{createActionRouter-BuOv-m4Y.mjs → createActionRouter-B6GvAuAX.mjs} +3 -2
- package/dist/{createAggregationRouter-CgJv_BU_.mjs → createAggregationRouter-wzwFa_dW.mjs} +3 -3
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-COpYKt2h.mjs → elevation-CIz1GjGm.mjs} +4 -3
- package/dist/{eventPlugin-A6EbthXR.d.mts → eventPlugin-Bg3cP2_u.d.mts} +38 -3
- package/dist/{eventPlugin-DJG2D7sy.mjs → eventPlugin-DrQ_FesK.mjs} +22 -4
- package/dist/events/index.d.mts +57 -314
- package/dist/events/index.mjs +163 -91
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +24 -2
- package/dist/factory/index.d.mts +4 -5
- package/dist/factory/index.mjs +28 -8
- package/dist/{fastify-augmentation-g_f9omSc.d.mts → fastify-augmentation-dPNBVeIj.d.mts} +25 -2
- package/dist/{generate-usRSdQmP.mjs → generate-D0wZupBg.mjs} +1 -1
- package/dist/headers-BvlQSDMB.mjs +45 -0
- package/dist/health-CB_m4S32.mjs +377 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +27 -3
- package/dist/{index-wMoWBLkn.d.mts → index-BMnbzvtO.d.mts} +3 -3
- package/dist/{integrations/jobs.d.mts → index-CG4yqMDB.d.mts} +82 -2
- package/dist/{index-BBsvQt7U.d.mts → index-DPRH0Cme.d.mts} +36 -106
- package/dist/{module-B4u10iOJ.d.mts → index-KUoxjIq1.d.mts} +263 -23
- package/dist/{index-CC5ruDGh.d.mts → index-f4PTrk3B.d.mts} +37 -5
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +5 -5
- package/dist/{init-CjO4p_q1.mjs → init-tlRSt667.mjs} +206 -208
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/jobs/index.d.mts +2 -0
- package/dist/integrations/{jobs.mjs → jobs/index.mjs} +110 -67
- package/dist/integrations/mcp/index.d.mts +4 -4
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/webhooks.d.mts +37 -30
- package/dist/integrations/webhooks.mjs +51 -62
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +41 -14
- package/dist/{interface-C1_WLTMK.d.mts → interface-BGZCzOGL.d.mts} +24 -31
- package/dist/{mcpPlugin-BtzIL_kM.mjs → mcpPlugin-Cb7olw0j.mjs} +11 -9
- package/dist/{memory-CLXA2VeJ.mjs → memory-CmnzXE5Z.mjs} +29 -0
- package/dist/middleware/index.d.mts +17 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/migrations/index.d.mts +94 -44
- package/dist/migrations/index.mjs +196 -44
- package/dist/{multipartBody-CHEQSZ6G.mjs → multipartBody-D0eBvkeq.mjs} +17 -0
- package/dist/{openapi-jSDx-VDv.mjs → openapi-DrLtog_B.mjs} +3 -3
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-tbxFHxjZ.mjs → permissions-BrtiBKGs.mjs} +229 -229
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/{actionPermissions-lszoJle1.mjs → pipeline-BN5G05rd.mjs} +3 -26
- package/dist/plugins/index.d.mts +6 -77
- package/dist/plugins/index.mjs +11 -95
- package/dist/plugins/response-cache.mjs +1 -0
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{index-Ba10fdKp.d.mts → preloadResources-0oe-JSSS.d.mts} +50 -50
- package/dist/{createApp-CjOeWJ9_.mjs → preloadResources-DfyngIN9.mjs} +233 -70
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +3 -3
- package/dist/presets/index.d.mts +2 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-BOe4Ugkl.mjs → presets-DwFDVKP0.mjs} +2 -2
- package/dist/{queryCachePlugin-DzqLN904.mjs → queryCachePlugin-DSGuSVLW.mjs} +39 -8
- package/dist/{queryCachePlugin-CfYWq-Ak.d.mts → queryCachePlugin-br4otiqF.d.mts} +2 -2
- package/dist/{registerRoute-CWO6mdYe.mjs → rateLimit-DrED-C4v.mjs} +2 -109
- package/dist/{realtime-I8OrfnMR.mjs → realtime-Cuw_deA-.mjs} +2 -2
- package/dist/{redis-stream-iVR2l7aS.d.mts → redis-stream-uIaauNpZ.d.mts} +22 -0
- package/dist/registerRoute-v0Wb6Plt.mjs +108 -0
- package/dist/registry/index.d.mts +2 -2
- package/dist/renewingLease-3cuQH7h8.mjs +44 -0
- package/dist/{requestContext-BZKOr13H.mjs → requestContext-MrlJwECf.mjs} +2 -2
- package/dist/schedules-CjOd2MG9.mjs +113 -0
- package/dist/schemas/index.d.mts +67 -67
- package/dist/schemas/index.mjs +2 -3
- package/dist/scim/index.mjs +1 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +4 -4
- package/dist/{scope-LONQ3mfZ.mjs → scope-DfaPntpo.mjs} +4 -5
- package/dist/{sse-0u4P4-OI.mjs → sse-BQ1w0dfl.mjs} +1 -1
- package/dist/testing/index.d.mts +4 -3
- package/dist/testing/index.mjs +2 -2
- package/dist/transactionContext-PNdoyLLm.mjs +64 -0
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-bHblZ9YN.mjs → types-CSyCxl2V.mjs} +38 -7
- package/dist/{types-DH0GupI7.d.mts → types-DjC-KUCz.d.mts} +118 -20
- package/dist/{types-Csxga7d3.d.mts → types-Dmw-6Qeo.d.mts} +96 -4
- package/dist/{types-5OuqeTDX.d.mts → types-QFGc3Cjv.d.mts} +1 -1
- package/dist/usage/index.d.mts +3 -52
- package/dist/usage/index.mjs +0 -0
- package/dist/userHelpers-Cg_y3fBz.mjs +29 -0
- package/dist/utils/index.d.mts +58 -85
- package/dist/utils/index.mjs +4 -2
- package/dist/{utils-CZyeRXTh.mjs → utils-taUVD4p3.mjs} +47 -337
- package/dist/{validate-C2mGgSXg.mjs → validate-Bk44F2yT.mjs} +1 -1
- package/dist/{versioning-D6PvCwpq.d.mts → versioning-DXgY5BUm.d.mts} +11 -29
- package/dist/{versioning-Dk-71iIz.mjs → versioning-gKWyryT3.mjs} +8 -1
- package/dist/{websocket-uuWqM6UY.d.mts → websocket-_Pd0CgR0.d.mts} +15 -1
- package/package.json +526 -513
- package/skills/arc/references/api-reference.md +3 -1
- package/skills/arc/references/integrations.md +20 -11
- package/dist/QueryCache-Z1B38koh.d.mts +0 -40
- package/dist/betterAuthOpenApi-CV1lxpRX.mjs +0 -2
- package/dist/health-ScyjIVo5.mjs +0 -202
- package/dist/requestId-5BhTVzBv.mjs +0 -113
- package/dist/types-XTApa8b6.mjs +0 -24
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as requestContext } from "./requestContext-BZKOr13H.mjs";
|
|
3
|
-
import { n as evaluateAndApplyPermission } from "./applyPermissionResult-Tw4iXtlf.mjs";
|
|
1
|
+
import { n as requestContext } from "./requestContext-MrlJwECf.mjs";
|
|
4
2
|
//#region src/core/middlewares/arcDecorator.ts
|
|
5
3
|
/**
|
|
6
4
|
* Build the `arcDecorator` preHandler for a resource.
|
|
@@ -179,76 +177,6 @@ function describeValue(v) {
|
|
|
179
177
|
return `${typeof v} (${JSON.stringify(v).slice(0, 80)})`;
|
|
180
178
|
}
|
|
181
179
|
//#endregion
|
|
182
|
-
//#region src/core/middlewares/permissions.ts
|
|
183
|
-
/**
|
|
184
|
-
* Build a PermissionContext from a Fastify request. Extracted so the CRUD
|
|
185
|
-
* permission middleware and the dynamic action-permission check use the same
|
|
186
|
-
* field layout — divergence here silently broke policy filters for actions.
|
|
187
|
-
*/
|
|
188
|
-
function buildPermissionContext(req, opts) {
|
|
189
|
-
const reqWithExtras = req;
|
|
190
|
-
const params = req.params;
|
|
191
|
-
return {
|
|
192
|
-
user: reqWithExtras.user ?? null,
|
|
193
|
-
request: req,
|
|
194
|
-
resource: opts.resource,
|
|
195
|
-
action: opts.action,
|
|
196
|
-
resourceId: opts.resourceId ?? params?.id,
|
|
197
|
-
params,
|
|
198
|
-
data: opts.data ?? req.body
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Static per-route CRUD permission gate. The permission and action are known
|
|
203
|
-
* at route-registration time, so the gate is a plain preHandler.
|
|
204
|
-
*
|
|
205
|
-
* Actions use the dynamic counterpart `buildActionPermissionMw` — their
|
|
206
|
-
* permission is resolved from `body.action` at request time.
|
|
207
|
-
*/
|
|
208
|
-
function buildCrudPermissionMw(permissionCheck, resourceName, action) {
|
|
209
|
-
if (!permissionCheck) return null;
|
|
210
|
-
return async (req, reply) => {
|
|
211
|
-
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
212
|
-
resource: resourceName,
|
|
213
|
-
action
|
|
214
|
-
}), req, reply);
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Dynamic per-action permission gate for the action router.
|
|
219
|
-
*
|
|
220
|
-
* Resolves the permission from `body.action` at request time and runs
|
|
221
|
-
* `evaluateAndApplyPermission` from the canonical `permissionMw` slot — so
|
|
222
|
-
* `_policyFilters` and `request.scope` are installed BEFORE `pluginMw`
|
|
223
|
-
* (idempotency) and `routeGuards` run. Previously this check lived inside
|
|
224
|
-
* the main action handler, which meant idempotency recorded unauthorized
|
|
225
|
-
* requests and route guards saw unfiltered scope — the very divergence
|
|
226
|
-
* routerShared exists to prevent.
|
|
227
|
-
*
|
|
228
|
-
* Also acts as a defensive fallback for invalid action names — the
|
|
229
|
-
* `oneOf` body schema normally rejects these at AJV validation, but
|
|
230
|
-
* hosts that disable schema validation still get a 400 here.
|
|
231
|
-
*/
|
|
232
|
-
function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, resourceName) {
|
|
233
|
-
const enumSet = new Set(actionEnum);
|
|
234
|
-
const validActions = [...actionEnum];
|
|
235
|
-
return async (req, reply) => {
|
|
236
|
-
const body = req.body ?? {};
|
|
237
|
-
const action = body.action;
|
|
238
|
-
if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
|
|
239
|
-
const permissionCheck = actionPermissions[action] ?? globalAuth;
|
|
240
|
-
if (!permissionCheck) return;
|
|
241
|
-
const { action: _discard, ...data } = body;
|
|
242
|
-
const params = req.params;
|
|
243
|
-
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
244
|
-
resource: resourceName,
|
|
245
|
-
action,
|
|
246
|
-
resourceId: params?.id,
|
|
247
|
-
data
|
|
248
|
-
}), req, reply, { defaultDenialMessage: (user) => user ? `Permission denied for '${action}'` : "Authentication required" });
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
//#endregion
|
|
252
180
|
//#region src/core/middlewares/pluginMw.ts
|
|
253
181
|
/**
|
|
254
182
|
* Pick the request-lifecycle plugin middleware for an HTTP method:
|
|
@@ -314,39 +242,4 @@ function buildRateLimitConfig(rateLimit) {
|
|
|
314
242
|
} };
|
|
315
243
|
}
|
|
316
244
|
//#endregion
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
|
|
320
|
-
*
|
|
321
|
-
* Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
|
|
322
|
-
* overlaps before Fastify ever runs, but a few cases still slip through to
|
|
323
|
-
* Fastify itself: two presets contributing the same `routes` entry, hosts
|
|
324
|
-
* mounting two resources at the same prefix, custom routes that share a
|
|
325
|
-
* URL with another plugin. Fastify's default message ("Method 'GET'
|
|
326
|
-
* already declared for route '/'") gives no hint at the resource-shaped
|
|
327
|
-
* fix — `disabledRoutes: ['list']` — so wrap it with one.
|
|
328
|
-
*/
|
|
329
|
-
function tryRegisterRoute(fastify, opts, context) {
|
|
330
|
-
try {
|
|
331
|
-
fastify.route(opts);
|
|
332
|
-
} catch (err) {
|
|
333
|
-
const fastifyErr = err;
|
|
334
|
-
if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
|
|
335
|
-
const method = opts.method;
|
|
336
|
-
const url = opts.url;
|
|
337
|
-
const opHint = context.op ? ` (op: ${context.op})` : "";
|
|
338
|
-
const fixHint = context.op && [
|
|
339
|
-
"list",
|
|
340
|
-
"get",
|
|
341
|
-
"create",
|
|
342
|
-
"update",
|
|
343
|
-
"delete"
|
|
344
|
-
].includes(context.op) ? ` Suppress the auto-CRUD route with \`disabledRoutes: ['${context.op}']\`.` : " If a custom route in `routes:` collides with auto-CRUD, add `disabledRoutes: ['list' | 'get' | 'create' | 'update' | 'delete']` to `defineResource()` to suppress the matching auto-CRUD route.";
|
|
345
|
-
const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
|
|
346
|
-
enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
|
|
347
|
-
enhanced.cause = err;
|
|
348
|
-
throw enhanced;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
//#endregion
|
|
352
|
-
export { selectPluginMw as a, buildRouteHooks as c, buildAuthMiddleware as d, buildAuthMiddlewareForPermissions as f, resolveRouterPluginMw as i, resolveRoutePreHandlers as l, buildRateLimitConfig as n, buildActionPermissionMw as o, buildArcDecorator as p, buildRouteConfig as r, buildCrudPermissionMw as s, tryRegisterRoute as t, routeHookOptions as u };
|
|
245
|
+
export { buildRouteHooks as a, buildAuthMiddleware as c, selectPluginMw as i, buildAuthMiddlewareForPermissions as l, buildRouteConfig as n, resolveRoutePreHandlers as o, resolveRouterPluginMw as r, routeHookOptions as s, buildRateLimitConfig as t, buildArcDecorator as u };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
2
|
import { arcLog } from "./logger/index.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { n as getUserRoles } from "./userHelpers-Cg_y3fBz.mjs";
|
|
4
|
+
import { c as getOrgId } from "./types-CSyCxl2V.mjs";
|
|
4
5
|
import { t as simpleEqualityMatcher } from "./simpleEqualityMatcher-Cg05HPsn.mjs";
|
|
5
6
|
import { i as applyFieldReadPermissions, n as evaluateAndApplyPermission } from "./applyPermissionResult-Tw4iXtlf.mjs";
|
|
6
|
-
import { t as getUserRoles } from "./types-XTApa8b6.mjs";
|
|
7
7
|
import { a as promoteStreamTokenToHeader } from "./streaming-BQcW6_Kq.mjs";
|
|
8
8
|
import { t as openSseStream } from "./sseStream-BQ7Qaynp.mjs";
|
|
9
9
|
import fp from "fastify-plugin";
|
|
@@ -59,6 +59,20 @@ interface RedisStreamTransportOptions {
|
|
|
59
59
|
* @default 10
|
|
60
60
|
*/
|
|
61
61
|
batchSize?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Bounded concurrency for processing a batch's ENTRIES (default: 1 —
|
|
64
|
+
* strict stream order, the safe default). With one slow handler,
|
|
65
|
+
* throughput per consumer is ~`1 / total handler latency`; raising this
|
|
66
|
+
* processes up to N entries of a batch concurrently.
|
|
67
|
+
*
|
|
68
|
+
* Trade-off: entries within a batch may complete OUT OF ORDER — only
|
|
69
|
+
* raise it when handlers are order-independent (idempotent, keyed
|
|
70
|
+
* upserts). Handlers on this transport should be short and I/O-bound
|
|
71
|
+
* regardless; heavy work belongs in jobs. Matching handlers for a single
|
|
72
|
+
* event always run sequentially.
|
|
73
|
+
* @default 1
|
|
74
|
+
*/
|
|
75
|
+
processingConcurrency?: number;
|
|
62
76
|
/**
|
|
63
77
|
* Max delivery attempts before moving to dead letter stream.
|
|
64
78
|
* @default 5
|
|
@@ -133,6 +147,7 @@ declare class RedisStreamTransport implements EventTransport {
|
|
|
133
147
|
private consumer;
|
|
134
148
|
private blockTimeMs;
|
|
135
149
|
private batchSize;
|
|
150
|
+
private processingConcurrency;
|
|
136
151
|
private maxRetries;
|
|
137
152
|
private claimTimeoutMs;
|
|
138
153
|
private deadLetterStream;
|
|
@@ -207,6 +222,13 @@ declare class RedisStreamTransport implements EventTransport {
|
|
|
207
222
|
private ensureGroup;
|
|
208
223
|
private pollLoop;
|
|
209
224
|
private readNewMessages;
|
|
225
|
+
/**
|
|
226
|
+
* Process a batch's entries with bounded concurrency
|
|
227
|
+
* (`processingConcurrency`, default 1 = strict order). Worker-pool over a
|
|
228
|
+
* shared index — at most N entries in flight, each entry's own handlers
|
|
229
|
+
* still sequential inside `processEntry`.
|
|
230
|
+
*/
|
|
231
|
+
private processEntries;
|
|
210
232
|
private claimPending;
|
|
211
233
|
private processEntry;
|
|
212
234
|
private getMatchingHandlers;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { f as createError } from "./errors-DkO8Nk6-.mjs";
|
|
2
|
+
import { n as evaluateAndApplyPermission } from "./applyPermissionResult-Tw4iXtlf.mjs";
|
|
3
|
+
//#region src/core/middlewares/permissions.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build a PermissionContext from a Fastify request. Extracted so the CRUD
|
|
6
|
+
* permission middleware and the dynamic action-permission check use the same
|
|
7
|
+
* field layout — divergence here silently broke policy filters for actions.
|
|
8
|
+
*/
|
|
9
|
+
function buildPermissionContext(req, opts) {
|
|
10
|
+
const reqWithExtras = req;
|
|
11
|
+
const params = req.params;
|
|
12
|
+
return {
|
|
13
|
+
user: reqWithExtras.user ?? null,
|
|
14
|
+
request: req,
|
|
15
|
+
resource: opts.resource,
|
|
16
|
+
action: opts.action,
|
|
17
|
+
resourceId: opts.resourceId ?? params?.id,
|
|
18
|
+
params,
|
|
19
|
+
data: opts.data ?? req.body
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Static per-route CRUD permission gate. The permission and action are known
|
|
24
|
+
* at route-registration time, so the gate is a plain preHandler.
|
|
25
|
+
*
|
|
26
|
+
* Actions use the dynamic counterpart `buildActionPermissionMw` — their
|
|
27
|
+
* permission is resolved from `body.action` at request time.
|
|
28
|
+
*/
|
|
29
|
+
function buildCrudPermissionMw(permissionCheck, resourceName, action) {
|
|
30
|
+
if (!permissionCheck) return null;
|
|
31
|
+
return async (req, reply) => {
|
|
32
|
+
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
33
|
+
resource: resourceName,
|
|
34
|
+
action
|
|
35
|
+
}), req, reply);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Dynamic per-action permission gate for the action router.
|
|
40
|
+
*
|
|
41
|
+
* Resolves the permission from `body.action` at request time and runs
|
|
42
|
+
* `evaluateAndApplyPermission` from the canonical `permissionMw` slot — so
|
|
43
|
+
* `_policyFilters` and `request.scope` are installed BEFORE `pluginMw`
|
|
44
|
+
* (idempotency) and `routeGuards` run. Previously this check lived inside
|
|
45
|
+
* the main action handler, which meant idempotency recorded unauthorized
|
|
46
|
+
* requests and route guards saw unfiltered scope — the very divergence
|
|
47
|
+
* routerShared exists to prevent.
|
|
48
|
+
*
|
|
49
|
+
* Also acts as a defensive fallback for invalid action names — the
|
|
50
|
+
* `oneOf` body schema normally rejects these at AJV validation, but
|
|
51
|
+
* hosts that disable schema validation still get a 400 here.
|
|
52
|
+
*/
|
|
53
|
+
function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, resourceName) {
|
|
54
|
+
const enumSet = new Set(actionEnum);
|
|
55
|
+
const validActions = [...actionEnum];
|
|
56
|
+
return async (req, reply) => {
|
|
57
|
+
const body = req.body ?? {};
|
|
58
|
+
const action = body.action;
|
|
59
|
+
if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
|
|
60
|
+
const permissionCheck = actionPermissions[action] ?? globalAuth;
|
|
61
|
+
if (!permissionCheck) return;
|
|
62
|
+
const { action: _discard, ...data } = body;
|
|
63
|
+
const params = req.params;
|
|
64
|
+
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
65
|
+
resource: resourceName,
|
|
66
|
+
action,
|
|
67
|
+
resourceId: params?.id,
|
|
68
|
+
data
|
|
69
|
+
}), req, reply, { defaultDenialMessage: (user) => user ? `Permission denied for '${action}'` : "Authentication required" });
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/core/middlewares/registerRoute.ts
|
|
74
|
+
/**
|
|
75
|
+
* Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
|
|
76
|
+
*
|
|
77
|
+
* Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
|
|
78
|
+
* overlaps before Fastify ever runs, but a few cases still slip through to
|
|
79
|
+
* Fastify itself: two presets contributing the same `routes` entry, hosts
|
|
80
|
+
* mounting two resources at the same prefix, custom routes that share a
|
|
81
|
+
* URL with another plugin. Fastify's default message ("Method 'GET'
|
|
82
|
+
* already declared for route '/'") gives no hint at the resource-shaped
|
|
83
|
+
* fix — `disabledRoutes: ['list']` — so wrap it with one.
|
|
84
|
+
*/
|
|
85
|
+
function tryRegisterRoute(fastify, opts, context) {
|
|
86
|
+
try {
|
|
87
|
+
fastify.route(opts);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const fastifyErr = err;
|
|
90
|
+
if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
|
|
91
|
+
const method = opts.method;
|
|
92
|
+
const url = opts.url;
|
|
93
|
+
const opHint = context.op ? ` (op: ${context.op})` : "";
|
|
94
|
+
const fixHint = context.op && [
|
|
95
|
+
"list",
|
|
96
|
+
"get",
|
|
97
|
+
"create",
|
|
98
|
+
"update",
|
|
99
|
+
"delete"
|
|
100
|
+
].includes(context.op) ? ` Suppress the auto-CRUD route with \`disabledRoutes: ['${context.op}']\`.` : " If a custom route in `routes:` collides with auto-CRUD, add `disabledRoutes: ['list' | 'get' | 'create' | 'update' | 'delete']` to `defineResource()` to suppress the matching auto-CRUD route.";
|
|
101
|
+
const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
|
|
102
|
+
enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
|
|
103
|
+
enhanced.cause = err;
|
|
104
|
+
throw enhanced;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { buildActionPermissionMw as n, buildCrudPermissionMw as r, tryRegisterRoute as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-
|
|
2
|
-
import "../fastify-augmentation-
|
|
1
|
+
import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-DPRH0Cme.mjs";
|
|
2
|
+
import "../fastify-augmentation-dPNBVeIj.mjs";
|
|
3
3
|
import { FastifyPluginAsync } from "fastify";
|
|
4
4
|
import { TenantPurgeProgress, TenantPurgeStrategy } from "@classytic/repo-core/repository";
|
|
5
5
|
//#region src/registry/assertNoTenantData.d.ts
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/lock/renewingLease.ts
|
|
2
|
+
/**
|
|
3
|
+
* Start renewing an ALREADY-ACQUIRED lease. The caller performed the
|
|
4
|
+
* initial `tryAcquire` (its contention policy differs per consumer); this
|
|
5
|
+
* keeps the lease alive until `stop()`.
|
|
6
|
+
*/
|
|
7
|
+
function startRenewingLease(options) {
|
|
8
|
+
const { lock, name, holderId, leaseMs, onLost, onError } = options;
|
|
9
|
+
const renewEveryMs = options.renewEveryMs ?? Math.max(10, Math.floor(leaseMs / 2));
|
|
10
|
+
let stopped = false;
|
|
11
|
+
let lost = false;
|
|
12
|
+
let timer;
|
|
13
|
+
let inFlight;
|
|
14
|
+
const scheduleNext = () => {
|
|
15
|
+
if (stopped) return;
|
|
16
|
+
timer = setTimeout(() => {
|
|
17
|
+
inFlight = Promise.resolve(lock.tryAcquire(name, holderId, leaseMs)).then((renewed) => {
|
|
18
|
+
if (!renewed && !lost) {
|
|
19
|
+
lost = true;
|
|
20
|
+
onLost?.();
|
|
21
|
+
}
|
|
22
|
+
}).catch((error) => {
|
|
23
|
+
onError?.(error);
|
|
24
|
+
}).finally(() => {
|
|
25
|
+
inFlight = void 0;
|
|
26
|
+
scheduleNext();
|
|
27
|
+
});
|
|
28
|
+
}, renewEveryMs);
|
|
29
|
+
timer.unref?.();
|
|
30
|
+
};
|
|
31
|
+
scheduleNext();
|
|
32
|
+
return {
|
|
33
|
+
get lost() {
|
|
34
|
+
return lost;
|
|
35
|
+
},
|
|
36
|
+
async stop() {
|
|
37
|
+
stopped = true;
|
|
38
|
+
if (timer) clearTimeout(timer);
|
|
39
|
+
await inFlight;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { startRenewingLease as t };
|
|
@@ -85,8 +85,8 @@ function getTraceHeaders() {
|
|
|
85
85
|
const store = requestContext.get();
|
|
86
86
|
if (!store) return void 0;
|
|
87
87
|
const headers = {};
|
|
88
|
-
if (store.traceparent) headers
|
|
89
|
-
if (store.tracestate) headers
|
|
88
|
+
if (store.traceparent) headers.traceparent = store.traceparent;
|
|
89
|
+
if (store.tracestate) headers.tracestate = store.tracestate;
|
|
90
90
|
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
91
91
|
}
|
|
92
92
|
//#endregion
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
|
+
import { t as startRenewingLease } from "./renewingLease-3cuQH7h8.mjs";
|
|
3
|
+
import fp from "fastify-plugin";
|
|
4
|
+
//#region src/plugins/schedules.ts
|
|
5
|
+
var schedules_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
default: () => schedules_default,
|
|
7
|
+
schedulesPlugin: () => schedulesPlugin
|
|
8
|
+
});
|
|
9
|
+
const MIN_SAFE_INTERVAL_MS = 1e3;
|
|
10
|
+
const MAX_DEFAULT_LEASE_MS = 3e5;
|
|
11
|
+
const schedulesPlugin = async (fastify, opts) => {
|
|
12
|
+
const { schedules, lock, enabled = true } = opts;
|
|
13
|
+
const holderId = opts.holderId ?? `arc-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
14
|
+
const stats = /* @__PURE__ */ new Map();
|
|
15
|
+
fastify.decorate("getScheduleStats", () => [...stats.values()].map((s) => ({ ...s })));
|
|
16
|
+
if (!enabled) {
|
|
17
|
+
fastify.log.debug("schedulesPlugin disabled — registered as no-op");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const s of schedules) {
|
|
22
|
+
if (!s.name.trim()) throw new TypeError("[arc-schedules] every schedule needs a name");
|
|
23
|
+
if (seen.has(s.name)) throw new TypeError(`[arc-schedules] duplicate schedule name "${s.name}"`);
|
|
24
|
+
seen.add(s.name);
|
|
25
|
+
if (!Number.isFinite(s.every) || s.every <= 0) throw new TypeError(`[arc-schedules] "${s.name}": \`every\` must be a positive number of ms`);
|
|
26
|
+
if (typeof s.handler !== "function") throw new TypeError(`[arc-schedules] "${s.name}": \`handler\` must be a function`);
|
|
27
|
+
if (s.jitterMs !== void 0 && (!Number.isFinite(s.jitterMs) || s.jitterMs < 0)) throw new TypeError(`[arc-schedules] "${s.name}": \`jitterMs\` must be a non-negative number of ms`);
|
|
28
|
+
if (s.leaseMs !== void 0 && (!Number.isFinite(s.leaseMs) || s.leaseMs <= 0)) throw new TypeError(`[arc-schedules] "${s.name}": \`leaseMs\` must be a positive number of ms`);
|
|
29
|
+
if (s.every < MIN_SAFE_INTERVAL_MS) fastify.log.warn(`[arc-schedules] "${s.name}" runs every ${s.every}ms — sub-second schedules are almost always a bug in production (DB pressure, lock churn). Intended for tests only.`);
|
|
30
|
+
}
|
|
31
|
+
const timers = /* @__PURE__ */ new Map();
|
|
32
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
33
|
+
let closing = false;
|
|
34
|
+
async function tick(s) {
|
|
35
|
+
const stat = stats.get(s.name);
|
|
36
|
+
if (!stat) return;
|
|
37
|
+
let lease;
|
|
38
|
+
try {
|
|
39
|
+
if (lock) {
|
|
40
|
+
const lockName = `arc:schedule:${s.name}`;
|
|
41
|
+
const leaseMs = s.leaseMs ?? Math.min(Math.floor(s.every * .9), MAX_DEFAULT_LEASE_MS);
|
|
42
|
+
if (!await lock.tryAcquire(lockName, holderId, leaseMs)) {
|
|
43
|
+
stat.skippedByLock++;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
lease = startRenewingLease({
|
|
47
|
+
lock,
|
|
48
|
+
name: lockName,
|
|
49
|
+
holderId,
|
|
50
|
+
leaseMs,
|
|
51
|
+
onLost: () => fastify.log.warn({ schedule: s.name }, "[arc-schedules] lease renewal lost — another holder took the lock; this run may now overlap. Handlers should be idempotent."),
|
|
52
|
+
onError: (err) => fastify.log.warn({
|
|
53
|
+
err,
|
|
54
|
+
schedule: s.name
|
|
55
|
+
}, "[arc-schedules] lease renewal errored (lease may lapse mid-run)")
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
stat.runs++;
|
|
59
|
+
stat.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
60
|
+
await s.handler(fastify);
|
|
61
|
+
stat.lastError = null;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
stat.failures++;
|
|
64
|
+
stat.lastError = err instanceof Error ? err.message : String(err);
|
|
65
|
+
fastify.log.error({
|
|
66
|
+
err,
|
|
67
|
+
schedule: s.name
|
|
68
|
+
}, "[arc-schedules] schedule run failed");
|
|
69
|
+
} finally {
|
|
70
|
+
await lease?.stop();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function scheduleNext(s, delay) {
|
|
74
|
+
if (closing) return;
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
const run = tick(s).finally(() => {
|
|
77
|
+
inFlight.delete(s.name);
|
|
78
|
+
scheduleNext(s, s.every);
|
|
79
|
+
});
|
|
80
|
+
inFlight.set(s.name, run);
|
|
81
|
+
}, delay);
|
|
82
|
+
timer.unref?.();
|
|
83
|
+
timers.set(s.name, timer);
|
|
84
|
+
}
|
|
85
|
+
fastify.addHook("onReady", async () => {
|
|
86
|
+
for (const s of schedules) {
|
|
87
|
+
if (s.enabled === false) continue;
|
|
88
|
+
stats.set(s.name, {
|
|
89
|
+
name: s.name,
|
|
90
|
+
runs: 0,
|
|
91
|
+
failures: 0,
|
|
92
|
+
skippedByLock: 0,
|
|
93
|
+
lastRunAt: null,
|
|
94
|
+
lastError: null
|
|
95
|
+
});
|
|
96
|
+
const jitter = !s.runOnStart && s.jitterMs ? Math.floor(Math.random() * s.jitterMs) : 0;
|
|
97
|
+
scheduleNext(s, s.runOnStart ? 0 : s.every + jitter);
|
|
98
|
+
}
|
|
99
|
+
if (stats.size > 0) fastify.log.debug(`[arc-schedules] ${stats.size} schedule(s) armed${lock ? ` (leader-safe, holder ${holderId})` : ""}`);
|
|
100
|
+
});
|
|
101
|
+
fastify.addHook("onClose", async () => {
|
|
102
|
+
closing = true;
|
|
103
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
104
|
+
timers.clear();
|
|
105
|
+
await Promise.allSettled([...inFlight.values()]);
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
var schedules_default = fp(schedulesPlugin, {
|
|
109
|
+
name: "arc-schedules",
|
|
110
|
+
fastify: "5.x"
|
|
111
|
+
});
|
|
112
|
+
//#endregion
|
|
113
|
+
export { schedules_default as n, schedules_exports as r, schedulesPlugin as t };
|
package/dist/schemas/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ErrorContract, ErrorDetail } from "@classytic/repo-core/errors";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Static, TObject, TSchema, TSchema as TSchema$1, Type, Type as Type$1 } from "typebox";
|
|
3
|
+
import { FastifyPluginAsyncTypebox, FastifyPluginCallbackTypebox, TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
|
|
4
4
|
//#region src/schemas/index.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Paginated list response — full union of every wire shape `toCanonicalList`
|
|
@@ -16,74 +16,74 @@ import { Static, TObject, TSchema, TSchema as TSchema$1, Type } from "@sinclair/
|
|
|
16
16
|
* variant when your endpoint never emits the others. HTTP status
|
|
17
17
|
* discriminates success vs error — no `success` field.
|
|
18
18
|
*/
|
|
19
|
-
declare function ArcListResponse<T extends TSchema$1>(itemSchema: T):
|
|
20
|
-
method:
|
|
21
|
-
data:
|
|
22
|
-
page:
|
|
23
|
-
limit:
|
|
24
|
-
total:
|
|
25
|
-
pages:
|
|
26
|
-
hasNext:
|
|
27
|
-
hasPrev:
|
|
28
|
-
}>,
|
|
29
|
-
method:
|
|
30
|
-
data:
|
|
31
|
-
limit:
|
|
32
|
-
hasMore:
|
|
33
|
-
next:
|
|
34
|
-
}>,
|
|
35
|
-
method:
|
|
36
|
-
data:
|
|
37
|
-
page:
|
|
38
|
-
limit:
|
|
39
|
-
total:
|
|
40
|
-
pages:
|
|
41
|
-
hasNext:
|
|
42
|
-
hasPrev:
|
|
43
|
-
}>,
|
|
44
|
-
data:
|
|
19
|
+
declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TUnion<[Type$1.TObject<{
|
|
20
|
+
method: Type$1.TLiteral<"offset">;
|
|
21
|
+
data: Type$1.TArray<T>;
|
|
22
|
+
page: Type$1.TInteger;
|
|
23
|
+
limit: Type$1.TInteger;
|
|
24
|
+
total: Type$1.TInteger;
|
|
25
|
+
pages: Type$1.TInteger;
|
|
26
|
+
hasNext: Type$1.TBoolean;
|
|
27
|
+
hasPrev: Type$1.TBoolean;
|
|
28
|
+
}>, Type$1.TObject<{
|
|
29
|
+
method: Type$1.TLiteral<"keyset">;
|
|
30
|
+
data: Type$1.TArray<T>;
|
|
31
|
+
limit: Type$1.TInteger;
|
|
32
|
+
hasMore: Type$1.TBoolean;
|
|
33
|
+
next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
|
|
34
|
+
}>, Type$1.TObject<{
|
|
35
|
+
method: Type$1.TLiteral<"aggregate">;
|
|
36
|
+
data: Type$1.TArray<T>;
|
|
37
|
+
page: Type$1.TInteger;
|
|
38
|
+
limit: Type$1.TInteger;
|
|
39
|
+
total: Type$1.TInteger;
|
|
40
|
+
pages: Type$1.TInteger;
|
|
41
|
+
hasNext: Type$1.TBoolean;
|
|
42
|
+
hasPrev: Type$1.TBoolean;
|
|
43
|
+
}>, Type$1.TObject<{
|
|
44
|
+
data: Type$1.TArray<T>;
|
|
45
45
|
}>]>;
|
|
46
46
|
/** Offset variant — `{ method: 'offset', data, page, limit, total, pages, hasNext, hasPrev }`. */
|
|
47
|
-
declare function ArcOffsetListResponse<T extends TSchema$1>(itemSchema: T):
|
|
48
|
-
method:
|
|
49
|
-
data:
|
|
50
|
-
page:
|
|
51
|
-
limit:
|
|
52
|
-
total:
|
|
53
|
-
pages:
|
|
54
|
-
hasNext:
|
|
55
|
-
hasPrev:
|
|
47
|
+
declare function ArcOffsetListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
|
|
48
|
+
method: Type$1.TLiteral<"offset">;
|
|
49
|
+
data: Type$1.TArray<T>;
|
|
50
|
+
page: Type$1.TInteger;
|
|
51
|
+
limit: Type$1.TInteger;
|
|
52
|
+
total: Type$1.TInteger;
|
|
53
|
+
pages: Type$1.TInteger;
|
|
54
|
+
hasNext: Type$1.TBoolean;
|
|
55
|
+
hasPrev: Type$1.TBoolean;
|
|
56
56
|
}>;
|
|
57
57
|
/** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
|
|
58
|
-
declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T):
|
|
59
|
-
method:
|
|
60
|
-
data:
|
|
61
|
-
limit:
|
|
62
|
-
hasMore:
|
|
63
|
-
next:
|
|
58
|
+
declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
|
|
59
|
+
method: Type$1.TLiteral<"keyset">;
|
|
60
|
+
data: Type$1.TArray<T>;
|
|
61
|
+
limit: Type$1.TInteger;
|
|
62
|
+
hasMore: Type$1.TBoolean;
|
|
63
|
+
next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
|
|
64
64
|
}>;
|
|
65
65
|
/** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
|
|
66
|
-
declare function ArcAggregateListResponse<T extends TSchema$1>(itemSchema: T):
|
|
67
|
-
method:
|
|
68
|
-
data:
|
|
69
|
-
page:
|
|
70
|
-
limit:
|
|
71
|
-
total:
|
|
72
|
-
pages:
|
|
73
|
-
hasNext:
|
|
74
|
-
hasPrev:
|
|
66
|
+
declare function ArcAggregateListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
|
|
67
|
+
method: Type$1.TLiteral<"aggregate">;
|
|
68
|
+
data: Type$1.TArray<T>;
|
|
69
|
+
page: Type$1.TInteger;
|
|
70
|
+
limit: Type$1.TInteger;
|
|
71
|
+
total: Type$1.TInteger;
|
|
72
|
+
pages: Type$1.TInteger;
|
|
73
|
+
hasNext: Type$1.TBoolean;
|
|
74
|
+
hasPrev: Type$1.TBoolean;
|
|
75
75
|
}>;
|
|
76
76
|
/** Bare variant — `{ data }`, no `method` discriminant. */
|
|
77
|
-
declare function ArcBareListResponse<T extends TSchema$1>(itemSchema: T):
|
|
78
|
-
data:
|
|
77
|
+
declare function ArcBareListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
|
|
78
|
+
data: Type$1.TArray<T>;
|
|
79
79
|
}>;
|
|
80
80
|
/**
|
|
81
81
|
* Delete response — `{ message, id?, soft? }` raw at the top level.
|
|
82
82
|
*/
|
|
83
|
-
declare function ArcDeleteResponse():
|
|
84
|
-
message:
|
|
85
|
-
id:
|
|
86
|
-
soft:
|
|
83
|
+
declare function ArcDeleteResponse(): Type$1.TObject<{
|
|
84
|
+
message: Type$1.TString;
|
|
85
|
+
id: Type$1.TOptional<Type$1.TString>;
|
|
86
|
+
soft: Type$1.TOptional<Type$1.TBoolean>;
|
|
87
87
|
}>;
|
|
88
88
|
/**
|
|
89
89
|
* Single field-scoped error detail — `Type.Unsafe<ErrorDetail>` over the
|
|
@@ -95,7 +95,7 @@ declare function ArcDeleteResponse(): import("@sinclair/typebox").TObject<{
|
|
|
95
95
|
* Exported standalone so hosts can embed it in custom 422 / 409 response
|
|
96
96
|
* schemas.
|
|
97
97
|
*/
|
|
98
|
-
declare function ArcErrorDetail():
|
|
98
|
+
declare function ArcErrorDetail(): Type$1.TUnsafe<ErrorDetail>;
|
|
99
99
|
/**
|
|
100
100
|
* Error response schema — `Type.Unsafe<ErrorContract>` over the canonical
|
|
101
101
|
* JSON Schema `errorContractSchema` from `@classytic/repo-core/errors`.
|
|
@@ -104,7 +104,7 @@ declare function ArcErrorDetail(): import("@sinclair/typebox").TUnsafe<ErrorDeta
|
|
|
104
104
|
* (`errorContractSchema`) and this TypeBox helper cannot drift —
|
|
105
105
|
* literally one source.
|
|
106
106
|
*/
|
|
107
|
-
declare function ArcErrorResponse():
|
|
107
|
+
declare function ArcErrorResponse(): Type$1.TUnsafe<ErrorContract>;
|
|
108
108
|
/**
|
|
109
109
|
* Standard pagination + sorting + filtering query parameters.
|
|
110
110
|
* Matches Arc's list endpoint conventions.
|
|
@@ -120,12 +120,12 @@ declare function ArcErrorResponse(): import("@sinclair/typebox").TUnsafe<ErrorCo
|
|
|
120
120
|
* projection forms at the request-validation gate even though arc passes
|
|
121
121
|
* them through unchanged.
|
|
122
122
|
*/
|
|
123
|
-
declare function ArcPaginationQuery():
|
|
124
|
-
page:
|
|
125
|
-
limit:
|
|
126
|
-
sort:
|
|
127
|
-
select:
|
|
128
|
-
populate:
|
|
123
|
+
declare function ArcPaginationQuery(): Type$1.TObject<{
|
|
124
|
+
page: Type$1.TOptional<Type$1.TInteger>;
|
|
125
|
+
limit: Type$1.TOptional<Type$1.TInteger>;
|
|
126
|
+
sort: Type$1.TOptional<Type$1.TString>;
|
|
127
|
+
select: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TArray<Type$1.TString>, Type$1.TRecord<"^.*$", Type$1.TUnion<[Type$1.TLiteral<0>, Type$1.TLiteral<1>]>>]>>;
|
|
128
|
+
populate: Type$1.TOptional<Type$1.TAny>;
|
|
129
129
|
}>;
|
|
130
130
|
//#endregion
|
|
131
|
-
export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, type FastifyPluginAsyncTypebox, type FastifyPluginCallbackTypebox, type Static, type TObject, type TSchema, Type, type TypeBoxTypeProvider
|
|
131
|
+
export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, type FastifyPluginAsyncTypebox, type FastifyPluginCallbackTypebox, type Static, type TObject, type TSchema, Type, type TypeBoxTypeProvider };
|