@classytic/arc 2.23.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.
- package/README.md +2 -0
- package/dist/{BaseController-BQ8KNvWC.mjs → BaseController-B65zbz9m.mjs} +94 -23
- package/dist/QueryCache-DOMT_iq7.d.mts +72 -0
- package/dist/actionPermissions-CAphcmUG.mjs +24 -0
- package/dist/{applyPermissionResult-Tw4iXtlf.mjs → applyPermissionResult-BnyVE9eD.mjs} +10 -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/cleanup/index.d.mts +313 -0
- package/dist/cleanup/index.mjs +1267 -0
- 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 +5 -4
- package/dist/core-BGa8TXJR.mjs +221 -0
- package/dist/{createActionRouter-BuOv-m4Y.mjs → createActionRouter-C-N7z2EL.mjs} +3 -2
- package/dist/{createAggregationRouter-CgJv_BU_.mjs → createAggregationRouter-wzwFa_dW.mjs} +3 -3
- package/dist/{createApp-CjOeWJ9_.mjs → createApp-Du5G54TM.mjs} +187 -96
- package/dist/{core-DQ6rDTyH.mjs → defineResource-2zvVVpxg.mjs} +297 -230
- 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 +83 -314
- package/dist/events/index.mjs +183 -95
- 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 +5 -5
- package/dist/factory/index.mjs +30 -8
- package/dist/{fastify-augmentation-g_f9omSc.d.mts → fastify-augmentation-OzYGIiU1.d.mts} +26 -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-CZY4EGga.d.mts +107 -0
- package/dist/{index-CC5ruDGh.d.mts → index-CjVg-Epg.d.mts} +37 -5
- package/dist/{index-BBsvQt7U.d.mts → index-DPRH0Cme.d.mts} +36 -106
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +7 -6
- 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-Cs6nRGCt.mjs} +12 -10
- 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/module-_-M7C5ES.mjs +29 -0
- package/dist/{multipartBody-CHEQSZ6G.mjs → multipartBody-D0eBvkeq.mjs} +17 -0
- package/dist/{openapi-jSDx-VDv.mjs → openapi-BLV4OMXb.mjs} +3 -3
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-tbxFHxjZ.mjs → permissions-BrtiBKGs.mjs} +229 -229
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/{actionPermissions-lszoJle1.mjs → pipeline-BGRnV-To.mjs} +4 -27
- 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-CM4uUaFv.d.mts} +51 -50
- package/dist/preloadResources-DSY5HGpS.mjs +45 -0
- 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-DEuDu9io.mjs} +3 -3
- package/dist/{redis-stream-iVR2l7aS.d.mts → redis-stream-uIaauNpZ.d.mts} +22 -0
- package/dist/registerRoute-DTp18Ah_.mjs +108 -0
- package/dist/registry/index.d.mts +2 -2
- package/dist/registry/index.mjs +1 -1
- package/dist/{registry-vcOCtPij.mjs → registry-9km_w1aP.mjs} +15 -0
- 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/cleanupStoreContract.d.mts +10 -0
- package/dist/testing/cleanupStoreContract.mjs +375 -0
- package/dist/testing/index.d.mts +5 -3
- package/dist/testing/index.mjs +4 -2
- package/dist/testing/outboxStoreContract.d.mts +13 -0
- package/dist/testing/outboxStoreContract.mjs +247 -0
- 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-C_ZZRcD6.d.mts +450 -0
- package/dist/{module-B4u10iOJ.d.mts → types-Cu6vVJmh.d.mts} +196 -60
- 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 +541 -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,5 +1,106 @@
|
|
|
1
|
+
import { r as EventHandler } from "./EventTransport-QmY7A258.mjs";
|
|
1
2
|
import { n as ErrorMapper } from "./errorHandler-C6yv4ZTW.mjs";
|
|
2
|
-
import { FastifyInstance } from "fastify";
|
|
3
|
+
import { FastifyInstance, FastifyPluginAsync } from "fastify";
|
|
4
|
+
import { LockAdapter } from "@classytic/repo-core/lock";
|
|
5
|
+
//#region src/plugins/health.d.ts
|
|
6
|
+
interface HealthCheck {
|
|
7
|
+
/** Name of the dependency */
|
|
8
|
+
name: string;
|
|
9
|
+
/**
|
|
10
|
+
* Function that returns true if healthy, false otherwise.
|
|
11
|
+
*
|
|
12
|
+
* Receives an `AbortSignal` aborted when the check's timeout elapses —
|
|
13
|
+
* pass it to fetch/driver calls so a stalled dependency probe actually
|
|
14
|
+
* STOPS instead of accumulating an abandoned operation per readiness
|
|
15
|
+
* poll. Ignoring the parameter is fine (back-compat): the timeout still
|
|
16
|
+
* bounds how long the PROBE waits, just not the underlying work.
|
|
17
|
+
*/
|
|
18
|
+
check: (signal?: AbortSignal) => Promise<boolean> | boolean;
|
|
19
|
+
/** Optional timeout in ms (default: 5000) */
|
|
20
|
+
timeout?: number;
|
|
21
|
+
/** Whether this check is critical for readiness (default: true) */
|
|
22
|
+
critical?: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface HealthOptions {
|
|
25
|
+
/** Route prefix (default: '/_health') */
|
|
26
|
+
prefix?: string;
|
|
27
|
+
/** Health check dependencies */
|
|
28
|
+
checks?: readonly HealthCheck[];
|
|
29
|
+
/** Enable metrics endpoint (default: false) */
|
|
30
|
+
metrics?: boolean;
|
|
31
|
+
/** Custom metrics collector function */
|
|
32
|
+
metricsCollector?: () => Promise<string> | string;
|
|
33
|
+
/** Version info to include in responses */
|
|
34
|
+
version?: string;
|
|
35
|
+
/** Collect HTTP request metrics (default: true if metrics enabled) */
|
|
36
|
+
collectHttpMetrics?: boolean;
|
|
37
|
+
}
|
|
38
|
+
declare const healthPlugin: FastifyPluginAsync<HealthOptions>;
|
|
39
|
+
declare const _default$1: FastifyPluginAsync<HealthOptions>;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/plugins/schedules.d.ts
|
|
42
|
+
interface ScheduleDefinition {
|
|
43
|
+
/** Unique name — also the lock key (`arc:schedule:<name>`). */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Interval in milliseconds between the END of one run and the next tick. */
|
|
46
|
+
every: number;
|
|
47
|
+
/** The work. Receives the Fastify instance; errors are logged, never fatal. */
|
|
48
|
+
handler: (fastify: FastifyInstance) => void | Promise<void>;
|
|
49
|
+
/** Fire once immediately when the app becomes ready (default: false). */
|
|
50
|
+
runOnStart?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Random extra delay in `[0, jitterMs)` added to the FIRST tick only.
|
|
53
|
+
* Spreads boot-aligned jobs so multiple same-cadence schedules don't all
|
|
54
|
+
* fire at exactly `boot + every` after every restart (and, with a lock,
|
|
55
|
+
* de-synchronizes replicas competing for the same lease). Ignored when
|
|
56
|
+
* `runOnStart` is true.
|
|
57
|
+
*/
|
|
58
|
+
jitterMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Lease duration when a `lock` is configured. Default: `every * 0.9`
|
|
61
|
+
* (capped at 5 minutes) — long enough that other replicas skip the same
|
|
62
|
+
* tick, short enough that a crashed leader fails over within one cycle.
|
|
63
|
+
*/
|
|
64
|
+
leaseMs?: number;
|
|
65
|
+
/** Per-schedule kill switch (default: true). */
|
|
66
|
+
enabled?: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface SchedulesPluginOptions {
|
|
69
|
+
schedules: readonly ScheduleDefinition[];
|
|
70
|
+
/**
|
|
71
|
+
* Ecosystem lock adapter (`LockAdapter` from `@classytic/repo-core/lock`)
|
|
72
|
+
* for multi-replica deployments. Omit for single-instance apps — every
|
|
73
|
+
* tick runs locally.
|
|
74
|
+
*/
|
|
75
|
+
lock?: LockAdapter;
|
|
76
|
+
/**
|
|
77
|
+
* Identity of this replica for lease ownership. Default: a random id per
|
|
78
|
+
* process (repo-core's `getInstanceId()` is a good explicit value).
|
|
79
|
+
*/
|
|
80
|
+
holderId?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Master switch — `false` registers a typed no-op (config-gated
|
|
83
|
+
* environments), same pattern as `idempotencyPlugin`.
|
|
84
|
+
*/
|
|
85
|
+
enabled?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/** Per-schedule runtime counters, readable via `fastify.getScheduleStats()`. */
|
|
88
|
+
interface ScheduleStats {
|
|
89
|
+
name: string;
|
|
90
|
+
runs: number;
|
|
91
|
+
failures: number;
|
|
92
|
+
skippedByLock: number;
|
|
93
|
+
lastRunAt: string | null;
|
|
94
|
+
lastError: string | null;
|
|
95
|
+
}
|
|
96
|
+
declare module "fastify" {
|
|
97
|
+
interface FastifyInstance {
|
|
98
|
+
getScheduleStats?: () => ScheduleStats[];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
declare const schedulesPlugin: FastifyPluginAsync<SchedulesPluginOptions>;
|
|
102
|
+
declare const _default: FastifyPluginAsync<SchedulesPluginOptions>;
|
|
103
|
+
//#endregion
|
|
3
104
|
//#region src/factory/loadResources.d.ts
|
|
4
105
|
/**
|
|
5
106
|
* loadResources — Auto-discover resource files from a directory.
|
|
@@ -218,7 +319,31 @@ interface LoadResourcesOptions<TContext = unknown> {
|
|
|
218
319
|
*/
|
|
219
320
|
declare function loadResources<TContext = unknown>(dir: string, options?: LoadResourcesOptions<TContext>): Promise<ResourceLike[]>;
|
|
220
321
|
//#endregion
|
|
221
|
-
//#region src/factory/module.d.ts
|
|
322
|
+
//#region src/factory/module/types.d.ts
|
|
323
|
+
/**
|
|
324
|
+
* A module's contribution of some arm (event handlers, scheduled jobs, …).
|
|
325
|
+
* Either a static array or a factory resolved AFTER all module bootstraps, so a
|
|
326
|
+
* contribution can close over booted engines / the Fastify instance instead of
|
|
327
|
+
* reaching for a global getter (the coupling the module contract removes).
|
|
328
|
+
*/
|
|
329
|
+
type ModuleContribution<T> = readonly T[] | ((fastify: FastifyInstance) => readonly T[] | Promise<readonly T[]>);
|
|
330
|
+
/**
|
|
331
|
+
* A domain-event subscription a module owns. Subscribed in dependency order
|
|
332
|
+
* during the `afterResources` phase (routes mounted, engines booted) and
|
|
333
|
+
* auto-unsubscribed at shutdown BEFORE module `onClose` (deps still alive).
|
|
334
|
+
*/
|
|
335
|
+
interface EventHandlerDefinition {
|
|
336
|
+
/** Event pattern(s) to subscribe — same matcher as `fastify.events.subscribe`. */
|
|
337
|
+
event: string | readonly string[];
|
|
338
|
+
/** The subscriber. */
|
|
339
|
+
handler: EventHandler;
|
|
340
|
+
/**
|
|
341
|
+
* Optional stable name — enables duplicate detection + teardown diagnostics.
|
|
342
|
+
* Third-party modules should prefix it with their module name, for example
|
|
343
|
+
* `inventory.order-created`; Arc does not silently rewrite public names.
|
|
344
|
+
*/
|
|
345
|
+
name?: string;
|
|
346
|
+
}
|
|
222
347
|
interface ArcModule<TExports = unknown> {
|
|
223
348
|
/** Stable identifier — appears in boot logs and duplicate-module detection. */
|
|
224
349
|
readonly name: string;
|
|
@@ -344,6 +469,74 @@ interface ArcModule<TExports = unknown> {
|
|
|
344
469
|
* module owns.
|
|
345
470
|
*/
|
|
346
471
|
afterResources?: (fastify: FastifyInstance) => void | Promise<void>;
|
|
472
|
+
/**
|
|
473
|
+
* Readiness checks this module contributes to the app's
|
|
474
|
+
* `healthPlugin` (`/_health/ready`). Collected across ALL modules in
|
|
475
|
+
* dependency order and merged BEFORE the host's app-level `arcPlugins.health`
|
|
476
|
+
* checks (modules-first, host-last) — the same additive convention arc uses
|
|
477
|
+
* for resources and error mappers. Check `name`s must be unique across the
|
|
478
|
+
* module graph (arc fails at boot on a collision, attributing both owners).
|
|
479
|
+
*
|
|
480
|
+
* STATIC by design (not a factory): declare the check up front and let its
|
|
481
|
+
* `check()` closure resolve dependencies LAZILY at probe time, so collection
|
|
482
|
+
* can happen before the health plugin registers and the worker probe
|
|
483
|
+
* (`createWorker`) receives the identical union without a second registration.
|
|
484
|
+
*
|
|
485
|
+
* ```ts
|
|
486
|
+
* defineModule({ name: "inventory", healthChecks: [
|
|
487
|
+
* { name: "flow-engine", check: () => flow.isReady() },
|
|
488
|
+
* ] })
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
readonly healthChecks?: readonly HealthCheck[];
|
|
492
|
+
/**
|
|
493
|
+
* Domain-event subscriptions this module owns — the declarative replacement
|
|
494
|
+
* for imperative `fastify.events.subscribe(...)` calls inside
|
|
495
|
+
* `afterResources`. Subscribed in dependency order during the
|
|
496
|
+
* `afterResources` phase (routes mounted, engines booted), and arc RETAINS
|
|
497
|
+
* every unsubscribe and invokes it at shutdown BEFORE module `onClose` (while
|
|
498
|
+
* module deps are still alive). Named handlers must be unique across the
|
|
499
|
+
* module graph; a module that declares handlers while the event subsystem is
|
|
500
|
+
* disabled (`arcPlugins.events: false`) fails at boot. Array or factory (the
|
|
501
|
+
* factory runs after bootstraps, so it can close over booted engines).
|
|
502
|
+
*
|
|
503
|
+
* ```ts
|
|
504
|
+
* defineModule({ name: "party", eventHandlers: [
|
|
505
|
+
* { name: "party.link-customer", event: ["customer:created"], handler: onCustomer },
|
|
506
|
+
* ] })
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
509
|
+
readonly eventHandlers?: ModuleContribution<EventHandlerDefinition>;
|
|
510
|
+
/**
|
|
511
|
+
* Durable workflows this module owns. OPAQUE to arc core — arc never imports
|
|
512
|
+
* `@classytic/streamline`; the value flows through untouched and the
|
|
513
|
+
* streamline integration (`@classytic/arc/integrations/streamline`, a peer)
|
|
514
|
+
* gives it meaning (name/shape validation, registration). Collected via
|
|
515
|
+
* `collectModuleWorkflows` at integration-init time (after module bootstraps),
|
|
516
|
+
* so a factory can close over the shared workflow container the integration
|
|
517
|
+
* decorates on the instance.
|
|
518
|
+
*
|
|
519
|
+
* ```ts
|
|
520
|
+
* defineModule({ name: "invoice",
|
|
521
|
+
* workflows: (f) => createInvoiceWorkflows(f.streamlineContainer) })
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
readonly workflows?: ModuleContribution<unknown>;
|
|
525
|
+
/**
|
|
526
|
+
* Recurring interval schedules this module owns. Arc composes them into its
|
|
527
|
+
* existing `schedulesPlugin` after bootstraps, in dependency order. Names
|
|
528
|
+
* must be unique across the module graph. Configure multi-replica locking via
|
|
529
|
+
* `arcPlugins.schedules`; explicitly disabling that runner while a module
|
|
530
|
+
* declares schedules fails boot instead of silently dropping work. Array or
|
|
531
|
+
* factory (the factory runs once after bootstraps and may close over engines).
|
|
532
|
+
*
|
|
533
|
+
* ```ts
|
|
534
|
+
* defineModule({ name: "loyalty", scheduledJobs: [
|
|
535
|
+
* { name: "loyalty.point.expiration", every: 3_600_000, handler: () => sweep() },
|
|
536
|
+
* ] })
|
|
537
|
+
* ```
|
|
538
|
+
*/
|
|
539
|
+
readonly scheduledJobs?: ModuleContribution<ScheduleDefinition>;
|
|
347
540
|
/**
|
|
348
541
|
* Teardown — registered as a Fastify `onClose` hook. Destroy engines, stop
|
|
349
542
|
* timers, flush the module's outbox. Modules close in REVERSE list order
|
|
@@ -375,31 +568,6 @@ interface ArcModule<TExports = unknown> {
|
|
|
375
568
|
* ```
|
|
376
569
|
*/
|
|
377
570
|
type ArcModuleInput<TExports = unknown> = ArcModule<TExports> | Promise<ArcModule<TExports>> | (() => ArcModule<TExports> | Promise<ArcModule<TExports>>);
|
|
378
|
-
/**
|
|
379
|
-
* Resolve any module-input form to a concrete `ArcModule`. A thunk is invoked
|
|
380
|
-
* (this is where a dynamic `import()` fires); a promise (or plain module) is
|
|
381
|
-
* awaited. Called once per module at the start of the bootstrap phase.
|
|
382
|
-
*/
|
|
383
|
-
declare function resolveModule(input: ArcModuleInput): Promise<ArcModule>;
|
|
384
|
-
/**
|
|
385
|
-
* Order modules for composition by their `dependsOn` edges — a STABLE
|
|
386
|
-
* topological sort. Called once at the start of the bootstrap phase; every
|
|
387
|
-
* subsequent phase (bootstrap, resources, afterResources, and reverse-order
|
|
388
|
-
* onClose) iterates the returned list, so a module's declared dependencies are
|
|
389
|
-
* always composed before it.
|
|
390
|
-
*
|
|
391
|
-
* "Stable" = modules with no edge between them keep their original list order,
|
|
392
|
-
* so declaring `dependsOn` on one module never silently reorders an unrelated
|
|
393
|
-
* one. Backward compatible: a `modules` array with NO `dependsOn` anywhere is
|
|
394
|
-
* returned unchanged.
|
|
395
|
-
*
|
|
396
|
-
* Fail-fast — throws (never reorders past a broken contract) on:
|
|
397
|
-
* - duplicate module names (the name is the graph key)
|
|
398
|
-
* - a `dependsOn` name not present in the composed set
|
|
399
|
-
* - a self-reference (`dependsOn` includes the module's own name)
|
|
400
|
-
* - a dependency cycle (reports the concrete `a → b → … → a` path)
|
|
401
|
-
*/
|
|
402
|
-
declare function orderModules(modules: readonly ArcModule[]): ArcModule[];
|
|
403
571
|
/**
|
|
404
572
|
* Typed module registry — hosts augment this via declaration merging so
|
|
405
573
|
* `getModuleExports(f, "order")` infers the export type without a manual type
|
|
@@ -418,37 +586,5 @@ declare function orderModules(modules: readonly ArcModule[]): ArcModule[];
|
|
|
418
586
|
* available for apps that don't augment it.
|
|
419
587
|
*/
|
|
420
588
|
interface ArcModuleRegistry {}
|
|
421
|
-
/**
|
|
422
|
-
* Identity helper for authoring a typed module — mirrors `defineResource`.
|
|
423
|
-
* Gives inference + a single obvious construction site; does no work.
|
|
424
|
-
*
|
|
425
|
-
* `TExports` is inferred from the `bootstrap` return value, so a module
|
|
426
|
-
* author gets a typed public export for free:
|
|
427
|
-
*
|
|
428
|
-
* ```ts
|
|
429
|
-
* export const accountingModule = (deps: Deps) =>
|
|
430
|
-
* defineModule({
|
|
431
|
-
* name: "accounting",
|
|
432
|
-
* bootstrap: async () => createAccountingEngine(deps), // TExports inferred
|
|
433
|
-
* });
|
|
434
|
-
* ```
|
|
435
|
-
*/
|
|
436
|
-
declare function defineModule<TExports = unknown>(module: ArcModule<TExports>): ArcModule<TExports>;
|
|
437
|
-
/**
|
|
438
|
-
* Typed accessor for a module's public export (its `bootstrap` return value),
|
|
439
|
-
* recorded at `fastify.arc.modules[name]`.
|
|
440
|
-
*
|
|
441
|
-
* Throwing, in line with the fail-fast contract (and `requireOrgId`-style
|
|
442
|
-
* accessors): a missing entry means the module either isn't composed, is
|
|
443
|
-
* composed AFTER the caller (list order = init order), or returned nothing —
|
|
444
|
-
* all wiring bugs that must surface at boot, not as `undefined` downstream.
|
|
445
|
-
*
|
|
446
|
-
* The type parameter is an assertion, not a proof. Two ways to type it:
|
|
447
|
-
* - augment `ArcModuleRegistry` once — then the name alone infers the type:
|
|
448
|
-
* `getModuleExports(f, "accounting")` → `AccountingEngine`.
|
|
449
|
-
* - or pass it inline: `getModuleExports<AccountingEngine>(f, "accounting")`.
|
|
450
|
-
*/
|
|
451
|
-
declare function getModuleExports<K extends keyof ArcModuleRegistry>(fastify: FastifyInstance, name: K): ArcModuleRegistry[K];
|
|
452
|
-
declare function getModuleExports<TExports = unknown>(fastify: FastifyInstance, name: string): TExports;
|
|
453
589
|
//#endregion
|
|
454
|
-
export {
|
|
590
|
+
export { _default$1 as _, ModuleContribution as a, ResourceModule as c, ScheduleStats as d, SchedulesPluginOptions as f, HealthOptions as g, HealthCheck as h, EventHandlerDefinition as i, loadResources as l, schedulesPlugin as m, ArcModuleInput as n, LoadResourcesOptions as o, _default as p, ArcModuleRegistry as r, ResourceLike as s, ArcModule as t, ScheduleDefinition as u, healthPlugin as v };
|
|
@@ -1,24 +1,39 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/utils/headers.d.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* Canonical header typing + accessor.
|
|
4
|
+
*
|
|
5
|
+
* Node/Fastify header values are `string | string[] | undefined` — repeated
|
|
6
|
+
* headers arrive as arrays. `IRequestContext.headers` is DECLARED as
|
|
7
|
+
* `Record<string, string | undefined>` for ergonomic host code, but the HTTP
|
|
8
|
+
* adapter passes Fastify's headers object through unchanged, so `string[]`
|
|
9
|
+
* CAN appear at runtime behind the narrower type (the declared type is
|
|
10
|
+
* scheduled to widen to {@link ArcHeaders} in v3). Until then, read headers
|
|
11
|
+
* through {@link getHeader} instead of indexing — it's honest about arrays
|
|
12
|
+
* and deterministic about duplicates.
|
|
13
|
+
*/
|
|
14
|
+
/** What a headers bag actually contains at runtime — HTTP or synthetic MCP. */
|
|
15
|
+
type ArcHeaders = Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
16
|
+
/**
|
|
17
|
+
* Read a single header value. Lowercases the name (Node normalizes incoming
|
|
18
|
+
* header names to lowercase) and returns the FIRST value when the header
|
|
19
|
+
* was repeated.
|
|
20
|
+
*
|
|
21
|
+
* Duplicate policy: first-value is deterministic, but for security-sensitive
|
|
22
|
+
* headers (authorization material, forwarded-for chains, content-length) a
|
|
23
|
+
* duplicate usually signals smuggling or a broken proxy — consumers that
|
|
24
|
+
* care should check `Array.isArray(headers[name])` and REJECT instead of
|
|
25
|
+
* picking a winner.
|
|
26
|
+
*/
|
|
27
|
+
declare function getHeader(headers: ArcHeaders | Record<string, string | undefined> | undefined, name: string): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Read an identity-bearing or protocol header and reject repeated values.
|
|
9
30
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* // In a permission check
|
|
13
|
-
* const scope = request.scope;
|
|
14
|
-
* if (isElevated(scope)) return true;
|
|
15
|
-
* if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
|
|
16
|
-
*
|
|
17
|
-
* // Get user identity from scope
|
|
18
|
-
* const userId = getUserId(scope);
|
|
19
|
-
* const globalRoles = getUserRoles(scope);
|
|
20
|
-
* ```
|
|
31
|
+
* Repeated security-sensitive headers are ambiguous at proxy boundaries and
|
|
32
|
+
* may indicate request smuggling. Callers must not silently choose a winner.
|
|
21
33
|
*/
|
|
34
|
+
declare function requireSingleHeaderValue(headers: ArcHeaders | Record<string, string | undefined> | undefined, name: string): string | undefined;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/scope/types.d.ts
|
|
22
37
|
/**
|
|
23
38
|
* A capability mandate — declarative, time-boxed, optionally cap-bounded
|
|
24
39
|
* authorization for a single high-value action by an AI agent or service.
|
|
@@ -118,7 +133,9 @@ interface Mandate {
|
|
|
118
133
|
* | elevated | Platform admin, explicit elevation |
|
|
119
134
|
*
|
|
120
135
|
* **Identity fields by kind:**
|
|
121
|
-
* - `userId`
|
|
136
|
+
* - `userId` — available on `authenticated`, `member`, `elevated` (a real human)
|
|
137
|
+
* - `userRoles` — available on `authenticated` and `member`; elevated scope
|
|
138
|
+
* bypasses role gates explicitly and therefore does not carry role claims
|
|
122
139
|
* - `clientId` — available on `service` (a machine identity, NOT a user)
|
|
123
140
|
* - `organizationId` — required on `member` and `service`, optional on `elevated`
|
|
124
141
|
* - `orgRoles` — org-level roles, only on `member` (from membership records)
|
|
@@ -517,9 +534,90 @@ declare function getOrgContext(request: {
|
|
|
517
534
|
declare function getRequestScope(request: {
|
|
518
535
|
scope?: RequestScope;
|
|
519
536
|
}): RequestScope;
|
|
537
|
+
/** Options for {@link scopeFirstCtx}. */
|
|
538
|
+
interface ScopeFirstCtxOptions {
|
|
539
|
+
/**
|
|
540
|
+
* Tenant-selection header consulted ONLY when the scope carries no org —
|
|
541
|
+
* the custom-auth/public bridge (same semantics as `getTenantFromRequest`).
|
|
542
|
+
* A validated scope org always wins; the header never overrides it.
|
|
543
|
+
* Default `'x-organization-id'`; pass `false` to disable the bridge and
|
|
544
|
+
* read strictly from scope.
|
|
545
|
+
*/
|
|
546
|
+
orgHeader?: string | false;
|
|
547
|
+
/**
|
|
548
|
+
* Actor id substituted when the scope carries no user/client identity
|
|
549
|
+
* (e.g. `'system'` / `'anonymous'`). When provided, the returned `actorId`
|
|
550
|
+
* narrows to `string`; omit to receive `actorId: undefined` on
|
|
551
|
+
* unauthenticated requests and apply your own policy.
|
|
552
|
+
*/
|
|
553
|
+
fallbackActorId?: string;
|
|
554
|
+
}
|
|
555
|
+
/** Return shape of {@link scopeFirstCtx}. */
|
|
556
|
+
interface ScopeFirstCtx {
|
|
557
|
+
actorId?: string;
|
|
558
|
+
organizationId?: string;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Request shape accepted by {@link scopeFirstCtx} — deliberately structural so
|
|
562
|
+
* ONE helper serves both surfaces:
|
|
563
|
+
* - a raw `FastifyRequest`, whose `scope` is the discriminated {@link RequestScope}
|
|
564
|
+
* - arc's `IRequestContext` (controller overrides, pipeline handlers, actions),
|
|
565
|
+
* whose `scope` is the FLAT projection (`{ organizationId?, userId?, orgRoles? }`
|
|
566
|
+
* lifted from `metadata._scope` — see `RequestScopeProjection`)
|
|
567
|
+
*/
|
|
568
|
+
interface ScopeFirstCtxRequest {
|
|
569
|
+
scope?: RequestScope | {
|
|
570
|
+
organizationId?: string | undefined;
|
|
571
|
+
userId?: string | undefined;
|
|
572
|
+
orgRoles?: string[] | undefined;
|
|
573
|
+
} | undefined;
|
|
574
|
+
/** `ArcHeaders` — readonly, repeated headers may arrive as arrays. Fastify's `IncomingHttpHeaders` assigns cleanly. */
|
|
575
|
+
headers?: ArcHeaders | undefined;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* The ONE shared actor/org derivation for resource handlers and module
|
|
579
|
+
* packages (`arc-*` fleet) — scope-FIRST, per the canonical precedence:
|
|
580
|
+
*
|
|
581
|
+
* 1. `request.scope` (arc's validated `RequestScope`, set by auth adapters):
|
|
582
|
+
* `userId` (human) or `clientId` (service) → `actorId`; `getOrgId(scope)`
|
|
583
|
+
* → `organizationId`.
|
|
584
|
+
* 2. The `x-organization-id` header — ONLY when the scope carries no org.
|
|
585
|
+
* This is the bridge for custom-auth/public routes where arc leaves scope
|
|
586
|
+
* unpopulated; it never overrides a validated scope.
|
|
587
|
+
*
|
|
588
|
+
* Replaces per-package hand-rolled clones of
|
|
589
|
+
* `req.user?._id ?? req.user?.id ?? headers['x-organization-id']` — that
|
|
590
|
+
* pattern re-implements (and drifts from) arc's canonical resolution. A
|
|
591
|
+
* package's `ctxOf(req)` should call this for the shared
|
|
592
|
+
* `{ actorId, organizationId }` and only ADD its kernel-specific fields
|
|
593
|
+
* (`actorKind` / `correlationId` / `roles` / `idempotencyKey`).
|
|
594
|
+
*
|
|
595
|
+
* Structural request shape — works on a raw `FastifyRequest` AND on arc's
|
|
596
|
+
* `IRequestContext` (see {@link ScopeFirstCtxRequest}: the discriminated
|
|
597
|
+
* `RequestScope` and the flat controller projection are both understood).
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```typescript
|
|
601
|
+
* import { scopeFirstCtx } from '@classytic/arc/scope';
|
|
602
|
+
*
|
|
603
|
+
* // Guaranteed actor (kernel context requires one):
|
|
604
|
+
* const { actorId, organizationId } = scopeFirstCtx(req, { fallbackActorId: 'system' });
|
|
605
|
+
*
|
|
606
|
+
* // Optional actor (package applies its own policy):
|
|
607
|
+
* const ctx = scopeFirstCtx(req);
|
|
608
|
+
* if (ctx.actorId) audit.record({ actor: ctx.actorId });
|
|
609
|
+
* ```
|
|
610
|
+
*/
|
|
611
|
+
declare function scopeFirstCtx(req: ScopeFirstCtxRequest, options: ScopeFirstCtxOptions & {
|
|
612
|
+
fallbackActorId: string;
|
|
613
|
+
}): {
|
|
614
|
+
actorId: string;
|
|
615
|
+
organizationId?: string;
|
|
616
|
+
};
|
|
617
|
+
declare function scopeFirstCtx(req: ScopeFirstCtxRequest, options?: ScopeFirstCtxOptions): ScopeFirstCtx;
|
|
520
618
|
/** Default public scope — used as initial decoration value */
|
|
521
619
|
declare const PUBLIC_SCOPE: Readonly<RequestScope>;
|
|
522
620
|
/** Default authenticated scope — used when user is logged in but no org */
|
|
523
621
|
declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
|
|
524
622
|
//#endregion
|
|
525
|
-
export {
|
|
623
|
+
export { requireOrgId as A, hasOrgAccess as C, isOrgInScope as D, isMember as E, getHeader as F, requireSingleHeaderValue as I, requireUserId as M, scopeFirstCtx as N, isService as O, ArcHeaders as P, getUserRoles as S, isElevated as T, getScopeContextMap as _, ScopeFirstCtx as a, getTenantFromRequest as b, getAncestorOrgIds as c, getMandate as d, getOrgContext as f, getScopeContext as g, getRequestScope as h, RequestScope as i, requireTeamId as j, requireClientId as k, getClientId as l, getOrgRoles as m, Mandate as n, ScopeFirstCtxOptions as o, getOrgId as p, PUBLIC_SCOPE as r, ScopeFirstCtxRequest as s, AUTHENTICATED_SCOPE as t, getDPoPJkt as u, getServiceScopes as v, isAuthenticated as w, getUserId as x, getTeamId as y };
|
|
@@ -1,5 +1,99 @@
|
|
|
1
|
-
import { i as RequestScope } from "./types-
|
|
1
|
+
import { i as RequestScope } from "./types-DjC-KUCz.mjs";
|
|
2
2
|
import { FastifyRequest } from "fastify";
|
|
3
|
+
//#region src/types/base.d.ts
|
|
4
|
+
declare module "fastify" {
|
|
5
|
+
interface FastifyRequest {
|
|
6
|
+
/** Request scope — set by auth adapter, read by permissions/presets/guards */
|
|
7
|
+
scope: RequestScope;
|
|
8
|
+
/**
|
|
9
|
+
* Current user — set by auth adapter (Better Auth, JWT, custom).
|
|
10
|
+
* `undefined` on public routes (`auth: false`) or unauthenticated requests.
|
|
11
|
+
* Guard with `if (request.user)` on routes that allow anonymous access.
|
|
12
|
+
*
|
|
13
|
+
* Kept as required (not `user?`) because `@fastify/jwt` declares it
|
|
14
|
+
* as required — declaration merges must have identical modifiers.
|
|
15
|
+
* The `| undefined` in the type achieves the same DX.
|
|
16
|
+
*/
|
|
17
|
+
user: Record<string, unknown> | undefined;
|
|
18
|
+
/** Policy-injected query filters (e.g. ownership, org-scoping) */
|
|
19
|
+
_policyFilters?: Record<string, unknown>;
|
|
20
|
+
/** Field mask — fields to include/exclude in responses */
|
|
21
|
+
fieldMask?: {
|
|
22
|
+
include?: string[];
|
|
23
|
+
exclude?: string[];
|
|
24
|
+
};
|
|
25
|
+
/** Arbitrary policy metadata for downstream consumers */
|
|
26
|
+
policyMetadata?: Record<string, unknown>;
|
|
27
|
+
/** Document loaded by policy middleware for ownership checks */
|
|
28
|
+
document?: unknown;
|
|
29
|
+
/** Ownership check context (field name + user field) */
|
|
30
|
+
_ownershipCheck?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
type AnyRecord = Record<string, unknown>;
|
|
34
|
+
/** MongoDB ObjectId — accepts string or any object with a `toString()` (e.g. mongoose ObjectId). */
|
|
35
|
+
type ObjectId = string | {
|
|
36
|
+
toString(): string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Flexible user type that accepts any object with id/_id properties.
|
|
40
|
+
* The actual user structure is defined by your app's auth system.
|
|
41
|
+
*/
|
|
42
|
+
type UserLike = UserBase & {
|
|
43
|
+
/** User email (optional) */
|
|
44
|
+
email?: string;
|
|
45
|
+
};
|
|
46
|
+
interface UserOrganization {
|
|
47
|
+
userId: string;
|
|
48
|
+
organizationId: string;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
interface JWTPayload {
|
|
52
|
+
sub: string;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Typed Fastify request with Arc decorations. Use in `raw: true` handlers
|
|
57
|
+
* instead of `(req as any).user`.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import type { ArcRequest } from '@classytic/arc';
|
|
62
|
+
*
|
|
63
|
+
* handler: async (req: ArcRequest, reply) => {
|
|
64
|
+
* req.user?.id; // typed
|
|
65
|
+
* req.scope.organizationId; // typed (when member)
|
|
66
|
+
* req.signal; // AbortSignal (Fastify 5)
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
type ArcRequest = FastifyRequest & {
|
|
71
|
+
scope: RequestScope;
|
|
72
|
+
user: Record<string, unknown> | undefined;
|
|
73
|
+
signal: AbortSignal;
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/utils/userHelpers.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* Extract a user ID from a user object. Accepts `id` or `_id` — returns
|
|
79
|
+
* `undefined` when neither is present. Used by arc's controllers to
|
|
80
|
+
* populate `createdBy` / `updatedBy` fields and for cache scoping.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { getUserId } from '@classytic/arc/utils';
|
|
85
|
+
* const uid = getUserId(request.user);
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function getUserId(user: UserLike | null | undefined): string | undefined;
|
|
89
|
+
/** Normalize a raw role value from Better Auth, JWT, or a custom identity provider. */
|
|
90
|
+
declare function normalizeRoles(value: unknown): string[];
|
|
91
|
+
/** Extract normalized roles from a user-like object. */
|
|
92
|
+
declare function getUserRoles(user: {
|
|
93
|
+
role?: unknown;
|
|
94
|
+
roles?: unknown;
|
|
95
|
+
} | null | undefined): string[];
|
|
96
|
+
//#endregion
|
|
3
97
|
//#region src/permissions/types.d.ts
|
|
4
98
|
/**
|
|
5
99
|
* User base interface - minimal shape Arc expects
|
|
@@ -24,8 +118,6 @@ interface UserBase {
|
|
|
24
118
|
* Normalize a raw role value (string, comma-separated string, or array) into a string[].
|
|
25
119
|
* Shared low-level helper used by both getUserRoles() and the Better Auth adapter.
|
|
26
120
|
*/
|
|
27
|
-
declare function normalizeRoles(value: unknown): string[];
|
|
28
|
-
declare function getUserRoles(user: UserBase | null | undefined): string[];
|
|
29
121
|
/**
|
|
30
122
|
* Context passed to permission check functions
|
|
31
123
|
*/
|
|
@@ -195,4 +287,4 @@ interface PermissionCheckMeta {
|
|
|
195
287
|
};
|
|
196
288
|
}
|
|
197
289
|
//#endregion
|
|
198
|
-
export {
|
|
290
|
+
export { getUserId as a, AnyRecord as c, ObjectId as d, UserLike as f, UserBase as i, ArcRequest as l, PermissionContext as n, getUserRoles as o, UserOrganization as p, PermissionResult as r, normalizeRoles as s, PermissionCheck as t, JWTPayload as u };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { V as ResourceDefinition } from "./index-
|
|
1
|
+
import { V as ResourceDefinition } from "./index-DPRH0Cme.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/integrations/mcp/types.d.ts
|
|
4
4
|
/** Behavioral hints for MCP clients (tool annotations per MCP spec) */
|
package/dist/usage/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { i as RequestScope } from "../types-
|
|
2
|
-
import { n as PermissionContext, t as PermissionCheck } from "../types-
|
|
1
|
+
import { i as RequestScope } from "../types-DjC-KUCz.mjs";
|
|
2
|
+
import { n as PermissionContext, t as PermissionCheck } from "../types-Dmw-6Qeo.mjs";
|
|
3
|
+
import { UsageBucket, UsageStore, usagePeriod } from "@classytic/repo-core/usage";
|
|
3
4
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
4
5
|
//#region src/usage/requireQuota.d.ts
|
|
5
6
|
interface QuotaOptions {
|
|
@@ -28,56 +29,6 @@ interface QuotaOptions {
|
|
|
28
29
|
}
|
|
29
30
|
declare function requireQuota(options: QuotaOptions): PermissionCheck;
|
|
30
31
|
//#endregion
|
|
31
|
-
//#region src/usage/stores/interface.d.ts
|
|
32
|
-
/**
|
|
33
|
-
* Usage store contract — period-bucketed counters per actor.
|
|
34
|
-
*
|
|
35
|
-
* CANONICAL HOME: `@classytic/repo-core/usage` (>=0.10) owns this
|
|
36
|
-
* contract for the ecosystem — kits implement it there without ever
|
|
37
|
-
* depending on arc (`@classytic/mongokit/usage`, ...). This file is a
|
|
38
|
-
* deliberate STRUCTURAL MIRROR of that shape — same trick as
|
|
39
|
-
* `ScheduleLockLike` vs repo-core's `LockAdapter` — so arc's emitted
|
|
40
|
-
* declarations carry no repo-core@>=0.10 subpath reference and the
|
|
41
|
-
* peer floor stays honest. Any conforming adapter assigns directly.
|
|
42
|
-
*
|
|
43
|
-
* The deliberately tiny surface (one increment, one read) is what makes
|
|
44
|
-
* every backend trivial: memory (dev/tests), Redis (`HINCRBY`), or any kit
|
|
45
|
-
* (Mongo `$inc` upsert) implements it in a handful of lines. Aggregation
|
|
46
|
-
* happens IN the store — arc never buffers counters in process memory
|
|
47
|
-
* beyond the built-in memory store, so multi-replica deployments stay
|
|
48
|
-
* correct as long as the store is shared.
|
|
49
|
-
*
|
|
50
|
-
* This is the HORIZONTAL usage layer (per-actor, per-period platform
|
|
51
|
-
* accounting for quotas/plans/billing). Itemized vertical records — e.g.
|
|
52
|
-
* `@classytic/arc-ai/usage`'s per-run AI token/cost tracking — keep their
|
|
53
|
-
* own richer stores and can sink into this one (`kind: 'ai.tokens'`).
|
|
54
|
-
*/
|
|
55
|
-
/** One counter cell: (actor, period, kind). */
|
|
56
|
-
interface UsageBucket {
|
|
57
|
-
/** Who consumed — org / user / client id, or an IP-derived fallback. */
|
|
58
|
-
actor: string;
|
|
59
|
-
/** Aggregation period, `YYYY-MM` (see `usagePeriod()`). */
|
|
60
|
-
period: string;
|
|
61
|
-
/** Namespaced counter, dot-separated: `api.requests`, `ai.tokens`, `storage.bytes`. */
|
|
62
|
-
kind: string;
|
|
63
|
-
}
|
|
64
|
-
interface UsageStore {
|
|
65
|
-
/** Store name for diagnostics (e.g. 'memory', 'redis', 'mongo'). */
|
|
66
|
-
readonly name: string;
|
|
67
|
-
/** Add `amount` to the bucket's counter, creating it at 0 first. MUST be atomic per bucket. */
|
|
68
|
-
increment(bucket: UsageBucket, amount: number): Promise<void> | void;
|
|
69
|
-
/** Every counter for an actor in a period: `{ 'api.requests': 40231, ... }`. */
|
|
70
|
-
summary(actor: string, period: string): Promise<Record<string, number>>;
|
|
71
|
-
/** Optional cleanup hook (connections, timers). */
|
|
72
|
-
close?(): Promise<void>;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Canonical period key for a date — calendar month, UTC: `2026-07`.
|
|
76
|
-
* Monthly is the billing-native granularity; stores that want finer
|
|
77
|
-
* windows can shard internally without changing the contract.
|
|
78
|
-
*/
|
|
79
|
-
declare function usagePeriod(date?: Date): string;
|
|
80
|
-
//#endregion
|
|
81
32
|
//#region src/usage/stores/memory.d.ts
|
|
82
33
|
declare class MemoryUsageStore implements UsageStore {
|
|
83
34
|
#private;
|
package/dist/usage/index.mjs
CHANGED
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/utils/userHelpers.ts
|
|
2
|
+
/**
|
|
3
|
+
* Extract a user ID from a user object. Accepts `id` or `_id` — returns
|
|
4
|
+
* `undefined` when neither is present. Used by arc's controllers to
|
|
5
|
+
* populate `createdBy` / `updatedBy` fields and for cache scoping.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { getUserId } from '@classytic/arc/utils';
|
|
10
|
+
* const uid = getUserId(request.user);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
function getUserId(user) {
|
|
14
|
+
if (!user) return void 0;
|
|
15
|
+
const id = user.id ?? user._id;
|
|
16
|
+
return id ? String(id) : void 0;
|
|
17
|
+
}
|
|
18
|
+
/** Normalize a raw role value from Better Auth, JWT, or a custom identity provider. */
|
|
19
|
+
function normalizeRoles(value) {
|
|
20
|
+
if (Array.isArray(value)) return value.map((role) => String(role).trim()).filter(Boolean);
|
|
21
|
+
if (typeof value === "string" && value.length > 0) return value.split(",").map((role) => role.trim()).filter(Boolean);
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
/** Extract normalized roles from a user-like object. */
|
|
25
|
+
function getUserRoles(user) {
|
|
26
|
+
return user ? normalizeRoles(user.role ?? user.roles) : [];
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { getUserRoles as n, normalizeRoles as r, getUserId as t };
|