@classytic/arc 2.16.1 → 2.17.1
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 +22 -2
- package/dist/{BaseController-Vk8I7C0x.mjs → BaseController-CgHomZ4d.mjs} +5 -47
- package/dist/audit/index.mjs +2 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.mjs +2 -2
- package/dist/auth/redis-session.mjs +28 -2
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +3 -3
- package/dist/{core-wKeGXn38.mjs → core-D8tpQ4ZL.mjs} +174 -31
- package/dist/{createActionRouter-eiOFtnC3.mjs → createActionRouter-BHUMb1-y.mjs} +5 -2
- package/dist/{createAggregationRouter-C4unhBnV.mjs → createAggregationRouter-BYVvhfc5.mjs} +1 -1
- package/dist/{createApp-DdPBtmzp.mjs → createApp-CzCEEsEm.mjs} +2 -2
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-Dci0AYLT.mjs → elevation-6mfTLBC-.mjs} +1 -1
- package/dist/{errorHandler-DNPzqaTT.mjs → errorHandler-wQejMBT3.mjs} +45 -4
- package/dist/events/transports/redis-stream-entry.mjs +34 -1
- package/dist/events/transports/redis.mjs +17 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/{fastify-augmentation-CJm0vh-l.d.mts → fastify-augmentation-7LIR0nDv.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +20 -4
- package/dist/{index-DBDgy6Hw.d.mts → index-C1JSBan-.d.mts} +177 -1
- package/dist/{index-CC_pgZjY.d.mts → index-CruZir1o.d.mts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +5 -5
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/jobs.d.mts +36 -1
- package/dist/integrations/jobs.mjs +82 -9
- package/dist/integrations/mcp/index.d.mts +65 -3
- package/dist/integrations/mcp/index.mjs +93 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/websocket.mjs +2 -1
- package/dist/{mcpPlugin-Dbgz9_Rj.mjs → mcpPlugin-C6FViZXw.mjs} +111 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/{multipartBody-BOvVSVCD.mjs → multipartBody-VjVWmZl8.mjs} +2 -1
- package/dist/{openapi-BRnd3pT7.mjs → openapi-DKDs9NfD.mjs} +2 -2
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-CTxMrreC.mjs → permissions-B1Qy55fg.mjs} +169 -169
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +3 -3
- package/dist/plugins/index.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +1 -1
- package/dist/presets/filesUpload.mjs +2 -2
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/search.d.mts +1 -1
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-C9BE6WaZ.mjs → presets-BYbPobbF.mjs} +5 -3
- package/dist/registry/index.d.mts +1 -1
- package/dist/{routerShared-ClV4cCaa.mjs → routerShared-nCyJGOKX.mjs} +127 -4
- package/dist/scope/index.mjs +2 -2
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/types/index.d.mts +1 -1
- package/dist/{types-Dw--SyTq.d.mts → types-BRmS8TNa.d.mts} +1 -1
- package/dist/{types-BD0BCdf6.d.mts → types-CoMjNmkF.d.mts} +12 -1
- package/dist/utils/index.d.mts +79 -2
- package/dist/utils/index.mjs +2 -2
- package/dist/{utils-DPXZnZUx.mjs → utils-BMQK8mRl.mjs} +111 -2
- package/package.json +1 -1
- package/skills/arc/SKILL.md +43 -1
- package/skills/arc/references/api-reference.md +2 -0
- package/skills/arc/references/mcp.md +30 -1
- /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
2
|
import { C as isService, S as isOrgInScope, a as getDPoPJkt, b as isElevated, d as getScopeContext, f as getScopeContextMap, g as getUserId, m as getTeamId, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-Bi0r0vjG.mjs";
|
|
3
|
-
import { t as getUserRoles } from "./types-
|
|
3
|
+
import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
|
|
4
4
|
import { t as MemoryCacheStore } from "./memory-QOLe11D5.mjs";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
//#region src/permissions/applyPermissionResult.ts
|
|
@@ -99,6 +99,173 @@ async function evaluateAndApplyPermission(check, context, request, reply, opts)
|
|
|
99
99
|
return true;
|
|
100
100
|
}
|
|
101
101
|
//#endregion
|
|
102
|
+
//#region src/permissions/fields.ts
|
|
103
|
+
/**
|
|
104
|
+
* Field-Level Permissions
|
|
105
|
+
*
|
|
106
|
+
* Control field visibility and writability per role.
|
|
107
|
+
* Integrated into the response path (read) and sanitization path (write).
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* import { fields, defineResource } from '@classytic/arc';
|
|
112
|
+
*
|
|
113
|
+
* const userResource = defineResource({
|
|
114
|
+
* name: 'user',
|
|
115
|
+
* adapter: userAdapter,
|
|
116
|
+
* fields: {
|
|
117
|
+
* salary: fields.visibleTo(['admin', 'hr']),
|
|
118
|
+
* internalNotes: fields.writableBy(['admin']),
|
|
119
|
+
* email: fields.redactFor(['viewer']),
|
|
120
|
+
* password: fields.hidden(),
|
|
121
|
+
* },
|
|
122
|
+
* });
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
/** Type guard for Mongoose-like documents with toObject() */
|
|
126
|
+
function isMongooseDoc(obj) {
|
|
127
|
+
return !!obj && typeof obj === "object" && "toObject" in obj && typeof obj.toObject === "function";
|
|
128
|
+
}
|
|
129
|
+
const fields = {
|
|
130
|
+
/**
|
|
131
|
+
* Field is never included in responses. Not writable via API.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* fields: { password: fields.hidden() }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
hidden() {
|
|
139
|
+
return { _type: "hidden" };
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* Field is only visible to users with specified roles.
|
|
143
|
+
* Other users don't see the field at all.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* fields: { salary: fields.visibleTo(['admin', 'hr']) }
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
visibleTo(roles) {
|
|
151
|
+
return {
|
|
152
|
+
_type: "visibleTo",
|
|
153
|
+
roles
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Field is only writable by users with specified roles.
|
|
158
|
+
* All users can still read the field. Users without the role
|
|
159
|
+
* have the field silently stripped from write operations.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* fields: { role: fields.writableBy(['admin']) }
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
writableBy(roles) {
|
|
167
|
+
return {
|
|
168
|
+
_type: "writableBy",
|
|
169
|
+
roles
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
/**
|
|
173
|
+
* Field is redacted (replaced with a placeholder) for specified roles.
|
|
174
|
+
* Other users see the real value.
|
|
175
|
+
*
|
|
176
|
+
* @param roles - Roles that see the redacted value
|
|
177
|
+
* @param redactValue - Replacement value (default: '***')
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* fields: {
|
|
182
|
+
* email: fields.redactFor(['viewer']),
|
|
183
|
+
* ssn: fields.redactFor(['basic'], '***-**-****'),
|
|
184
|
+
* }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
redactFor(roles, redactValue = "***") {
|
|
188
|
+
return {
|
|
189
|
+
_type: "redactFor",
|
|
190
|
+
roles,
|
|
191
|
+
redactValue
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Apply field-level READ permissions to a response object.
|
|
197
|
+
* Strips hidden fields, enforces visibility, and applies redaction.
|
|
198
|
+
*
|
|
199
|
+
* @param data - The response object (mutated in place for performance)
|
|
200
|
+
* @param fieldPermissions - Field permission map from resource config
|
|
201
|
+
* @param userRoles - Current user's roles (empty array for unauthenticated)
|
|
202
|
+
* @returns The filtered object
|
|
203
|
+
*/
|
|
204
|
+
function applyFieldReadPermissions(data, fieldPermissions, userRoles) {
|
|
205
|
+
if (!data || typeof data !== "object") return data;
|
|
206
|
+
const result = { ...isMongooseDoc(data) ? data.toObject() : data };
|
|
207
|
+
for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
|
|
208
|
+
case "hidden":
|
|
209
|
+
delete result[field];
|
|
210
|
+
break;
|
|
211
|
+
case "visibleTo":
|
|
212
|
+
if (!perm.roles?.some((r) => userRoles.includes(r))) delete result[field];
|
|
213
|
+
break;
|
|
214
|
+
case "redactFor":
|
|
215
|
+
if (perm.roles?.some((r) => userRoles.includes(r))) result[field] = perm.redactValue ?? "***";
|
|
216
|
+
break;
|
|
217
|
+
case "writableBy": break;
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Apply field-level WRITE permissions to request body.
|
|
223
|
+
*
|
|
224
|
+
* Returns both the filtered body and the list of denied fields. Callers are
|
|
225
|
+
* expected to reject the request when `deniedFields.length > 0` — silently
|
|
226
|
+
* stripping fields hides misconfigurations and real attacks. See
|
|
227
|
+
* `BodySanitizer` for the default policy.
|
|
228
|
+
*
|
|
229
|
+
* @param body - The request body (returns a new filtered copy)
|
|
230
|
+
* @param fieldPermissions - Field permission map from resource config
|
|
231
|
+
* @param userRoles - Current user's roles
|
|
232
|
+
*/
|
|
233
|
+
function applyFieldWritePermissions(body, fieldPermissions, userRoles) {
|
|
234
|
+
const result = { ...body };
|
|
235
|
+
const deniedFields = [];
|
|
236
|
+
for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
|
|
237
|
+
case "hidden":
|
|
238
|
+
if (field in result) {
|
|
239
|
+
deniedFields.push(field);
|
|
240
|
+
delete result[field];
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
case "writableBy":
|
|
244
|
+
if (field in result && !perm.roles?.some((r) => userRoles.includes(r))) {
|
|
245
|
+
deniedFields.push(field);
|
|
246
|
+
delete result[field];
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
body: result,
|
|
252
|
+
deniedFields
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Resolve effective roles by merging global user roles with org-level roles.
|
|
257
|
+
*
|
|
258
|
+
* Global roles come from `req.user.role` (normalized via getUserRoles()).
|
|
259
|
+
* Org roles come from `req.context.orgRoles` (set by BA adapter's org bridge).
|
|
260
|
+
*
|
|
261
|
+
* When no org context exists, returns global roles only — backward compatible.
|
|
262
|
+
*/
|
|
263
|
+
function resolveEffectiveRoles(userRoles, orgRoles) {
|
|
264
|
+
if (orgRoles.length === 0) return [...userRoles];
|
|
265
|
+
if (userRoles.length === 0) return [...orgRoles];
|
|
266
|
+
return [...new Set([...userRoles, ...orgRoles])];
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
102
269
|
//#region src/permissions/agent.ts
|
|
103
270
|
/**
|
|
104
271
|
* Agent-Auth Permission Helpers — DPoP + capability mandates for AI-agent flows
|
|
@@ -1148,173 +1315,6 @@ function createDynamicPermissionMatrix(config) {
|
|
|
1148
1315
|
};
|
|
1149
1316
|
}
|
|
1150
1317
|
//#endregion
|
|
1151
|
-
//#region src/permissions/fields.ts
|
|
1152
|
-
/**
|
|
1153
|
-
* Field-Level Permissions
|
|
1154
|
-
*
|
|
1155
|
-
* Control field visibility and writability per role.
|
|
1156
|
-
* Integrated into the response path (read) and sanitization path (write).
|
|
1157
|
-
*
|
|
1158
|
-
* @example
|
|
1159
|
-
* ```typescript
|
|
1160
|
-
* import { fields, defineResource } from '@classytic/arc';
|
|
1161
|
-
*
|
|
1162
|
-
* const userResource = defineResource({
|
|
1163
|
-
* name: 'user',
|
|
1164
|
-
* adapter: userAdapter,
|
|
1165
|
-
* fields: {
|
|
1166
|
-
* salary: fields.visibleTo(['admin', 'hr']),
|
|
1167
|
-
* internalNotes: fields.writableBy(['admin']),
|
|
1168
|
-
* email: fields.redactFor(['viewer']),
|
|
1169
|
-
* password: fields.hidden(),
|
|
1170
|
-
* },
|
|
1171
|
-
* });
|
|
1172
|
-
* ```
|
|
1173
|
-
*/
|
|
1174
|
-
/** Type guard for Mongoose-like documents with toObject() */
|
|
1175
|
-
function isMongooseDoc(obj) {
|
|
1176
|
-
return !!obj && typeof obj === "object" && "toObject" in obj && typeof obj.toObject === "function";
|
|
1177
|
-
}
|
|
1178
|
-
const fields = {
|
|
1179
|
-
/**
|
|
1180
|
-
* Field is never included in responses. Not writable via API.
|
|
1181
|
-
*
|
|
1182
|
-
* @example
|
|
1183
|
-
* ```typescript
|
|
1184
|
-
* fields: { password: fields.hidden() }
|
|
1185
|
-
* ```
|
|
1186
|
-
*/
|
|
1187
|
-
hidden() {
|
|
1188
|
-
return { _type: "hidden" };
|
|
1189
|
-
},
|
|
1190
|
-
/**
|
|
1191
|
-
* Field is only visible to users with specified roles.
|
|
1192
|
-
* Other users don't see the field at all.
|
|
1193
|
-
*
|
|
1194
|
-
* @example
|
|
1195
|
-
* ```typescript
|
|
1196
|
-
* fields: { salary: fields.visibleTo(['admin', 'hr']) }
|
|
1197
|
-
* ```
|
|
1198
|
-
*/
|
|
1199
|
-
visibleTo(roles) {
|
|
1200
|
-
return {
|
|
1201
|
-
_type: "visibleTo",
|
|
1202
|
-
roles
|
|
1203
|
-
};
|
|
1204
|
-
},
|
|
1205
|
-
/**
|
|
1206
|
-
* Field is only writable by users with specified roles.
|
|
1207
|
-
* All users can still read the field. Users without the role
|
|
1208
|
-
* have the field silently stripped from write operations.
|
|
1209
|
-
*
|
|
1210
|
-
* @example
|
|
1211
|
-
* ```typescript
|
|
1212
|
-
* fields: { role: fields.writableBy(['admin']) }
|
|
1213
|
-
* ```
|
|
1214
|
-
*/
|
|
1215
|
-
writableBy(roles) {
|
|
1216
|
-
return {
|
|
1217
|
-
_type: "writableBy",
|
|
1218
|
-
roles
|
|
1219
|
-
};
|
|
1220
|
-
},
|
|
1221
|
-
/**
|
|
1222
|
-
* Field is redacted (replaced with a placeholder) for specified roles.
|
|
1223
|
-
* Other users see the real value.
|
|
1224
|
-
*
|
|
1225
|
-
* @param roles - Roles that see the redacted value
|
|
1226
|
-
* @param redactValue - Replacement value (default: '***')
|
|
1227
|
-
*
|
|
1228
|
-
* @example
|
|
1229
|
-
* ```typescript
|
|
1230
|
-
* fields: {
|
|
1231
|
-
* email: fields.redactFor(['viewer']),
|
|
1232
|
-
* ssn: fields.redactFor(['basic'], '***-**-****'),
|
|
1233
|
-
* }
|
|
1234
|
-
* ```
|
|
1235
|
-
*/
|
|
1236
|
-
redactFor(roles, redactValue = "***") {
|
|
1237
|
-
return {
|
|
1238
|
-
_type: "redactFor",
|
|
1239
|
-
roles,
|
|
1240
|
-
redactValue
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
};
|
|
1244
|
-
/**
|
|
1245
|
-
* Apply field-level READ permissions to a response object.
|
|
1246
|
-
* Strips hidden fields, enforces visibility, and applies redaction.
|
|
1247
|
-
*
|
|
1248
|
-
* @param data - The response object (mutated in place for performance)
|
|
1249
|
-
* @param fieldPermissions - Field permission map from resource config
|
|
1250
|
-
* @param userRoles - Current user's roles (empty array for unauthenticated)
|
|
1251
|
-
* @returns The filtered object
|
|
1252
|
-
*/
|
|
1253
|
-
function applyFieldReadPermissions(data, fieldPermissions, userRoles) {
|
|
1254
|
-
if (!data || typeof data !== "object") return data;
|
|
1255
|
-
const result = { ...isMongooseDoc(data) ? data.toObject() : data };
|
|
1256
|
-
for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
|
|
1257
|
-
case "hidden":
|
|
1258
|
-
delete result[field];
|
|
1259
|
-
break;
|
|
1260
|
-
case "visibleTo":
|
|
1261
|
-
if (!perm.roles?.some((r) => userRoles.includes(r))) delete result[field];
|
|
1262
|
-
break;
|
|
1263
|
-
case "redactFor":
|
|
1264
|
-
if (perm.roles?.some((r) => userRoles.includes(r))) result[field] = perm.redactValue ?? "***";
|
|
1265
|
-
break;
|
|
1266
|
-
case "writableBy": break;
|
|
1267
|
-
}
|
|
1268
|
-
return result;
|
|
1269
|
-
}
|
|
1270
|
-
/**
|
|
1271
|
-
* Apply field-level WRITE permissions to request body.
|
|
1272
|
-
*
|
|
1273
|
-
* Returns both the filtered body and the list of denied fields. Callers are
|
|
1274
|
-
* expected to reject the request when `deniedFields.length > 0` — silently
|
|
1275
|
-
* stripping fields hides misconfigurations and real attacks. See
|
|
1276
|
-
* `BodySanitizer` for the default policy.
|
|
1277
|
-
*
|
|
1278
|
-
* @param body - The request body (returns a new filtered copy)
|
|
1279
|
-
* @param fieldPermissions - Field permission map from resource config
|
|
1280
|
-
* @param userRoles - Current user's roles
|
|
1281
|
-
*/
|
|
1282
|
-
function applyFieldWritePermissions(body, fieldPermissions, userRoles) {
|
|
1283
|
-
const result = { ...body };
|
|
1284
|
-
const deniedFields = [];
|
|
1285
|
-
for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
|
|
1286
|
-
case "hidden":
|
|
1287
|
-
if (field in result) {
|
|
1288
|
-
deniedFields.push(field);
|
|
1289
|
-
delete result[field];
|
|
1290
|
-
}
|
|
1291
|
-
break;
|
|
1292
|
-
case "writableBy":
|
|
1293
|
-
if (field in result && !perm.roles?.some((r) => userRoles.includes(r))) {
|
|
1294
|
-
deniedFields.push(field);
|
|
1295
|
-
delete result[field];
|
|
1296
|
-
}
|
|
1297
|
-
break;
|
|
1298
|
-
}
|
|
1299
|
-
return {
|
|
1300
|
-
body: result,
|
|
1301
|
-
deniedFields
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
/**
|
|
1305
|
-
* Resolve effective roles by merging global user roles with org-level roles.
|
|
1306
|
-
*
|
|
1307
|
-
* Global roles come from `req.user.role` (normalized via getUserRoles()).
|
|
1308
|
-
* Org roles come from `req.context.orgRoles` (set by BA adapter's org bridge).
|
|
1309
|
-
*
|
|
1310
|
-
* When no org context exists, returns global roles only — backward compatible.
|
|
1311
|
-
*/
|
|
1312
|
-
function resolveEffectiveRoles(userRoles, orgRoles) {
|
|
1313
|
-
if (orgRoles.length === 0) return [...userRoles];
|
|
1314
|
-
if (userRoles.length === 0) return [...orgRoles];
|
|
1315
|
-
return [...new Set([...userRoles, ...orgRoles])];
|
|
1316
|
-
}
|
|
1317
|
-
//#endregion
|
|
1318
1318
|
//#region src/permissions/roleHierarchy.ts
|
|
1319
1319
|
/**
|
|
1320
1320
|
* Create a role hierarchy from a parent → children map.
|
|
@@ -1490,4 +1490,4 @@ function readOnly(overrides) {
|
|
|
1490
1490
|
}, overrides);
|
|
1491
1491
|
}
|
|
1492
1492
|
//#endregion
|
|
1493
|
-
export {
|
|
1493
|
+
export { requireMandate as A, requireAuth as C, when as D, roles as E, applyPermissionResult as F, evaluateAndApplyPermission as I, normalizePermissionResult as L, applyFieldWritePermissions as M, fields as N, requireAgentScope as O, resolveEffectiveRoles as P, not as S, requireRoles as T, requireTeamMembership as _, presets_exports as a, anyOf as b, readOnly as c, createOrgPermissions as d, requireOrgInScope as f, requireServiceScope as g, requireScopeContext as h, ownerWithAdminBypass as i, applyFieldReadPermissions as j, requireDPoP as k, createRoleHierarchy as l, requireOrgRole as m, authenticated as n, publicRead as o, requireOrgMembership as p, fullPublic as r, publicReadAdminWrite as s, adminOnly as t, createDynamicPermissionMatrix as u, allOf as v, requireOwnership as w, denyAll as x, allowPublic as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-
|
|
1
|
+
import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-C1JSBan-.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/pipeline/guard.d.ts
|
|
4
4
|
interface GuardOptions {
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook } from "../index-
|
|
2
|
-
import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-
|
|
1
|
+
import { Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook } from "../index-C1JSBan-.mjs";
|
|
2
|
+
import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-7LIR0nDv.mjs";
|
|
3
3
|
import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-5H4930tj.mjs";
|
|
4
4
|
import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-DuqTqeqn.mjs";
|
|
5
5
|
import { t as TracingOptions } from "../tracing-bRqqUxFO.mjs";
|
|
6
|
+
import * as _$node_stream0 from "node:stream";
|
|
6
7
|
import { PaginatedResult } from "@classytic/repo-core/pagination";
|
|
7
8
|
import { FastifyInstance, FastifyPluginAsync } from "fastify";
|
|
8
|
-
import * as _$node_stream0 from "node:stream";
|
|
9
9
|
|
|
10
10
|
//#region src/plugins/createPlugin.d.ts
|
|
11
11
|
interface PluginResourceResult {
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { t as hasEvents } from "../typeGuards-BzkXkvVv.mjs";
|
|
|
5
5
|
import { t as HookSystem } from "../HookSystem-Cmf7-Etp.mjs";
|
|
6
6
|
import { t as ResourceRegistry } from "../ResourceRegistry-CzSzVK1m.mjs";
|
|
7
7
|
import { n as caching_default, t as cachingPlugin } from "../caching-8MfrrUYU.mjs";
|
|
8
|
-
import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-
|
|
8
|
+
import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-wQejMBT3.mjs";
|
|
9
9
|
import { n as metrics_default, t as metricsPlugin } from "../metrics-B-PU4-Yu.mjs";
|
|
10
10
|
import { t as replyHelpersPlugin } from "../replyHelpers-boS5Rx26.mjs";
|
|
11
11
|
import { n as sse_default, t as ssePlugin } from "../sse-jh5OqFjR.mjs";
|
|
@@ -58,7 +58,7 @@ try {
|
|
|
58
58
|
function createTracerProvider(options) {
|
|
59
59
|
if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
|
|
60
60
|
const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
61
|
-
const resolvedVersion = serviceVersion ?? "2.
|
|
61
|
+
const resolvedVersion = serviceVersion ?? "2.17.1";
|
|
62
62
|
const exporter = new OTLPTraceExporter({ url: exporterUrl });
|
|
63
63
|
const provider = new NodeTracerProvider({ resource: { attributes: {
|
|
64
64
|
"service.name": serviceName,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rt as PresetResult } from "../index-
|
|
1
|
+
import { rt as PresetResult } from "../index-C1JSBan-.mjs";
|
|
2
2
|
import { i as RequestScope } from "../types-CfapW2t8.mjs";
|
|
3
3
|
import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
|
|
4
4
|
import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-HcAgP5Ni.mjs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as NotFoundError, u as ValidationError } from "../errors-j4aJm1Wg.mjs";
|
|
2
2
|
import { c as getOrgId, g as getUserId } from "../types-Bi0r0vjG.mjs";
|
|
3
|
-
import { C as
|
|
4
|
-
import { t as multipartBody } from "../multipartBody-
|
|
3
|
+
import { C as requireAuth, y as allowPublic } from "../permissions-B1Qy55fg.mjs";
|
|
4
|
+
import { t as multipartBody } from "../multipartBody-VjVWmZl8.mjs";
|
|
5
5
|
//#region src/presets/filesUpload.ts
|
|
6
6
|
const DEFAULT_FIELD_NAME = "file";
|
|
7
7
|
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
package/dist/presets/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Kt as AnyRecord, bt as IRequestContext, d as PaginationResult, rt as PresetResult, st as ResourceConfig, yt as IControllerResponse } from "../index-
|
|
1
|
+
import { Kt as AnyRecord, bt as IRequestContext, d as PaginationResult, rt as PresetResult, st as ResourceConfig, yt as IControllerResponse } from "../index-C1JSBan-.mjs";
|
|
2
2
|
import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
|
|
3
3
|
import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
|
|
4
4
|
import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
|
package/dist/presets/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { multiTenantPreset } from "./multiTenant.mjs";
|
|
2
|
-
import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-
|
|
2
|
+
import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-BYbPobbF.mjs";
|
|
3
3
|
import { filesUploadPreset } from "./filesUpload.mjs";
|
|
4
4
|
import { searchPreset } from "./search.mjs";
|
|
5
5
|
export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dt as RouteDefinition, ft as RouteMcpConfig, ht as ControllerHandler, rt as PresetResult } from "../index-
|
|
1
|
+
import { dt as RouteDefinition, ft as RouteMcpConfig, ht as ControllerHandler, rt as PresetResult } from "../index-C1JSBan-.mjs";
|
|
2
2
|
import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/presets/search.d.ts
|
package/dist/presets/search.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { b as isElevated, n as PUBLIC_SCOPE } from "./types-Bi0r0vjG.mjs";
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as requireAuth, T as requireRoles, y as allowPublic } from "./permissions-B1Qy55fg.mjs";
|
|
3
3
|
import { multiTenantPreset } from "./presets/multiTenant.mjs";
|
|
4
4
|
//#region src/presets/ownedByUser.ts
|
|
5
5
|
/**
|
|
@@ -70,14 +70,16 @@ function softDeletePreset() {
|
|
|
70
70
|
handler: "getDeleted",
|
|
71
71
|
summary: "Get soft-deleted items",
|
|
72
72
|
permissions: permissions.list ?? requireRoles(["admin"]),
|
|
73
|
-
operation: "listDeleted"
|
|
73
|
+
operation: "listDeleted",
|
|
74
|
+
_presetSource: "softDelete"
|
|
74
75
|
}, {
|
|
75
76
|
method: "POST",
|
|
76
77
|
path: "/:id/restore",
|
|
77
78
|
handler: "restore",
|
|
78
79
|
summary: "Restore soft-deleted item",
|
|
79
80
|
permissions: permissions.update ?? requireRoles(["admin"]),
|
|
80
|
-
operation: "restore"
|
|
81
|
+
operation: "restore",
|
|
82
|
+
_presetSource: "softDelete"
|
|
81
83
|
}]
|
|
82
84
|
};
|
|
83
85
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { J as CrudRouteKey, R as RegisterOptions, V as ResourceDefinition, at as ResolvedTenantPurge, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-
|
|
1
|
+
import { J as CrudRouteKey, R as RegisterOptions, V as ResourceDefinition, at as ResolvedTenantPurge, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-C1JSBan-.mjs";
|
|
2
2
|
import { FastifyPluginAsync } from "fastify";
|
|
3
3
|
import { TenantPurgeProgress, TenantPurgeStrategy } from "@classytic/repo-core/repository";
|
|
4
4
|
|
|
@@ -1,10 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as SYSTEM_FIELDS } from "./constants-TrJVIJl0.mjs";
|
|
2
|
+
import { f as createError, r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
|
|
2
3
|
import { b as isElevated, c as getOrgId, g as getUserId, n as PUBLIC_SCOPE, x as isMember } from "./types-Bi0r0vjG.mjs";
|
|
3
4
|
import { t as requestContext } from "./requestContext-SSaaTgW8.mjs";
|
|
4
|
-
import { I as evaluateAndApplyPermission,
|
|
5
|
-
import { t as getUserRoles } from "./types-
|
|
5
|
+
import { I as evaluateAndApplyPermission, M as applyFieldWritePermissions, P as resolveEffectiveRoles, j as applyFieldReadPermissions } from "./permissions-B1Qy55fg.mjs";
|
|
6
|
+
import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
|
|
6
7
|
import { t as executePipeline } from "./pipe-DiCyvyPN.mjs";
|
|
7
8
|
import { isPaginatedResult, toCanonicalList } from "@classytic/repo-core/pagination";
|
|
9
|
+
//#region src/core/BodySanitizer.ts
|
|
10
|
+
/**
|
|
11
|
+
* BodySanitizer - Composable body sanitization logic extracted from BaseController.
|
|
12
|
+
*
|
|
13
|
+
* Strips readonly fields, system-managed fields, and applies field-level
|
|
14
|
+
* write permissions from request bodies before create/update operations.
|
|
15
|
+
*
|
|
16
|
+
* Designed to be used standalone or composed into controllers.
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_FIELD_WRITE_DENIAL_POLICY = "reject";
|
|
19
|
+
var BodySanitizer = class {
|
|
20
|
+
schemaOptions;
|
|
21
|
+
onFieldWriteDenied;
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.schemaOptions = config.schemaOptions;
|
|
24
|
+
this.onFieldWriteDenied = config.onFieldWriteDenied ?? "reject";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Strip readonly and system-managed fields from request body.
|
|
28
|
+
* Prevents clients from overwriting _id, timestamps, __v, etc.
|
|
29
|
+
*
|
|
30
|
+
* Also applies field-level write permissions when the request has
|
|
31
|
+
* field permission metadata.
|
|
32
|
+
*/
|
|
33
|
+
sanitize(body, _operation, req, meta) {
|
|
34
|
+
let sanitized = { ...body };
|
|
35
|
+
for (const field of SYSTEM_FIELDS) delete sanitized[field];
|
|
36
|
+
const scopeForRules = req ? (meta ?? req.metadata)?._scope ?? PUBLIC_SCOPE : void 0;
|
|
37
|
+
const scopeIsElevated = scopeForRules ? isElevated(scopeForRules) : false;
|
|
38
|
+
const fieldRules = this.schemaOptions.fieldRules ?? {};
|
|
39
|
+
for (const [field, rules] of Object.entries(fieldRules)) {
|
|
40
|
+
const bypass = Boolean(rules.preserveForElevated) && scopeIsElevated;
|
|
41
|
+
if ((rules.systemManaged || rules.readonly) && !bypass) delete sanitized[field];
|
|
42
|
+
if (_operation === "update" && (rules.immutable || rules.immutableAfterCreate) && !bypass) delete sanitized[field];
|
|
43
|
+
}
|
|
44
|
+
if (req) {
|
|
45
|
+
const arcContext = meta ?? req.metadata;
|
|
46
|
+
const scope = arcContext?._scope ?? PUBLIC_SCOPE;
|
|
47
|
+
if (!isElevated(scope)) {
|
|
48
|
+
const fieldPerms = arcContext?.arc?.fields;
|
|
49
|
+
if (fieldPerms) {
|
|
50
|
+
const effectiveRoles = resolveEffectiveRoles(getUserRoles(req.user), isMember(scope) ? scope.orgRoles : []);
|
|
51
|
+
const { body: filtered, deniedFields } = applyFieldWritePermissions(sanitized, fieldPerms, effectiveRoles);
|
|
52
|
+
if (deniedFields.length > 0 && this.onFieldWriteDenied === "reject") throw new ForbiddenError(`Not permitted to write field${deniedFields.length === 1 ? "" : "s"}: ${deniedFields.join(", ")}`);
|
|
53
|
+
sanitized = filtered;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return sanitized;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
8
61
|
//#region src/scope/projection.ts
|
|
9
62
|
/**
|
|
10
63
|
* Compute the request-scope projection. Returns `undefined` when no
|
|
@@ -597,5 +650,75 @@ function describeValue(v) {
|
|
|
597
650
|
if (Array.isArray(v)) return `an array of length ${v.length}`;
|
|
598
651
|
return `${typeof v} (${JSON.stringify(v).slice(0, 80)})`;
|
|
599
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Build a preHandler that enforces field-write permissions on the request body.
|
|
655
|
+
*
|
|
656
|
+
* Auto-CRUD's create/update routes get this for free via `BodySanitizer.sanitize()`
|
|
657
|
+
* inside `BaseController`. Custom routes (`config.routes`, presets, action endpoints)
|
|
658
|
+
* never went through that path — so a host that declared
|
|
659
|
+
* `fields: { role: fields.writableBy(['admin']) }` and a custom
|
|
660
|
+
* `POST /users/promote` happily accepted `{ role: 'admin' }` from any caller.
|
|
661
|
+
*
|
|
662
|
+
* This preHandler closes that gap. It applies ONLY the `applyFieldWritePermissions`
|
|
663
|
+
* step — no `SYSTEM_FIELDS` strip, no `fieldRules` strip — because custom-route
|
|
664
|
+
* bodies don't necessarily mirror the resource's adapter shape, and we'd
|
|
665
|
+
* generate surprising 403s by enforcing rules tied to that shape.
|
|
666
|
+
*
|
|
667
|
+
* Returns `null` when the resource has no field permissions configured — the
|
|
668
|
+
* caller should then skip wiring it into the preHandler chain.
|
|
669
|
+
*/
|
|
670
|
+
function buildFieldWritePreHandler(fieldPermissions, policy = DEFAULT_FIELD_WRITE_DENIAL_POLICY) {
|
|
671
|
+
if (!fieldPermissions || Object.keys(fieldPermissions).length === 0) return null;
|
|
672
|
+
return async (request, _reply) => {
|
|
673
|
+
const body = request.body;
|
|
674
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return;
|
|
675
|
+
const scope = request.scope ?? PUBLIC_SCOPE;
|
|
676
|
+
if (isElevated(scope)) return;
|
|
677
|
+
const { body: filtered, deniedFields } = applyFieldWritePermissions(body, fieldPermissions, resolveEffectiveRoles(getUserRoles(request.user), isMember(scope) ? scope.orgRoles : []));
|
|
678
|
+
if (deniedFields.length > 0 && policy === "reject") throw new ForbiddenError(`Not permitted to write field${deniedFields.length === 1 ? "" : "s"}: ${deniedFields.join(", ")}`);
|
|
679
|
+
request.body = filtered;
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* True for HTTP methods that carry a request body and should run the
|
|
684
|
+
* field-write preHandler. GET/DELETE/HEAD/OPTIONS skip enforcement.
|
|
685
|
+
*/
|
|
686
|
+
function methodCarriesBody(method) {
|
|
687
|
+
const m = method.toUpperCase();
|
|
688
|
+
return m === "POST" || m === "PUT" || m === "PATCH";
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
|
|
692
|
+
*
|
|
693
|
+
* Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
|
|
694
|
+
* overlaps before Fastify ever runs, but a few cases still slip through to
|
|
695
|
+
* Fastify itself: two presets contributing the same `routes` entry, hosts
|
|
696
|
+
* mounting two resources at the same prefix, custom routes that share a
|
|
697
|
+
* URL with another plugin. Fastify's default message ("Method 'GET'
|
|
698
|
+
* already declared for route '/'") gives no hint at the resource-shaped
|
|
699
|
+
* fix — `disabledRoutes: ['list']` — so wrap it with one.
|
|
700
|
+
*/
|
|
701
|
+
function tryRegisterRoute(fastify, opts, context) {
|
|
702
|
+
try {
|
|
703
|
+
fastify.route(opts);
|
|
704
|
+
} catch (err) {
|
|
705
|
+
const fastifyErr = err;
|
|
706
|
+
if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
|
|
707
|
+
const method = opts.method;
|
|
708
|
+
const url = opts.url;
|
|
709
|
+
const opHint = context.op ? ` (op: ${context.op})` : "";
|
|
710
|
+
const fixHint = context.op && [
|
|
711
|
+
"list",
|
|
712
|
+
"get",
|
|
713
|
+
"create",
|
|
714
|
+
"update",
|
|
715
|
+
"delete"
|
|
716
|
+
].includes(context.op) ? ` Suppress the auto-CRUD route with \`disabledRoutes: ['${context.op}']\`.` : " If a custom route in `routes:` collides with auto-CRUD, add `disabledRoutes: ['list' | 'get' | 'create' | 'update' | 'delete']` to `defineResource()` to suppress the matching auto-CRUD route.";
|
|
717
|
+
const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
|
|
718
|
+
enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
|
|
719
|
+
enhanced.cause = err;
|
|
720
|
+
throw enhanced;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
600
723
|
//#endregion
|
|
601
|
-
export {
|
|
724
|
+
export { buildRequestScopeProjection as C, sendControllerResponse as S, createCrudHandlers as _, buildAuthMiddlewareForPermissions as a, getControllerContext as b, buildPipelineHandler as c, methodCarriesBody as d, resolvePipelineSteps as f, tryRegisterRoute as g, selectPluginMw as h, buildAuthMiddleware as i, buildPreHandlerChain as l, resolveRouterPluginMw as m, buildActionPipelineHandler as n, buildCrudPermissionMw as o, resolveRoutePreHandlers as p, buildArcDecorator as r, buildFieldWritePreHandler as s, buildActionPermissionMw as t, buildRateLimitConfig as u, createFastifyHandler as v, BodySanitizer as w, getControllerScope as x, createRequestContext as y };
|
package/dist/scope/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C as isService, D as requireUserId, E as requireTeamId, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-Bi0r0vjG.mjs";
|
|
2
|
-
import { n as normalizeRoles } from "../types-
|
|
3
|
-
import { n as elevation_default, t as elevationPlugin } from "../elevation-
|
|
2
|
+
import { n as normalizeRoles } from "../types-DV9WDfeg.mjs";
|
|
3
|
+
import { n as elevation_default, t as elevationPlugin } from "../elevation-6mfTLBC-.mjs";
|
|
4
4
|
//#region src/scope/rateLimitKey.ts
|
|
5
5
|
function createTenantKeyGenerator(opts) {
|
|
6
6
|
if (opts?.strategy) return opts.strategy;
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Kt as AnyRecord, V as ResourceDefinition } from "../index-
|
|
2
|
-
import { d as preloadResourcesAsync, r as CreateAppOptions, u as preloadResources } from "../types-
|
|
1
|
+
import { Kt as AnyRecord, V as ResourceDefinition } from "../index-C1JSBan-.mjs";
|
|
2
|
+
import { d as preloadResourcesAsync, r as CreateAppOptions, u as preloadResources } from "../types-BRmS8TNa.mjs";
|
|
3
3
|
import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
|
|
4
4
|
import { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
5
5
|
import { Mock } from "vitest";
|