@classytic/arc 2.15.4 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +1 -0
  2. package/bin/arc.js +12 -0
  3. package/dist/{BaseController-dx3m2J8V.mjs → BaseController-DlCCTIxJ.mjs} +61 -19
  4. package/dist/{HookSystem-Iiebom92.mjs → HookSystem-Cmf7-Etp.mjs} +8 -4
  5. package/dist/{QueryCache-D41bfdBB.d.mts → QueryCache-SvmT_9ti.d.mts} +1 -1
  6. package/dist/{ResourceRegistry-CTERg_2x.mjs → ResourceRegistry-f48hFk3m.mjs} +52 -9
  7. package/dist/audit/index.d.mts +1 -1
  8. package/dist/audit/index.mjs +4 -2
  9. package/dist/auth/index.d.mts +4 -4
  10. package/dist/auth/index.mjs +4 -4
  11. package/dist/auth/redis-session.d.mts +1 -1
  12. package/dist/{betterAuthOpenApi--M_i87dQ.mjs → betterAuthOpenApi-ClWxaceA.mjs} +10 -6
  13. package/dist/buildHandler-BZX6zzDM.mjs +300 -0
  14. package/dist/cache/index.d.mts +3 -3
  15. package/dist/cache/index.mjs +3 -3
  16. package/dist/{caching-SM8gghN6.mjs → caching-TeHE8G-v.mjs} +1 -1
  17. package/dist/cli/commands/describe.d.mts +35 -1
  18. package/dist/cli/commands/describe.mjs +52 -12
  19. package/dist/cli/commands/docs.d.mts +1 -4
  20. package/dist/cli/commands/docs.mjs +4 -16
  21. package/dist/cli/commands/generate.d.mts +2 -20
  22. package/dist/cli/commands/generate.mjs +1 -546
  23. package/dist/cli/commands/init.d.mts +2 -40
  24. package/dist/cli/commands/init.mjs +1 -3045
  25. package/dist/cli/commands/introspect.mjs +53 -64
  26. package/dist/cli/index.d.mts +2 -2
  27. package/dist/cli/index.mjs +2 -2
  28. package/dist/{constants-Cxde4rpC.mjs → constants-TrJVIJl0.mjs} +7 -0
  29. package/dist/core/index.d.mts +3 -3
  30. package/dist/core/index.mjs +5 -5
  31. package/dist/{core-CvmOqEms.mjs → core-DBJ_j6rX.mjs} +222 -44
  32. package/dist/createActionRouter-DUpN3Dd1.mjs +288 -0
  33. package/dist/{createAggregationRouter-B0bPDf5b.mjs → createAggregationRouter-Dq-TUCuY.mjs} +3 -2
  34. package/dist/{createApp-PFegs47-.mjs → createApp-DNccuhyI.mjs} +16 -14
  35. package/dist/{defineEvent-D5h7EvAx.mjs → defineEvent-DRwY0fYm.mjs} +1 -1
  36. package/dist/docs/index.d.mts +2 -2
  37. package/dist/docs/index.mjs +1 -1
  38. package/dist/{errorHandler-Bk-AGhkU.mjs → errorHandler-DpoXQHZ9.mjs} +17 -14
  39. package/dist/errors-C1lX_jlm.d.mts +91 -0
  40. package/dist/{eventPlugin-CaKTYkYM.mjs → eventPlugin-C2cGqtRO.mjs} +1 -1
  41. package/dist/{eventPlugin-qXpqTebY.d.mts → eventPlugin-CtHC_av1.d.mts} +1 -1
  42. package/dist/events/index.d.mts +3 -3
  43. package/dist/events/index.mjs +5 -5
  44. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  45. package/dist/events/transports/redis.d.mts +1 -1
  46. package/dist/factory/index.d.mts +1 -1
  47. package/dist/factory/index.mjs +2 -2
  48. package/dist/{fields-COhcH3fk.d.mts → fields-Anj0xdih.d.mts} +1 -1
  49. package/dist/generate-BWFwgcCM.d.mts +38 -0
  50. package/dist/generate-CYac-OLv.mjs +654 -0
  51. package/dist/hooks/index.d.mts +1 -1
  52. package/dist/hooks/index.mjs +1 -1
  53. package/dist/idempotency/index.d.mts +2 -2
  54. package/dist/idempotency/index.mjs +1 -1
  55. package/dist/idempotency/redis.d.mts +1 -1
  56. package/dist/{index-BTqLEvhu.d.mts → index-3oIimXQn.d.mts} +12 -12
  57. package/dist/{index-BstGxcc3.d.mts → index-B-ulKx5P.d.mts} +55 -4
  58. package/dist/{index-BswOSJCE.d.mts → index-CkW0flkU.d.mts} +355 -16
  59. package/dist/index.d.mts +6 -6
  60. package/dist/index.mjs +7 -8
  61. package/dist/init-Dv71MsJr.d.mts +71 -0
  62. package/dist/init-HDvoO9L5.mjs +3098 -0
  63. package/dist/integrations/event-gateway.d.mts +2 -2
  64. package/dist/integrations/event-gateway.mjs +1 -1
  65. package/dist/integrations/index.d.mts +2 -2
  66. package/dist/integrations/jobs.mjs +3 -3
  67. package/dist/integrations/mcp/index.d.mts +239 -7
  68. package/dist/integrations/mcp/index.mjs +2 -528
  69. package/dist/integrations/mcp/testing.d.mts +2 -2
  70. package/dist/integrations/mcp/testing.mjs +6 -10
  71. package/dist/integrations/streamline.mjs +26 -1
  72. package/dist/integrations/websocket-redis.d.mts +1 -1
  73. package/dist/integrations/websocket.d.mts +1 -1
  74. package/dist/integrations/websocket.mjs +1 -0
  75. package/dist/loadResourcesFromEntry-BLMEI2Xa.mjs +51 -0
  76. package/dist/{resourceToTools-tFYUNmM0.mjs → mcpPlugin-7vGV51ED.mjs} +1021 -318
  77. package/dist/{memory-UBydS5ku.mjs → memory-QOLe11D5.mjs} +2 -0
  78. package/dist/middleware/index.d.mts +1 -1
  79. package/dist/middleware/index.mjs +1 -1
  80. package/dist/{openapi-BHXhoX8O.mjs → openapi-34T9yNwd.mjs} +47 -36
  81. package/dist/permissions/index.d.mts +2 -2
  82. package/dist/permissions/index.mjs +1 -1
  83. package/dist/{permissions-ohQyv50e.mjs → permissions-CTxMrreC.mjs} +2 -2
  84. package/dist/{pipe-Zr0KXjQe.mjs → pipe-DiCyvyPN.mjs} +1 -0
  85. package/dist/pipeline/index.d.mts +1 -1
  86. package/dist/pipeline/index.mjs +1 -1
  87. package/dist/plugins/index.d.mts +5 -5
  88. package/dist/plugins/index.mjs +10 -10
  89. package/dist/plugins/response-cache.mjs +5 -5
  90. package/dist/plugins/tracing-entry.d.mts +1 -1
  91. package/dist/plugins/tracing-entry.mjs +1 -1
  92. package/dist/{pluralize-DQgqgifU.mjs → pluralize-B9M8xvy-.mjs} +2 -1
  93. package/dist/presets/filesUpload.d.mts +4 -4
  94. package/dist/presets/filesUpload.mjs +2 -2
  95. package/dist/presets/index.d.mts +1 -1
  96. package/dist/presets/index.mjs +1 -1
  97. package/dist/presets/multiTenant.d.mts +1 -1
  98. package/dist/presets/multiTenant.mjs +4 -3
  99. package/dist/presets/search.d.mts +2 -2
  100. package/dist/presets/search.mjs +1 -1
  101. package/dist/{presets-BbkjdPeH.mjs → presets-C9BE6WaZ.mjs} +2 -2
  102. package/dist/{queryCachePlugin-m1XsgAIJ.mjs → queryCachePlugin-B4XMSSe7.mjs} +2 -2
  103. package/dist/{queryCachePlugin-CqMdLI2-.d.mts → queryCachePlugin-Biqzfbi5.d.mts} +2 -2
  104. package/dist/{redis-DiMkdHEl.d.mts → redis-Cyzrz6SX.d.mts} +1 -1
  105. package/dist/{redis-stream-D6HzR1Z_.d.mts → redis-stream-DT-YjzrB.d.mts} +1 -1
  106. package/dist/registry/index.d.mts +319 -2
  107. package/dist/registry/index.mjs +3 -3
  108. package/dist/registry-BBE23CDj.mjs +576 -0
  109. package/dist/{routerShared-DrOa-26E.mjs → routerShared-CZV5aabX.mjs} +3 -3
  110. package/dist/scope/index.d.mts +3 -3
  111. package/dist/scope/index.mjs +3 -3
  112. package/dist/{sse-Bz-5ZeTt.mjs → sse-BY6sTy4P.mjs} +1 -1
  113. package/dist/testing/index.d.mts +2 -2
  114. package/dist/testing/index.mjs +16 -7
  115. package/dist/testing/storageContract.d.mts +1 -1
  116. package/dist/types/index.d.mts +5 -5
  117. package/dist/types/storage.d.mts +1 -1
  118. package/dist/{types-C_s5moIu.mjs → types-Bi0r0vjG.mjs} +53 -1
  119. package/dist/{types-BQsjgQzS.d.mts → types-BsJMEQ4D.d.mts} +106 -12
  120. package/dist/{types-DrBaUwyV.d.mts → types-D-fYtKjb.d.mts} +33 -10
  121. package/dist/{types-CTYvcwHe.d.mts → types-DVfpSfx2.d.mts} +42 -1
  122. package/dist/utils/index.d.mts +1286 -2
  123. package/dist/utils/index.mjs +1 -1
  124. package/dist/{utils-_h9B3c57.mjs → utils-DC5ycPfr.mjs} +89 -40
  125. package/dist/{buildHandler-CcFOpJLh.mjs → validate-By96rH0r.mjs} +8 -299
  126. package/dist/{versioning-hmkPcDlX.d.mts → versioning-ZwX9tmbS.d.mts} +1 -1
  127. package/package.json +21 -28
  128. package/skills/arc/SKILL.md +300 -706
  129. package/skills/arc/references/auth.md +19 -7
  130. package/skills/arc-code-review/SKILL.md +1 -1
  131. package/skills/arc-code-review/references/arc-cheatsheet.md +100 -322
  132. package/dist/createActionRouter-S3MLVYot.mjs +0 -220
  133. package/dist/index-bRjYu21O.d.mts +0 -1320
  134. package/dist/org/index.d.mts +0 -66
  135. package/dist/org/index.mjs +0 -486
  136. package/dist/org/types.d.mts +0 -82
  137. package/dist/org/types.mjs +0 -1
  138. package/dist/registry-I-ogLgL9.mjs +0 -46
  139. /package/dist/{EventTransport-CT_52aWU.d.mts → EventTransport-C-2oAHtw.d.mts} +0 -0
  140. /package/dist/{EventTransport-DLWoUMHy.mjs → EventTransport-Hxvv5QQz.mjs} +0 -0
  141. /package/dist/{actionPermissions-CyUkQu6O.mjs → actionPermissions-Bjmvn7Eb.mjs} +0 -0
  142. /package/dist/{elevation-BXOWoGCF.d.mts → elevation-0YBpa663.d.mts} +0 -0
  143. /package/dist/{elevation-DgoeTyfX.mjs → elevation-Dci0AYLT.mjs} +0 -0
  144. /package/dist/{errorHandler-DFr45ZG4.d.mts → errorHandler-mHuyWzZE.d.mts} +0 -0
  145. /package/dist/{externalPaths-BD5nw6St.d.mts → externalPaths-DFg-2KTp.d.mts} +0 -0
  146. /package/dist/{interface-beEtJyWM.d.mts → interface-CH0OQudo.d.mts} +0 -0
  147. /package/dist/{interface-DfLGcus7.d.mts → interface-NwJ_qPlY.d.mts} +0 -0
  148. /package/dist/{keys-CGcCbNyu.mjs → keys-DopsCuyQ.mjs} +0 -0
  149. /package/dist/{loadResources-DBMQg_Aj.mjs → loadResources-ChQEj8ih.mjs} +0 -0
  150. /package/dist/{metrics-Qnvwc-LQ.mjs → metrics-TuOmguhi.mjs} +0 -0
  151. /package/dist/{replyHelpers-CK-FNO8E.mjs → replyHelpers-C-gD32oF.mjs} +0 -0
  152. /package/dist/{schemaIR-lYhC2gE5.mjs → schemaIR-Ctc89DSn.mjs} +0 -0
  153. /package/dist/{sessionManager-C4Le_UB3.d.mts → sessionManager-BqFegc0W.d.mts} +0 -0
  154. /package/dist/{storage-Dfzt4VTl.d.mts → storage-D2KZJAmn.d.mts} +0 -0
  155. /package/dist/{store-helpers-BkIN9-vu.mjs → store-helpers-B0sunfZZ.mjs} +0 -0
  156. /package/dist/{tracing-QJVprktp.d.mts → tracing-Dm8n7Cnn.d.mts} +0 -0
  157. /package/dist/{versioning-BUrT5aP4.mjs → versioning-B6mimogM.mjs} +0 -0
  158. /package/dist/{websocket-ChC2rqe1.d.mts → websocket-BkjeGZRn.d.mts} +0 -0
package/README.md CHANGED
@@ -122,6 +122,7 @@ export default defineResource({
122
122
  actions: {
123
123
  approve: { handler: approveOrder, permissions: requireRoles(['admin']) },
124
124
  },
125
+ // mcp: false, // opt out of MCP tool generation for this resource (2.16)
125
126
  });
126
127
  ```
127
128
 
package/bin/arc.js CHANGED
@@ -292,6 +292,16 @@ function parseInitOptions(rawArgs) {
292
292
  opts.edge = true;
293
293
  break;
294
294
 
295
+ case '--docker':
296
+ // 2.16 — Docker scaffolding is opt-in (frameworks don't dictate
297
+ // deployment). `--no-docker` skips even the interactive prompt.
298
+ opts.docker = true;
299
+ break;
300
+
301
+ case '--no-docker':
302
+ opts.docker = false;
303
+ break;
304
+
295
305
  case '--skip-install':
296
306
  opts.skipInstall = true;
297
307
  break;
@@ -342,6 +352,8 @@ INIT OPTIONS
342
352
  --ts, --typescript Generate TypeScript (default)
343
353
  --js, --javascript Generate JavaScript
344
354
  --edge, --serverless Target Edge/Serverless environments
355
+ --docker Emit Dockerfile + docker-compose.yml (opt-in, 2.16)
356
+ --no-docker Skip Docker scaffolding even in interactive mode
345
357
  --force, -f Overwrite existing directory
346
358
  --skip-install Skip npm install after scaffolding
347
359
 
@@ -1,11 +1,11 @@
1
- import { h as SYSTEM_FIELDS, o as DEFAULT_TENANT_FIELD } from "./constants-Cxde4rpC.mjs";
1
+ import { h as SYSTEM_FIELDS, o as DEFAULT_TENANT_FIELD } from "./constants-TrJVIJl0.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
3
  import { d as createDomainError, f as createError, i as NotFoundError, r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
4
- import { b as isMember, c as getOrgId, n as PUBLIC_SCOPE, y as isElevated } from "./types-C_s5moIu.mjs";
5
- import { N as simpleEqualityMatcher, _ as ArcQueryParser, r as scheduleBackground, t as getUserId } from "./utils-_h9B3c57.mjs";
6
- import { d as applyFieldWritePermissions, p as resolveEffectiveRoles } from "./permissions-ohQyv50e.mjs";
4
+ import { b as isElevated, c as getOrgId, n as PUBLIC_SCOPE, x as isMember } from "./types-Bi0r0vjG.mjs";
5
+ import { N as simpleEqualityMatcher, _ as ArcQueryParser, r as scheduleBackground, t as getUserId } from "./utils-DC5ycPfr.mjs";
6
+ import { d as applyFieldWritePermissions, p as resolveEffectiveRoles } from "./permissions-CTxMrreC.mjs";
7
7
  import { t as getUserRoles } from "./types-D57iXYb8.mjs";
8
- import { t as buildQueryKey } from "./keys-CGcCbNyu.mjs";
8
+ import { t as buildQueryKey } from "./keys-DopsCuyQ.mjs";
9
9
  //#region src/core/AccessControl.ts
10
10
  const log = arcLog("access-control");
11
11
  var AccessControl = class {
@@ -892,9 +892,10 @@ var BaseCrudController = class {
892
892
  const cacheConfig = this.resolveCacheConfig("list");
893
893
  const qc = req.server?.queryCache;
894
894
  if (!cacheConfig || !qc) return null;
895
- const version = await qc.getResourceVersion(this.resourceName);
895
+ const resourceName = this.resourceName ?? "_unnamed";
896
+ const version = await qc.getResourceVersion(resourceName);
896
897
  const { userId, orgId } = this.cacheScope(req);
897
- const key = buildQueryKey(this.resourceName, "list", version, options, userId, orgId);
898
+ const key = buildQueryKey(resourceName, "list", version, options, userId, orgId);
898
899
  const { data, status } = await qc.get(key);
899
900
  if (status === "fresh") return this.cacheResponse(data, "HIT");
900
901
  if (status === "stale") {
@@ -912,9 +913,10 @@ var BaseCrudController = class {
912
913
  const cacheConfig = this.resolveCacheConfig("byId");
913
914
  const qc = req.server?.queryCache;
914
915
  if (!cacheConfig || !qc) return null;
915
- const version = await qc.getResourceVersion(this.resourceName);
916
+ const resourceName = this.resourceName ?? "_unnamed";
917
+ const version = await qc.getResourceVersion(resourceName);
916
918
  const { userId, orgId } = this.cacheScope(req);
917
- const key = buildQueryKey(this.resourceName, "get", version, {
919
+ const key = buildQueryKey(resourceName, "get", version, {
918
920
  id,
919
921
  ...options
920
922
  }, userId, orgId);
@@ -1149,6 +1151,45 @@ var BaseCrudController = class {
1149
1151
  }
1150
1152
  };
1151
1153
  //#endregion
1154
+ //#region src/utils/repoFeature.ts
1155
+ /**
1156
+ * Repository feature-detection helper for arc mixins.
1157
+ *
1158
+ * Arc's `RepositoryLike` is the minimum contract every adapter
1159
+ * implements (`getById`, `getOne`, `create`, `update`, `delete`,
1160
+ * `find`). Beyond that, kits MAY expose specialized methods —
1161
+ * `createMany`, `updateMany`, `deleteMany`, `getTree`, `getBySlug`,
1162
+ * `getDeleted`, `restore`, etc. — that arc mixins detect at call
1163
+ * time and surface as 501 when missing.
1164
+ *
1165
+ * Every mixin used to spell out its own narrowing cast inline:
1166
+ *
1167
+ * ```ts
1168
+ * const repo = this.repository as unknown as {
1169
+ * getTree?: (options?: unknown) => Promise<AnyRecord[]>;
1170
+ * };
1171
+ * ```
1172
+ *
1173
+ * Centralizing the cast keeps every feature-detection site reading
1174
+ * the same shape. The helper does not intersect with `RepositoryLike`
1175
+ * on purpose: some standard methods (`createMany`, `updateMany`) live
1176
+ * on `StandardRepo` with kit-generic signatures, and intersecting
1177
+ * would force the caller's narrowed signature to merge with the wider
1178
+ * default, producing mismatched return types. Declare every method
1179
+ * you need (base or feature) in `TFeatures`.
1180
+ *
1181
+ * @example
1182
+ * ```ts
1183
+ * const repo = withRepoFeatures<{
1184
+ * createMany?: (items: unknown[], options?: unknown) => Promise<AnyRecord[]>;
1185
+ * }>(this.repository);
1186
+ * if (!repo.createMany) throw createError(501, "...");
1187
+ * ```
1188
+ */
1189
+ function withRepoFeatures(repo) {
1190
+ return repo;
1191
+ }
1192
+ //#endregion
1152
1193
  //#region src/core/mixins/bulk.ts
1153
1194
  /**
1154
1195
  * BulkMixin — `bulkCreate` / `bulkUpdate` / `bulkDelete` endpoints.
@@ -1170,7 +1211,7 @@ var BaseCrudController = class {
1170
1211
  function BulkMixin(Base) {
1171
1212
  return class BulkController extends Base {
1172
1213
  async bulkCreate(req) {
1173
- const repo = this.repository;
1214
+ const repo = withRepoFeatures(this.repository);
1174
1215
  if (!repo.createMany) throw createError(501, "Repository does not support createMany");
1175
1216
  const rawItems = req.body?.items;
1176
1217
  if (!Array.isArray(rawItems) || rawItems.length === 0) throw createError(400, "Bulk create requires a non-empty items array");
@@ -1293,7 +1334,7 @@ function BulkMixin(Base) {
1293
1334
  };
1294
1335
  }
1295
1336
  async bulkUpdate(req) {
1296
- const repo = this.repository;
1337
+ const repo = withRepoFeatures(this.repository);
1297
1338
  if (!repo.updateMany) throw createError(501, "Repository does not support updateMany");
1298
1339
  const body = req.body;
1299
1340
  if (!body.filter || Object.keys(body.filter).length === 0) throw createError(400, "Bulk update requires a non-empty filter");
@@ -1332,7 +1373,7 @@ function BulkMixin(Base) {
1332
1373
  * fetch loop. Per-doc lifecycle hooks do NOT fire.
1333
1374
  */
1334
1375
  async bulkDelete(req) {
1335
- const repo = this.repository;
1376
+ const repo = withRepoFeatures(this.repository);
1336
1377
  if (!repo.deleteMany) throw createError(501, "Repository does not support deleteMany");
1337
1378
  const body = req.body;
1338
1379
  let userFilter;
@@ -1368,7 +1409,7 @@ function SlugMixin(Base) {
1368
1409
  ...this.queryResolver.resolve(req, this.meta(req)),
1369
1410
  ...this.tenantRepoOptions(req)
1370
1411
  };
1371
- const repo = this.repository;
1412
+ const repo = withRepoFeatures(this.repository);
1372
1413
  let item = null;
1373
1414
  if (repo.getBySlug) item = await repo.getBySlug(slug, options);
1374
1415
  else if (repo.getOne) {
@@ -1399,7 +1440,7 @@ function SlugMixin(Base) {
1399
1440
  function SoftDeleteMixin(Base) {
1400
1441
  return class SoftDeleteController extends Base {
1401
1442
  async getDeleted(req) {
1402
- const repo = this.repository;
1443
+ const repo = withRepoFeatures(this.repository);
1403
1444
  if (!repo.getDeleted) throw createError(501, "Soft delete not implemented");
1404
1445
  const parsed = this.queryResolver.resolve(req, this.meta(req));
1405
1446
  return {
@@ -1408,8 +1449,9 @@ function SoftDeleteMixin(Base) {
1408
1449
  };
1409
1450
  }
1410
1451
  async restore(req) {
1411
- const repo = this.repository;
1412
- if (!repo.restore) throw createError(501, "Restore not implemented");
1452
+ const repo = withRepoFeatures(this.repository);
1453
+ const restore = repo.restore;
1454
+ if (!restore) throw createError(501, "Restore not implemented");
1413
1455
  const id = req.params.id;
1414
1456
  if (!id) throw createError(400, "ID parameter is required");
1415
1457
  const existing = await this.accessControl.fetchWithAccessControl(id, req, repo, { includeDeleted: true });
@@ -1431,7 +1473,7 @@ function SoftDeleteMixin(Base) {
1431
1473
  message: err.message
1432
1474
  });
1433
1475
  }
1434
- const repoRestore = () => repo.restore(repoId);
1476
+ const repoRestore = () => restore(repoId);
1435
1477
  let item;
1436
1478
  if (hooks && this.resourceName) item = await hooks.executeAround(this.resourceName, "restore", existing, repoRestore, {
1437
1479
  user,
@@ -1458,7 +1500,7 @@ function SoftDeleteMixin(Base) {
1458
1500
  function TreeMixin(Base) {
1459
1501
  return class TreeController extends Base {
1460
1502
  async getTree(req) {
1461
- const repo = this.repository;
1503
+ const repo = withRepoFeatures(this.repository);
1462
1504
  if (!repo.getTree) throw createError(501, "Tree structure not implemented");
1463
1505
  const options = this.queryResolver.resolve(req, this.meta(req));
1464
1506
  return {
@@ -1467,7 +1509,7 @@ function TreeMixin(Base) {
1467
1509
  };
1468
1510
  }
1469
1511
  async getChildren(req) {
1470
- const repo = this.repository;
1512
+ const repo = withRepoFeatures(this.repository);
1471
1513
  if (!repo.getChildren) throw createError(501, "Tree structure not implemented");
1472
1514
  const parentField = this._presetFields.parentField ?? "parent";
1473
1515
  const parentId = req.params[parentField] ?? req.params.parent ?? req.params.id;
@@ -36,6 +36,7 @@ var HookSystem = class {
36
36
  finalPriority = resourceOrOptions.priority ?? 10;
37
37
  dependsOn = resourceOrOptions.dependsOn;
38
38
  } else {
39
+ if (operation === void 0 || phase === void 0 || handler === void 0) throw new TypeError("[Arc] HookSystem.register(): positional form requires (resource, operation, phase, handler). Pass an object literal for the named form.");
39
40
  resource = resourceOrOptions;
40
41
  finalOperation = operation;
41
42
  finalPhase = phase;
@@ -53,8 +54,9 @@ var HookSystem = class {
53
54
  priority: finalPriority,
54
55
  dependsOn
55
56
  };
56
- const hooks = this.hooks.get(key);
57
+ const hooks = this.hooks.get(key) ?? [];
57
58
  hooks.push(registration);
59
+ if (!this.hooks.has(key)) this.hooks.set(key, hooks);
58
60
  hooks.sort((a, b) => a.priority - b.priority);
59
61
  return () => {
60
62
  const idx = hooks.indexOf(registration);
@@ -95,6 +97,7 @@ var HookSystem = class {
95
97
  const next = async () => {
96
98
  if (index < allHooks.length) {
97
99
  const hook = allHooks[index++];
100
+ if (!hook) return execute();
98
101
  const ctx = {
99
102
  resource,
100
103
  operation,
@@ -195,15 +198,16 @@ var HookSystem = class {
195
198
  }
196
199
  const queue = [];
197
200
  const result = [];
198
- for (const hook of hooks) if (inDegree.get(hook) === 0) queue.push(hook);
201
+ for (const hook of hooks) if ((inDegree.get(hook) ?? 0) === 0) queue.push(hook);
199
202
  queue.sort((a, b) => a.priority - b.priority);
200
203
  while (queue.length > 0) {
201
204
  const current = queue.shift();
205
+ if (!current) break;
202
206
  result.push(current);
203
207
  if (current.name && dependents.has(current.name)) {
204
- const deps = dependents.get(current.name);
208
+ const deps = dependents.get(current.name) ?? [];
205
209
  for (const dep of deps) {
206
- const newDegree = inDegree.get(dep) - 1;
210
+ const newDegree = (inDegree.get(dep) ?? 0) - 1;
207
211
  inDegree.set(dep, newDegree);
208
212
  if (newDegree === 0) {
209
213
  const insertIdx = queue.findIndex((q) => q.priority > dep.priority);
@@ -1,4 +1,4 @@
1
- import { r as CacheStore } from "./interface-beEtJyWM.mjs";
1
+ import { r as CacheStore } from "./interface-CH0OQudo.mjs";
2
2
 
3
3
  //#region src/cache/QueryCache.d.ts
4
4
  /** Metadata wrapper stored in CacheStore */
@@ -1,4 +1,4 @@
1
- import "./constants-Cxde4rpC.mjs";
1
+ import "./constants-TrJVIJl0.mjs";
2
2
  //#region src/registry/ResourceRegistry.ts
3
3
  /**
4
4
  * Resource Registry
@@ -7,12 +7,32 @@ import "./constants-Cxde4rpC.mjs";
7
7
  */
8
8
  var ResourceRegistry = class {
9
9
  _resources;
10
+ /**
11
+ * Parallel map of full `DataAdapter` instances. The public `RegistryEntry.adapter`
12
+ * field is intentionally narrowed to `{ type, name }` so introspection can be
13
+ * JSON-serialized — but cascade / cleanup / migration helpers need the live
14
+ * adapter (and its `.repository`) to do real work. Stored here, behind
15
+ * `getAdapter(name)`, so the public metadata stays serializable and the
16
+ * runtime-only handle is still reachable.
17
+ */
18
+ _adapters;
10
19
  _frozen;
11
20
  constructor() {
12
21
  this._resources = /* @__PURE__ */ new Map();
22
+ this._adapters = /* @__PURE__ */ new Map();
13
23
  this._frozen = false;
14
24
  }
15
25
  /**
26
+ * Get the live `DataAdapter` for a registered resource — the handle whose
27
+ * `.repository` cascade / cleanup helpers actually call. Returns `undefined`
28
+ * when the resource is unknown OR was registered without an adapter
29
+ * (service-only resources). Typed loosely (`unknown`-shaped fields) so
30
+ * consumers cast to their kit's adapter type at the use site.
31
+ */
32
+ getAdapter(name) {
33
+ return this._adapters.get(name);
34
+ }
35
+ /**
16
36
  * Register a resource
17
37
  */
18
38
  register(resource, options = {}) {
@@ -34,7 +54,7 @@ var ResourceRegistry = class {
34
54
  customRoutes: (resource.routes ?? []).map((r) => ({
35
55
  method: r.method,
36
56
  path: r.path,
37
- handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
57
+ handler: typeof r.handler === "string" ? r.handler : typeof r.handler === "function" ? r.handler.name || "anonymous" : "anonymous",
38
58
  operation: r.operation,
39
59
  summary: r.summary,
40
60
  description: r.description,
@@ -45,6 +65,8 @@ var ResourceRegistry = class {
45
65
  events: Object.keys(resource.events ?? {}),
46
66
  registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
47
67
  disableDefaultRoutes: resource.disableDefaultRoutes,
68
+ tenantField: resource.tenantField,
69
+ resolvedTenantPurge: resource.resolvedTenantPurge,
48
70
  updateMethod: resource.updateMethod,
49
71
  disabledRoutes: resource.disabledRoutes ? [...resource.disabledRoutes] : void 0,
50
72
  openApiSchemas: options.openApiSchemas,
@@ -60,7 +82,8 @@ var ResourceRegistry = class {
60
82
  description: entry.description,
61
83
  schema: entry.schema,
62
84
  permissions: entry.permissions,
63
- mcp: entry.mcp
85
+ mcp: entry.mcp,
86
+ id: entry.id
64
87
  };
65
88
  }) : void 0,
66
89
  aggregations: resource.aggregations ? Object.entries(resource.aggregations).map(([name, entry]) => ({
@@ -78,6 +101,7 @@ var ResourceRegistry = class {
78
101
  plugin: resource.toPlugin()
79
102
  };
80
103
  this._resources.set(resource.name, entry);
104
+ if (resource.adapter) this._adapters.set(resource.name, resource.adapter);
81
105
  return this;
82
106
  }
83
107
  /**
@@ -216,11 +240,20 @@ var ResourceRegistry = class {
216
240
  handler: typeof ar.handler === "string" ? ar.handler : void 0,
217
241
  summary: ar.summary
218
242
  });
219
- if (r.actions && r.actions.length > 0) routes.push({
220
- method: "POST",
221
- path: `${r.prefix}/:id/action`,
222
- operation: "action"
223
- });
243
+ if (r.actions && r.actions.length > 0) {
244
+ const hasIdBound = r.actions.some((a) => a.id !== false);
245
+ const hasIdLess = r.actions.some((a) => a.id === false);
246
+ if (hasIdBound) routes.push({
247
+ method: "POST",
248
+ path: `${r.prefix}/:id/action`,
249
+ operation: "action"
250
+ });
251
+ if (hasIdLess) routes.push({
252
+ method: "POST",
253
+ path: `${r.prefix}/action`,
254
+ operation: "action"
255
+ });
256
+ }
224
257
  for (const agg of r.aggregations ?? []) routes.push({
225
258
  method: "GET",
226
259
  path: `${r.prefix}/aggregations/${agg.name}`,
@@ -248,10 +281,20 @@ var ResourceRegistry = class {
248
281
  this._frozen = false;
249
282
  }
250
283
  /**
251
- * Reset registry — clear all resources and unfreeze
284
+ * Reset registry — clear all resources, drop their parallel live-adapter
285
+ * handles, and unfreeze.
286
+ *
287
+ * The `_adapters` map (added in 2.15.5 so cascade / cleanup helpers can
288
+ * reach `adapter.repository` after registration) must clear alongside
289
+ * `_resources`. Pre-2.16 this method dropped the public entries but
290
+ * left the live adapters dangling, so a test that called `reset()`
291
+ * between cases would see ghost adapters and either (a) leak a live
292
+ * Mongo connection across describe blocks or (b) `cascadeDeleteForOrganization`
293
+ * iterate over a defunct registry entry. Clear both halves together.
252
294
  */
253
295
  reset() {
254
296
  this._resources.clear();
297
+ this._adapters.clear();
255
298
  this._frozen = false;
256
299
  }
257
300
  /** @internal Alias for unfreeze() */
@@ -1,4 +1,4 @@
1
- import { d as UserBase } from "../fields-COhcH3fk.mjs";
1
+ import { d as UserBase } from "../fields-Anj0xdih.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
  import { RepositoryLike } from "@classytic/repo-core/adapter";
4
4
 
@@ -144,8 +144,10 @@ var MemoryAuditStore = class {
144
144
  const actions = Array.isArray(options.action) ? options.action : [options.action];
145
145
  results = results.filter((e) => actions.includes(e.action));
146
146
  }
147
- if (options.from) results = results.filter((e) => e.timestamp >= options.from);
148
- if (options.to) results = results.filter((e) => e.timestamp <= options.to);
147
+ const from = options.from;
148
+ if (from !== void 0) results = results.filter((e) => e.timestamp >= from);
149
+ const to = options.to;
150
+ if (to !== void 0) results = results.filter((e) => e.timestamp <= to);
149
151
  const offset = options.offset ?? 0;
150
152
  const limit = options.limit ?? 100;
151
153
  results = results.slice(offset, offset + limit);
@@ -1,7 +1,7 @@
1
- import { Rt as AuthHelpers, zt as AuthPluginOptions } from "../index-BswOSJCE.mjs";
2
- import { c as PermissionCheck } from "../fields-COhcH3fk.mjs";
3
- import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
4
- import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-C4Le_UB3.mjs";
1
+ import { Bt as AuthHelpers, Vt as AuthPluginOptions } from "../index-CkW0flkU.mjs";
2
+ import { c as PermissionCheck } from "../fields-Anj0xdih.mjs";
3
+ import { t as ExternalOpenApiPaths } from "../externalPaths-DFg-2KTp.mjs";
4
+ import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-BqFegc0W.mjs";
5
5
  import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest } from "fastify";
6
6
 
7
7
  //#region src/auth/authPlugin.d.ts
@@ -1,7 +1,7 @@
1
1
  import { d as createDomainError, l as UnauthorizedError, p as isArcError, t as ArcError } from "../errors-j4aJm1Wg.mjs";
2
- import { _ as requireOrgMembership, v as requireOrgRole, x as requireTeamMembership } from "../permissions-ohQyv50e.mjs";
2
+ import { _ as requireOrgMembership, v as requireOrgRole, x as requireTeamMembership } from "../permissions-CTxMrreC.mjs";
3
3
  import { n as normalizeRoles, t as getUserRoles } from "../types-D57iXYb8.mjs";
4
- import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi--M_i87dQ.mjs";
4
+ import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-ClWxaceA.mjs";
5
5
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/auth/authPlugin.ts
@@ -13,7 +13,7 @@ function parseExpiresIn(input, defaultValue) {
13
13
  if (/^\d+$/.test(input)) return parseInt(input, 10);
14
14
  const match = /^(\d+)\s*([smhd])$/i.exec(input);
15
15
  if (!match) return defaultValue;
16
- return parseInt(match[1], 10) * ({
16
+ return parseInt(match[1] ?? "0", 10) * ({
17
17
  s: 1,
18
18
  m: 60,
19
19
  h: 3600,
@@ -611,7 +611,7 @@ function createBetterAuthAdapter(options) {
611
611
  if (!fastify.hasDecorator("authenticate")) fastify.decorate("authenticate", authenticate);
612
612
  if (!fastify.hasDecorator("optionalAuthenticate")) fastify.decorate("optionalAuthenticate", optionalAuthenticate);
613
613
  if (!extractedOpenApi && openapiOpt !== false && auth.api && typeof auth.api === "object") {
614
- const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi--M_i87dQ.mjs").then((n) => n.t);
614
+ const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-ClWxaceA.mjs").then((n) => n.t);
615
615
  extractedOpenApi = extractBetterAuthOpenApi(auth.api, {
616
616
  basePath,
617
617
  userFields
@@ -1,4 +1,4 @@
1
- import { i as SessionData, s as SessionStore } from "../sessionManager-C4Le_UB3.mjs";
1
+ import { i as SessionData, s as SessionStore } from "../sessionManager-BqFegc0W.mjs";
2
2
 
3
3
  //#region src/auth/redis-session.d.ts
4
4
  /** Minimal Redis client interface — compatible with ioredis */
@@ -23,12 +23,16 @@ function toOpenApiPath(path) {
23
23
  function extractPathParams(path) {
24
24
  const params = [];
25
25
  const matches = path.matchAll(/:(\w+)/g);
26
- for (const match of matches) params.push({
27
- name: match[1],
28
- in: "path",
29
- required: true,
30
- schema: { type: "string" }
31
- });
26
+ for (const match of matches) {
27
+ const name = match[1];
28
+ if (!name) continue;
29
+ params.push({
30
+ name,
31
+ in: "path",
32
+ required: true,
33
+ schema: { type: "string" }
34
+ });
35
+ }
32
36
  return params;
33
37
  }
34
38
  /**