@classytic/arc 2.8.0 → 2.8.3
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 +10 -1
- package/dist/{BaseController-CpMfCXdn.mjs → BaseController-DAGGc5Xn.mjs} +76 -25
- package/dist/{EventTransport-n1KBxC_N.d.mts → EventTransport-CinyO7zQ.d.mts} +37 -1
- package/dist/{ResourceRegistry-BOtJuRCs.mjs → ResourceRegistry-Dq3_zBQP.mjs} +17 -5
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-BxGgSHjj.mjs → adapters-BBqAVvPK.mjs} +11 -0
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +1 -1
- package/dist/audit/mongodb.d.mts +1 -1
- package/dist/audit/mongodb.mjs +1 -1
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +3 -3
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-CHCIuA-p.mjs → betterAuthOpenApi-C5lDyRH2.mjs} +1 -1
- package/dist/cache/index.d.mts +2 -2
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +10 -10
- package/dist/cli/commands/introspect.mjs +3 -3
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/{core-BfrfxNqO.mjs → core-DKSwNSXf.mjs} +1 -1
- package/dist/{createActionRouter-CbkIAaGh.mjs → createActionRouter-Df1BuawX.mjs} +87 -21
- package/dist/{createApp-Cy8eUNKQ.mjs → createApp-BOYjBgdI.mjs} +16 -7
- package/dist/{defineResource-CovBXvTB.mjs → defineResource-Bb_Bdhtw.mjs} +60 -33
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/dynamic/index.d.mts +2 -2
- package/dist/dynamic/index.mjs +1 -1
- package/dist/{errorHandler-BeN-ERN7.d.mts → errorHandler-CdZDavNH.d.mts} +2 -2
- package/dist/{errorHandler-BW08lEiy.mjs → errorHandler-mzqk4cGl.mjs} +1 -1
- package/dist/{eventPlugin-CAOWMQS8.d.mts → eventPlugin-CVxlE6De.d.mts} +1 -1
- package/dist/{eventPlugin-x4jo3sG0.mjs → eventPlugin-D91S2YF4.mjs} +19 -1
- package/dist/events/index.d.mts +399 -28
- package/dist/events/index.mjs +345 -29
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +3 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -152
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +18 -6
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +10 -1
- package/dist/{index-BpMhrFgn.d.mts → index-BgmMdpm8.d.mts} +1 -1
- package/dist/{index-CBru2y5Y.d.mts → index-CSkeivBx.d.mts} +3 -3
- package/dist/{index-qct60lnl.d.mts → index-CpTSDqmD.d.mts} +60 -6
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +7 -7
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/{interface-IJqN3pXK.d.mts → interface-BVuMfeVv.d.mts} +596 -125
- package/dist/loadResources-Bksk8ydA.mjs +154 -0
- package/dist/{mongodb-B1eVtFhw.d.mts → mongodb-B8U2xaLj.d.mts} +1 -1
- package/dist/{mongodb-NShVZDMr.d.mts → mongodb-X7LbEjTN.d.mts} +10 -1
- package/dist/{openapi-AYLVjqVe.mjs → openapi-CYCuekCn.mjs} +50 -3
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -3
- package/dist/plugins/index.d.mts +5 -5
- package/dist/plugins/index.mjs +8 -8
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/policies/index.d.mts +1 -1
- package/dist/presets/index.d.mts +3 -3
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/{presets-BFrGvvjL.mjs → presets-C2xgzW6x.mjs} +10 -18
- package/dist/{queryCachePlugin-BCFVXnxK.d.mts → queryCachePlugin-CnTZZTC5.d.mts} +1 -1
- package/dist/{redis-stream-CF1lrKVk.d.mts → redis-stream-D54N5oXs.d.mts} +1 -1
- package/dist/{redis-Bunu3qWg.d.mts → redis-z3sFr1UP.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +1 -1
- package/dist/{resourceToTools-C_1SMiCz.mjs → resourceToTools-O_HwWXFa.mjs} +194 -64
- package/dist/rpc/index.d.mts +1 -1
- package/dist/rpc/index.mjs +1 -1
- package/dist/scope/index.d.mts +2 -2
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/types/index.d.mts +5 -5
- package/dist/{types-gUxAIZHp.d.mts → types-Bg2X42_m.d.mts} +30 -9
- package/dist/{types-BoaZHr-2.d.mts → types-CVC4HOKi.d.mts} +1 -1
- package/dist/{types-Ct0PUUSp.d.mts → types-CcG4avic.d.mts} +1 -1
- package/dist/utils/index.d.mts +43 -17
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-B-l6410F.mjs → utils-yYT3HDXt.mjs} +65 -13
- package/package.json +10 -9
- package/skills/arc/SKILL.md +79 -6
- /package/dist/{caching-CHH-iHs3.mjs → caching-CjybdRwx.mjs} +0 -0
- /package/dist/{circuitBreaker-BGVoB1hD.d.mts → circuitBreaker-CvXkjfrW.d.mts} +0 -0
- /package/dist/{circuitBreaker-l18oRgL5.mjs → circuitBreaker-cmi5XDv5.mjs} +0 -0
- /package/dist/{elevation-UJO3-NvX.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
- /package/dist/{errors-Cg58SLNi.mjs → errors-BF2bIOIS.mjs} +0 -0
- /package/dist/{errors-BI8kEKsO.d.mts → errors-Bmn3eZT6.d.mts} +0 -0
- /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
- /package/dist/{fields-DoeDgh2b.d.mts → fields-DC4So2M2.d.mts} +0 -0
- /package/dist/{interface-CkkWm5uR.d.mts → interface-B-pe8fhj.d.mts} +0 -0
- /package/dist/{interface-bpoLKKqx.d.mts → interface-DplgQO2e.d.mts} +0 -0
- /package/dist/{metrics-DuhiSEZI.mjs → metrics-TuOmguhi.mjs} +0 -0
- /package/dist/{mongodb-5Ff3w8jy.mjs → mongodb-B5O6xaW1.mjs} +0 -0
- /package/dist/{pluralize-BneOJkpi.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
- /package/dist/{requestContext-xHIKedG6.mjs → requestContext-DYvHl113.mjs} +0 -0
- /package/dist/{schemaConverter-Y5EejTnJ.mjs → schemaConverter-OxfCshus.mjs} +0 -0
- /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
- /package/dist/{sse-CD5Hghpu.mjs → sse-CJpt7LGI.mjs} +0 -0
- /package/dist/{tracing-xqXzWeaf.d.mts → tracing-DxjKk7eW.d.mts} +0 -0
- /package/dist/{types-CN6JvmYz.d.mts → types-C72d3NDn.d.mts} +0 -0
- /package/dist/{versioning-CPU_5Xfs.mjs → versioning-Cm8qoFDg.mjs} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { r as RequestScope } from "./types-
|
|
2
|
-
import { n as FieldPermissionMap } from "./fields-
|
|
3
|
-
import { i as UserBase, t as PermissionCheck } from "./types-
|
|
1
|
+
import { r as RequestScope } from "./types-C72d3NDn.mjs";
|
|
2
|
+
import { n as FieldPermissionMap } from "./fields-DC4So2M2.mjs";
|
|
3
|
+
import { i as UserBase, t as PermissionCheck } from "./types-CVC4HOKi.mjs";
|
|
4
4
|
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
|
|
5
5
|
|
|
6
6
|
//#region src/hooks/HookSystem.d.ts
|
|
7
7
|
type HookPhase = "before" | "around" | "after";
|
|
8
|
-
type HookOperation = "create" | "update" | "delete" | "read" | "list";
|
|
8
|
+
type HookOperation = "create" | "update" | "delete" | "restore" | "read" | "list";
|
|
9
9
|
interface HookContext<T = AnyRecord> {
|
|
10
10
|
resource: string;
|
|
11
11
|
operation: HookOperation;
|
|
@@ -318,116 +318,479 @@ type PipelineConfig = PipelineStep[] | {
|
|
|
318
318
|
//#endregion
|
|
319
319
|
//#region src/types/repository.d.ts
|
|
320
320
|
/**
|
|
321
|
-
* Repository Interface
|
|
321
|
+
* Repository Interface — Database-Agnostic CRUD Contract
|
|
322
322
|
*
|
|
323
|
-
* This is the
|
|
324
|
-
*
|
|
323
|
+
* This is the canonical contract every arc-compatible repository follows.
|
|
324
|
+
* It is intentionally structural: any object matching the shape works,
|
|
325
|
+
* including the reference implementation at `@classytic/mongokit` and any
|
|
326
|
+
* future `prismakit` / `pgkit` / `sqlitekit` that mirrors it.
|
|
325
327
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
328
|
+
* ## Design
|
|
329
|
+
*
|
|
330
|
+
* The interface is tiered so a minimal adapter can ship with five methods
|
|
331
|
+
* while a mature one (mongokit 3.6+) can opt into the full surface without
|
|
332
|
+
* type assertions:
|
|
333
|
+
*
|
|
334
|
+
* 1. **Required** — `getAll`, `getById`, `create`, `update`, `delete`.
|
|
335
|
+
* Every resource needs these; arc's BaseController assumes they exist.
|
|
336
|
+
*
|
|
337
|
+
* 2. **Recommended** — `getOne` / `getByQuery`. Used by AccessControl to
|
|
338
|
+
* enforce compound filters (idField + org scope + policy). Without them,
|
|
339
|
+
* arc falls back to `getById` + post-fetch checks, which is slower and
|
|
340
|
+
* produces wrong 404s on custom idFields.
|
|
341
|
+
*
|
|
342
|
+
* 3. **Optional capabilities** — batch ops, soft delete, aggregation,
|
|
343
|
+
* transactions, etc. Declared as optional so kits implement only what
|
|
344
|
+
* their underlying DB supports. arc feature-detects at runtime.
|
|
345
|
+
*
|
|
346
|
+
* All options/results are named types so custom kits can import and
|
|
347
|
+
* implement them directly:
|
|
348
|
+
*
|
|
349
|
+
* ```ts
|
|
350
|
+
* import type {
|
|
351
|
+
* CrudRepository,
|
|
352
|
+
* DeleteOptions,
|
|
353
|
+
* DeleteResult,
|
|
354
|
+
* PaginationResult,
|
|
355
|
+
* UpdateManyResult,
|
|
356
|
+
* BulkWriteOperation,
|
|
357
|
+
* BulkWriteResult,
|
|
358
|
+
* } from '@classytic/arc';
|
|
329
359
|
*
|
|
330
|
-
*
|
|
360
|
+
* class PgRepository<TDoc> implements CrudRepository<TDoc> { … }
|
|
361
|
+
* ```
|
|
362
|
+
*
|
|
363
|
+
* @example Reference implementation
|
|
364
|
+
* ```ts
|
|
365
|
+
* import type { CrudRepository } from '@classytic/arc';
|
|
331
366
|
* const userRepo: CrudRepository<UserDocument> = new Repository(UserModel);
|
|
332
367
|
* ```
|
|
368
|
+
*
|
|
369
|
+
* ## Contract gotchas (learned from mongokit 3.6 integration)
|
|
370
|
+
*
|
|
371
|
+
* If you build a custom kit that implements this contract, these are the
|
|
372
|
+
* behaviors arc's tests specifically verify. Align your kit here and
|
|
373
|
+
* arc's `BaseController` + presets will work out of the box:
|
|
374
|
+
*
|
|
375
|
+
* 1. **`getById` / `getOne` miss semantics** — MAY return `null` or throw a
|
|
376
|
+
* 404-style error whose message contains "not found". Arc handles both.
|
|
377
|
+
* Pick one and document it in your kit.
|
|
378
|
+
*
|
|
379
|
+
* 2. **`deleteMany` with soft-delete** — if your kit intercepts
|
|
380
|
+
* `deleteMany` and rewrites it to `updateMany`, the returned
|
|
381
|
+
* `deletedCount` may be `0` even when N docs were soft-deleted. The
|
|
382
|
+
* authoritative count comes from a follow-up query. Consumers shouldn't
|
|
383
|
+
* rely on `deletedCount` reflecting soft-delete work unless your kit
|
|
384
|
+
* promises it.
|
|
385
|
+
*
|
|
386
|
+
* 3. **Lifecycle hooks are shared with plugins** — never use
|
|
387
|
+
* `removeAllListeners(event)` to clean up test hooks. That silently
|
|
388
|
+
* removes soft-delete, cascade, multi-tenant, and audit plugin
|
|
389
|
+
* listeners too, which then makes subsequent operations misbehave
|
|
390
|
+
* (e.g. a soft-delete becomes a hard delete). Always use
|
|
391
|
+
* `.off(event, fn)` with the specific handler reference you registered.
|
|
392
|
+
*
|
|
393
|
+
* 4. **Hard-delete mode** — `delete(id, { mode: 'hard' })` and
|
|
394
|
+
* `deleteMany(q, { mode: 'hard' })` MUST bypass soft-delete
|
|
395
|
+
* interception while still running policy / multi-tenant / cascade /
|
|
396
|
+
* audit hooks. Kits without soft-delete should accept and ignore the
|
|
397
|
+
* flag.
|
|
398
|
+
*
|
|
399
|
+
* 5. **Keyset pagination auto-detection** — `getAll({ sort, limit })`
|
|
400
|
+
* without `page` SHOULD return a `KeysetPaginatedResult` with
|
|
401
|
+
* `method: "keyset"`. Kits that only offer offset pagination can return
|
|
402
|
+
* the legacy offset shape; arc's types still satisfy.
|
|
403
|
+
*
|
|
404
|
+
* 6. **`idField` identity** — kits that key on anything other than `"_id"`
|
|
405
|
+
* MUST set `readonly idField` on the repository so arc's BaseController
|
|
406
|
+
* passes route params straight through to `update`/`delete`/`restore`
|
|
407
|
+
* without translating them.
|
|
408
|
+
*
|
|
409
|
+
* 7. **`before:restore` / `after:restore` hooks** — if you implement
|
|
410
|
+
* `restore`, fire these hooks symmetrically with `before:delete` /
|
|
411
|
+
* `after:delete` so hosts can wire cascade-restore flows.
|
|
412
|
+
*
|
|
413
|
+
* See `tests/core/repository-contract-mongokit.test.ts` for a runnable
|
|
414
|
+
* reference against mongokit 3.6. Copy it, swap in your kit's repository,
|
|
415
|
+
* and make it pass — if everything's green, arc will work against your
|
|
416
|
+
* kit.
|
|
417
|
+
*/
|
|
418
|
+
/**
|
|
419
|
+
* Opaque transaction session. Adapters bind this to their own type
|
|
420
|
+
* (Mongoose `ClientSession`, Prisma transaction client, `pg.Client`, …).
|
|
333
421
|
*/
|
|
422
|
+
type RepositorySession = unknown;
|
|
334
423
|
/**
|
|
335
|
-
* Query options for read operations
|
|
424
|
+
* Query options for read operations. Extended ad-hoc by adapters via the
|
|
425
|
+
* index signature — kit authors should namespace custom flags (e.g.
|
|
426
|
+
* `__pgHint`) to avoid collisions.
|
|
336
427
|
*/
|
|
337
428
|
interface QueryOptions {
|
|
338
|
-
/** Transaction session —
|
|
339
|
-
session?:
|
|
340
|
-
/**
|
|
341
|
-
select?: string | string[] | Record<string, 0 | 1>;
|
|
342
|
-
/** Relations to populate - string, array, or Mongoose populate options */
|
|
343
|
-
populate?: string | string[] | Record<string, unknown>;
|
|
344
|
-
/** Return plain JS objects instead of Mongoose documents */
|
|
429
|
+
/** Transaction session — adapter-specific concrete type */
|
|
430
|
+
session?: RepositorySession;
|
|
431
|
+
/** Return plain objects instead of driver documents */
|
|
345
432
|
lean?: boolean;
|
|
346
|
-
/**
|
|
433
|
+
/** Include soft-deleted docs in reads (honored by soft-delete plugin) */
|
|
434
|
+
includeDeleted?: boolean;
|
|
435
|
+
/** Forwarded to policy/tenant hooks */
|
|
436
|
+
user?: Record<string, unknown>;
|
|
437
|
+
/** Arc request-scoped metadata (orgId, roles, requestId, …) */
|
|
438
|
+
context?: Record<string, unknown>;
|
|
439
|
+
/**
|
|
440
|
+
* Adapter-specific escape hatch — `select`, `populate`, `populateOptions`,
|
|
441
|
+
* `readPreference`, `maxTimeMS`, and every kit's driver-specific flags
|
|
442
|
+
* flow through here. Arc intentionally does NOT type these concretely
|
|
443
|
+
* because each kit's DB shapes them differently: mongoose uses
|
|
444
|
+
* `PopulateOptions[]`, prisma uses `{ include: {...} }`, pgkit uses SQL
|
|
445
|
+
* JOIN hints, etc. Typing them as (say) `string | Record<string, unknown>`
|
|
446
|
+
* would REJECT the narrower shapes real kits actually expose, breaking
|
|
447
|
+
* structural assignability of `Repository<T> → CrudRepository<T>`.
|
|
448
|
+
*/
|
|
347
449
|
[key: string]: unknown;
|
|
348
450
|
}
|
|
349
451
|
/**
|
|
350
|
-
*
|
|
452
|
+
* Options for write operations (create/update). Superset of QueryOptions
|
|
453
|
+
* so callers can pass a single options object.
|
|
454
|
+
*/
|
|
455
|
+
interface WriteOptions extends QueryOptions {
|
|
456
|
+
/** Upsert on update/replace operations */
|
|
457
|
+
upsert?: boolean;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Options for delete operations.
|
|
461
|
+
*
|
|
462
|
+
* `mode: 'hard'` opts out of the soft-delete interception when the adapter
|
|
463
|
+
* has a soft-delete plugin wired. Policy, cascade, audit, and cache hooks
|
|
464
|
+
* still fire — only the soft-delete rewrite is bypassed. Use for GDPR
|
|
465
|
+
* erasure or admin purge paths.
|
|
466
|
+
*/
|
|
467
|
+
interface DeleteOptions extends QueryOptions {
|
|
468
|
+
/**
|
|
469
|
+
* Force physical deletion even when soft-delete is active, or force soft
|
|
470
|
+
* when the default would be hard. Adapters without soft-delete support
|
|
471
|
+
* MUST ignore this flag (it is a hint, not a contract).
|
|
472
|
+
*/
|
|
473
|
+
mode?: "hard" | "soft";
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Result of a single delete operation.
|
|
477
|
+
*
|
|
478
|
+
* Matches mongokit's shape. Adapters without soft-delete awareness can omit
|
|
479
|
+
* `soft` and `count`. Arc's BaseController uses the `success` flag to decide
|
|
480
|
+
* whether to return 200 or 404.
|
|
481
|
+
*/
|
|
482
|
+
interface DeleteResult {
|
|
483
|
+
success: boolean;
|
|
484
|
+
message: string;
|
|
485
|
+
/** Primary key of the removed doc (string form) */
|
|
486
|
+
id?: string;
|
|
487
|
+
/** True when a soft-delete plugin intercepted the operation */
|
|
488
|
+
soft?: boolean;
|
|
489
|
+
/** For batch-variant implementations that return the delete count inline */
|
|
490
|
+
count?: number;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Result of a batch delete (`deleteMany`) — distinct from single `delete`
|
|
494
|
+
* because MongoDB's driver returns a different shape for batch operations.
|
|
495
|
+
*
|
|
496
|
+
* **Soft-delete gotcha** — when a soft-delete plugin intercepts
|
|
497
|
+
* `deleteMany` by rewriting it to `updateMany` internally (mongokit 3.6
|
|
498
|
+
* does this in `before:deleteMany`), the `deletedCount` returned here may
|
|
499
|
+
* be `0` because the underlying `Model.deleteMany` was never called. The
|
|
500
|
+
* affected-row count lives inside the hook's `updateMany` result and is
|
|
501
|
+
* not surfaced to the caller. Consumers that need the exact soft-deleted
|
|
502
|
+
* count should run a follow-up query (`repo.count({ deletedAt: { $ne:
|
|
503
|
+
* null }, ...filter })`). 3rd-party kits with soft-delete should document
|
|
504
|
+
* which convention they follow.
|
|
505
|
+
*/
|
|
506
|
+
interface DeleteManyResult {
|
|
507
|
+
/** Driver-reported acknowledgement */
|
|
508
|
+
acknowledged?: boolean;
|
|
509
|
+
/**
|
|
510
|
+
* Number of documents removed. May be 0 when soft-delete intercepts;
|
|
511
|
+
* see the "Soft-delete gotcha" note above.
|
|
512
|
+
*/
|
|
513
|
+
deletedCount: number;
|
|
514
|
+
/** True when a soft-delete plugin intercepted and did `updateMany` instead */
|
|
515
|
+
soft?: boolean;
|
|
516
|
+
}
|
|
517
|
+
/** Result of a bulk update operation. Matches MongoDB driver shape. */
|
|
518
|
+
interface UpdateManyResult {
|
|
519
|
+
acknowledged?: boolean;
|
|
520
|
+
matchedCount: number;
|
|
521
|
+
modifiedCount: number;
|
|
522
|
+
upsertedCount?: number;
|
|
523
|
+
upsertedId?: unknown;
|
|
524
|
+
}
|
|
525
|
+
/** Shape of a single operation passed to `bulkWrite`. */
|
|
526
|
+
type BulkWriteOperation<TDoc = unknown> = {
|
|
527
|
+
insertOne: {
|
|
528
|
+
document: Partial<TDoc>;
|
|
529
|
+
};
|
|
530
|
+
} | {
|
|
531
|
+
updateOne: {
|
|
532
|
+
filter: Record<string, unknown>;
|
|
533
|
+
update: Record<string, unknown>;
|
|
534
|
+
upsert?: boolean;
|
|
535
|
+
};
|
|
536
|
+
} | {
|
|
537
|
+
updateMany: {
|
|
538
|
+
filter: Record<string, unknown>;
|
|
539
|
+
update: Record<string, unknown>;
|
|
540
|
+
upsert?: boolean;
|
|
541
|
+
};
|
|
542
|
+
} | {
|
|
543
|
+
deleteOne: {
|
|
544
|
+
filter: Record<string, unknown>;
|
|
545
|
+
};
|
|
546
|
+
} | {
|
|
547
|
+
deleteMany: {
|
|
548
|
+
filter: Record<string, unknown>;
|
|
549
|
+
};
|
|
550
|
+
} | {
|
|
551
|
+
replaceOne: {
|
|
552
|
+
filter: Record<string, unknown>;
|
|
553
|
+
replacement: Partial<TDoc>;
|
|
554
|
+
upsert?: boolean;
|
|
555
|
+
};
|
|
556
|
+
};
|
|
557
|
+
/** Result of a heterogeneous bulk write. */
|
|
558
|
+
interface BulkWriteResult {
|
|
559
|
+
ok?: number;
|
|
560
|
+
insertedCount?: number;
|
|
561
|
+
matchedCount?: number;
|
|
562
|
+
modifiedCount?: number;
|
|
563
|
+
deletedCount?: number;
|
|
564
|
+
upsertedCount?: number;
|
|
565
|
+
insertedIds?: Record<number, unknown>;
|
|
566
|
+
upsertedIds?: Record<number, unknown>;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Pagination parameters for list operations.
|
|
570
|
+
*
|
|
571
|
+
* Supports three modes, auto-detected by the adapter:
|
|
572
|
+
* - **Offset** — pass `page` + `limit`.
|
|
573
|
+
* - **Keyset** — pass `sort` + `limit` (+ optional `after` cursor). Required
|
|
574
|
+
* for infinite scroll on large collections; O(1) per page.
|
|
575
|
+
* - **Raw** — pass neither; adapter returns all matching docs.
|
|
351
576
|
*/
|
|
352
577
|
interface PaginationParams<TDoc = unknown> {
|
|
353
578
|
/** Filter criteria */
|
|
354
579
|
filters?: Partial<TDoc> & Record<string, unknown>;
|
|
355
|
-
/** Sort
|
|
580
|
+
/** Sort spec — string (`"-createdAt"`) or object (`{ createdAt: -1 }`) */
|
|
356
581
|
sort?: string | Record<string, 1 | -1>;
|
|
357
|
-
/** Page number (1-indexed) */
|
|
582
|
+
/** Page number (1-indexed) — triggers offset pagination */
|
|
358
583
|
page?: number;
|
|
359
584
|
/** Items per page */
|
|
360
585
|
limit?: number;
|
|
361
|
-
/**
|
|
586
|
+
/** Opaque cursor from a prior `next` field — triggers keyset pagination */
|
|
587
|
+
after?: string;
|
|
588
|
+
/** Allow additional options (select, populate, search, …) */
|
|
362
589
|
[key: string]: unknown;
|
|
363
590
|
}
|
|
364
591
|
/**
|
|
365
|
-
*
|
|
592
|
+
* Offset-based paginated result (the default shape when `page` is provided).
|
|
593
|
+
*
|
|
594
|
+
* `method` is optional so legacy adapters returning the bare `{ docs, page,
|
|
595
|
+
* limit, total, pages, hasNext, hasPrev }` shape still satisfy the type.
|
|
366
596
|
*/
|
|
367
|
-
interface
|
|
368
|
-
/**
|
|
597
|
+
interface OffsetPaginatedResult<TDoc> {
|
|
598
|
+
/** Discriminator — omitted or `"offset"` */
|
|
599
|
+
method?: "offset";
|
|
369
600
|
docs: TDoc[];
|
|
370
|
-
/** Current page number */
|
|
371
601
|
page: number;
|
|
372
|
-
/** Items per page */
|
|
373
602
|
limit: number;
|
|
374
|
-
/** Total document count */
|
|
375
603
|
total: number;
|
|
376
|
-
/** Total page count */
|
|
377
604
|
pages: number;
|
|
378
|
-
/** Has next page */
|
|
379
605
|
hasNext: boolean;
|
|
380
|
-
/** Has previous page */
|
|
381
606
|
hasPrev: boolean;
|
|
382
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* Keyset-based paginated result (returned when `sort` is provided without
|
|
610
|
+
* `page`). Ideal for infinite scroll — no `count()` query, O(1) per page.
|
|
611
|
+
*/
|
|
612
|
+
interface KeysetPaginatedResult<TDoc> {
|
|
613
|
+
/** Discriminator — always `"keyset"` */
|
|
614
|
+
method: "keyset";
|
|
615
|
+
docs: TDoc[];
|
|
616
|
+
limit: number;
|
|
617
|
+
hasMore: boolean;
|
|
618
|
+
/** Opaque cursor token for the next page, or `null` at the end */
|
|
619
|
+
next: string | null;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Discriminated union of all pagination result shapes.
|
|
623
|
+
* Consumers narrow on the `method` discriminator.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```ts
|
|
627
|
+
* const result = await repo.getAll(params);
|
|
628
|
+
* if (result.method === "keyset") {
|
|
629
|
+
* // result.next, result.hasMore
|
|
630
|
+
* } else {
|
|
631
|
+
* // result.page, result.total, result.pages
|
|
632
|
+
* }
|
|
633
|
+
* ```
|
|
634
|
+
*/
|
|
635
|
+
type PaginationResult<TDoc> = OffsetPaginatedResult<TDoc> | KeysetPaginatedResult<TDoc>;
|
|
636
|
+
/**
|
|
637
|
+
* Legacy alias. Existing code typed as `PaginatedResult<TDoc>` continues
|
|
638
|
+
* to work unchanged — it resolves to the offset shape, which is the most
|
|
639
|
+
* common. New code should prefer `PaginationResult<TDoc>` for the full
|
|
640
|
+
* discriminated union.
|
|
641
|
+
*/
|
|
642
|
+
type PaginatedResult<TDoc> = OffsetPaginatedResult<TDoc>;
|
|
383
643
|
/**
|
|
384
644
|
* Standard CRUD Repository Interface
|
|
385
645
|
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
646
|
+
* The canonical contract arc consumes. Tiered so minimal adapters only
|
|
647
|
+
* implement the required five methods; richer kits declare the optional
|
|
648
|
+
* capabilities they support.
|
|
649
|
+
*
|
|
650
|
+
* Every optional method is feature-detected at runtime by arc's
|
|
651
|
+
* BaseController and presets — implement only what your DB can express.
|
|
388
652
|
*
|
|
389
653
|
* @typeParam TDoc - The document/entity type
|
|
390
654
|
*/
|
|
391
655
|
interface CrudRepository<TDoc> {
|
|
392
656
|
/**
|
|
393
|
-
*
|
|
657
|
+
* Native primary key field. Defaults to `"_id"` (Mongo convention).
|
|
658
|
+
*
|
|
659
|
+
* Set to match `defineResource({ idField })` for kits that key on a
|
|
660
|
+
* custom field (e.g. `"id"`, `"uuid"`, `"slug"`). Arc's BaseController
|
|
661
|
+
* reads this to decide whether to pass route params straight through
|
|
662
|
+
* to `update`/`delete`/`restore` or to translate them via a fetched
|
|
663
|
+
* doc's `_id` first.
|
|
664
|
+
*/
|
|
665
|
+
readonly idField?: string;
|
|
666
|
+
/**
|
|
667
|
+
* List documents with pagination. Adapter auto-selects offset vs keyset
|
|
668
|
+
* mode based on the presence of `page` or `after` in `params`.
|
|
669
|
+
*
|
|
670
|
+
* Return shapes (all valid under the contract):
|
|
671
|
+
* - `OffsetPaginatedResult<TDoc>` — when `page` is given
|
|
672
|
+
* - `KeysetPaginatedResult<TDoc>` — when `sort` + optional `after` are given
|
|
673
|
+
* - `TDoc[]` — raw array, when neither `page` nor `sort` drives pagination
|
|
674
|
+
*
|
|
675
|
+
* Arc's BaseController narrows the union before returning to clients.
|
|
394
676
|
*/
|
|
395
|
-
getAll(params?: PaginationParams<TDoc>, options?: QueryOptions): Promise<
|
|
677
|
+
getAll(params?: PaginationParams<TDoc>, options?: QueryOptions): Promise<PaginationResult<TDoc> | TDoc[]>;
|
|
396
678
|
/**
|
|
397
|
-
*
|
|
679
|
+
* Fetch a single document by its primary key.
|
|
680
|
+
*
|
|
681
|
+
* **Miss semantics — kits may EITHER return `null` OR throw a 404-style
|
|
682
|
+
* error.** Arc's `BaseController` handles both: `AccessControl.fetchWith
|
|
683
|
+
* AccessControl` catches errors whose message contains "not found" and
|
|
684
|
+
* converts them to null. 3rd-party kit authors: pick one convention and
|
|
685
|
+
* document it. mongokit 3.6 throws by default; pass
|
|
686
|
+
* `{ throwOnNotFound: false }` to get null. A SQL kit that returns null
|
|
687
|
+
* directly is equally valid.
|
|
398
688
|
*/
|
|
399
689
|
getById(id: string, options?: QueryOptions): Promise<TDoc | null>;
|
|
690
|
+
/** Insert a single document. */
|
|
691
|
+
create(data: Partial<TDoc>, options?: WriteOptions): Promise<TDoc>;
|
|
692
|
+
/** Update a document by primary key. Returns the updated doc or null. */
|
|
693
|
+
update(id: string, data: Partial<TDoc>, options?: WriteOptions): Promise<TDoc | null>;
|
|
400
694
|
/**
|
|
401
|
-
*
|
|
695
|
+
* Delete a document by primary key. Pass `{ mode: 'hard' }` to bypass
|
|
696
|
+
* soft-delete interception.
|
|
402
697
|
*/
|
|
403
|
-
|
|
404
|
-
session?: unknown;
|
|
405
|
-
[key: string]: unknown;
|
|
406
|
-
}): Promise<TDoc>;
|
|
698
|
+
delete(id: string, options?: DeleteOptions): Promise<DeleteResult>;
|
|
407
699
|
/**
|
|
408
|
-
*
|
|
700
|
+
* Find a single doc by a compound filter. Used by arc's AccessControl to
|
|
701
|
+
* combine `idField + orgId + policy` in one query. Without it, arc falls
|
|
702
|
+
* back to `getById` + post-fetch scope checks (slower; 404s on custom
|
|
703
|
+
* idFields if the doc lives outside the user's scope).
|
|
704
|
+
*
|
|
705
|
+
* Miss semantics match `getById` — kits may return null or throw. Arc
|
|
706
|
+
* handles both. See the note on `getById` above.
|
|
409
707
|
*/
|
|
410
|
-
|
|
708
|
+
getOne?(filter: Record<string, unknown>, options?: QueryOptions): Promise<TDoc | null>;
|
|
709
|
+
/** Alias many kits expose alongside `getOne`. Arc checks both. */
|
|
710
|
+
getByQuery?(filter: Record<string, unknown>, options?: QueryOptions): Promise<TDoc | null>;
|
|
711
|
+
/** Count matching documents. Respects soft-delete when applicable. */
|
|
712
|
+
count?(filter?: Record<string, unknown>, options?: QueryOptions): Promise<number>;
|
|
411
713
|
/**
|
|
412
|
-
*
|
|
714
|
+
* Cheap existence check. Kits may return `boolean` or `{ _id }` — arc
|
|
715
|
+
* coerces to boolean at the call site.
|
|
413
716
|
*/
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
/**
|
|
717
|
+
exists?(filter: Record<string, unknown>, options?: QueryOptions): Promise<boolean | {
|
|
718
|
+
_id: unknown;
|
|
719
|
+
} | null>;
|
|
720
|
+
/** Return the distinct values of a field matching the filter. */
|
|
721
|
+
distinct?<T = unknown>(field: string, filter?: Record<string, unknown>, options?: QueryOptions): Promise<T[]>;
|
|
722
|
+
/** Return all matching docs as a raw array (no pagination metadata). */
|
|
723
|
+
findAll?(filter?: Record<string, unknown>, options?: QueryOptions): Promise<TDoc[]>;
|
|
724
|
+
/**
|
|
725
|
+
* Atomic "find or create" — return the doc matching the filter, or
|
|
726
|
+
* insert `data` and return it if none exists. MAY return `null` when
|
|
727
|
+
* neither path produces a document (e.g. race loss + validation error
|
|
728
|
+
* handling — mongokit returns null in this window).
|
|
729
|
+
*/
|
|
730
|
+
getOrCreate?(filter: Record<string, unknown>, data: Partial<TDoc>, options?: WriteOptions): Promise<TDoc | null>;
|
|
731
|
+
/** Insert multiple documents in one call. */
|
|
732
|
+
createMany?(items: Array<Partial<TDoc>>, options?: WriteOptions): Promise<TDoc[]>;
|
|
733
|
+
/**
|
|
734
|
+
* Update all documents matching `filter`. Should reject empty filters
|
|
735
|
+
* to prevent accidental mass updates (mongokit does this).
|
|
736
|
+
*/
|
|
737
|
+
updateMany?(filter: Record<string, unknown>, data: Record<string, unknown>, options?: WriteOptions): Promise<UpdateManyResult>;
|
|
738
|
+
/**
|
|
739
|
+
* Delete all documents matching `filter`. Soft-deletes when a soft-delete
|
|
740
|
+
* plugin is wired; pass `{ mode: 'hard' }` to force physical removal.
|
|
741
|
+
*/
|
|
742
|
+
deleteMany?(filter: Record<string, unknown>, options?: DeleteOptions): Promise<DeleteManyResult>;
|
|
743
|
+
/**
|
|
744
|
+
* Heterogeneous bulk write (insertOne / updateOne / deleteMany / …).
|
|
745
|
+
*
|
|
746
|
+
* Structurally typed as `unknown` because each kit uses its own operation
|
|
747
|
+
* shape — mongoose uses `AnyBulkWriteOperation[]`, prisma builds these
|
|
748
|
+
* from its client-extension API, pgkit uses SQL primitives. Arc does
|
|
749
|
+
* not call `bulkWrite` internally, so the exact shape is kit-specific.
|
|
750
|
+
* See `BulkWriteOperation<TDoc>` (exported from arc) for a reference
|
|
751
|
+
* shape you can use when implementing your own kit; mongokit-compatible
|
|
752
|
+
* callers should import its own operation types.
|
|
753
|
+
*/
|
|
754
|
+
bulkWrite?: unknown;
|
|
755
|
+
/** Restore a soft-deleted document. Should fire `before:restore` hooks. */
|
|
756
|
+
restore?(id: string, options?: QueryOptions): Promise<TDoc | null>;
|
|
757
|
+
/** Paginated list of soft-deleted documents. */
|
|
758
|
+
getDeleted?(params?: PaginationParams<TDoc>, options?: QueryOptions): Promise<PaginationResult<TDoc> | TDoc[]>;
|
|
759
|
+
/**
|
|
760
|
+
* Run an aggregation pipeline.
|
|
761
|
+
*
|
|
762
|
+
* Structurally typed as `unknown` because each kit uses a different
|
|
763
|
+
* stage type (mongoose's `PipelineStage`, prisma's client-extension
|
|
764
|
+
* builders, pgkit's query-builder primitives, …). Arc does not call
|
|
765
|
+
* `aggregate` internally — it's a capability consumers use directly on
|
|
766
|
+
* the repo. Cast or re-declare at the call site using your kit's types.
|
|
767
|
+
*/
|
|
768
|
+
aggregate?: unknown;
|
|
769
|
+
/**
|
|
770
|
+
* Paginated aggregation. Same kit-specificity reasoning as `aggregate`
|
|
771
|
+
* — structurally `unknown`, type-safe at the call site.
|
|
772
|
+
*/
|
|
773
|
+
aggregatePaginate?: unknown;
|
|
774
|
+
/**
|
|
775
|
+
* Run `callback` inside a transaction. Adapters should auto-retry on
|
|
776
|
+
* transient transaction errors and expose a `session` the callback can
|
|
777
|
+
* forward to subsequent repo calls.
|
|
778
|
+
*/
|
|
779
|
+
withTransaction?<T>(callback: (session: RepositorySession) => Promise<T>, options?: Record<string, unknown>): Promise<T>;
|
|
780
|
+
/** slugLookup preset — fetch by a business slug. */
|
|
781
|
+
getBySlug?(slug: string, options?: QueryOptions): Promise<TDoc | null>;
|
|
782
|
+
/** tree preset — return the full hierarchy. */
|
|
783
|
+
getTree?(options?: QueryOptions): Promise<TDoc[]>;
|
|
784
|
+
/** tree preset — return direct children of a node. */
|
|
785
|
+
getChildren?(parentId: string, options?: QueryOptions): Promise<TDoc[]>;
|
|
422
786
|
[key: string]: unknown;
|
|
423
787
|
}
|
|
424
788
|
/**
|
|
425
|
-
* Extract document type from a repository
|
|
789
|
+
* Extract document type from a repository.
|
|
426
790
|
*
|
|
427
791
|
* @example
|
|
428
|
-
* ```
|
|
792
|
+
* ```ts
|
|
429
793
|
* type UserDoc = InferDoc<typeof userRepository>;
|
|
430
|
-
* // UserDoc is now the document type of userRepository
|
|
431
794
|
* ```
|
|
432
795
|
*/
|
|
433
796
|
type InferDoc<R> = R extends CrudRepository<infer T> ? T : never;
|
|
@@ -465,7 +828,18 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
465
828
|
readonly customSchemas: CrudSchemas;
|
|
466
829
|
readonly permissions: ResourcePermissions;
|
|
467
830
|
readonly additionalRoutes: AdditionalRoute[];
|
|
831
|
+
/**
|
|
832
|
+
* Original v2.8 `routes` declaration — retained for downstream consumers
|
|
833
|
+
* (OpenAPI, MCP, registry, CLI introspect). Preserves fields dropped during
|
|
834
|
+
* normalization to `additionalRoutes` (notably `mcp`, `description`,
|
|
835
|
+
* `annotations`). Undefined when the resource was defined with the legacy
|
|
836
|
+
* `additionalRoutes` shape.
|
|
837
|
+
*
|
|
838
|
+
* Added in 2.8.1 — the source-of-truth fix for "canonical resource manifest".
|
|
839
|
+
*/
|
|
840
|
+
readonly routes?: readonly RouteDefinition[];
|
|
468
841
|
readonly middlewares: MiddlewareConfig;
|
|
842
|
+
readonly routeGuards?: RouteHandlerMethod$1[];
|
|
469
843
|
readonly disableDefaultRoutes: boolean;
|
|
470
844
|
readonly disabledRoutes: CrudRouteKey[];
|
|
471
845
|
readonly actions?: ActionsMap;
|
|
@@ -583,7 +957,7 @@ declare class ResourceRegistry {
|
|
|
583
957
|
/**
|
|
584
958
|
* Minimal server accessor — exposes safe, read-only server decorators.
|
|
585
959
|
* Allows controller handlers to publish events, log, and audit
|
|
586
|
-
* without switching to `
|
|
960
|
+
* without switching to `raw: true`.
|
|
587
961
|
*/
|
|
588
962
|
interface ServerAccessor {
|
|
589
963
|
/** Event bus — publish domain events from any handler */
|
|
@@ -696,7 +1070,7 @@ interface IRequestContext<TBody = unknown, TParams extends Record<string, string
|
|
|
696
1070
|
metadata?: TMetadata;
|
|
697
1071
|
/**
|
|
698
1072
|
* Fastify server accessor — publish events, log, and audit
|
|
699
|
-
* from any handler without switching to `
|
|
1073
|
+
* from any handler without switching to `raw: true`.
|
|
700
1074
|
*
|
|
701
1075
|
* @example
|
|
702
1076
|
* ```typescript
|
|
@@ -732,7 +1106,7 @@ interface IControllerResponse<T = unknown> {
|
|
|
732
1106
|
* Controller handler — Arc's standard pattern.
|
|
733
1107
|
*
|
|
734
1108
|
* Receives a request context object, returns IControllerResponse.
|
|
735
|
-
* Use with `
|
|
1109
|
+
* Use with `raw: false` in routes.
|
|
736
1110
|
*
|
|
737
1111
|
* **Generic parameters:**
|
|
738
1112
|
* - `TResponse` — shape of `IControllerResponse.data` (default: `unknown`)
|
|
@@ -764,12 +1138,12 @@ interface IControllerResponse<T = unknown> {
|
|
|
764
1138
|
* return { success: true, data: product };
|
|
765
1139
|
* };
|
|
766
1140
|
*
|
|
767
|
-
*
|
|
1141
|
+
* routes: [{
|
|
768
1142
|
* method: 'POST',
|
|
769
1143
|
* path: '/products',
|
|
770
1144
|
* handler: createProduct,
|
|
771
1145
|
* permissions: requireAuth(),
|
|
772
|
-
*
|
|
1146
|
+
* raw: false, // Arc wraps this into Fastify handler
|
|
773
1147
|
* }]
|
|
774
1148
|
* ```
|
|
775
1149
|
*/
|
|
@@ -778,7 +1152,7 @@ type ControllerHandler<TResponse = unknown, TBody = unknown, TParams extends Rec
|
|
|
778
1152
|
* Fastify native handler
|
|
779
1153
|
*
|
|
780
1154
|
* Standard Fastify request/reply pattern.
|
|
781
|
-
* Use with `
|
|
1155
|
+
* Use with `raw: true` in routes.
|
|
782
1156
|
*
|
|
783
1157
|
* @example
|
|
784
1158
|
* ```typescript
|
|
@@ -788,12 +1162,12 @@ type ControllerHandler<TResponse = unknown, TBody = unknown, TParams extends Rec
|
|
|
788
1162
|
* return reply.send(file.buffer);
|
|
789
1163
|
* };
|
|
790
1164
|
*
|
|
791
|
-
*
|
|
1165
|
+
* routes: [{
|
|
792
1166
|
* method: 'GET',
|
|
793
1167
|
* path: '/files/:id/download',
|
|
794
1168
|
* handler: downloadFile,
|
|
795
1169
|
* permissions: requireAuth(),
|
|
796
|
-
*
|
|
1170
|
+
* raw: true, // Use as-is, no wrapping
|
|
797
1171
|
* }]
|
|
798
1172
|
* ```
|
|
799
1173
|
*/
|
|
@@ -845,8 +1219,8 @@ interface AccessControlConfig {
|
|
|
845
1219
|
}
|
|
846
1220
|
/** Minimal repository interface for access-controlled fetch operations */
|
|
847
1221
|
interface AccessControlRepository {
|
|
848
|
-
getById(id: string, options?:
|
|
849
|
-
getOne?: (filter: AnyRecord, options?:
|
|
1222
|
+
getById(id: string, options?: QueryOptions): Promise<unknown>;
|
|
1223
|
+
getOne?: (filter: AnyRecord, options?: QueryOptions) => Promise<unknown>;
|
|
850
1224
|
}
|
|
851
1225
|
declare class AccessControl {
|
|
852
1226
|
private readonly tenantField;
|
|
@@ -890,7 +1264,7 @@ declare class AccessControl {
|
|
|
890
1264
|
* Replaces the duplicated pattern in get/update/delete:
|
|
891
1265
|
* buildIdFilter -> getOne (or getById + checkOrgScope + checkPolicyFilters)
|
|
892
1266
|
*/
|
|
893
|
-
fetchWithAccessControl<TDoc>(id: string, req: IRequestContext, repository: AccessControlRepository, queryOptions?:
|
|
1267
|
+
fetchWithAccessControl<TDoc>(id: string, req: IRequestContext, repository: AccessControlRepository, queryOptions?: QueryOptions): Promise<TDoc | null>;
|
|
894
1268
|
/**
|
|
895
1269
|
* Post-fetch access control validation for items fetched by non-ID queries
|
|
896
1270
|
* (e.g., getBySlug, restore). Applies org scope, policy filters, and
|
|
@@ -1123,7 +1497,7 @@ declare class BaseController<TDoc = AnyRecord, TRepository extends RepositoryLik
|
|
|
1123
1497
|
soft?: boolean;
|
|
1124
1498
|
}>>;
|
|
1125
1499
|
getBySlug(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
1126
|
-
getDeleted(req: IRequestContext): Promise<IControllerResponse<
|
|
1500
|
+
getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginationResult<TDoc>>>;
|
|
1127
1501
|
restore(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
|
|
1128
1502
|
getTree(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
1129
1503
|
getChildren(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
@@ -1223,7 +1597,7 @@ declare module 'fastify' {
|
|
|
1223
1597
|
/**
|
|
1224
1598
|
* Typed Fastify request with Arc decorations.
|
|
1225
1599
|
*
|
|
1226
|
-
* Use this in `
|
|
1600
|
+
* Use this in `raw: true` handlers instead of `(req as any).user`.
|
|
1227
1601
|
*
|
|
1228
1602
|
* @example
|
|
1229
1603
|
* ```typescript
|
|
@@ -1654,11 +2028,26 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
1654
2028
|
*/
|
|
1655
2029
|
fields?: FieldPermissionMap;
|
|
1656
2030
|
middlewares?: MiddlewareConfig;
|
|
1657
|
-
/** @deprecated Use `routes` instead. Will error in v3. */
|
|
1658
|
-
additionalRoutes?: AdditionalRoute[];
|
|
1659
2031
|
/**
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
2032
|
+
* PreHandler guards auto-applied to **every** route on this resource
|
|
2033
|
+
* (CRUD + custom `routes` + preset routes). Runs after auth/permissions,
|
|
2034
|
+
* before per-route `preHandler`. Use for mode gates, tenant checks,
|
|
2035
|
+
* feature flags — anything that applies to every endpoint.
|
|
2036
|
+
*
|
|
2037
|
+
* @example
|
|
2038
|
+
* ```typescript
|
|
2039
|
+
* defineResource({
|
|
2040
|
+
* routeGuards: [requireFlowMode('standard')],
|
|
2041
|
+
* routes: [
|
|
2042
|
+
* { method: 'GET', path: '/', raw: true, handler: listHandler },
|
|
2043
|
+
* // guard runs automatically — no per-route boilerplate
|
|
2044
|
+
* ],
|
|
2045
|
+
* });
|
|
2046
|
+
* ```
|
|
2047
|
+
*/
|
|
2048
|
+
routeGuards?: RouteHandlerMethod[];
|
|
2049
|
+
/**
|
|
2050
|
+
* Custom routes beyond CRUD. Presets also merge their routes here.
|
|
1662
2051
|
*
|
|
1663
2052
|
* @example
|
|
1664
2053
|
* ```typescript
|
|
@@ -1958,6 +2347,24 @@ interface AdditionalRoute {
|
|
|
1958
2347
|
}>;
|
|
1959
2348
|
isError?: boolean;
|
|
1960
2349
|
}>;
|
|
2350
|
+
/**
|
|
2351
|
+
* MCP tool generation config preserved from v2.8 `routes`.
|
|
2352
|
+
* - `false`: skip MCP tool generation for this route
|
|
2353
|
+
* - `true` / omitted: auto-generate when the route goes through Arc's pipeline
|
|
2354
|
+
* - object: explicit description/annotations overrides
|
|
2355
|
+
*
|
|
2356
|
+
* Added in 2.8.1 — previously dropped during `routes → additionalRoutes`
|
|
2357
|
+
* normalization, breaking MCP opt-out and per-route annotations.
|
|
2358
|
+
*/
|
|
2359
|
+
mcp?: boolean | {
|
|
2360
|
+
readonly description?: string;
|
|
2361
|
+
readonly annotations?: {
|
|
2362
|
+
readonly readOnlyHint?: boolean;
|
|
2363
|
+
readonly destructiveHint?: boolean;
|
|
2364
|
+
readonly idempotentHint?: boolean;
|
|
2365
|
+
readonly openWorldHint?: boolean;
|
|
2366
|
+
};
|
|
2367
|
+
};
|
|
1961
2368
|
}
|
|
1962
2369
|
/** HTTP methods for custom routes */
|
|
1963
2370
|
type RouteMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
@@ -2075,9 +2482,17 @@ interface RouteSchemaOptions {
|
|
|
2075
2482
|
filterableFields?: string[];
|
|
2076
2483
|
fieldRules?: Record<string, {
|
|
2077
2484
|
systemManaged?: boolean;
|
|
2485
|
+
hidden?: boolean;
|
|
2078
2486
|
immutable?: boolean;
|
|
2079
2487
|
immutableAfterCreate?: boolean;
|
|
2080
|
-
optional?: boolean;
|
|
2488
|
+
optional?: boolean; /** String minimum length — auto-maps to OpenAPI `minLength` and MCP tool schema */
|
|
2489
|
+
minLength?: number; /** String maximum length — auto-maps to OpenAPI `maxLength` and MCP tool schema */
|
|
2490
|
+
maxLength?: number; /** Number minimum — auto-maps to OpenAPI `minimum` and MCP tool schema */
|
|
2491
|
+
min?: number; /** Number maximum — auto-maps to OpenAPI `maximum` and MCP tool schema */
|
|
2492
|
+
max?: number; /** Regex pattern — auto-maps to OpenAPI `pattern` and MCP tool schema */
|
|
2493
|
+
pattern?: string; /** Allowed values — auto-maps to OpenAPI `enum` and MCP tool schema */
|
|
2494
|
+
enum?: ReadonlyArray<string | number>; /** Human-readable description — auto-maps to OpenAPI `description` */
|
|
2495
|
+
description?: string;
|
|
2081
2496
|
[key: string]: unknown;
|
|
2082
2497
|
}>;
|
|
2083
2498
|
query?: Record<string, unknown>;
|
|
@@ -2298,7 +2713,8 @@ interface PresetHook {
|
|
|
2298
2713
|
}
|
|
2299
2714
|
interface PresetResult {
|
|
2300
2715
|
name: string;
|
|
2301
|
-
|
|
2716
|
+
/** Preset routes — merged into the resource's `routes` array. */
|
|
2717
|
+
routes?: RouteDefinition[] | ((permissions: ResourcePermissions) => RouteDefinition[]);
|
|
2302
2718
|
middlewares?: MiddlewareConfig;
|
|
2303
2719
|
schemaOptions?: RouteSchemaOptions;
|
|
2304
2720
|
controllerOptions?: Record<string, unknown>;
|
|
@@ -2493,6 +2909,8 @@ interface CrudRouterOptions {
|
|
|
2493
2909
|
* Set to `false` to disable rate limiting for this resource.
|
|
2494
2910
|
*/
|
|
2495
2911
|
rateLimit?: RateLimitConfig | false;
|
|
2912
|
+
/** PreHandler guards applied to every route (CRUD + custom + preset). */
|
|
2913
|
+
routeGuards?: RouteHandlerMethod[];
|
|
2496
2914
|
}
|
|
2497
2915
|
interface ResourceMetadata {
|
|
2498
2916
|
name: string;
|
|
@@ -2502,7 +2920,17 @@ interface ResourceMetadata {
|
|
|
2502
2920
|
module?: string;
|
|
2503
2921
|
permissions?: ResourcePermissions;
|
|
2504
2922
|
presets: string[];
|
|
2505
|
-
|
|
2923
|
+
customRoutes?: Array<{
|
|
2924
|
+
method: string;
|
|
2925
|
+
path: string;
|
|
2926
|
+
handler: string;
|
|
2927
|
+
operation?: string;
|
|
2928
|
+
summary?: string;
|
|
2929
|
+
description?: string;
|
|
2930
|
+
permissions?: PermissionCheck;
|
|
2931
|
+
raw?: boolean;
|
|
2932
|
+
schema?: Record<string, unknown>;
|
|
2933
|
+
}>;
|
|
2506
2934
|
routes: Array<{
|
|
2507
2935
|
method: string;
|
|
2508
2936
|
path: string;
|
|
@@ -2544,6 +2972,33 @@ interface RegistryEntry extends ResourceMetadata {
|
|
|
2544
2972
|
audit?: boolean | {
|
|
2545
2973
|
operations?: ("create" | "update" | "delete")[];
|
|
2546
2974
|
};
|
|
2975
|
+
/**
|
|
2976
|
+
* v2.8 declarative actions metadata — populated from `ResourceConfig.actions`.
|
|
2977
|
+
*
|
|
2978
|
+
* Consumed by OpenAPI generation (renders `POST /:id/action` with a
|
|
2979
|
+
* discriminated body schema) and MCP tool generation.
|
|
2980
|
+
*
|
|
2981
|
+
* Added in 2.8.1.
|
|
2982
|
+
*/
|
|
2983
|
+
actions?: Array<{
|
|
2984
|
+
readonly name: string;
|
|
2985
|
+
readonly description?: string; /** Raw per-action schema (JSON Schema, Zod v4, or legacy field map) */
|
|
2986
|
+
readonly schema?: Record<string, unknown>; /** Per-action permission check (if different from resource-level `actionPermissions`) */
|
|
2987
|
+
readonly permissions?: PermissionCheck; /** MCP tool generation flag — `false` to skip, object for overrides */
|
|
2988
|
+
readonly mcp?: boolean | {
|
|
2989
|
+
readonly description?: string;
|
|
2990
|
+
readonly annotations?: Record<string, unknown>;
|
|
2991
|
+
};
|
|
2992
|
+
}>;
|
|
2993
|
+
/**
|
|
2994
|
+
* Resource-level fallback permission for actions without per-action
|
|
2995
|
+
* permissions. Used by OpenAPI to determine auth requirements and by MCP
|
|
2996
|
+
* as the fallback in `createActionToolHandler`.
|
|
2997
|
+
*
|
|
2998
|
+
* Added in 2.8.1 — previously not surfaced to downstream consumers,
|
|
2999
|
+
* causing OpenAPI to mark action endpoints as public when runtime required auth.
|
|
3000
|
+
*/
|
|
3001
|
+
actionPermissions?: PermissionCheck;
|
|
2547
3002
|
}
|
|
2548
3003
|
interface RegistryStats {
|
|
2549
3004
|
total?: number;
|
|
@@ -2601,68 +3056,84 @@ type TypedRepository<TDoc> = CrudRepository<TDoc>;
|
|
|
2601
3056
|
//#endregion
|
|
2602
3057
|
//#region src/adapters/interface.d.ts
|
|
2603
3058
|
/**
|
|
2604
|
-
* Minimal repository
|
|
2605
|
-
* Any repository with these method signatures is accepted — no `as any` needed.
|
|
3059
|
+
* Minimal structural repository shape for flexible adapter compatibility.
|
|
2606
3060
|
*
|
|
2607
|
-
*
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
*
|
|
2611
|
-
* Any repository with these method signatures is accepted.
|
|
3061
|
+
* `RepositoryLike` is the **loose** variant of `CrudRepository<TDoc>` — it
|
|
3062
|
+
* uses `unknown` for document payloads so any object with the right method
|
|
3063
|
+
* names satisfies it without type assertions. Prefer `CrudRepository<TDoc>`
|
|
3064
|
+
* for kits you own; use `RepositoryLike` when wrapping third-party repos.
|
|
2612
3065
|
*
|
|
2613
|
-
*
|
|
2614
|
-
* getAll, getById, create, update, delete
|
|
3066
|
+
* Both interfaces declare the same tiered capabilities:
|
|
2615
3067
|
*
|
|
2616
|
-
* **
|
|
2617
|
-
*
|
|
3068
|
+
* - **Required** — `getAll`, `getById`, `create`, `update`, `delete`
|
|
3069
|
+
* - **Recommended** — `getOne` / `getByQuery` (used by AccessControl for
|
|
3070
|
+
* compound filters like `idField + orgId + policy`)
|
|
3071
|
+
* - **Optional** — feature-detected at runtime by presets and the
|
|
3072
|
+
* BaseController. Declare only what your DB supports.
|
|
2618
3073
|
*
|
|
2619
|
-
*
|
|
2620
|
-
*
|
|
2621
|
-
* getDeleted — softDelete preset (list soft-deleted)
|
|
2622
|
-
* restore — softDelete preset (restore soft-deleted)
|
|
2623
|
-
* getTree — tree preset (hierarchical queries)
|
|
2624
|
-
* getChildren — tree preset (child nodes)
|
|
2625
|
-
* createMany — bulk preset (batch create)
|
|
2626
|
-
* updateMany — bulk preset (batch update by filter)
|
|
2627
|
-
* deleteMany — bulk preset (batch delete by filter)
|
|
3074
|
+
* See [CrudRepository](../types/repository.ts) for full prose-level docs
|
|
3075
|
+
* on each method and the design rationale behind the tiering.
|
|
2628
3076
|
*/
|
|
2629
3077
|
interface RepositoryLike {
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
update(id: string, data: unknown, options?: unknown): Promise<unknown>;
|
|
2634
|
-
delete(id: string, options?: unknown): Promise<unknown>;
|
|
2635
|
-
/**
|
|
2636
|
-
* The repository's native primary key field. When set, Arc's BaseController
|
|
2637
|
-
* will pass route params through to `update()`/`delete()`/`restore()` calls
|
|
3078
|
+
/**
|
|
3079
|
+
* The repository's native primary key field. When set, arc's BaseController
|
|
3080
|
+
* passes route params through to `update()`/`delete()`/`restore()` calls
|
|
2638
3081
|
* unchanged instead of translating them to `_id`.
|
|
2639
3082
|
*
|
|
2640
|
-
*
|
|
2641
|
-
* natively look up by a custom field (e.g.
|
|
2642
|
-
* `new Repository(Model, [], {}, { idField: 'id' })`). Without it,
|
|
2643
|
-
* try to translate route ids → fetched doc's `_id
|
|
2644
|
-
* don't key on `_id`.
|
|
3083
|
+
* Match this to your `defineResource({ idField })` for repositories that
|
|
3084
|
+
* natively look up by a custom field (e.g. mongokit's
|
|
3085
|
+
* `new Repository(Model, [], {}, { idField: 'id' })`). Without it, arc
|
|
3086
|
+
* will try to translate route ids → fetched doc's `_id`, which 404s on
|
|
3087
|
+
* repos that don't key on `_id`.
|
|
2645
3088
|
*
|
|
2646
|
-
* Defaults to `'_id'` (Mongo).
|
|
3089
|
+
* Defaults to `'_id'` (Mongo). Kits that always use `_id` may omit it.
|
|
2647
3090
|
*/
|
|
2648
3091
|
readonly idField?: string;
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
3092
|
+
getAll(params?: PaginationParams, options?: QueryOptions): Promise<unknown>;
|
|
3093
|
+
getById(id: string, options?: QueryOptions): Promise<unknown>;
|
|
3094
|
+
create(data: unknown, options?: WriteOptions): Promise<unknown>;
|
|
3095
|
+
update(id: string, data: unknown, options?: WriteOptions): Promise<unknown>;
|
|
3096
|
+
/**
|
|
3097
|
+
* Delete by primary key. Pass `{ mode: 'hard' }` to bypass soft-delete
|
|
3098
|
+
* interception (required by arc's hard-delete flow — `?hard=true` on
|
|
3099
|
+
* the DELETE route forwards this option).
|
|
3100
|
+
*/
|
|
3101
|
+
delete(id: string, options?: DeleteOptions): Promise<unknown>;
|
|
3102
|
+
/**
|
|
3103
|
+
* Find a single doc by compound filter. Used by AccessControl for
|
|
3104
|
+
* `idField + org + policy` scoping. Without this, arc falls back to
|
|
3105
|
+
* `getById` + post-fetch security checks (slower, and 404s on custom
|
|
3106
|
+
* idFields that live outside the user's scope).
|
|
3107
|
+
*/
|
|
3108
|
+
getOne?(filter: Record<string, unknown>, options?: QueryOptions): Promise<unknown>;
|
|
3109
|
+
/** Alias many kits expose alongside `getOne`. Arc checks both. */
|
|
3110
|
+
getByQuery?(filter: Record<string, unknown>, options?: QueryOptions): Promise<unknown>;
|
|
3111
|
+
count?(filter?: Record<string, unknown>, options?: QueryOptions): Promise<number>;
|
|
3112
|
+
exists?(filter: Record<string, unknown>, options?: QueryOptions): Promise<boolean | {
|
|
3113
|
+
_id: unknown;
|
|
3114
|
+
} | null>;
|
|
3115
|
+
distinct?<T = unknown>(field: string, filter?: Record<string, unknown>, options?: QueryOptions): Promise<T[]>;
|
|
3116
|
+
findAll?(filter?: Record<string, unknown>, options?: QueryOptions): Promise<unknown[]>;
|
|
3117
|
+
getOrCreate?(filter: Record<string, unknown>, data: unknown, options?: WriteOptions): Promise<unknown>;
|
|
3118
|
+
createMany?(items: unknown[], options?: WriteOptions): Promise<unknown[]>;
|
|
3119
|
+
updateMany?(filter: Record<string, unknown>, data: Record<string, unknown>, options?: WriteOptions): Promise<UpdateManyResult>;
|
|
3120
|
+
deleteMany?(filter: Record<string, unknown>, options?: DeleteOptions): Promise<DeleteManyResult>;
|
|
3121
|
+
bulkWrite?: unknown;
|
|
3122
|
+
restore?(id: string, options?: QueryOptions): Promise<unknown>;
|
|
3123
|
+
getDeleted?(params?: PaginationParams, options?: QueryOptions): Promise<PaginationResult<unknown> | unknown[]>;
|
|
3124
|
+
aggregate?: unknown;
|
|
3125
|
+
aggregatePaginate?: unknown;
|
|
3126
|
+
withTransaction?<T>(callback: (session: RepositorySession) => Promise<T>, options?: Record<string, unknown>): Promise<T>;
|
|
3127
|
+
getBySlug?(slug: string, options?: QueryOptions): Promise<unknown>;
|
|
3128
|
+
getTree?(options?: QueryOptions): Promise<unknown>;
|
|
3129
|
+
getChildren?(parentId: string, options?: QueryOptions): Promise<unknown>;
|
|
2660
3130
|
[key: string]: unknown;
|
|
2661
3131
|
}
|
|
2662
3132
|
interface DataAdapter<TDoc = unknown> {
|
|
2663
3133
|
/**
|
|
2664
|
-
* Repository implementing CRUD operations
|
|
2665
|
-
*
|
|
3134
|
+
* Repository implementing CRUD operations. Accepts the typed
|
|
3135
|
+
* `CrudRepository<TDoc>` or the loose `RepositoryLike` — arc checks
|
|
3136
|
+
* capabilities at runtime via feature detection.
|
|
2666
3137
|
*/
|
|
2667
3138
|
repository: CrudRepository<TDoc> | RepositoryLike;
|
|
2668
3139
|
/** Adapter identifier for introspection */
|
|
@@ -2753,4 +3224,4 @@ interface ValidationResult {
|
|
|
2753
3224
|
}
|
|
2754
3225
|
type AdapterFactory<TDoc> = (config: unknown) => DataAdapter<TDoc>;
|
|
2755
3226
|
//#endregion
|
|
2756
|
-
export { PresetFunction as $,
|
|
3227
|
+
export { PresetFunction as $, DeleteOptions as $t, EventsDecorator as A, beforeCreate as An, BaseController as At, InferResourceDoc as B, FastifyHandler as Bt, ConfigError as C, HookPhase as Cn, TypedResourceConfig as Ct, CrudRouterOptions as D, afterCreate as Dn, ValidationResult$1 as Dt, CrudRouteKey as E, HookSystemOptions as En, ValidateOptions as Et, GracefulShutdownOptions as F, BodySanitizerConfig as Ft, LookupOption as G, RegisterOptions as Gt, IntrospectionPluginOptions as H, IControllerResponse as Ht, HealthCheck as I, AccessControl as It, ObjectId as J, defineResource as Jt, MiddlewareConfig as K, ResourceRegistry as Kt, HealthOptions as L, AccessControlConfig as Lt, FastifyWithAuth as M, beforeUpdate as Mn, QueryResolver as Mt, FastifyWithDecorators as N, createHookSystem as Nn, QueryResolverConfig as Nt, CrudSchemas as O, afterDelete as On, envelope as Ot, FieldRule as P, defineHook as Pn, BodySanitizer as Pt, PopulateOption as Q, DeleteManyResult as Qt, InferAdapterDoc as R, ControllerHandler as Rt, AuthenticatorContext as S, HookOperation as Sn, TypedRepository as St, CrudController as T, HookSystem as Tn, UserOrganization as Tt, JWTPayload as U, IRequestContext as Ut, IntrospectionData as V, IController as Vt, JwtContext as W, RouteHandler as Wt, OwnershipCheck as X, BulkWriteResult as Xt, OpenApiSchemas as Y, BulkWriteOperation as Yt, ParsedQuery as Z, CrudRepository as Zt, ArcInternalMetadata as _, PipelineStep as _n, RouteMcpConfig as _t, RelationMetadata as a, PaginationParams as an, RegistryStats as at, AuthPluginOptions as b, HookContext as bn, TokenPair as bt, ValidationResult as c, RepositorySession as cn, RequestWithExtras as ct, ActionHandlerFn as d, Guard as dn, ResourceHookContext as dt, DeleteResult as en, PresetHook as et, ActionsMap as f, Interceptor as fn, ResourceHooks as ft, ArcDecorator as g, PipelineContext as gn, RouteHandlerMethod$1 as gt, ApiResponse as h, PipelineConfig as hn, RouteDefinition as ht, FieldMetadata as i, PaginatedResult as in, RegistryEntry as it, FastifyRequestExtras as j, beforeDelete as jn, BaseControllerOptions as jt, EventDefinition as k, afterUpdate as kn, getUserId as kt, ActionDefinition as l, UpdateManyResult as ln, ResourceCacheConfig as lt, AnyRecord as m, OperationFilter as mn, ResourcePermissions as mt, AdapterSchemaContext as n, KeysetPaginatedResult as nn, QueryParserInterface as nt, RepositoryLike as o, PaginationResult as on, RequestContext as ot, AdditionalRoute as p, NextFunction as pn, ResourceMetadata as pt, MiddlewareHandler as q, ResourceDefinition as qt, DataAdapter as r, OffsetPaginatedResult as rn, RateLimitConfig as rt, SchemaMetadata as s, QueryOptions as sn, RequestIdOptions as st, AdapterFactory as t, InferDoc as tn, PresetResult as tt, ActionEntry as u, WriteOptions as un, ResourceConfig as ut, ArcRequest as v, Transform as vn, RouteSchemaOptions as vt, ControllerQueryOptions as w, HookRegistration as wn, UserLike as wt, Authenticator as x, HookHandler as xn, TypedController as xt, AuthHelpers as y, DefineHookOptions as yn, ServiceContext as yt, InferDocType as z, ControllerLike as zt };
|