@classytic/arc 2.10.3 → 2.10.8

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 (135) hide show
  1. package/README.md +1 -1
  2. package/dist/{BaseController-CbKKIflT.mjs → BaseController-DVNKvoX4.mjs} +151 -131
  3. package/dist/actionPermissions-TUVR3uiZ.mjs +22 -0
  4. package/dist/adapters/index.d.mts +2 -2
  5. package/dist/audit/index.d.mts +2 -2
  6. package/dist/audit/index.mjs +15 -17
  7. package/dist/auth/index.d.mts +4 -4
  8. package/dist/auth/index.mjs +5 -5
  9. package/dist/auth/redis-session.d.mts +1 -1
  10. package/dist/cache/index.d.mts +2 -2
  11. package/dist/cache/index.mjs +3 -3
  12. package/dist/cli/commands/docs.mjs +2 -2
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/cli/commands/init.mjs +1 -1
  15. package/dist/cli/commands/introspect.mjs +1 -1
  16. package/dist/context/index.d.mts +58 -0
  17. package/dist/context/index.mjs +2 -0
  18. package/dist/core/index.d.mts +2 -2
  19. package/dist/core/index.mjs +2 -2
  20. package/dist/{core-CcR01lup.mjs → core-3MWJosCH.mjs} +139 -91
  21. package/dist/{createApp-BuvPma24.mjs → createApp-BwnEAO2h.mjs} +54 -20
  22. package/dist/docs/index.d.mts +2 -2
  23. package/dist/docs/index.mjs +2 -2
  24. package/dist/{elevation-C7hgL_aI.mjs → elevation-Dci0AYLT.mjs} +2 -2
  25. package/dist/errorHandler-2ii4RIYr.d.mts +114 -0
  26. package/dist/{errorHandler-Bb49BvPD.mjs → errorHandler-CSxe7KIM.mjs} +1 -1
  27. package/dist/{eventPlugin-DCUjuiQT.mjs → eventPlugin-ByU4Cv0e.mjs} +1 -1
  28. package/dist/{eventPlugin-CxWgpd6K.d.mts → eventPlugin-D1ThQ1Pp.d.mts} +1 -1
  29. package/dist/events/index.d.mts +4 -4
  30. package/dist/events/index.mjs +69 -51
  31. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  32. package/dist/events/transports/redis.d.mts +1 -1
  33. package/dist/factory/index.d.mts +1 -1
  34. package/dist/factory/index.mjs +2 -2
  35. package/dist/{fields-Lo1VUDpt.d.mts → fields-C8Y0XLAu.d.mts} +1 -1
  36. package/dist/hooks/index.d.mts +1 -1
  37. package/dist/hooks/index.mjs +1 -1
  38. package/dist/idempotency/index.d.mts +3 -3
  39. package/dist/idempotency/index.mjs +38 -27
  40. package/dist/idempotency/redis.d.mts +1 -1
  41. package/dist/{index-Cl0uoKd5.d.mts → index-BGbpGVyM.d.mts} +2362 -2155
  42. package/dist/{index-DStwgFUK.d.mts → index-BziRPS4H.d.mts} +1 -1
  43. package/dist/{index-ChIw3776.d.mts → index-C_Noptz-.d.mts} +3 -3
  44. package/dist/{index-8qw4y6ff.d.mts → index-EqQN6p0W.d.mts} +3 -3
  45. package/dist/index.d.mts +7 -219
  46. package/dist/index.mjs +8 -128
  47. package/dist/integrations/event-gateway.d.mts +1 -1
  48. package/dist/integrations/event-gateway.mjs +1 -1
  49. package/dist/integrations/index.d.mts +1 -1
  50. package/dist/integrations/mcp/index.d.mts +2 -2
  51. package/dist/integrations/mcp/index.mjs +1 -1
  52. package/dist/integrations/mcp/testing.d.mts +1 -1
  53. package/dist/integrations/mcp/testing.mjs +1 -1
  54. package/dist/logger/index.d.mts +81 -0
  55. package/dist/{logger-DLg8-Ueg.mjs → logger/index.mjs} +1 -6
  56. package/dist/middleware/index.d.mts +109 -0
  57. package/dist/middleware/index.mjs +70 -0
  58. package/dist/multipartBody-CUQGVlM_.mjs +123 -0
  59. package/dist/{openapi-B5F8AddX.mjs → openapi-DpNpqBmo.mjs} +9 -7
  60. package/dist/org/index.d.mts +2 -2
  61. package/dist/permissions/index.d.mts +2 -2
  62. package/dist/permissions/index.mjs +3 -3
  63. package/dist/{permissions-Dk6mshja.mjs → permissions-wkqRwicB.mjs} +2 -2
  64. package/dist/pipe-CGJxqDGx.mjs +62 -0
  65. package/dist/pipeline/index.d.mts +62 -0
  66. package/dist/pipeline/index.mjs +53 -0
  67. package/dist/plugins/index.d.mts +25 -5
  68. package/dist/plugins/index.mjs +9 -9
  69. package/dist/plugins/tracing-entry.d.mts +1 -1
  70. package/dist/plugins/tracing-entry.mjs +1 -1
  71. package/dist/presets/filesUpload.d.mts +4 -4
  72. package/dist/presets/filesUpload.mjs +255 -1
  73. package/dist/presets/index.d.mts +1 -1
  74. package/dist/presets/index.mjs +2 -2
  75. package/dist/presets/multiTenant.d.mts +1 -1
  76. package/dist/presets/multiTenant.mjs +42 -8
  77. package/dist/presets/search.d.mts +2 -2
  78. package/dist/presets/search.mjs +1 -1
  79. package/dist/{presets-fLJVXdVn.mjs → presets-CrwOvuXI.mjs} +1 -1
  80. package/dist/{queryCachePlugin-DQCEfJis.mjs → queryCachePlugin-ChLNZvFT.mjs} +2 -2
  81. package/dist/{queryCachePlugin-BKbWjgDG.d.mts → queryCachePlugin-Dumka73q.d.mts} +1 -1
  82. package/dist/{queryParser-DBqBB6AC.mjs → queryParser-NR__Qiju.mjs} +68 -1
  83. package/dist/{redis-DqyeggCa.d.mts → redis-MXLp1oOf.d.mts} +1 -1
  84. package/dist/{redis-stream-CakIQmwR.d.mts → redis-stream-bkO88VHx.d.mts} +1 -1
  85. package/dist/registry/index.d.mts +1 -1
  86. package/dist/registry/index.mjs +2 -2
  87. package/dist/{requestContext-xHIKedG6.mjs → requestContext-C38GskNt.mjs} +1 -1
  88. package/dist/{resourceToTools-BElv3xPT.mjs → resourceToTools-BhF3JV5p.mjs} +8 -3
  89. package/dist/scope/index.d.mts +2 -2
  90. package/dist/scope/index.mjs +2 -2
  91. package/dist/{sse-yBCgOLGu.mjs → sse-D8UeDwis.mjs} +1 -1
  92. package/dist/{store-helpers-ZCSMJJAX.mjs → store-helpers-DYYUQbQN.mjs} +4 -0
  93. package/dist/testing/index.d.mts +2 -2
  94. package/dist/testing/index.mjs +11 -2
  95. package/dist/testing/storageContract.d.mts +1 -1
  96. package/dist/types/index.d.mts +4 -4
  97. package/dist/types/index.mjs +1 -1
  98. package/dist/types/storage.d.mts +1 -1
  99. package/dist/{types-Btdda02s.d.mts → types-CVKBssX5.d.mts} +1 -1
  100. package/dist/{types-Co8k3NyS.d.mts → types-CVdgPXBW.d.mts} +22 -9
  101. package/dist/utils/index.d.mts +73 -3
  102. package/dist/utils/index.mjs +4 -4
  103. package/dist/{utils-B2fNOD_i.mjs → utils-LMwVidKy.mjs} +20 -2
  104. package/dist/versioning-CeUXHfjw.d.mts +117 -0
  105. package/package.json +22 -6
  106. package/skills/arc/SKILL.md +1 -1
  107. package/dist/errorHandler-DRQ3EqfL.d.mts +0 -218
  108. package/dist/filesUpload-t21LS-py.mjs +0 -377
  109. /package/dist/{EventTransport-CUw5NNWe.d.mts → EventTransport-CfVEGaEl.d.mts} +0 -0
  110. /package/dist/{HookSystem-BNYKnrXF.mjs → HookSystem-BjFu7zf1.mjs} +0 -0
  111. /package/dist/{ResourceRegistry-BPd6NQDm.mjs → ResourceRegistry-CcN2LVrc.mjs} +0 -0
  112. /package/dist/{betterAuthOpenApi-BBRVhjQN.mjs → betterAuthOpenApi--rdY15Ld.mjs} +0 -0
  113. /package/dist/{caching-CBpK_SCM.mjs → caching-3h93rkJM.mjs} +0 -0
  114. /package/dist/{createActionRouter-Bp_5c_2b.mjs → createActionRouter-C8UUB3Px.mjs} +0 -0
  115. /package/dist/{elevation-C5SwtkAn.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
  116. /package/dist/{errors-CCSsMpXE.d.mts → errors-BI8kEKsO.d.mts} +0 -0
  117. /package/dist/{errors-D5c-5BJL.mjs → errors-BqdUDja_.mjs} +0 -0
  118. /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
  119. /package/dist/{fields-bxkeltzz.mjs → fields-CTMWOUDt.mjs} +0 -0
  120. /package/dist/{interface-CSbZdv_3.d.mts → interface-B-pe8fhj.d.mts} +0 -0
  121. /package/dist/{interface-D218ikEo.d.mts → interface-yhyb_pLY.d.mts} +0 -0
  122. /package/dist/{keys-qcD-TVJl.mjs → keys-nWQGUTu1.mjs} +0 -0
  123. /package/dist/{loadResources-BAzJItAJ.mjs → loadResources-Bksk8ydA.mjs} +0 -0
  124. /package/dist/{memory-B5Amv9A1.mjs → memory-DqI-449b.mjs} +0 -0
  125. /package/dist/{metrics-DuhiSEZI.mjs → metrics-TuOmguhi.mjs} +0 -0
  126. /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
  127. /package/dist/{registry-B3lRFBWo.mjs → registry-B0Wl7uVV.mjs} +0 -0
  128. /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
  129. /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
  130. /package/dist/{storage-CVk_SEn2.d.mts → storage-BwGQXUpd.d.mts} +0 -0
  131. /package/dist/{tracing-65B51Dw3.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
  132. /package/dist/{types-Csi3FLfq.mjs → types-CDnTEpga.mjs} +0 -0
  133. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  134. /package/dist/{types-BD85MlEK.d.mts → types-tgR4Pt8F.d.mts} +0 -0
  135. /package/dist/{versioning-C2U_bLY0.mjs → versioning-B6mimogM.mjs} +0 -0
package/README.md CHANGED
@@ -602,7 +602,7 @@ await app.register(eventGatewayPlugin, {
602
602
  Functional composition for cross-cutting concerns:
603
603
 
604
604
  ```typescript
605
- import { pipe, guard, transform, intercept } from '@classytic/arc';
605
+ import { pipe, guard, transform, intercept } from '@classytic/arc/pipeline';
606
606
 
607
607
  const isActive = guard('isActive', (ctx) => ctx.query?.filters?.isActive !== false);
608
608
  const slugify = transform('slugify', (ctx) => ({ ...ctx, body: { ...ctx.body, slug: toSlug(ctx.body.name) } }));
@@ -1,25 +1,26 @@
1
1
  import { h as SYSTEM_FIELDS, o as DEFAULT_TENANT_FIELD } from "./constants-BhY1OHoH.mjs";
2
+ import { arcLog } from "./logger/index.mjs";
2
3
  import { _ as isElevated, n as PUBLIC_SCOPE, o as getOrgId, v as isMember } from "./types-AOD8fxIw.mjs";
3
- import { t as buildQueryKey } from "./keys-qcD-TVJl.mjs";
4
- import { n as getUserId } from "./types-Csi3FLfq.mjs";
5
- import { i as resolveEffectiveRoles, n as applyFieldWritePermissions } from "./fields-bxkeltzz.mjs";
6
- import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
7
- import { r as ForbiddenError } from "./errors-D5c-5BJL.mjs";
8
- import { t as ArcQueryParser } from "./queryParser-DBqBB6AC.mjs";
4
+ import { r as simpleEqualityMatcher, t as ArcQueryParser } from "./queryParser-NR__Qiju.mjs";
5
+ import { t as buildQueryKey } from "./keys-nWQGUTu1.mjs";
6
+ import { n as getUserId } from "./types-CDnTEpga.mjs";
7
+ import { i as resolveEffectiveRoles, n as applyFieldWritePermissions } from "./fields-CTMWOUDt.mjs";
8
+ import { t as getUserRoles } from "./types-D57iXYb8.mjs";
9
+ import { r as ForbiddenError } from "./errors-BqdUDja_.mjs";
9
10
  //#region src/core/AccessControl.ts
10
- var AccessControl = class AccessControl {
11
+ const log = arcLog("access-control");
12
+ var AccessControl = class {
11
13
  tenantField;
12
14
  idField;
13
15
  _adapterMatchesFilter;
14
- /** Patterns that indicate dangerous regex (nested quantifiers, excessive backtracking).
15
- * Uses [^...] character classes instead of .+ to avoid backtracking in the detector itself. */
16
- static DANGEROUS_REGEX = /(\{[0-9]+,\}[^{]*\{[0-9]+,\})|(\+[^+]*\+)|(\*[^*]*\*)|(\.\*){3,}|\\1/;
17
- /** Forbidden paths that could lead to prototype pollution */
18
- static FORBIDDEN_PATHS = [
19
- "__proto__",
20
- "constructor",
21
- "prototype"
22
- ];
16
+ /**
17
+ * One-shot latch for the "adapter didn't supply matchesFilter, in-memory
18
+ * policy-filter re-check is skipped" warning. The primary fetch path
19
+ * (`getOne(compoundFilter)`) already applied filters at the DB layer;
20
+ * this warn only fires when `validateItemAccess` runs and the adapter
21
+ * hasn't provided a native matcher for the post-hoc re-check.
22
+ */
23
+ _warnedNoMatcher = false;
23
24
  constructor(config) {
24
25
  this.tenantField = config.tenantField;
25
26
  this.idField = config.idField;
@@ -40,17 +41,54 @@ var AccessControl = class AccessControl {
40
41
  return filter;
41
42
  }
42
43
  /**
43
- * Check if item matches policy filters (for get/update/delete operations)
44
- * Validates that fetched item satisfies all policy constraints
44
+ * Check if a post-fetch item matches the request's `_policyFilters`.
45
+ *
46
+ * **When this runs:** only on paths where the primary fetch path did NOT
47
+ * apply policy filters at the DB layer — notably `validateItemAccess`
48
+ * (used by `getBySlug` and cache revalidation). The main `fetchDetailed`
49
+ * path builds a compound filter (`buildIdFilter`) and passes it to
50
+ * `repository.getOne(compoundFilter)`, so the DB has already enforced
51
+ * the filter and an in-memory re-check would be redundant.
52
+ *
53
+ * **Evaluation order (fail-closed):**
54
+ * 1. No `_policyFilters` set → `true` (nothing to enforce).
55
+ * 2. Adapter supplied `matchesFilter` → delegate to it verbatim. Adapters
56
+ * are expected to handle every filter shape the host emits
57
+ * (mongokit/sqlitekit evaluate at the DB layer; Prisma/custom engines
58
+ * can wrap their own predicate engine).
59
+ * 3. No adapter matcher → fall back to `simpleEqualityMatcher` — arc's
60
+ * built-in flat-key equality helper. This is defense-in-depth for the
61
+ * common case: arc's own permission helpers emit flat filters
62
+ * (`{userId: …}`, `{organizationId: …}`), which this matcher evaluates
63
+ * correctly. Operator-shaped filters (`$in`, `$ne`, `$regex`, `$and`,
64
+ * `$or`) are **rejected** (the matcher returns `false`) — fail-closed
65
+ * rather than fail-open. A one-shot warn flags the gap so adapter
66
+ * authors can wire a richer matcher.
45
67
  *
46
- * Delegates to adapter-provided matchesFilter if available (for SQL, etc.),
47
- * otherwise falls back to built-in MongoDB-style matching.
68
+ * Arc deliberately does NOT ship a full MongoDB-syntax matcher:
69
+ * re-implementing Mongo in JS was dead code for mongokit users (the DB
70
+ * did it) and silently wrong for non-Mongo adapters. The flat-equality
71
+ * fallback is small (~20 LOC), correct in both dialects, and closes the
72
+ * previous `getBySlug`-style policy-bypass path.
48
73
  */
49
74
  checkPolicyFilters(item, req) {
50
75
  const policyFilters = this._meta(req)?._policyFilters;
51
- if (!policyFilters) return true;
76
+ if (!policyFilters || Object.keys(policyFilters).length === 0) return true;
52
77
  if (this._adapterMatchesFilter) return this._adapterMatchesFilter(item, policyFilters);
53
- return this.defaultMatchesPolicyFilters(item, policyFilters);
78
+ if (Object.values(policyFilters).some((v) => v !== null && typeof v === "object" && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype && Object.keys(v).some((k) => k.startsWith("$")))) this._warnNoMatcher(policyFilters);
79
+ return simpleEqualityMatcher(item, policyFilters);
80
+ }
81
+ /**
82
+ * Emit a one-shot warn when policy filters contain operators (`$in`,
83
+ * `$ne`, `$regex`, etc.) and no `DataAdapter.matchesFilter` is wired —
84
+ * arc's flat-equality fallback fail-closes on operators, so the host
85
+ * sees 404s on docs that should match. Latched on `_warnedNoMatcher`
86
+ * so subsequent requests stay quiet.
87
+ */
88
+ _warnNoMatcher(policyFilters) {
89
+ if (this._warnedNoMatcher) return;
90
+ this._warnedNoMatcher = true;
91
+ log.warn("`_policyFilters` contains operator-shaped entries (e.g. `$in`, `$ne`, `$regex`) but `DataAdapter.matchesFilter` is not set. Arc's flat-equality fallback cannot evaluate operators and will reject these items on non-compound fetches (`validateItemAccess`, `getBySlug`, cache revalidation). Wire up `matchesFilter` on your adapter — use `matchFilter` from `@classytic/repo-core/filter` for IR-based adapters, or your DB's native predicate engine.", { policyFilterKeys: Object.keys(policyFilters) });
54
92
  }
55
93
  /**
56
94
  * Check org/tenant scope for a document — uses configurable tenantField.
@@ -64,7 +102,6 @@ var AccessControl = class AccessControl {
64
102
  const scope = arcContext?._scope;
65
103
  const orgId = scope ? getOrgId(scope) : void 0;
66
104
  if (!item || !orgId) return true;
67
- if (scope && isElevated(scope) && !orgId) return true;
68
105
  const itemOrgId = item[this.tenantField];
69
106
  if (!itemOrgId) return false;
70
107
  return String(itemOrgId) === String(orgId);
@@ -122,7 +159,25 @@ var AccessControl = class AccessControl {
122
159
  };
123
160
  if (hasCompoundFilters) {
124
161
  const idOnly = { [this.idField]: id };
125
- const rawDoc = await repository.getOne(idOnly);
162
+ const rawGetOne = repository.getOne.bind(repository);
163
+ let rawDoc = null;
164
+ try {
165
+ rawDoc = await rawGetOne(idOnly);
166
+ } catch (unscopedErr) {
167
+ if (translateStatus404(unscopedErr)) return {
168
+ doc: null,
169
+ reason: "NOT_FOUND"
170
+ };
171
+ try {
172
+ rawDoc = await rawGetOne(idOnly, queryOptions);
173
+ } catch (scopedErr) {
174
+ if (translateStatus404(scopedErr)) return {
175
+ doc: null,
176
+ reason: "NOT_FOUND"
177
+ };
178
+ throw scopedErr;
179
+ }
180
+ }
126
181
  if (rawDoc) {
127
182
  const arcContext = this._meta(req);
128
183
  if (!this.checkOrgScope(rawDoc, arcContext)) return {
@@ -183,101 +238,6 @@ var AccessControl = class AccessControl {
183
238
  _meta(req) {
184
239
  return req.metadata;
185
240
  }
186
- /**
187
- * Check if a value matches a MongoDB query operator
188
- */
189
- matchesOperator(itemValue, operator, filterValue) {
190
- const equalsByValue = (a, b) => String(a) === String(b);
191
- switch (operator) {
192
- case "$eq": return equalsByValue(itemValue, filterValue);
193
- case "$ne": return !equalsByValue(itemValue, filterValue);
194
- case "$gt": return typeof itemValue === "number" && typeof filterValue === "number" && itemValue > filterValue;
195
- case "$gte": return typeof itemValue === "number" && typeof filterValue === "number" && itemValue >= filterValue;
196
- case "$lt": return typeof itemValue === "number" && typeof filterValue === "number" && itemValue < filterValue;
197
- case "$lte": return typeof itemValue === "number" && typeof filterValue === "number" && itemValue <= filterValue;
198
- case "$in":
199
- if (!Array.isArray(filterValue)) return false;
200
- if (Array.isArray(itemValue)) return itemValue.some((v) => filterValue.some((fv) => equalsByValue(v, fv)));
201
- return filterValue.some((fv) => equalsByValue(itemValue, fv));
202
- case "$nin":
203
- if (!Array.isArray(filterValue)) return false;
204
- if (Array.isArray(itemValue)) return itemValue.every((v) => filterValue.every((fv) => !equalsByValue(v, fv)));
205
- return filterValue.every((fv) => !equalsByValue(itemValue, fv));
206
- case "$exists": return filterValue ? itemValue !== void 0 : itemValue === void 0;
207
- case "$regex":
208
- if (typeof itemValue === "string" && (typeof filterValue === "string" || filterValue instanceof RegExp)) return (typeof filterValue === "string" ? AccessControl.safeRegex(filterValue) : filterValue)?.test(itemValue) ?? false;
209
- return false;
210
- default: return false;
211
- }
212
- }
213
- /**
214
- * Check if item matches a single filter condition
215
- * Supports nested paths (e.g., "owner.id", "metadata.status")
216
- */
217
- matchesFilter(item, key, filterValue) {
218
- const itemValue = key.includes(".") ? this.getNestedValue(item, key) : item[key];
219
- if (filterValue && typeof filterValue === "object" && !Array.isArray(filterValue)) {
220
- if (Object.keys(filterValue).some((op) => op.startsWith("$"))) {
221
- for (const [operator, opValue] of Object.entries(filterValue)) if (!this.matchesOperator(itemValue, operator, opValue)) return false;
222
- return true;
223
- }
224
- }
225
- if (Array.isArray(itemValue)) return itemValue.some((v) => String(v) === String(filterValue));
226
- return String(itemValue) === String(filterValue);
227
- }
228
- /**
229
- * Built-in MongoDB-style policy filter matching.
230
- * Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex, $and, $or
231
- */
232
- defaultMatchesPolicyFilters(item, policyFilters) {
233
- if (policyFilters.$and && Array.isArray(policyFilters.$and)) {
234
- if (!policyFilters.$and.every((condition) => {
235
- return Object.entries(condition).every(([key, value]) => {
236
- return this.matchesFilter(item, key, value);
237
- });
238
- })) return false;
239
- }
240
- if (policyFilters.$or && Array.isArray(policyFilters.$or)) {
241
- if (!policyFilters.$or.some((condition) => {
242
- return Object.entries(condition).every(([key, value]) => {
243
- return this.matchesFilter(item, key, value);
244
- });
245
- })) return false;
246
- }
247
- for (const [key, value] of Object.entries(policyFilters)) {
248
- if (key.startsWith("$")) continue;
249
- if (!this.matchesFilter(item, key, value)) return false;
250
- }
251
- return true;
252
- }
253
- /**
254
- * Get nested value from object using dot notation (e.g., "owner.id")
255
- * Security: Validates path against forbidden patterns to prevent prototype pollution
256
- */
257
- getNestedValue(obj, path) {
258
- if (AccessControl.FORBIDDEN_PATHS.some((p) => path.toLowerCase().includes(p))) return;
259
- const keys = path.split(".");
260
- let value = obj;
261
- for (const key of keys) {
262
- if (value == null) return void 0;
263
- if (AccessControl.FORBIDDEN_PATHS.includes(key.toLowerCase())) return;
264
- value = value[key];
265
- }
266
- return value;
267
- }
268
- /**
269
- * Create a safe RegExp from a string, guarding against ReDoS.
270
- * Returns null if the pattern is invalid or dangerous.
271
- */
272
- static safeRegex(pattern) {
273
- if (pattern.length > 200) return null;
274
- if (AccessControl.DANGEROUS_REGEX.test(pattern)) return null;
275
- try {
276
- return new RegExp(pattern);
277
- } catch {
278
- return null;
279
- }
280
- }
281
241
  };
282
242
  var BodySanitizer = class {
283
243
  schemaOptions;
@@ -296,10 +256,13 @@ var BodySanitizer = class {
296
256
  sanitize(body, _operation, req, meta) {
297
257
  let sanitized = { ...body };
298
258
  for (const field of SYSTEM_FIELDS) delete sanitized[field];
259
+ const scopeForRules = req ? (meta ?? req.metadata)?._scope ?? PUBLIC_SCOPE : void 0;
260
+ const scopeIsElevated = scopeForRules ? isElevated(scopeForRules) : false;
299
261
  const fieldRules = this.schemaOptions.fieldRules ?? {};
300
262
  for (const [field, rules] of Object.entries(fieldRules)) {
301
- if (rules.systemManaged || rules.readonly) delete sanitized[field];
302
- if (_operation === "update" && (rules.immutable || rules.immutableAfterCreate)) delete sanitized[field];
263
+ const bypass = Boolean(rules.preserveForElevated) && scopeIsElevated;
264
+ if ((rules.systemManaged || rules.readonly) && !bypass) delete sanitized[field];
265
+ if (_operation === "update" && (rules.immutable || rules.immutableAfterCreate) && !bypass) delete sanitized[field];
303
266
  }
304
267
  if (req) {
305
268
  const arcContext = meta ?? req.metadata;
@@ -336,6 +299,7 @@ var QueryResolver = class {
336
299
  queryParser;
337
300
  maxLimit;
338
301
  defaultLimit;
302
+ /** `undefined` means "no default sort" (caller passed `false`). */
339
303
  defaultSort;
340
304
  schemaOptions;
341
305
  tenantField;
@@ -343,7 +307,7 @@ var QueryResolver = class {
343
307
  this.queryParser = config.queryParser ?? getDefaultQueryParser();
344
308
  this.maxLimit = config.maxLimit ?? 100;
345
309
  this.defaultLimit = config.defaultLimit ?? 20;
346
- this.defaultSort = config.defaultSort ?? "-createdAt";
310
+ this.defaultSort = config.defaultSort === false ? void 0 : config.defaultSort ?? "-createdAt";
347
311
  this.schemaOptions = config.schemaOptions ?? {};
348
312
  this.tenantField = config.tenantField !== void 0 ? config.tenantField : DEFAULT_TENANT_FIELD;
349
313
  }
@@ -474,6 +438,7 @@ var BaseController = class {
474
438
  queryParser;
475
439
  maxLimit;
476
440
  defaultLimit;
441
+ /** `undefined` means "no default sort" (caller passed `false`). */
477
442
  defaultSort;
478
443
  resourceName;
479
444
  tenantField;
@@ -493,7 +458,7 @@ var BaseController = class {
493
458
  this.queryParser = options.queryParser ?? getDefaultQueryParser();
494
459
  this.maxLimit = options.maxLimit ?? 100;
495
460
  this.defaultLimit = options.defaultLimit ?? 20;
496
- this.defaultSort = options.defaultSort ?? "-createdAt";
461
+ this.defaultSort = options.defaultSort === false ? void 0 : options.defaultSort ?? "-createdAt";
497
462
  this.resourceName = options.resourceName;
498
463
  this.tenantField = options.tenantField !== void 0 ? options.tenantField : DEFAULT_TENANT_FIELD;
499
464
  this.idField = options.idField ?? repository?.idField ?? "_id";
@@ -513,7 +478,7 @@ var BaseController = class {
513
478
  queryParser: this.queryParser,
514
479
  maxLimit: this.maxLimit,
515
480
  defaultLimit: this.defaultLimit,
516
- defaultSort: this.defaultSort,
481
+ defaultSort: options.defaultSort,
517
482
  schemaOptions: this.schemaOptions,
518
483
  tenantField: this.tenantField
519
484
  });
@@ -533,6 +498,48 @@ var BaseController = class {
533
498
  getTenantField() {
534
499
  return this.tenantField || void 0;
535
500
  }
501
+ /**
502
+ * Build top-level tenant options to thread into the repository call.
503
+ *
504
+ * **Why this exists:** repo plugins (e.g. `@classytic/mongokit`'s
505
+ * `multiTenantPlugin`) read tenant scope from the TOP of the repository
506
+ * operation context — `context.organizationId`, not `context.data.organizationId`
507
+ * or `context.context.organizationId`. Without this stamping, a tenant-scoped
508
+ * repository throws `Missing 'organizationId' in context for '<op>'` even
509
+ * though arc already injected the tenant into the request body.
510
+ *
511
+ * **What this returns:**
512
+ * - `{ [tenantField]: orgId }` when the resource is tenant-scoped and the
513
+ * caller's scope carries an org ID (member, service key bound to an org,
514
+ * elevated admin impersonating an org).
515
+ * - `{}` otherwise — platform-universal resources (`tenantField: false`),
516
+ * public/anonymous reads, elevated admins without an org target.
517
+ *
518
+ * **Call sites:** every `this.repository.*` CRUD entry — `create`, `update`,
519
+ * `delete`, `getAll` (via list), plus merged into `QueryOptions` for the
520
+ * access-controlled read path (`accessControl.fetchDetailed` → `getById`/`getOne`).
521
+ *
522
+ * **Name of the field:** uses the instance's own `tenantField` configuration
523
+ * (default `organizationId`). Matches mongokit's `multiTenantPlugin` default
524
+ * `contextKey` so host apps don't need to override either side.
525
+ *
526
+ * Multi-field tenancy (via `multiTenantPreset({ tenantFields: [...] })`)
527
+ * resolves additional fields at middleware time and stashes them on
528
+ * `_tenantFields` — {@link tenantRepoOptions} merges those in too.
529
+ */
530
+ tenantRepoOptions(req) {
531
+ const out = {};
532
+ if (this.tenantField) {
533
+ const scope = this.meta(req)?._scope;
534
+ const orgId = scope ? getOrgId(scope) : void 0;
535
+ if (orgId) out[this.tenantField] = orgId;
536
+ }
537
+ const presetFields = req._tenantFields;
538
+ if (presetFields && typeof presetFields === "object") {
539
+ for (const [key, value] of Object.entries(presetFields)) if (value != null && out[key] == null) out[key] = value;
540
+ }
541
+ return out;
542
+ }
536
543
  /** Extract typed Arc internal metadata from request */
537
544
  meta(req) {
538
545
  return req.metadata;
@@ -655,7 +662,11 @@ var BaseController = class {
655
662
  /** Execute list query through hooks (extracted for cache revalidation) */
656
663
  async executeListQuery(options, req) {
657
664
  const hooks = this.getHooks(req);
658
- const repoGetAll = async () => this.repository.getAll(options);
665
+ const getAllParams = {
666
+ ...options,
667
+ ...this.tenantRepoOptions(req)
668
+ };
669
+ const repoGetAll = async () => this.repository.getAll(getAllParams);
659
670
  return hooks && this.resourceName ? await hooks.executeAround(this.resourceName, "list", options, repoGetAll, {
660
671
  user: req.user,
661
672
  context: this.meta(req)
@@ -668,7 +679,10 @@ var BaseController = class {
668
679
  error: "ID parameter is required",
669
680
  status: 400
670
681
  };
671
- const options = this.queryResolver.resolve(req, this.meta(req));
682
+ const options = {
683
+ ...this.queryResolver.resolve(req, this.meta(req)),
684
+ ...this.tenantRepoOptions(req)
685
+ };
672
686
  const cacheConfig = this.resolveCacheConfig("byId");
673
687
  const qc = req.server?.queryCache;
674
688
  if (cacheConfig && qc) {
@@ -764,7 +778,8 @@ var BaseController = class {
764
778
  }
765
779
  const repoCreate = async () => this.repository.create(processedData, {
766
780
  user,
767
- context: arcContext
781
+ context: arcContext,
782
+ ...this.tenantRepoOptions(req)
768
783
  });
769
784
  let item;
770
785
  if (hooks && this.resourceName) {
@@ -796,7 +811,7 @@ var BaseController = class {
796
811
  const user = req.user;
797
812
  const userId = getUserId(user);
798
813
  if (userId) data.updatedBy = userId;
799
- const { doc: existing, reason: updateReason } = await this.accessControl.fetchDetailed(id, req, this.repository);
814
+ const { doc: existing, reason: updateReason } = await this.accessControl.fetchDetailed(id, req, this.repository, this.tenantRepoOptions(req));
800
815
  if (!existing) return this.notFoundResponse(updateReason);
801
816
  if (!this.accessControl.checkOwnership(existing, req)) return {
802
817
  success: false,
@@ -829,7 +844,8 @@ var BaseController = class {
829
844
  }
830
845
  const repoUpdate = async () => this.repository.update(repoId, processedData, {
831
846
  user,
832
- context: arcContext
847
+ context: arcContext,
848
+ ...this.tenantRepoOptions(req)
833
849
  });
834
850
  let item;
835
851
  if (hooks && this.resourceName) {
@@ -867,7 +883,7 @@ var BaseController = class {
867
883
  };
868
884
  const arcContext = this.meta(req);
869
885
  const user = req.user;
870
- const { doc: existing, reason: deleteReason } = await this.accessControl.fetchDetailed(id, req, this.repository);
886
+ const { doc: existing, reason: deleteReason } = await this.accessControl.fetchDetailed(id, req, this.repository, this.tenantRepoOptions(req));
871
887
  if (!existing) return this.notFoundResponse(deleteReason);
872
888
  if (!this.accessControl.checkOwnership(existing, req)) return {
873
889
  success: false,
@@ -898,6 +914,7 @@ var BaseController = class {
898
914
  const repoDelete = async () => this.repository.delete(repoId, {
899
915
  user,
900
916
  context: arcContext,
917
+ ...this.tenantRepoOptions(req),
901
918
  ...deleteMode ? { mode: deleteMode } : {}
902
919
  });
903
920
  let result;
@@ -933,7 +950,10 @@ var BaseController = class {
933
950
  async getBySlug(req) {
934
951
  const slugField = this._presetFields.slugField ?? "slug";
935
952
  const slug = req.params[slugField] ?? req.params.slug;
936
- const options = this.queryResolver.resolve(req, this.meta(req));
953
+ const options = {
954
+ ...this.queryResolver.resolve(req, this.meta(req)),
955
+ ...this.tenantRepoOptions(req)
956
+ };
937
957
  const repo = this.repository;
938
958
  let item = null;
939
959
  if (repo.getBySlug) item = await repo.getBySlug(slug, options);
@@ -0,0 +1,22 @@
1
+ //#region src/core/actionPermissions.ts
2
+ /**
3
+ * Return the effective `PermissionCheck` for a single action, or `undefined`
4
+ * when the resource declares no gate at any level.
5
+ *
6
+ * Callers decide what "no gate" means:
7
+ * - HTTP: boot-time throw in `normalizeActionsToRouterConfig`.
8
+ * - MCP: treated as allow (legacy) — but the HTTP fallback now fills the
9
+ * gap when `permissions.update` is set, so the MCP hole closes too.
10
+ * - OpenAPI: docs advertise the endpoint as unauthenticated.
11
+ */
12
+ function resolveActionPermission(input) {
13
+ const { action, resourcePermissions, resourceActionPermissions, globalAuth } = input;
14
+ const explicit = typeof action !== "function" && action.permissions ? action.permissions : void 0;
15
+ if (explicit) return explicit;
16
+ if (resourceActionPermissions) return resourceActionPermissions;
17
+ if (globalAuth) return globalAuth;
18
+ const updateFallback = resourcePermissions?.update;
19
+ if (updateFallback) return updateFallback;
20
+ }
21
+ //#endregion
22
+ export { resolveActionPermission as t };
@@ -1,3 +1,3 @@
1
- import { At as SchemaMetadata, Dt as FieldMetadata, Et as DataAdapter, Ot as RelationMetadata, jt as ValidationResult, kt as RepositoryLike, wt as AdapterFactory } from "../index-Cl0uoKd5.mjs";
2
- import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, d as DrizzleAdapterOptions, f as createDrizzleAdapter, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter, u as DrizzleAdapter } from "../index-DStwgFUK.mjs";
1
+ import { _n as RepositoryLike, fn as AdapterFactory, gn as RelationMetadata, hn as FieldMetadata, mn as DataAdapter, vn as SchemaMetadata, yn as ValidationResult } from "../index-BGbpGVyM.mjs";
2
+ import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, d as DrizzleAdapterOptions, f as createDrizzleAdapter, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter, u as DrizzleAdapter } from "../index-BziRPS4H.mjs";
3
3
  export { AdapterFactory, DataAdapter, DrizzleAdapter, DrizzleAdapterOptions, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createDrizzleAdapter, createMongooseAdapter, createPrismaAdapter };
@@ -1,5 +1,5 @@
1
- import { d as UserBase } from "../fields-Lo1VUDpt.mjs";
2
- import { kt as RepositoryLike } from "../index-Cl0uoKd5.mjs";
1
+ import { _n as RepositoryLike } from "../index-BGbpGVyM.mjs";
2
+ import { d as UserBase } from "../fields-C8Y0XLAu.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/audit/stores/interface.d.ts
@@ -1,12 +1,13 @@
1
1
  import fp from "fastify-plugin";
2
+ import { and, anyOf, eq, gte, lt, lte } from "@classytic/repo-core/filter";
2
3
  //#region src/audit/repository-audit-adapter.ts
3
4
  function repositoryAsAuditStore(repository) {
5
+ const idField = repository.idField ?? "_id";
4
6
  return {
5
7
  name: "repository",
6
8
  async log(entry) {
7
9
  const doc = {
8
- _id: entry.id,
9
- id: entry.id,
10
+ [idField]: entry.id,
10
11
  resource: entry.resource,
11
12
  documentId: entry.documentId,
12
13
  action: entry.action,
@@ -25,35 +26,32 @@ function repositoryAsAuditStore(repository) {
25
26
  },
26
27
  async purgeOlderThan(cutoff) {
27
28
  if (!repository.deleteMany) return 0;
28
- return (await repository.deleteMany({ timestamp: { $lt: cutoff } })).deletedCount ?? 0;
29
+ return (await repository.deleteMany(lt("timestamp", cutoff))).deletedCount ?? 0;
29
30
  },
30
31
  async query(opts = {}) {
31
32
  if (!repository.getAll) throw new Error("auditPlugin: repository.getAll is required for query(). It's on repo-core's MinimalRepo floor — every kit (mongokit, sqlitekit, custom) implements it.");
32
- const filter = {};
33
- if (opts.resource) filter.resource = opts.resource;
34
- if (opts.documentId) filter.documentId = opts.documentId;
35
- if (opts.userId) filter.userId = opts.userId;
36
- if (opts.organizationId) filter.organizationId = opts.organizationId;
33
+ const clauses = [];
34
+ if (opts.resource) clauses.push(eq("resource", opts.resource));
35
+ if (opts.documentId) clauses.push(eq("documentId", opts.documentId));
36
+ if (opts.userId) clauses.push(eq("userId", opts.userId));
37
+ if (opts.organizationId) clauses.push(eq("organizationId", opts.organizationId));
37
38
  if (opts.action) {
38
39
  const actions = Array.isArray(opts.action) ? opts.action : [opts.action];
39
- filter.action = actions.length === 1 ? actions[0] : { $in: actions };
40
- }
41
- if (opts.from || opts.to) {
42
- const range = {};
43
- if (opts.from) range.$gte = opts.from;
44
- if (opts.to) range.$lte = opts.to;
45
- filter.timestamp = range;
40
+ clauses.push(actions.length === 1 ? eq("action", actions[0]) : anyOf("action", actions));
46
41
  }
42
+ if (opts.from) clauses.push(gte("timestamp", opts.from));
43
+ if (opts.to) clauses.push(lte("timestamp", opts.to));
44
+ const filters = clauses.length > 0 ? and(...clauses) : void 0;
47
45
  const limit = opts.limit ?? 100;
48
46
  const page = Math.floor((opts.offset ?? 0) / limit) + 1;
49
47
  const result = await repository.getAll({
50
- filters: filter,
48
+ ...filters ? { filters } : {},
51
49
  sort: { timestamp: -1 },
52
50
  page,
53
51
  limit
54
52
  });
55
53
  return (Array.isArray(result) ? result : result.docs ?? []).map((d) => ({
56
- id: String(d._id ?? d.id ?? ""),
54
+ id: String(d[idField] ?? ""),
57
55
  resource: d.resource ?? "",
58
56
  documentId: d.documentId ?? "",
59
57
  action: d.action ?? "create",
@@ -1,7 +1,7 @@
1
- import { c as PermissionCheck } from "../fields-Lo1VUDpt.mjs";
2
- import { A as AuthHelpers, j as AuthPluginOptions } from "../index-Cl0uoKd5.mjs";
3
- import { t as ExternalOpenApiPaths } from "../externalPaths-BQ8QijNH.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-BkzVU8h2.mjs";
1
+ import { Ut as AuthHelpers, Wt as AuthPluginOptions } from "../index-BGbpGVyM.mjs";
2
+ import { c as PermissionCheck } from "../fields-C8Y0XLAu.mjs";
3
+ import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.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-D-oNWHz3.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
- import { n as normalizeRoles, t as getUserRoles } from "../types-DV9WDfeg.mjs";
2
- import { t as ArcError } from "../errors-D5c-5BJL.mjs";
3
- import { _ as requireTeamMembership, m as requireOrgRole, p as requireOrgMembership } from "../permissions-Dk6mshja.mjs";
4
- import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-BBRVhjQN.mjs";
1
+ import { n as normalizeRoles, t as getUserRoles } from "../types-D57iXYb8.mjs";
2
+ import { t as ArcError } from "../errors-BqdUDja_.mjs";
3
+ import { _ as requireTeamMembership, m as requireOrgRole, p as requireOrgMembership } from "../permissions-wkqRwicB.mjs";
4
+ import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi--rdY15Ld.mjs";
5
5
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/auth/authPlugin.ts
@@ -684,7 +684,7 @@ function createBetterAuthAdapter(options) {
684
684
  if (!fastify.hasDecorator("authenticate")) fastify.decorate("authenticate", authenticate);
685
685
  if (!fastify.hasDecorator("optionalAuthenticate")) fastify.decorate("optionalAuthenticate", optionalAuthenticate);
686
686
  if (!extractedOpenApi && openapiOpt !== false && auth.api && typeof auth.api === "object") {
687
- const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-BBRVhjQN.mjs").then((n) => n.t);
687
+ const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi--rdY15Ld.mjs").then((n) => n.t);
688
688
  extractedOpenApi = extractBetterAuthOpenApi(auth.api, {
689
689
  basePath,
690
690
  userFields
@@ -1,4 +1,4 @@
1
- import { i as SessionData, s as SessionStore } from "../sessionManager-BkzVU8h2.mjs";
1
+ import { i as SessionData, s as SessionStore } from "../sessionManager-D-oNWHz3.mjs";
2
2
 
3
3
  //#region src/auth/redis-session.d.ts
4
4
  /** Minimal Redis client interface — compatible with ioredis */
@@ -1,5 +1,5 @@
1
- import { n as CacheStats, r as CacheStore, t as CacheLogger } from "../interface-D218ikEo.mjs";
2
- import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-BKbWjgDG.mjs";
1
+ import { n as CacheStats, r as CacheStore, t as CacheLogger } from "../interface-yhyb_pLY.mjs";
2
+ import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-Dumka73q.mjs";
3
3
 
4
4
  //#region src/cache/keys.d.ts
5
5
  /**
@@ -1,6 +1,6 @@
1
- import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-qcD-TVJl.mjs";
2
- import { t as MemoryCacheStore } from "../memory-B5Amv9A1.mjs";
3
- import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-DQCEfJis.mjs";
1
+ import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-nWQGUTu1.mjs";
2
+ import { t as MemoryCacheStore } from "../memory-DqI-449b.mjs";
3
+ import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-ChLNZvFT.mjs";
4
4
  //#region src/cache/redis.ts
5
5
  /**
6
6
  * Redis-backed cache store.
@@ -1,5 +1,5 @@
1
- import { t as ResourceRegistry } from "../../ResourceRegistry-BPd6NQDm.mjs";
2
- import { t as buildOpenApiSpec } from "../../openapi-B5F8AddX.mjs";
1
+ import { t as ResourceRegistry } from "../../ResourceRegistry-CcN2LVrc.mjs";
2
+ import { t as buildOpenApiSpec } from "../../openapi-DpNpqBmo.mjs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { mkdirSync, writeFileSync } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { t as pluralize } from "../../pluralize-A0tWEl1K.mjs";
1
+ import { t as pluralize } from "../../pluralize-CWP6MB39.mjs";
2
2
  import { join } from "node:path";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  //#region src/cli/commands/generate.ts
@@ -102,7 +102,7 @@ async function installDependencies(projectPath, config, pm) {
102
102
  ];
103
103
  if (config.auth === "better-auth") deps.push("better-auth@^1.6.0", "mongodb@latest");
104
104
  else deps.push("@fastify/jwt@latest", "bcryptjs@latest");
105
- if (config.adapter === "mongokit") deps.push("@classytic/mongokit@^3.10.2", "@classytic/repo-core@^0.1.0", "mongoose@^9.4.1");
105
+ if (config.adapter === "mongokit") deps.push("@classytic/mongokit@^3.11.0", "@classytic/repo-core@^0.2.0", "mongoose@^9.4.1");
106
106
  const devDeps = ["vitest@latest", "pino-pretty@latest"];
107
107
  if (config.typescript) devDeps.push("typescript@latest", "@types/node@latest", "tsx@latest");
108
108
  const installCmd = getInstallCommand(pm, deps, false);
@@ -1,4 +1,4 @@
1
- import { t as ResourceRegistry } from "../../ResourceRegistry-BPd6NQDm.mjs";
1
+ import { t as ResourceRegistry } from "../../ResourceRegistry-CcN2LVrc.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  //#region src/cli/commands/introspect.ts