@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.
- package/README.md +1 -1
- package/dist/{BaseController-CbKKIflT.mjs → BaseController-DVNKvoX4.mjs} +151 -131
- package/dist/actionPermissions-TUVR3uiZ.mjs +22 -0
- package/dist/adapters/index.d.mts +2 -2
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +15 -17
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +5 -5
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/context/index.d.mts +58 -0
- package/dist/context/index.mjs +2 -0
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{core-CcR01lup.mjs → core-3MWJosCH.mjs} +139 -91
- package/dist/{createApp-BuvPma24.mjs → createApp-BwnEAO2h.mjs} +54 -20
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-C7hgL_aI.mjs → elevation-Dci0AYLT.mjs} +2 -2
- package/dist/errorHandler-2ii4RIYr.d.mts +114 -0
- package/dist/{errorHandler-Bb49BvPD.mjs → errorHandler-CSxe7KIM.mjs} +1 -1
- package/dist/{eventPlugin-DCUjuiQT.mjs → eventPlugin-ByU4Cv0e.mjs} +1 -1
- package/dist/{eventPlugin-CxWgpd6K.d.mts → eventPlugin-D1ThQ1Pp.d.mts} +1 -1
- package/dist/events/index.d.mts +4 -4
- package/dist/events/index.mjs +69 -51
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-Lo1VUDpt.d.mts → fields-C8Y0XLAu.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/index.mjs +38 -27
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-Cl0uoKd5.d.mts → index-BGbpGVyM.d.mts} +2362 -2155
- package/dist/{index-DStwgFUK.d.mts → index-BziRPS4H.d.mts} +1 -1
- package/dist/{index-ChIw3776.d.mts → index-C_Noptz-.d.mts} +3 -3
- package/dist/{index-8qw4y6ff.d.mts → index-EqQN6p0W.d.mts} +3 -3
- package/dist/index.d.mts +7 -219
- package/dist/index.mjs +8 -128
- 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/logger/index.d.mts +81 -0
- package/dist/{logger-DLg8-Ueg.mjs → logger/index.mjs} +1 -6
- package/dist/middleware/index.d.mts +109 -0
- package/dist/middleware/index.mjs +70 -0
- package/dist/multipartBody-CUQGVlM_.mjs +123 -0
- package/dist/{openapi-B5F8AddX.mjs → openapi-DpNpqBmo.mjs} +9 -7
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-Dk6mshja.mjs → permissions-wkqRwicB.mjs} +2 -2
- package/dist/pipe-CGJxqDGx.mjs +62 -0
- package/dist/pipeline/index.d.mts +62 -0
- package/dist/pipeline/index.mjs +53 -0
- package/dist/plugins/index.d.mts +25 -5
- package/dist/plugins/index.mjs +9 -9
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +255 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +42 -8
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-fLJVXdVn.mjs → presets-CrwOvuXI.mjs} +1 -1
- package/dist/{queryCachePlugin-DQCEfJis.mjs → queryCachePlugin-ChLNZvFT.mjs} +2 -2
- package/dist/{queryCachePlugin-BKbWjgDG.d.mts → queryCachePlugin-Dumka73q.d.mts} +1 -1
- package/dist/{queryParser-DBqBB6AC.mjs → queryParser-NR__Qiju.mjs} +68 -1
- package/dist/{redis-DqyeggCa.d.mts → redis-MXLp1oOf.d.mts} +1 -1
- package/dist/{redis-stream-CakIQmwR.d.mts → redis-stream-bkO88VHx.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{requestContext-xHIKedG6.mjs → requestContext-C38GskNt.mjs} +1 -1
- package/dist/{resourceToTools-BElv3xPT.mjs → resourceToTools-BhF3JV5p.mjs} +8 -3
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-yBCgOLGu.mjs → sse-D8UeDwis.mjs} +1 -1
- package/dist/{store-helpers-ZCSMJJAX.mjs → store-helpers-DYYUQbQN.mjs} +4 -0
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +11 -2
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-Btdda02s.d.mts → types-CVKBssX5.d.mts} +1 -1
- package/dist/{types-Co8k3NyS.d.mts → types-CVdgPXBW.d.mts} +22 -9
- package/dist/utils/index.d.mts +73 -3
- package/dist/utils/index.mjs +4 -4
- package/dist/{utils-B2fNOD_i.mjs → utils-LMwVidKy.mjs} +20 -2
- package/dist/versioning-CeUXHfjw.d.mts +117 -0
- package/package.json +22 -6
- package/skills/arc/SKILL.md +1 -1
- package/dist/errorHandler-DRQ3EqfL.d.mts +0 -218
- package/dist/filesUpload-t21LS-py.mjs +0 -377
- /package/dist/{EventTransport-CUw5NNWe.d.mts → EventTransport-CfVEGaEl.d.mts} +0 -0
- /package/dist/{HookSystem-BNYKnrXF.mjs → HookSystem-BjFu7zf1.mjs} +0 -0
- /package/dist/{ResourceRegistry-BPd6NQDm.mjs → ResourceRegistry-CcN2LVrc.mjs} +0 -0
- /package/dist/{betterAuthOpenApi-BBRVhjQN.mjs → betterAuthOpenApi--rdY15Ld.mjs} +0 -0
- /package/dist/{caching-CBpK_SCM.mjs → caching-3h93rkJM.mjs} +0 -0
- /package/dist/{createActionRouter-Bp_5c_2b.mjs → createActionRouter-C8UUB3Px.mjs} +0 -0
- /package/dist/{elevation-C5SwtkAn.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
- /package/dist/{errors-CCSsMpXE.d.mts → errors-BI8kEKsO.d.mts} +0 -0
- /package/dist/{errors-D5c-5BJL.mjs → errors-BqdUDja_.mjs} +0 -0
- /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
- /package/dist/{fields-bxkeltzz.mjs → fields-CTMWOUDt.mjs} +0 -0
- /package/dist/{interface-CSbZdv_3.d.mts → interface-B-pe8fhj.d.mts} +0 -0
- /package/dist/{interface-D218ikEo.d.mts → interface-yhyb_pLY.d.mts} +0 -0
- /package/dist/{keys-qcD-TVJl.mjs → keys-nWQGUTu1.mjs} +0 -0
- /package/dist/{loadResources-BAzJItAJ.mjs → loadResources-Bksk8ydA.mjs} +0 -0
- /package/dist/{memory-B5Amv9A1.mjs → memory-DqI-449b.mjs} +0 -0
- /package/dist/{metrics-DuhiSEZI.mjs → metrics-TuOmguhi.mjs} +0 -0
- /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
- /package/dist/{registry-B3lRFBWo.mjs → registry-B0Wl7uVV.mjs} +0 -0
- /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
- /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
- /package/dist/{storage-CVk_SEn2.d.mts → storage-BwGQXUpd.d.mts} +0 -0
- /package/dist/{tracing-65B51Dw3.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
- /package/dist/{types-Csi3FLfq.mjs → types-CDnTEpga.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{types-BD85MlEK.d.mts → types-tgR4Pt8F.d.mts} +0 -0
- /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
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
-
|
|
11
|
+
const log = arcLog("access-control");
|
|
12
|
+
var AccessControl = class {
|
|
11
13
|
tenantField;
|
|
12
14
|
idField;
|
|
13
15
|
_adapterMatchesFilter;
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
44
|
-
*
|
|
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
|
-
*
|
|
47
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
302
|
-
if (
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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-
|
|
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 };
|
package/dist/audit/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
package/dist/audit/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
33
|
-
if (opts.resource)
|
|
34
|
-
if (opts.documentId)
|
|
35
|
-
if (opts.userId)
|
|
36
|
-
if (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
|
-
|
|
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:
|
|
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
|
|
54
|
+
id: String(d[idField] ?? ""),
|
|
57
55
|
resource: d.resource ?? "",
|
|
58
56
|
documentId: d.documentId ?? "",
|
|
59
57
|
action: d.action ?? "create",
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { t as ExternalOpenApiPaths } from "../externalPaths-
|
|
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-
|
|
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
|
package/dist/auth/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as normalizeRoles, t as getUserRoles } from "../types-
|
|
2
|
-
import { t as ArcError } from "../errors-
|
|
3
|
-
import { _ as requireTeamMembership, m as requireOrgRole, p as requireOrgMembership } from "../permissions-
|
|
4
|
-
import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi
|
|
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
|
|
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-
|
|
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 */
|
package/dist/cache/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as CacheStats, r as CacheStore, t as CacheLogger } from "../interface-
|
|
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-
|
|
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
|
/**
|
package/dist/cache/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-
|
|
2
|
-
import { t as MemoryCacheStore } from "../memory-
|
|
3
|
-
import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-
|
|
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-
|
|
2
|
-
import { t as buildOpenApiSpec } from "../../openapi-
|
|
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-
|
|
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.
|
|
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-
|
|
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
|