@classytic/arc 2.34.0 → 2.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseController-CbkuhNI2.mjs → BaseController-CTJKm6Cb.mjs} +95 -14
- package/dist/{EventTransport-tq4CEB-q.mjs → EventTransport-D23Uvhg4.mjs} +1 -1
- package/dist/{HookSystem-DqXkMXkB.mjs → HookSystem-DgwxNZTx.mjs} +6 -4
- package/dist/{ResourceRegistry-DoALUlOO.mjs → ResourceRegistry-DNuYVbmv.mjs} +2 -2
- package/dist/{pipeline-q69DZzgG.mjs → actionPermissions-D97DniUQ.mjs} +30 -7
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +2 -2
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +6 -6
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-CMMLWmuZ.mjs → authorizationDecision-EsiY3nUu.mjs} +2 -2
- package/dist/{base-Csjc2oF-.d.mts → base-C-NL2fTi.d.mts} +16 -1
- package/dist/{buildHandler-BrdYrG_1.mjs → buildHandler-Dohd2JuY.mjs} +4 -4
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-BcGmvnHh.mjs → caching-CP3GLJ7U.mjs} +2 -2
- package/dist/cleanup/index.d.mts +2 -2
- package/dist/cleanup/index.mjs +4 -4
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +3 -3
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-BxCoxmiN.mjs → constants-DUoKAGYD.mjs} +1 -1
- package/dist/context/index.d.mts +16 -1
- package/dist/context/index.mjs +64 -4
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +7 -8
- package/dist/{core-BYKa8wHu.mjs → core-DTXsiNTJ.mjs} +3 -3
- package/dist/{createActionRouter-D6T0jWhS.mjs → createActionRouter-CZz6JO93.mjs} +27 -64
- package/dist/{createAggregationRouter-QLV2PrWP.mjs → createAggregationRouter-DKrGv_EC.mjs} +7 -7
- package/dist/defineAggregation-CpQfcM8R.mjs +25 -0
- package/dist/{defineResource-ZhuLonLm.mjs → defineResource-DWzjyxb2.mjs} +62 -66
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +3 -3
- package/dist/{elevation-B0tlY8y1.mjs → elevation-D_cmo80j.mjs} +4 -4
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-C7xBqHsI.mjs → errorHandler-2RT0hLSI.mjs} +3 -3
- package/dist/{errors-Cd_yy7aW.mjs → errors-DDKni6Tz.mjs} +1 -1
- package/dist/{eventPlugin-DSNEZWfV.d.mts → eventPlugin-B47sfCJ8.d.mts} +1 -1
- package/dist/{eventPlugin-Dar8qRaj.mjs → eventPlugin-c3YwGzz9.mjs} +5 -5
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +5 -5
- package/dist/events/transports/redis-stream-entry.mjs +1 -1
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.d.mts +5 -5
- package/dist/factory/index.mjs +5 -6
- package/dist/{fastify-augmentation-DVsTAy6X.d.mts → fastify-augmentation-Bk_9PoiV.d.mts} +3 -3
- package/dist/{fields-CojeUkS4.mjs → fields-DdP225wt.mjs} +1 -1
- package/dist/{generate-DiB0nHPR.mjs → generate-B1bKdcV9.mjs} +2 -2
- package/dist/{headers-CveNO9gR.mjs → headers-DKslnhey.mjs} +2 -2
- package/dist/{health-CasG2A28.mjs → health-k6NODS8f.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.mjs +22 -56
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-B-DvUzsr.d.mts → index-BtYoMip1.d.mts} +21 -3
- package/dist/{index-BcR2bFZQ.d.mts → index-C01wWGJt.d.mts} +33 -105
- package/dist/{index-C5NnMEKF.d.mts → index-C50BUxKs.d.mts} +1 -1
- package/dist/{index-CFAJEWCX.d.mts → index-RdpLP71b.d.mts} +7 -30
- package/dist/{index-ugr1tQlW.d.mts → index-eyg5WEcy.d.mts} +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +11 -12
- package/dist/{init-mdJ-v4Xp.mjs → init-LuGxIbQ_.mjs} +1 -1
- package/dist/integrations/event-gateway.mjs +2 -2
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +2 -2
- package/dist/integrations/mcp/index.d.mts +4 -4
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +3 -3
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.d.mts +4 -2
- package/dist/integrations/webhooks.mjs +28 -27
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.mjs +1 -1
- package/dist/{interface-DznJrm9R.mjs → interface-B7awDx3b.mjs} +1 -1
- package/dist/{jsonBody-CfGH-GxO.mjs → jsonBody-CiHih3ZU.mjs} +2 -2
- package/dist/{keys-WeKvY1dE.mjs → keys-BcNl12HN.mjs} +1 -1
- package/dist/{loadResources-Bj-aSsXi.mjs → loadResources-DGlfRQc9.mjs} +2 -2
- package/dist/{loadResourcesFromEntry-DwaxNcE2.mjs → loadResourcesFromEntry-VYPGSas8.mjs} +2 -2
- package/dist/logger/index.d.mts +23 -4
- package/dist/logger/index.mjs +50 -7
- package/dist/{mcpPlugin-DX3PHD7K.mjs → mcpPlugin-NxSlc-Ks.mjs} +15 -16
- package/dist/{memory-qYSB7rMQ.mjs → memory-BU-C-MV3.mjs} +2 -2
- package/dist/{metrics-D7uchLjN.mjs → metrics-7_1GbJpl.mjs} +2 -2
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +7 -4
- package/dist/{module-CwKsgZXp.mjs → module-CQNHzE7g.mjs} +1 -1
- package/dist/{multipartBody-4w-S3-PU.mjs → multipartBody-Bi5yqW0M.mjs} +1 -1
- package/dist/{openapi-CkV5mQ53.mjs → openapi-RpNbPBL-.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +2 -2
- package/dist/outbox-admin/index.mjs +4 -4
- package/dist/{outbox-module-D1QfHqO4.mjs → outbox-module-ClcFrq98.mjs} +6 -4
- package/dist/{outbox-module-C1Bs_FXF.d.mts → outbox-module-b1TyDGGi.d.mts} +24 -1
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-CP47jdi7.mjs → permissions-DgPrZsJh.mjs} +36 -9
- package/dist/{pipe-BzzoEhHj.mjs → pipe-Cjy1UiXX.mjs} +2 -2
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plugins/index.d.mts +92 -7
- package/dist/plugins/index.mjs +168 -17
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-B7xf8qZF.mjs → pluralize-B7m0dPm8.mjs} +1 -1
- package/dist/{preloadResources-g07ldtzh.d.mts → preloadResources-BuutdWgr.d.mts} +54 -270
- package/dist/{createApp-zR-DEesr.mjs → preloadResources-CjnW4iIX.mjs} +170 -34
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +2 -2
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +3 -3
- package/dist/presets/search.d.mts +9 -47
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-BAPijK_A.mjs → presets-CsGg8B58.mjs} +4 -4
- package/dist/{projection-BKM0Ui-s.mjs → projection-koISNyQo.mjs} +2 -2
- package/dist/{queryCachePlugin-DhsZ0ZIc.mjs → queryCachePlugin-DHueQT9O.mjs} +5 -5
- package/dist/{rateLimitKey-XngqVNEV.mjs → rateLimitKey-Bma8VXDO.mjs} +1 -1
- package/dist/{realtime-G1TC6Yzc.mjs → realtime-BBoC91Bz.mjs} +9 -9
- package/dist/{rateLimit-D4-ASwsI.mjs → registerRoute-BTgORTlR.mjs} +114 -3
- package/dist/registry/index.d.mts +2 -2
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-CLxCR5LF.mjs → registry-BzvRCYS6.mjs} +15 -56
- package/dist/{renewingLease-BnWtbA2U.mjs → renewingLease-CgIGh0Ab.mjs} +14 -2
- package/dist/{replyHelpers-BSBVCwL3.mjs → replyHelpers-BN16KPHr.mjs} +2 -2
- package/dist/{requestContext-uYE35JxI.mjs → requestContext-B93dmpf0.mjs} +1 -1
- package/dist/{rolldown-runtime-DNg-KNCk.mjs → rolldown-runtime-JJWfp8rp.mjs} +1 -1
- package/dist/{runtimeCapabilities-Dm88DaTW.mjs → runtimeCapabilities-jK99Ciw_.mjs} +1 -1
- package/dist/{schedules-CAcZgia0.mjs → schedules-CKKEL21P.mjs} +6 -4
- package/dist/{schemaConverter-XH1RZRyu.mjs → schemaConverter-gg0_aUPF.mjs} +19 -32
- package/dist/{schemaIR-CDvclAew.mjs → schemaIR-CngYvpCF.mjs} +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +5 -5
- package/dist/{scope-BaWcqBvr.mjs → scope-B1RsHdjF.mjs} +4 -4
- package/dist/simpleEqualityMatcher-_9NAMHUD.mjs +43 -0
- package/dist/{sse-Cf0jUHsh.mjs → sse-Bxt03Hk5.mjs} +5 -5
- package/dist/{sseStream-Bvpi-UXQ.mjs → sseStream-BMHTLlhR.mjs} +2 -2
- package/dist/{store-helpers-B88p99Z2.mjs → store-helpers-Brt8OxWZ.mjs} +1 -1
- package/dist/{streaming-DsT1B5zJ.mjs → streaming-CiTJW8kZ.mjs} +1 -1
- package/dist/{subscribe-helpers-CLIOHsOK.mjs → subscribe-helpers-B4fNHRNc.mjs} +21 -49
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +6 -6
- package/dist/testing/index.mjs +10 -11
- package/dist/testing/outboxStoreContract.mjs +1 -1
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/{transactionContext-BYtWnwAS.mjs → transactionContext-D2HwJM2z.mjs} +1 -1
- package/dist/{typeGuards-jjKJDN13.mjs → typeGuards-IE1laMDf.mjs} +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CoqIRrkY.mjs → types-B8lwJv9Q.mjs} +10 -53
- package/dist/{types-BfC83KsP.d.mts → types-BXnTOsXP.d.mts} +1 -1
- package/dist/{types-BcTumAz_.d.mts → types-DDD5RKTu.d.mts} +30 -87
- package/dist/{types-ChuShpCP.d.mts → types-Ddl9OjmE.d.mts} +18 -121
- package/dist/usage/index.d.mts +2 -2
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-CWF5pbZD.mjs → userHelpers-xQAi5bPF.mjs} +1 -1
- package/dist/utils/index.d.mts +16 -42
- package/dist/utils/index.mjs +11 -11
- package/dist/{utils-h1V_sT-t.mjs → utils-DgNFsJ2c.mjs} +3 -3
- package/dist/{validate-CdQlz52F.mjs → validate-DI3L5V9w.mjs} +2 -2
- package/dist/{versioning-rZbyLVD-.mjs → versioning-COpAyfpZ.mjs} +3 -3
- package/package.json +8 -8
- package/dist/actionPermissions-CUeCmpUd.mjs +0 -25
- package/dist/defineAggregation-Nv-DwfOf.mjs +0 -48
- package/dist/preloadResources-BkyqeTDz.mjs +0 -46
- package/dist/registerRoute-Ba8v0PCa.mjs +0 -113
- package/dist/repositoryFilter-BANpz6BX.mjs +0 -78
- package/dist/simpleEqualityMatcher-Se8VrsaP.mjs +0 -69
package/dist/usage/index.mjs
CHANGED
|
Binary file
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
import { F as getHeader, I as requireSingleHeaderValue, P as ArcHeaders } from "../types-
|
|
3
|
-
import { f as getUserId, m as normalizeRoles, p as getUserRoles, t as AnyRecord } from "../base-
|
|
4
|
-
import { $ as OpenApiSchemas, S as QueryParserInterface, b as ParsedQuery } from "../index-
|
|
2
|
+
import { F as getHeader, I as requireSingleHeaderValue, P as ArcHeaders } from "../types-Ddl9OjmE.mjs";
|
|
3
|
+
import { f as getUserId, m as normalizeRoles, p as getUserRoles, t as AnyRecord } from "../base-C-NL2fTi.mjs";
|
|
4
|
+
import { $ as OpenApiSchemas, S as QueryParserInterface, b as ParsedQuery } from "../index-C01wWGJt.mjs";
|
|
5
5
|
import { n as ErrorMapper } from "../errorHandler-DI5AN2b5.mjs";
|
|
6
6
|
import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorOptions, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-BGdVwP25.mjs";
|
|
7
7
|
import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
|
|
@@ -1243,50 +1243,24 @@ declare function convertRouteSchema(schema: Record<string, unknown>, target?: Js
|
|
|
1243
1243
|
* `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
|
|
1244
1244
|
* matcher for `DataAdapter.matchesFilter` / `BaseController({ matchesFilter })`.
|
|
1245
1245
|
*
|
|
1246
|
-
*
|
|
1247
|
-
* `
|
|
1248
|
-
*
|
|
1249
|
-
* filter entry matches. Array item values are matched implicitly (contains).
|
|
1246
|
+
* Compares `item[key]` to each filter value by string coercion, so a Mongo
|
|
1247
|
+
* `ObjectId` matches its string form; array values match by contains. ALL
|
|
1248
|
+
* entries must match.
|
|
1250
1249
|
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
* -
|
|
1254
|
-
*
|
|
1255
|
-
*
|
|
1250
|
+
* NOT an operator engine: no `$in`/`$ne`/`$gt`/`$regex`/`$exists`, no
|
|
1251
|
+
* `$and`/`$or`, no dot paths, no schema coercion. Most `_policyFilters` from
|
|
1252
|
+
* arc's built-in permission helpers are flat equality (`{ ownerId }`,
|
|
1253
|
+
* `{ organizationId }`), and this covers exactly that — defense-in-depth for
|
|
1254
|
+
* hosts on minimal repos with no `getOne(compoundFilter)` path.
|
|
1256
1255
|
*
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1259
|
-
* `
|
|
1260
|
-
* this helper is a safe, tested, 15-line defense-in-depth matcher that
|
|
1261
|
-
* hosts using minimal repos (no `getOne(compoundFilter)` DB path) can opt
|
|
1262
|
-
* into without arc shipping a full Mongo-syntax engine.
|
|
1263
|
-
*
|
|
1264
|
-
* **When to use:**
|
|
1265
|
-
* - Your adapter/repo doesn't natively filter on `getOne(compoundFilter)`
|
|
1266
|
-
* - Your `_policyFilters` are flat equality (from arc's built-in permission helpers)
|
|
1267
|
-
* - You want defense-in-depth on `validateItemAccess` / `fetchDetailed`'s `getById` fallback
|
|
1268
|
-
*
|
|
1269
|
-
* **When NOT to use:**
|
|
1270
|
-
* - Your `_policyFilters` use operators (`$in`, `$ne`, etc.) — supply a
|
|
1271
|
-
* native matcher (mongokit's repo does the filter at the DB layer; for
|
|
1272
|
-
* custom repos, wrap the kit's own predicate engine).
|
|
1273
|
-
* - You're a mongokit / sqlitekit / Prisma user — the DB-level filter
|
|
1274
|
-
* applied by `getOne(compoundFilter)` already covers this.
|
|
1256
|
+
* Do NOT use it with operator-shaped filters — supply a native matcher instead
|
|
1257
|
+
* (a kit's predicate engine). Kit users need neither: the DB-level filter in
|
|
1258
|
+
* `getOne(compoundFilter)` already applies.
|
|
1275
1259
|
*
|
|
1276
1260
|
* @example
|
|
1277
1261
|
* ```ts
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
* // On a custom adapter
|
|
1281
|
-
* const adapter: DataAdapter = {
|
|
1282
|
-
* repository,
|
|
1283
|
-
* type: 'custom',
|
|
1284
|
-
* name: 'in-memory',
|
|
1285
|
-
* matchesFilter: simpleEqualityMatcher,
|
|
1286
|
-
* };
|
|
1287
|
-
*
|
|
1288
|
-
* // Or directly on BaseController for ad-hoc controllers
|
|
1289
|
-
* new BaseController(repo, { matchesFilter: simpleEqualityMatcher });
|
|
1262
|
+
* const adapter: DataAdapter = { repository, type: 'custom', name: 'in-memory',
|
|
1263
|
+
* matchesFilter: simpleEqualityMatcher };
|
|
1290
1264
|
* ```
|
|
1291
1265
|
*/
|
|
1292
1266
|
declare function simpleEqualityMatcher(item: unknown, filters: Record<string, unknown>): boolean;
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-
|
|
3
|
-
import { n as requireSingleHeaderValue, t as getHeader } from "../headers-
|
|
4
|
-
import { n as getUserRoles, r as normalizeRoles, t as getUserId } from "../userHelpers-
|
|
5
|
-
import { t as simpleEqualityMatcher } from "../simpleEqualityMatcher-
|
|
6
|
-
import { A as createCircuitBreakerRegistry, C as defineCompensation, D as CircuitBreakerRegistry, E as CircuitBreakerError, O as CircuitState, S as defineErrorMapper, T as CircuitBreaker, _ as createQueryParser, a as deleteResponse, b as NEUTRAL_DIALECT, c as getDefaultCrudSchemas, d as listResponse, f as offsetListResponse, g as ArcQueryParser, h as responses, i as bareListResponse, k as createCircuitBreaker, l as getListQueryParams, m as queryParams, n as scheduleBackground, o as errorContractSchema, p as paginationSchema, r as aggregateListResponse, s as errorDetailSchema, t as createStateMachine, u as keysetListResponse, v as handleRaw, w as withCompensation, x as defineGuard, y as MONGO_DIALECT } from "../utils-
|
|
7
|
-
import { n as declareRuntimeCapability } from "../runtimeCapabilities-
|
|
8
|
-
import { a as isZodSchema, i as isJsonSchema, n as convertRouteSchema, o as toJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-
|
|
9
|
-
import { a as promoteStreamTokenToHeader, i as pipeUIMessageStreamToReply, n as forwardedStreamHeaders, r as isReadableStream, t as UI_MESSAGE_STREAM_HEADERS } from "../streaming-
|
|
10
|
-
import { t as openSseStream } from "../sseStream-
|
|
11
|
-
import { t as hasEvents } from "../typeGuards-
|
|
1
|
+
/*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
|
|
2
|
+
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-DDKni6Tz.mjs";
|
|
3
|
+
import { n as requireSingleHeaderValue, t as getHeader } from "../headers-DKslnhey.mjs";
|
|
4
|
+
import { n as getUserRoles, r as normalizeRoles, t as getUserId } from "../userHelpers-xQAi5bPF.mjs";
|
|
5
|
+
import { t as simpleEqualityMatcher } from "../simpleEqualityMatcher-_9NAMHUD.mjs";
|
|
6
|
+
import { A as createCircuitBreakerRegistry, C as defineCompensation, D as CircuitBreakerRegistry, E as CircuitBreakerError, O as CircuitState, S as defineErrorMapper, T as CircuitBreaker, _ as createQueryParser, a as deleteResponse, b as NEUTRAL_DIALECT, c as getDefaultCrudSchemas, d as listResponse, f as offsetListResponse, g as ArcQueryParser, h as responses, i as bareListResponse, k as createCircuitBreaker, l as getListQueryParams, m as queryParams, n as scheduleBackground, o as errorContractSchema, p as paginationSchema, r as aggregateListResponse, s as errorDetailSchema, t as createStateMachine, u as keysetListResponse, v as handleRaw, w as withCompensation, x as defineGuard, y as MONGO_DIALECT } from "../utils-DgNFsJ2c.mjs";
|
|
7
|
+
import { n as declareRuntimeCapability } from "../runtimeCapabilities-jK99Ciw_.mjs";
|
|
8
|
+
import { a as isZodSchema, i as isJsonSchema, n as convertRouteSchema, o as toJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-gg0_aUPF.mjs";
|
|
9
|
+
import { a as promoteStreamTokenToHeader, i as pipeUIMessageStreamToReply, n as forwardedStreamHeaders, r as isReadableStream, t as UI_MESSAGE_STREAM_HEADERS } from "../streaming-CiTJW8kZ.mjs";
|
|
10
|
+
import { t as openSseStream } from "../sseStream-BMHTLlhR.mjs";
|
|
11
|
+
import { t as hasEvents } from "../typeGuards-IE1laMDf.mjs";
|
|
12
12
|
export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, ValidationError, aggregateListResponse, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, declareRuntimeCapability, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, forwardedStreamHeaders, getDefaultCrudSchemas, getHeader, getListQueryParams, getUserId, getUserRoles, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, normalizeRoles, offsetListResponse, openSseStream, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, requireSingleHeaderValue, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, withCompensation };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { m as RESERVED_QUERY_PARAMS } from "./constants-
|
|
1
|
+
/*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
|
|
2
|
+
import { m as RESERVED_QUERY_PARAMS } from "./constants-DUoKAGYD.mjs";
|
|
3
3
|
import { arcLog } from "./logger/index.mjs";
|
|
4
|
-
import { t as ArcError, u as ValidationError } from "./errors-
|
|
4
|
+
import { t as ArcError, u as ValidationError } from "./errors-DDKni6Tz.mjs";
|
|
5
5
|
import { errorContractSchema as errorContractSchema$1, errorDetailSchema as errorDetailSchema$1 } from "@classytic/repo-core/errors";
|
|
6
6
|
import { scheduleBackground } from "@classytic/repo-core/cache";
|
|
7
7
|
//#region src/utils/circuitBreaker.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
/*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-JJWfp8rp.mjs";
|
|
3
3
|
//#region src/core/aggregation/normalize.ts
|
|
4
4
|
/**
|
|
5
5
|
* Framework cap on URL-driven aggregation limits when the host opts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
3
|
-
import { d as createDomainError } from "./errors-
|
|
1
|
+
/*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-JJWfp8rp.mjs";
|
|
3
|
+
import { d as createDomainError } from "./errors-DDKni6Tz.mjs";
|
|
4
4
|
import fp from "fastify-plugin";
|
|
5
5
|
//#region src/plugins/versioning.ts
|
|
6
6
|
var versioning_exports = /* @__PURE__ */ __exportAll({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.36.0",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -351,8 +351,8 @@
|
|
|
351
351
|
"node": ">=22"
|
|
352
352
|
},
|
|
353
353
|
"peerDependencies": {
|
|
354
|
-
"@classytic/primitives": ">=0.
|
|
355
|
-
"@classytic/repo-core": ">=0.
|
|
354
|
+
"@classytic/primitives": ">=0.25.0",
|
|
355
|
+
"@classytic/repo-core": ">=0.25.0",
|
|
356
356
|
"@classytic/streamline": ">=2.8.0",
|
|
357
357
|
"@fastify/cors": ">=11.0.0",
|
|
358
358
|
"@fastify/helmet": ">=13.0.0",
|
|
@@ -373,7 +373,7 @@
|
|
|
373
373
|
"@opentelemetry/sdk-node": ">=0.50.0",
|
|
374
374
|
"better-auth": ">=1.6.2",
|
|
375
375
|
"bullmq": ">=5.0.0",
|
|
376
|
-
"fastify": ">=5.
|
|
376
|
+
"fastify": ">=5.12.0",
|
|
377
377
|
"fastify-raw-body": ">=5.0.0",
|
|
378
378
|
"h3-js": ">=4.0.0",
|
|
379
379
|
"ioredis": ">=5.0.0",
|
|
@@ -483,8 +483,8 @@
|
|
|
483
483
|
"@classytic/gitkit": ">=0.1.0",
|
|
484
484
|
"@classytic/mongokit": "^3.33.0",
|
|
485
485
|
"@classytic/pgkit": "^0.1.0",
|
|
486
|
-
"@classytic/primitives": "^0.
|
|
487
|
-
"@classytic/repo-core": "^0.
|
|
486
|
+
"@classytic/primitives": "^0.25.0",
|
|
487
|
+
"@classytic/repo-core": "^0.25.0",
|
|
488
488
|
"@classytic/sqlitekit": "^0.9.0",
|
|
489
489
|
"@classytic/streamline": "^2.8.2",
|
|
490
490
|
"@electric-sql/pglite": "^0.5.4",
|
|
@@ -505,12 +505,12 @@
|
|
|
505
505
|
"@vitest/coverage-v8": "^4.1.9",
|
|
506
506
|
"ajv": "^8.20.0",
|
|
507
507
|
"better-auth": "^1.6.25",
|
|
508
|
-
"better-sqlite3": "^
|
|
508
|
+
"better-sqlite3": "^12.4.1",
|
|
509
509
|
"bullmq": "^6.1.2",
|
|
510
510
|
"dotenv": "^17.4.2",
|
|
511
511
|
"drizzle-orm": "^0.45.2",
|
|
512
512
|
"fast-check": "^4.8.0",
|
|
513
|
-
"fastify": "^5.12.
|
|
513
|
+
"fastify": "^5.12.1",
|
|
514
514
|
"fastify-raw-body": "^6.0.1",
|
|
515
515
|
"h3-js": "^4.4.0",
|
|
516
516
|
"ioredis": "^6.0.0",
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
-
//#region src/core/actionPermissions.ts
|
|
3
|
-
/**
|
|
4
|
-
* Return the effective `PermissionCheck` for a single action, or `undefined`
|
|
5
|
-
* when the resource declares no gate at any level.
|
|
6
|
-
*
|
|
7
|
-
* Callers decide what "no gate" means:
|
|
8
|
-
* - HTTP: boot-time throw in `normalizeActionsToRouterConfig`.
|
|
9
|
-
* - MCP: tool-generation throw in `resourceToTools` (mirrors HTTP — the
|
|
10
|
-
* two surfaces fail closed identically so MCP can't expose an
|
|
11
|
-
* unauthenticated mutating tool when the HTTP plugin lifecycle hasn't
|
|
12
|
-
* run).
|
|
13
|
-
* - OpenAPI: docs advertise the endpoint as unauthenticated.
|
|
14
|
-
*/
|
|
15
|
-
function resolveActionPermission(input) {
|
|
16
|
-
const { action, resourcePermissions, resourceActionPermissions, globalAuth } = input;
|
|
17
|
-
const explicit = typeof action !== "function" && action.permissions ? action.permissions : void 0;
|
|
18
|
-
if (explicit) return explicit;
|
|
19
|
-
if (resourceActionPermissions) return resourceActionPermissions;
|
|
20
|
-
if (globalAuth) return globalAuth;
|
|
21
|
-
const updateFallback = resourcePermissions?.update;
|
|
22
|
-
if (updateFallback) return updateFallback;
|
|
23
|
-
}
|
|
24
|
-
//#endregion
|
|
25
|
-
export { resolveActionPermission as t };
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
-
//#region src/core/aggregation/defineAggregation.ts
|
|
3
|
-
/**
|
|
4
|
-
* Declare a single resource aggregation. Exported configs flow into
|
|
5
|
-
* `defineResource({ aggregations: { ... } })` either as named keys or
|
|
6
|
-
* via auto-discovery patterns (loadAggregations — future).
|
|
7
|
-
*
|
|
8
|
-
* @example Inline
|
|
9
|
-
* ```ts
|
|
10
|
-
* defineResource({
|
|
11
|
-
* name: 'order',
|
|
12
|
-
* aggregations: {
|
|
13
|
-
* revenueByStatus: defineAggregation({
|
|
14
|
-
* groupBy: 'status',
|
|
15
|
-
* measures: { count: 'count', revenue: 'sum:totalPrice' },
|
|
16
|
-
* permissions: requireRoles(['admin']),
|
|
17
|
-
* }),
|
|
18
|
-
* },
|
|
19
|
-
* });
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* @example Multi-file
|
|
23
|
-
* ```ts
|
|
24
|
-
* // orders/aggregations/revenue-by-status.ts
|
|
25
|
-
* import { defineAggregation } from '@classytic/arc';
|
|
26
|
-
*
|
|
27
|
-
* export const revenueByStatus = defineAggregation({
|
|
28
|
-
* groupBy: 'status',
|
|
29
|
-
* measures: { count: 'count', revenue: 'sum:totalPrice' },
|
|
30
|
-
* permissions: requireRoles(['admin']),
|
|
31
|
-
* timeout: 5000,
|
|
32
|
-
* cache: { staleTime: 60 },
|
|
33
|
-
* });
|
|
34
|
-
*
|
|
35
|
-
* // orders/order.resource.ts
|
|
36
|
-
* import * as aggregations from './aggregations/index.js';
|
|
37
|
-
*
|
|
38
|
-
* defineResource({
|
|
39
|
-
* name: 'order',
|
|
40
|
-
* aggregations, // 30+ entries flow in
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
function defineAggregation(config) {
|
|
45
|
-
return config;
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
|
-
export { defineAggregation as t };
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
-
//#region src/factory/preloadResources.ts
|
|
3
|
-
/**
|
|
4
|
-
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
5
|
-
*
|
|
6
|
-
* Accepts either:
|
|
7
|
-
* - `{ import: 'default' }` form: values are the resource directly
|
|
8
|
-
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
9
|
-
*
|
|
10
|
-
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
11
|
-
*/
|
|
12
|
-
function preloadResources(globResult) {
|
|
13
|
-
const resources = [];
|
|
14
|
-
for (const [path, value] of Object.entries(globResult)) {
|
|
15
|
-
const resource = pickResource(value);
|
|
16
|
-
if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
17
|
-
resources.push(resource);
|
|
18
|
-
}
|
|
19
|
-
return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
23
|
-
*
|
|
24
|
-
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
25
|
-
* cannot be evaluated at import time of the preload file.
|
|
26
|
-
*/
|
|
27
|
-
async function preloadResourcesAsync(globResult) {
|
|
28
|
-
return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
|
|
29
|
-
const resource = pickResource(await loader());
|
|
30
|
-
if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
31
|
-
return resource;
|
|
32
|
-
}))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
33
|
-
}
|
|
34
|
-
function pickResource(value) {
|
|
35
|
-
if (!value || typeof value !== "object") return void 0;
|
|
36
|
-
if (typeof value.toPlugin === "function") return value;
|
|
37
|
-
const mod = value;
|
|
38
|
-
const candidates = [
|
|
39
|
-
mod.default,
|
|
40
|
-
mod.resource,
|
|
41
|
-
...Object.values(mod)
|
|
42
|
-
];
|
|
43
|
-
for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
|
-
export { preloadResourcesAsync as n, preloadResources as t };
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
-
import { n as evaluateAndApplyPermission } from "./authorizationDecision-CMMLWmuZ.mjs";
|
|
3
|
-
import { f as createError } from "./errors-Cd_yy7aW.mjs";
|
|
4
|
-
import { u as getRequestScope } from "./types-CoqIRrkY.mjs";
|
|
5
|
-
//#region src/core/middlewares/permissions.ts
|
|
6
|
-
/**
|
|
7
|
-
* Build a PermissionContext from a Fastify request. Extracted so the CRUD
|
|
8
|
-
* permission middleware and the dynamic action-permission check use the same
|
|
9
|
-
* field layout — divergence here silently broke policy filters for actions.
|
|
10
|
-
*/
|
|
11
|
-
function buildPermissionContext(req, opts) {
|
|
12
|
-
const reqWithExtras = req;
|
|
13
|
-
const params = req.params;
|
|
14
|
-
return {
|
|
15
|
-
user: reqWithExtras.user ?? null,
|
|
16
|
-
scope: getRequestScope(req),
|
|
17
|
-
request: req,
|
|
18
|
-
resource: opts.resource,
|
|
19
|
-
action: opts.action,
|
|
20
|
-
resourceId: opts.resourceId ?? params?.id,
|
|
21
|
-
params,
|
|
22
|
-
data: opts.data ?? req.body
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Static per-route CRUD permission gate. The permission and action are known
|
|
27
|
-
* at route-registration time, so the gate is a plain preHandler.
|
|
28
|
-
*
|
|
29
|
-
* Actions use the dynamic counterpart `buildActionPermissionMw` — their
|
|
30
|
-
* permission is resolved from `body.action` at request time.
|
|
31
|
-
*/
|
|
32
|
-
function buildCrudPermissionMw(permissionCheck, resourceName, action) {
|
|
33
|
-
if (!permissionCheck) return null;
|
|
34
|
-
return async (req, reply) => {
|
|
35
|
-
const context = buildPermissionContext(req, {
|
|
36
|
-
resource: resourceName,
|
|
37
|
-
action
|
|
38
|
-
});
|
|
39
|
-
await evaluateAndApplyPermission(permissionCheck, context, req, reply);
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Dynamic per-action permission gate for the action router.
|
|
44
|
-
*
|
|
45
|
-
* Resolves the permission from `body.action` at request time and runs
|
|
46
|
-
* `evaluateAndApplyPermission` from the canonical `permissionMw` slot — so
|
|
47
|
-
* `_policyFilters` and `request.scope` are installed BEFORE `pluginMw`
|
|
48
|
-
* (idempotency) and `routeGuards` run. Previously this check lived inside
|
|
49
|
-
* the main action handler, which meant idempotency recorded unauthorized
|
|
50
|
-
* requests and route guards saw unfiltered scope — the very divergence
|
|
51
|
-
* routerShared exists to prevent.
|
|
52
|
-
*
|
|
53
|
-
* Also acts as a defensive fallback for invalid action names — the
|
|
54
|
-
* `oneOf` body schema normally rejects these at AJV validation, but
|
|
55
|
-
* hosts that disable schema validation still get a 400 here.
|
|
56
|
-
*/
|
|
57
|
-
function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, resourceName) {
|
|
58
|
-
const enumSet = new Set(actionEnum);
|
|
59
|
-
const validActions = [...actionEnum];
|
|
60
|
-
return async (req, reply) => {
|
|
61
|
-
const body = req.body ?? {};
|
|
62
|
-
const action = body.action;
|
|
63
|
-
if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
|
|
64
|
-
const permissionCheck = actionPermissions[action] ?? globalAuth;
|
|
65
|
-
if (!permissionCheck) return;
|
|
66
|
-
const { action: _discard, ...data } = body;
|
|
67
|
-
const params = req.params;
|
|
68
|
-
const context = buildPermissionContext(req, {
|
|
69
|
-
resource: resourceName,
|
|
70
|
-
action,
|
|
71
|
-
resourceId: params?.id,
|
|
72
|
-
data
|
|
73
|
-
});
|
|
74
|
-
await evaluateAndApplyPermission(permissionCheck, context, req, reply, { defaultDenialMessage: (user) => user ? `Permission denied for '${action}'` : "Authentication required" });
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/core/middlewares/registerRoute.ts
|
|
79
|
-
/**
|
|
80
|
-
* Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
|
|
81
|
-
*
|
|
82
|
-
* Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
|
|
83
|
-
* overlaps before Fastify ever runs, but a few cases still slip through to
|
|
84
|
-
* Fastify itself: two presets contributing the same `routes` entry, hosts
|
|
85
|
-
* mounting two resources at the same prefix, custom routes that share a
|
|
86
|
-
* URL with another plugin. Fastify's default message ("Method 'GET'
|
|
87
|
-
* already declared for route '/'") gives no hint at the resource-shaped
|
|
88
|
-
* fix — `disabledRoutes: ['list']` — so wrap it with one.
|
|
89
|
-
*/
|
|
90
|
-
function tryRegisterRoute(fastify, opts, context) {
|
|
91
|
-
try {
|
|
92
|
-
fastify.route(opts);
|
|
93
|
-
} catch (err) {
|
|
94
|
-
const fastifyErr = err;
|
|
95
|
-
if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
|
|
96
|
-
const method = opts.method;
|
|
97
|
-
const url = opts.url;
|
|
98
|
-
const opHint = context.op ? ` (op: ${context.op})` : "";
|
|
99
|
-
const fixHint = context.op && [
|
|
100
|
-
"list",
|
|
101
|
-
"get",
|
|
102
|
-
"create",
|
|
103
|
-
"update",
|
|
104
|
-
"delete"
|
|
105
|
-
].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.";
|
|
106
|
-
const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
|
|
107
|
-
enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
|
|
108
|
-
enhanced.cause = err;
|
|
109
|
-
throw enhanced;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
//#endregion
|
|
113
|
-
export { buildActionPermissionMw as n, buildCrudPermissionMw as r, tryRegisterRoute as t };
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
-
import { arcLog } from "./logger/index.mjs";
|
|
3
|
-
import { policyRecordToFilter } from "@classytic/repo-core/filter";
|
|
4
|
-
//#region src/core/repositoryFilter.ts
|
|
5
|
-
const NATIVE_FILTER_DIALECT = Symbol.for("@classytic/arc/native-filter-dialect");
|
|
6
|
-
/**
|
|
7
|
-
* Mark a server-owned policy as intentionally adapter-native. This is an
|
|
8
|
-
* explicit escape hatch for semantics absent from repo-core's universal IR
|
|
9
|
-
* (for example MongoDB `$elemMatch`). Never apply it to client input.
|
|
10
|
-
*/
|
|
11
|
-
function nativePolicyFilter(dialect, record) {
|
|
12
|
-
return Object.freeze({
|
|
13
|
-
...record,
|
|
14
|
-
[NATIVE_FILTER_DIALECT]: dialect
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
function nativeDialects(value, found = /* @__PURE__ */ new Set()) {
|
|
18
|
-
if (value === null || typeof value !== "object") return found;
|
|
19
|
-
const dialect = value[NATIVE_FILTER_DIALECT];
|
|
20
|
-
if (typeof dialect === "string") found.add(dialect);
|
|
21
|
-
if (Array.isArray(value)) for (const entry of value) nativeDialects(entry, found);
|
|
22
|
-
else for (const entry of Object.values(value)) nativeDialects(entry, found);
|
|
23
|
-
return found;
|
|
24
|
-
}
|
|
25
|
-
function stripNativeMarkers(value) {
|
|
26
|
-
if (Array.isArray(value)) return value.map(stripNativeMarkers);
|
|
27
|
-
if (value === null || typeof value !== "object" || value instanceof Date) return value;
|
|
28
|
-
const out = {};
|
|
29
|
-
for (const [key, entry] of Object.entries(value)) out[key] = stripNativeMarkers(entry);
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
const log = arcLog("repository-filter");
|
|
33
|
-
let warnedUnconvertible = false;
|
|
34
|
-
/** True for a `{ $op: ... }` operator object (all keys `$`-prefixed). */
|
|
35
|
-
function isOperatorObject(value) {
|
|
36
|
-
if (value === null || typeof value !== "object") return false;
|
|
37
|
-
if (Array.isArray(value) || value instanceof Date) return false;
|
|
38
|
-
const keys = Object.keys(value);
|
|
39
|
-
return keys.length > 0 && keys.some((k) => k.startsWith("$"));
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Does this compound filter contain any `$`-operator (top-level logical like
|
|
43
|
-
* `$and`/`$or`, or a field operator object like `{ $in: [...] }`)? If not, it is
|
|
44
|
-
* pure flat equality and portable as-is.
|
|
45
|
-
*/
|
|
46
|
-
function filterHasDollarOperator(record) {
|
|
47
|
-
for (const [key, value] of Object.entries(record)) {
|
|
48
|
-
if (key.startsWith("$")) return true;
|
|
49
|
-
if (isOperatorObject(value)) return true;
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Normalize a compound query/policy filter for the repository layer. Flat
|
|
55
|
-
* equality records pass through unchanged; anything carrying `$`-operators is
|
|
56
|
-
* converted to repo-core {@link Filter} IR so semantics stay explicit. Common
|
|
57
|
-
* operators compile identically across kits; capability-specific operators
|
|
58
|
-
* such as array-element matching fail loudly on adapters that cannot model them.
|
|
59
|
-
*/
|
|
60
|
-
function toRepositoryFilter(record) {
|
|
61
|
-
const dialects = nativeDialects(record);
|
|
62
|
-
if (dialects.size > 0) {
|
|
63
|
-
if (dialects.size > 1) throw new Error(`Cannot compose native policy-filter dialects: ${[...dialects].sort().join(", ")}`);
|
|
64
|
-
return stripNativeMarkers(record);
|
|
65
|
-
}
|
|
66
|
-
if (!filterHasDollarOperator(record)) return record;
|
|
67
|
-
try {
|
|
68
|
-
return policyRecordToFilter(record);
|
|
69
|
-
} catch (err) {
|
|
70
|
-
if (!warnedUnconvertible) {
|
|
71
|
-
warnedUnconvertible = true;
|
|
72
|
-
log.warn(`A \`\$\`-operator filter could not be normalized to portable Filter IR (operator outside arc's policy dialect). Passing the raw record to the repository — MongoKit handles it; non-Mongo kits may not. (${err instanceof Error ? err.message : String(err)})`);
|
|
73
|
-
}
|
|
74
|
-
return record;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
//#endregion
|
|
78
|
-
export { toRepositoryFilter as n, nativePolicyFilter as t };
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
-
//#region src/utils/simpleEqualityMatcher.ts
|
|
3
|
-
/**
|
|
4
|
-
* `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
|
|
5
|
-
* matcher for `DataAdapter.matchesFilter` / `BaseController({ matchesFilter })`.
|
|
6
|
-
*
|
|
7
|
-
* **What it does:** for each `[key, expected]` in the filter, compares
|
|
8
|
-
* `item[key]` to `expected` via string coercion (so Mongo `ObjectId` values
|
|
9
|
-
* match their string representation) and returns `true` only if every
|
|
10
|
-
* filter entry matches. Array item values are matched implicitly (contains).
|
|
11
|
-
*
|
|
12
|
-
* **What it does NOT do:**
|
|
13
|
-
* - No `$eq` / `$ne` / `$in` / `$nin` / `$gt` / `$lt` / `$regex` / `$exists`
|
|
14
|
-
* - No `$and` / `$or`
|
|
15
|
-
* - No dot-path traversal (`"owner.id"`)
|
|
16
|
-
* - No schema-specific coercion
|
|
17
|
-
*
|
|
18
|
-
* **Why it exists:** 95%+ of arc's `_policyFilters` are produced by built-in
|
|
19
|
-
* permission helpers and are shaped like `{ ownerId: "u1" }` or
|
|
20
|
-
* `{ organizationId: "org_x" }` — flat equality. For that common shape,
|
|
21
|
-
* this helper is a safe, tested, 15-line defense-in-depth matcher that
|
|
22
|
-
* hosts using minimal repos (no `getOne(compoundFilter)` DB path) can opt
|
|
23
|
-
* into without arc shipping a full Mongo-syntax engine.
|
|
24
|
-
*
|
|
25
|
-
* **When to use:**
|
|
26
|
-
* - Your adapter/repo doesn't natively filter on `getOne(compoundFilter)`
|
|
27
|
-
* - Your `_policyFilters` are flat equality (from arc's built-in permission helpers)
|
|
28
|
-
* - You want defense-in-depth on `validateItemAccess` / `fetchDetailed`'s `getById` fallback
|
|
29
|
-
*
|
|
30
|
-
* **When NOT to use:**
|
|
31
|
-
* - Your `_policyFilters` use operators (`$in`, `$ne`, etc.) — supply a
|
|
32
|
-
* native matcher (mongokit's repo does the filter at the DB layer; for
|
|
33
|
-
* custom repos, wrap the kit's own predicate engine).
|
|
34
|
-
* - You're a mongokit / sqlitekit / Prisma user — the DB-level filter
|
|
35
|
-
* applied by `getOne(compoundFilter)` already covers this.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* import { simpleEqualityMatcher } from '@classytic/arc/utils';
|
|
40
|
-
*
|
|
41
|
-
* // On a custom adapter
|
|
42
|
-
* const adapter: DataAdapter = {
|
|
43
|
-
* repository,
|
|
44
|
-
* type: 'custom',
|
|
45
|
-
* name: 'in-memory',
|
|
46
|
-
* matchesFilter: simpleEqualityMatcher,
|
|
47
|
-
* };
|
|
48
|
-
*
|
|
49
|
-
* // Or directly on BaseController for ad-hoc controllers
|
|
50
|
-
* new BaseController(repo, { matchesFilter: simpleEqualityMatcher });
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
function simpleEqualityMatcher(item, filters) {
|
|
54
|
-
if (!item || typeof item !== "object") return false;
|
|
55
|
-
const obj = item;
|
|
56
|
-
for (const [key, expected] of Object.entries(filters)) {
|
|
57
|
-
if (expected && typeof expected === "object" && !Array.isArray(expected) && Object.getPrototypeOf(expected) === Object.prototype && Object.keys(expected).some((k) => k.startsWith("$"))) return false;
|
|
58
|
-
const actual = obj[key];
|
|
59
|
-
if (Array.isArray(actual)) {
|
|
60
|
-
const expectedStr = String(expected);
|
|
61
|
-
if (!actual.some((v) => String(v) === expectedStr)) return false;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (String(actual) !== String(expected)) return false;
|
|
65
|
-
}
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
//#endregion
|
|
69
|
-
export { simpleEqualityMatcher as t };
|