@classytic/arc 1.1.0 → 2.1.2
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 +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
- package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
- package/dist/HookSystem-BsGV-j2l.mjs +405 -0
- package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +82 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +276 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-C3T5DTUx.mjs +141 -0
- package/dist/audited-C3T5DTUx.mjs.map +1 -0
- package/dist/auth/index.d.mts +189 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +1102 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth/redis-session.d.mts +44 -0
- package/dist/auth/redis-session.d.mts.map +1 -0
- package/dist/auth/redis-session.mjs +76 -0
- package/dist/auth/redis-session.mjs.map +1 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
- package/dist/cache/index.d.mts +146 -0
- package/dist/cache/index.d.mts.map +1 -0
- package/dist/cache/index.mjs +92 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/caching-Bl28lYsR.mjs +94 -0
- package/dist/caching-Bl28lYsR.mjs.map +1 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
- package/dist/cli/commands/describe.d.mts +19 -0
- package/dist/cli/commands/describe.d.mts.map +1 -0
- package/dist/cli/commands/describe.mjs +239 -0
- package/dist/cli/commands/describe.mjs.map +1 -0
- package/dist/cli/commands/docs.d.mts +14 -0
- package/dist/cli/commands/docs.d.mts.map +1 -0
- package/dist/cli/commands/docs.mjs +53 -0
- package/dist/cli/commands/docs.mjs.map +1 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
- package/dist/cli/commands/generate.d.mts.map +1 -0
- package/dist/cli/commands/generate.mjs +358 -0
- package/dist/cli/commands/generate.mjs.map +1 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
- package/dist/cli/commands/init.d.mts.map +1 -0
- package/dist/cli/commands/{init.js → init.mjs} +807 -616
- package/dist/cli/commands/init.mjs.map +1 -0
- package/dist/cli/commands/introspect.d.mts +11 -0
- package/dist/cli/commands/introspect.d.mts.map +1 -0
- package/dist/cli/commands/introspect.mjs +76 -0
- package/dist/cli/commands/introspect.mjs.map +1 -0
- package/dist/cli/index.d.mts +17 -0
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/index.mjs +157 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/constants-DdXFXQtN.mjs +85 -0
- package/dist/constants-DdXFXQtN.mjs.map +1 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-CUgNqegw.mjs +560 -0
- package/dist/createApp-CUgNqegw.mjs.map +1 -0
- package/dist/defineResource-k0_BDn8v.mjs +2197 -0
- package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
- package/dist/discovery/index.d.mts +47 -0
- package/dist/discovery/index.d.mts.map +1 -0
- package/dist/discovery/index.mjs +110 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/docs/index.d.mts +163 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +73 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/elevation-BRy3yFWT.mjs +113 -0
- package/dist/elevation-BRy3yFWT.mjs.map +1 -0
- package/dist/elevation-B_2dRLVP.d.mts +88 -0
- package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
- package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
- package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
- package/dist/errorHandler-C1okiriz.mjs +109 -0
- package/dist/errorHandler-C1okiriz.mjs.map +1 -0
- package/dist/errors-B9bZok84.mjs +212 -0
- package/dist/errors-B9bZok84.mjs.map +1 -0
- package/dist/errors-ChKiFz62.d.mts +125 -0
- package/dist/errors-ChKiFz62.d.mts.map +1 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
- package/dist/eventPlugin-DGR_B2on.mjs +230 -0
- package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
- package/dist/events/index.d.mts +54 -0
- package/dist/events/index.d.mts.map +1 -0
- package/dist/events/index.mjs +52 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +178 -0
- package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
- package/dist/events/transports/redis.d.mts +77 -0
- package/dist/events/transports/redis.d.mts.map +1 -0
- package/dist/events/transports/redis.mjs +125 -0
- package/dist/events/transports/redis.mjs.map +1 -0
- package/dist/externalPaths-DlINfKbP.d.mts +51 -0
- package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
- package/dist/factory/index.d.mts +64 -0
- package/dist/factory/index.d.mts.map +1 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
- package/dist/fields-DyaDVX4J.d.mts +110 -0
- package/dist/fields-DyaDVX4J.d.mts.map +1 -0
- package/dist/fields-iagOozy0.mjs +115 -0
- package/dist/fields-iagOozy0.mjs.map +1 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +97 -0
- package/dist/idempotency/index.d.mts.map +1 -0
- package/dist/idempotency/index.mjs +320 -0
- package/dist/idempotency/index.mjs.map +1 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +115 -0
- package/dist/idempotency/mongodb.mjs.map +1 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +104 -0
- package/dist/idempotency/redis.mjs.map +1 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +105 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/event-gateway.d.mts +47 -0
- package/dist/integrations/event-gateway.d.mts.map +1 -0
- package/dist/integrations/event-gateway.mjs +44 -0
- package/dist/integrations/event-gateway.mjs.map +1 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +104 -0
- package/dist/integrations/jobs.d.mts.map +1 -0
- package/dist/integrations/jobs.mjs +124 -0
- package/dist/integrations/jobs.mjs.map +1 -0
- package/dist/integrations/streamline.d.mts +61 -0
- package/dist/integrations/streamline.d.mts.map +1 -0
- package/dist/integrations/streamline.mjs +126 -0
- package/dist/integrations/streamline.mjs.map +1 -0
- package/dist/integrations/websocket.d.mts +83 -0
- package/dist/integrations/websocket.d.mts.map +1 -0
- package/dist/integrations/websocket.mjs +289 -0
- package/dist/integrations/websocket.mjs.map +1 -0
- package/dist/interface-B01JvPVc.d.mts +78 -0
- package/dist/interface-B01JvPVc.d.mts.map +1 -0
- package/dist/interface-CZe8IkMf.d.mts +55 -0
- package/dist/interface-CZe8IkMf.d.mts.map +1 -0
- package/dist/interface-Ch8HU9uM.d.mts +1098 -0
- package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
- package/dist/keys-BqNejWup.mjs +43 -0
- package/dist/keys-BqNejWup.mjs.map +1 -0
- package/dist/logger-Df2O2WsW.mjs +79 -0
- package/dist/logger-Df2O2WsW.mjs.map +1 -0
- package/dist/memory-cQgelFOj.mjs +144 -0
- package/dist/memory-cQgelFOj.mjs.map +1 -0
- package/dist/migrations/index.d.mts +157 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +261 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/mongodb-BfJVlUJH.mjs +94 -0
- package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
- package/dist/mongodb-CGzRbfAK.d.mts +119 -0
- package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
- package/dist/mongodb-JN-9JA7K.d.mts +72 -0
- package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
- package/dist/openapi-G3Cw7XuM.mjs +524 -0
- package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
- package/dist/org/index.d.mts +69 -0
- package/dist/org/index.d.mts.map +1 -0
- package/dist/org/index.mjs +514 -0
- package/dist/org/index.mjs.map +1 -0
- package/dist/org/types.d.mts +83 -0
- package/dist/org/types.d.mts.map +1 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +279 -0
- package/dist/permissions/index.d.mts.map +1 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +173 -0
- package/dist/plugins/index.d.mts.map +1 -0
- package/dist/plugins/index.mjs +523 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/plugins/response-cache.d.mts +88 -0
- package/dist/plugins/response-cache.d.mts.map +1 -0
- package/dist/plugins/response-cache.mjs +284 -0
- package/dist/plugins/response-cache.mjs.map +1 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +186 -0
- package/dist/plugins/tracing-entry.mjs.map +1 -0
- package/dist/pluralize-CEweyOEm.mjs +87 -0
- package/dist/pluralize-CEweyOEm.mjs.map +1 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -169
- package/dist/policies/index.d.mts.map +1 -0
- package/dist/policies/index.mjs +322 -0
- package/dist/policies/index.mjs.map +1 -0
- package/dist/presets/{index.d.ts → index.d.mts} +63 -131
- package/dist/presets/index.d.mts.map +1 -0
- package/dist/presets/index.mjs +144 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/presets/multiTenant.d.mts +25 -0
- package/dist/presets/multiTenant.d.mts.map +1 -0
- package/dist/presets/multiTenant.mjs +114 -0
- package/dist/presets/multiTenant.mjs.map +1 -0
- package/dist/presets-BITljm96.mjs +120 -0
- package/dist/presets-BITljm96.mjs.map +1 -0
- package/dist/presets-DzSMwlKj.d.mts +58 -0
- package/dist/presets-DzSMwlKj.d.mts.map +1 -0
- package/dist/prisma-DJbMt3yf.mjs +628 -0
- package/dist/prisma-DJbMt3yf.mjs.map +1 -0
- package/dist/prisma-Dg9GoVdj.d.mts +275 -0
- package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
- package/dist/redis-D-JAeLtm.d.mts +50 -0
- package/dist/redis-D-JAeLtm.d.mts.map +1 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
- package/dist/registry/index.d.mts +12 -0
- package/dist/registry/index.d.mts.map +1 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-QQD6ROJc.mjs +56 -0
- package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
- package/dist/schemaConverter-BwrmWroW.mjs +99 -0
- package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
- package/dist/schemas/index.d.mts +64 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +83 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/scope/index.d.mts +22 -0
- package/dist/scope/index.d.mts.map +1 -0
- package/dist/scope/index.mjs +66 -0
- package/dist/scope/index.mjs.map +1 -0
- package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
- package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
- package/dist/sse-B3c3_yZp.mjs +124 -0
- package/dist/sse-B3c3_yZp.mjs.map +1 -0
- package/dist/testing/index.d.mts +908 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +1977 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/tracing-Cc7vVQPp.d.mts +71 -0
- package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
- package/dist/typeGuards-DhMNLuvU.mjs +10 -0
- package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
- package/dist/types/index.d.mts +947 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +15 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-Beqn1Un7.mjs +39 -0
- package/dist/types-Beqn1Un7.mjs.map +1 -0
- package/dist/types-CIgB7UUl.d.mts +446 -0
- package/dist/types-CIgB7UUl.d.mts.map +1 -0
- package/dist/types-aYB4V7uN.d.mts +87 -0
- package/dist/types-aYB4V7uN.d.mts.map +1 -0
- package/dist/utils/index.d.mts +748 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-DJbMt3yf.mjs","names":[],"sources":["../src/adapters/types.ts","../src/adapters/mongoose.ts","../src/adapters/prisma.ts"],"sourcesContent":["/**\r\n * Type Utilities for Adapters\r\n *\r\n * Type-safe helpers for working with database adapters.\r\n * Eliminates the need for 'as any' casts in application code.\r\n */\r\n\r\nimport type { Model, Document } from 'mongoose';\r\nimport type { CrudRepository } from '../types/index.js';\r\n\r\n// ============================================================================\r\n// Type Inference Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Infer document type from Mongoose model\r\n *\r\n * @example\r\n * const ProductModel = mongoose.model('Product', productSchema);\r\n * type ProductDoc = InferMongooseDoc<typeof ProductModel>;\r\n * // Result: ProductDocument with all fields typed\r\n */\r\nexport type InferMongooseDoc<M> = M extends Model<infer D> ? D : never;\r\n\r\n/**\r\n * Infer document type from repository\r\n *\r\n * @example\r\n * const productRepo = new ProductRepository();\r\n * type ProductDoc = InferRepoDoc<typeof productRepo>;\r\n */\r\nexport type InferRepoDoc<R> = R extends CrudRepository<infer D> ? D : never;\r\n\r\n/**\r\n * Infer document type from data adapter\r\n *\r\n * @example\r\n * const adapter = createMongooseAdapter({ model, repository });\r\n * type Doc = InferAdapterDoc<typeof adapter>;\r\n */\r\nexport type InferAdapterDoc<A> = A extends { repository: CrudRepository<infer D> } ? D : never;\r\n\r\n/**\r\n * Extract clean document type (removes Mongoose-specific fields)\r\n *\r\n * @example\r\n * type CleanProduct = CleanDoc<ProductDocument>;\r\n * // Result: Product without _id, __v, save(), etc.\r\n */\r\nexport type CleanDoc<T> = T extends Document\r\n ? Omit<T, keyof Document | '_id' | '__v' | '$__' | '$isNew' | 'save' | 'remove'>\r\n : T;\r\n\r\n// ============================================================================\r\n// Adapter Constraint Types\r\n// ============================================================================\r\n\r\n/**\r\n * Ensures type is a valid Mongoose document\r\n */\r\nexport type MongooseDocument = Document & Record<string, unknown>;\r\n\r\n/**\r\n * Ensures type is a valid repository\r\n */\r\nexport type ValidRepository<TDoc> = CrudRepository<TDoc> & {\r\n getAll: CrudRepository<TDoc>['getAll'];\r\n getById: CrudRepository<TDoc>['getById'];\r\n create: CrudRepository<TDoc>['create'];\r\n update: CrudRepository<TDoc>['update'];\r\n delete: CrudRepository<TDoc>['delete'];\r\n};\r\n\r\n/**\r\n * Ensures model matches repository document type\r\n */\r\nexport type MatchingModel<TDoc> = Model<TDoc & Document>;\r\n\r\n// ============================================================================\r\n// Type Guards\r\n// ============================================================================\r\n\r\n/**\r\n * Check if value is a Mongoose model\r\n */\r\nexport function isMongooseModel(value: unknown): value is Model<Document> {\r\n return (\r\n typeof value === 'function' &&\r\n value.prototype &&\r\n 'modelName' in value &&\r\n 'schema' in value\r\n );\r\n}\r\n\r\n/**\r\n * Check if value is a repository\r\n */\r\nexport function isRepository(value: unknown): value is CrudRepository<unknown> {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n 'getAll' in value &&\r\n 'getById' in value &&\r\n 'create' in value &&\r\n 'update' in value &&\r\n 'delete' in value\r\n );\r\n}\r\n\r\n// Types are already exported at declaration\r\n// Functions (isMongooseModel, isRepository) are already exported at declaration\r\n","/**\r\n * Mongoose Adapter - Type-Safe Database Adapter\r\n *\r\n * Bridges Mongoose models with Arc's resource system.\r\n * Proper generics eliminate the need for 'as any' casts.\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { DataAdapter, SchemaMetadata, RepositoryLike } from './interface.js';\r\nimport type { CrudRepository, RouteSchemaOptions, OpenApiSchemas, AnyRecord } from '../types/index.js';\r\nimport { SYSTEM_FIELDS } from '../constants.js';\r\nimport { isMongooseModel, isRepository } from './types.js';\r\n\r\n/**\r\n * Mongoose SchemaType internal shape (not fully exposed by @types/mongoose)\r\n * Used to extract field metadata from schema paths\r\n */\r\ninterface MongooseSchemaType {\r\n instance: string;\r\n isRequired?: boolean;\r\n options?: {\r\n ref?: string;\r\n enum?: Array<string | number>;\r\n minlength?: number;\r\n maxlength?: number;\r\n min?: number;\r\n max?: number;\r\n [key: string]: unknown;\r\n };\r\n [key: string]: unknown;\r\n}\r\n\r\n// ============================================================================\r\n// Mongoose Adapter Options\r\n// ============================================================================\r\n\r\n/**\r\n * Options for creating a Mongoose adapter\r\n *\r\n * @typeParam TDoc - The document type (inferred or explicit)\r\n */\r\n/**\r\n * Options for creating a Mongoose adapter\r\n *\r\n * @typeParam TDoc - The document type (inferred or explicit)\r\n */\r\nexport interface MongooseAdapterOptions<TDoc = unknown> {\r\n /** Mongoose model instance — preserves document type for type safety */\r\n model: Model<TDoc>;\r\n /** Repository implementing CRUD operations - accepts any repository-like object */\r\n repository: CrudRepository<TDoc> | RepositoryLike;\r\n /**\r\n * External schema generator plugin for OpenAPI docs.\r\n * When provided, replaces the built-in basic type conversion.\r\n * Receives the Mongoose model and schema options, must return OpenApiSchemas.\r\n *\r\n * @example MongoKit integration\r\n * ```typescript\r\n * import { buildCrudSchemasFromModel } from '@classytic/mongokit';\r\n *\r\n * createMongooseAdapter({\r\n * model: JobModel,\r\n * repository: jobRepository,\r\n * schemaGenerator: (model, options) => buildCrudSchemasFromModel(model, options),\r\n * });\r\n * ```\r\n */\r\n schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions) => OpenApiSchemas;\r\n}\r\n\r\n// ============================================================================\r\n// Mongoose Adapter\r\n// ============================================================================\r\n\r\n/**\r\n * Mongoose data adapter with proper type safety\r\n *\r\n * @typeParam TDoc - The document type\r\n */\r\nexport class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {\r\n readonly type = 'mongoose' as const;\r\n readonly name: string;\r\n readonly model: Model<TDoc>;\r\n readonly repository: CrudRepository<TDoc> | RepositoryLike;\r\n private readonly schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions) => OpenApiSchemas;\r\n\r\n constructor(options: MongooseAdapterOptions<TDoc>) {\r\n // Runtime validation\r\n if (!isMongooseModel(options.model)) {\r\n throw new TypeError(\r\n 'MongooseAdapter: Invalid model. Expected Mongoose Model instance.\\n' +\r\n 'Usage: createMongooseAdapter({ model: YourModel, repository: yourRepo })'\r\n );\r\n }\r\n\r\n if (!isRepository(options.repository)) {\r\n throw new TypeError(\r\n 'MongooseAdapter: Invalid repository. Expected CrudRepository instance.\\n' +\r\n 'Usage: createMongooseAdapter({ model: YourModel, repository: yourRepo })'\r\n );\r\n }\r\n\r\n this.model = options.model;\r\n this.repository = options.repository;\r\n this.schemaGenerator = options.schemaGenerator;\r\n this.name = `MongooseAdapter<${options.model.modelName}>`;\r\n }\r\n\r\n /**\r\n * Get schema metadata from Mongoose model\r\n */\r\n getSchemaMetadata(): SchemaMetadata {\r\n const schema = this.model.schema;\r\n const paths = schema.paths;\r\n const fields: SchemaMetadata['fields'] = {};\r\n\r\n for (const [fieldName, schemaType] of Object.entries(paths)) {\r\n // Skip internal fields\r\n if (fieldName.startsWith('_') && fieldName !== '_id') continue;\r\n\r\n const typeInfo = schemaType as MongooseSchemaType;\r\n const mongooseType = typeInfo.instance || 'Mixed';\r\n\r\n // Map Mongoose types to our FieldMetadata types\r\n const typeMap: Record<string, 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array' | 'objectId' | 'enum'> = {\r\n String: 'string',\r\n Number: 'number',\r\n Boolean: 'boolean',\r\n Date: 'date',\r\n ObjectID: 'objectId',\r\n ObjectId: 'objectId',\r\n Array: 'array',\r\n Mixed: 'object',\r\n Buffer: 'object',\r\n Embedded: 'object',\r\n };\r\n\r\n fields[fieldName] = {\r\n type: typeMap[mongooseType] ?? 'object',\r\n required: !!typeInfo.isRequired,\r\n ref: typeInfo.options?.ref,\r\n };\r\n }\r\n\r\n return {\r\n name: this.model.modelName,\r\n fields,\r\n relations: this.extractRelations(paths),\r\n };\r\n }\r\n\r\n /**\r\n * Generate OpenAPI schemas from Mongoose model.\r\n *\r\n * If a `schemaGenerator` plugin was provided (e.g. MongoKit's buildCrudSchemasFromModel),\r\n * it is used instead of the built-in basic conversion.\r\n */\r\n generateSchemas(schemaOptions?: RouteSchemaOptions): OpenApiSchemas | null {\r\n try {\r\n // Delegate to external schema generator plugin when available\r\n if (this.schemaGenerator) {\r\n return this.schemaGenerator(this.model, schemaOptions);\r\n }\r\n\r\n // Built-in basic conversion (fallback)\r\n const schema = this.model.schema;\r\n const paths = schema.paths;\r\n const properties: AnyRecord = {};\r\n const required: string[] = [];\r\n\r\n // Extract field rules from schema options\r\n const fieldRules = schemaOptions?.fieldRules || {};\r\n const blockedFields = new Set<string>(\r\n Object.entries(fieldRules)\r\n .filter(([, rules]) => rules.systemManaged || rules.hidden)\r\n .map(([field]) => field)\r\n );\r\n\r\n for (const [fieldName, schemaType] of Object.entries(paths)) {\r\n // Skip internal and blocked fields\r\n if (fieldName.startsWith('__')) continue;\r\n if (blockedFields.has(fieldName)) continue;\r\n\r\n const typeInfo = schemaType as MongooseSchemaType;\r\n properties[fieldName] = this.mongooseTypeToOpenApi(typeInfo);\r\n\r\n if (typeInfo.isRequired) {\r\n required.push(fieldName);\r\n }\r\n }\r\n\r\n // Filter out system-managed fields for input schemas.\r\n // Uses SYSTEM_FIELDS from constants (excludes __v which is Mongoose-internal\r\n // and already absent from input schemas).\r\n const systemFieldSet = new Set<string>(SYSTEM_FIELDS);\r\n const inputProperties = Object.fromEntries(\r\n Object.entries(properties).filter(\r\n ([field]) => !systemFieldSet.has(field)\r\n )\r\n );\r\n\r\n const inputRequired = required.filter(\r\n (field) => !systemFieldSet.has(field)\r\n );\r\n\r\n return {\r\n createBody: {\r\n type: 'object',\r\n properties: inputProperties,\r\n required: inputRequired.length > 0 ? inputRequired : undefined,\r\n },\r\n updateBody: {\r\n type: 'object',\r\n properties: inputProperties,\r\n // All fields optional for PATCH\r\n },\r\n response: {\r\n type: 'object',\r\n properties,\r\n },\r\n };\r\n } catch {\r\n // Schema generation is optional - fail silently\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Extract relation metadata\r\n */\r\n private extractRelations(paths: Record<string, unknown>): SchemaMetadata['relations'] {\r\n const relations: Record<string, { type: 'one-to-one' | 'one-to-many' | 'many-to-many'; target: string; foreignKey?: string }> = {};\r\n\r\n for (const [fieldName, schemaType] of Object.entries(paths)) {\r\n const ref = (schemaType as MongooseSchemaType).options?.ref;\r\n if (ref) {\r\n relations[fieldName] = {\r\n type: 'one-to-one', // Mongoose refs are typically one-to-one\r\n target: ref,\r\n foreignKey: fieldName,\r\n };\r\n }\r\n }\r\n\r\n return Object.keys(relations).length > 0 ? relations : undefined;\r\n }\r\n\r\n /**\r\n * Convert Mongoose type to OpenAPI type\r\n */\r\n private mongooseTypeToOpenApi(typeInfo: MongooseSchemaType): AnyRecord {\r\n const instance = typeInfo.instance;\r\n const options = typeInfo.options || {};\r\n\r\n const baseType: AnyRecord = {};\r\n\r\n switch (instance) {\r\n case 'String':\r\n baseType.type = 'string';\r\n if (options.enum) baseType.enum = options.enum;\r\n if (options.minlength) baseType.minLength = options.minlength;\r\n if (options.maxlength) baseType.maxLength = options.maxlength;\r\n break;\r\n case 'Number':\r\n baseType.type = 'number';\r\n if (options.min !== undefined) baseType.minimum = options.min;\r\n if (options.max !== undefined) baseType.maximum = options.max;\r\n break;\r\n case 'Boolean':\r\n baseType.type = 'boolean';\r\n break;\r\n case 'Date':\r\n baseType.type = 'string';\r\n baseType.format = 'date-time';\r\n break;\r\n case 'ObjectID':\r\n case 'ObjectId':\r\n baseType.type = 'string';\r\n baseType.pattern = '^[a-f\\\\d]{24}$';\r\n break;\r\n case 'Array':\r\n baseType.type = 'array';\r\n baseType.items = { type: 'string' }; // Default, can be improved\r\n break;\r\n default:\r\n baseType.type = 'object';\r\n }\r\n\r\n return baseType;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Factory Function with Type Inference\r\n// ============================================================================\r\n\r\n/**\r\n * Create Mongoose adapter with flexible type acceptance.\r\n * Accepts any repository with CRUD methods — no `as any` needed.\r\n *\r\n * @example\r\n * ```typescript\r\n * // Object form (explicit)\r\n * const adapter = createMongooseAdapter({\r\n * model: ProductModel,\r\n * repository: productRepository,\r\n * });\r\n *\r\n * // Shorthand form (2-arg) — most common path\r\n * const adapter = createMongooseAdapter(ProductModel, productRepository);\r\n * ```\r\n */\r\nexport function createMongooseAdapter<TDoc = unknown>(\r\n model: Model<TDoc>,\r\n repository: CrudRepository<TDoc> | RepositoryLike,\r\n): DataAdapter<TDoc>;\r\nexport function createMongooseAdapter<TDoc = unknown>(\r\n options: MongooseAdapterOptions<TDoc>,\r\n): DataAdapter<TDoc>;\r\nexport function createMongooseAdapter<TDoc = unknown>(\r\n modelOrOptions: Model<TDoc> | MongooseAdapterOptions<TDoc>,\r\n repository?: CrudRepository<TDoc> | RepositoryLike,\r\n): DataAdapter<TDoc> {\r\n if (isMongooseModel(modelOrOptions)) {\r\n if (!repository) {\r\n throw new TypeError(\r\n 'createMongooseAdapter: repository is required when using 2-arg form.\\n' +\r\n 'Usage: createMongooseAdapter(Model, repository)'\r\n );\r\n }\r\n return new MongooseAdapter<TDoc>({ model: modelOrOptions as Model<TDoc>, repository });\r\n }\r\n return new MongooseAdapter<TDoc>(modelOrOptions as MongooseAdapterOptions<TDoc>);\r\n}\r\n\r\n// ============================================================================\r\n// Exports\r\n// ============================================================================\r\n\r\nexport default createMongooseAdapter;\r\n","/**\n * Prisma Adapter - PostgreSQL/MySQL/SQLite Implementation\n *\n * @experimental This adapter is implemented but has no integration tests yet.\n * Use in production at your own risk. The Mongoose adapter is the recommended\n * and battle-tested path.\n *\n * Bridges Prisma Client with Arc's DataAdapter interface.\n * Supports Prisma 5+ with all database providers.\n *\n * Implemented features:\n * - Schema generation (OpenAPI docs from DMMF)\n * - Health checks (database connectivity)\n * - Query parsing (URL params → Prisma where/orderBy)\n * - Policy filter translation\n * - Soft delete preset support\n *\n * Known gaps:\n * - No integration test coverage\n * - Multi-tenant isolation relies on caller-provided policyFilters (no auto-enforcement)\n *\n * @example\n * ```typescript\n * import { PrismaClient, Prisma } from '@prisma/client';\n * import { createPrismaAdapter, PrismaQueryParser } from '@classytic/arc/adapters';\n *\n * const prisma = new PrismaClient();\n *\n * const userAdapter = createPrismaAdapter({\n * client: prisma,\n * modelName: 'user',\n * repository: new UserRepository(prisma),\n * dmmf: Prisma.dmmf, // For schema generation\n * queryParser: new PrismaQueryParser(), // Optional: custom parser\n * });\n * ```\n */\n\nimport type { DataAdapter, SchemaMetadata, FieldMetadata, ValidationResult } from './interface.js';\nimport type { CrudRepository, OpenApiSchemas, RouteSchemaOptions, ParsedQuery, QueryParserInterface, AnyRecord } from '../types/index.js';\nimport { DEFAULT_MAX_LIMIT, DEFAULT_LIMIT, RESERVED_QUERY_PARAMS } from '../constants.js';\n\n// ============================================================================\n// Prisma DMMF Types (runtime shapes from @prisma/client)\n// ============================================================================\n\n/** Prisma DMMF field shape */\ninterface DmmfField {\n name: string;\n type: string;\n kind: string;\n isList: boolean;\n isRequired: boolean;\n isUnique?: boolean;\n isId?: boolean;\n isGenerated?: boolean;\n hasDefaultValue?: boolean;\n default?: unknown;\n documentation?: string;\n relationName?: string;\n}\n\n/** Prisma DMMF enum value */\ninterface DmmfEnumValue {\n name: string;\n}\n\n/** Prisma DMMF enum */\ninterface DmmfEnum {\n name: string;\n values: DmmfEnumValue[];\n}\n\n/** Prisma DMMF model shape */\ninterface DmmfModel {\n name: string;\n fields: DmmfField[];\n uniqueIndexes?: Array<{ fields: string[] }>;\n}\n\n/** Prisma DMMF datamodel */\ninterface DmmfDatamodel {\n models: DmmfModel[];\n enums?: DmmfEnum[];\n}\n\n/** Prisma DMMF root shape */\ninterface PrismaDmmf {\n datamodel?: DmmfDatamodel;\n}\n\n/** Prisma client delegate (model accessor) */\ninterface PrismaDelegate {\n findMany(args?: unknown): Promise<unknown[]>;\n}\n\n/** Prisma client shape */\ninterface PrismaClientLike {\n $disconnect(): Promise<void>;\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Prisma Query Parser\n// ============================================================================\n\n/**\n * Options for PrismaQueryParser\n */\nexport interface PrismaQueryParserOptions {\n /** Maximum allowed limit value (default: 1000) */\n maxLimit?: number;\n /** Default limit for pagination (default: 20) */\n defaultLimit?: number;\n /** Enable soft delete filtering by default (default: true) */\n softDeleteEnabled?: boolean;\n /** Field name for soft delete (default: 'deletedAt') */\n softDeleteField?: string;\n}\n\n/**\n * Prisma Query Parser - Converts URL parameters to Prisma query format\n *\n * Translates Arc's query format to Prisma's where/orderBy/take/skip structure.\n *\n * @example\n * ```typescript\n * const parser = new PrismaQueryParser();\n *\n * // URL: ?status=active&price[gte]=100&sort=-createdAt&page=2&limit=10\n * const prismaQuery = parser.toPrismaQuery(parsedQuery);\n * // Returns:\n * // {\n * // where: { status: 'active', price: { gte: 100 }, deletedAt: null },\n * // orderBy: { createdAt: 'desc' },\n * // take: 10,\n * // skip: 10,\n * // }\n * ```\n */\nexport class PrismaQueryParser implements QueryParserInterface {\n private readonly maxLimit: number;\n private readonly defaultLimit: number;\n private readonly softDeleteEnabled: boolean;\n private readonly softDeleteField: string;\n\n /** Map Arc operators to Prisma operators */\n private readonly operatorMap: Record<string, string> = {\n $eq: 'equals',\n $ne: 'not',\n $gt: 'gt',\n $gte: 'gte',\n $lt: 'lt',\n $lte: 'lte',\n $in: 'in',\n $nin: 'notIn',\n $regex: 'contains',\n $exists: undefined as unknown as string, // Handled specially in translateFilters\n };\n\n constructor(options: PrismaQueryParserOptions = {}) {\n this.maxLimit = options.maxLimit ?? DEFAULT_MAX_LIMIT;\n this.defaultLimit = options.defaultLimit ?? DEFAULT_LIMIT;\n this.softDeleteEnabled = options.softDeleteEnabled ?? true;\n this.softDeleteField = options.softDeleteField ?? 'deletedAt';\n }\n\n /**\n * Parse URL query parameters (delegates to ArcQueryParser format)\n */\n parse(query: Record<string, unknown> | null | undefined): ParsedQuery {\n const q = query ?? {};\n\n const page = this.parseNumber(q.page, 1);\n const limit = Math.min(this.parseNumber(q.limit, this.defaultLimit), this.maxLimit);\n\n return {\n filters: this.parseFilters(q),\n limit,\n page,\n sort: this.parseSort(q.sort),\n search: q.search as string | undefined,\n select: this.parseSelect(q.select),\n };\n }\n\n /**\n * Convert ParsedQuery to Prisma query options\n */\n toPrismaQuery(parsed: ParsedQuery, policyFilters?: Record<string, unknown>): PrismaQueryOptions {\n const where: Record<string, unknown> = {};\n\n // Apply filters\n if (parsed.filters) {\n Object.assign(where, this.translateFilters(parsed.filters));\n }\n\n // Apply policy filters (multi-tenant, ownership, etc.)\n if (policyFilters) {\n Object.assign(where, this.translateFilters(policyFilters));\n }\n\n // Apply soft delete filter\n if (this.softDeleteEnabled) {\n where[this.softDeleteField] = null;\n }\n\n // Build orderBy\n const orderBy: Array<Record<string, 'asc' | 'desc'>> | undefined = parsed.sort\n ? Object.entries(parsed.sort).map(([field, dir]) => ({\n [field]: (dir === 1 ? 'asc' : 'desc') as 'asc' | 'desc',\n }))\n : undefined;\n\n // Build pagination\n const take = parsed.limit ?? this.defaultLimit;\n const skip = parsed.page ? (parsed.page - 1) * take : 0;\n\n // Build select\n const select = parsed.select\n ? Object.fromEntries(\n Object.entries(parsed.select)\n .filter(([, v]) => v === 1)\n .map(([k]) => [k, true])\n )\n : undefined;\n\n return {\n where: Object.keys(where).length > 0 ? where : undefined,\n orderBy: orderBy && orderBy.length > 0 ? orderBy : undefined,\n take,\n skip,\n select: select && Object.keys(select).length > 0 ? select : undefined,\n };\n }\n\n /**\n * Translate Arc/MongoDB-style filters to Prisma where clause\n */\n private translateFilters(filters: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [field, value] of Object.entries(filters)) {\n if (value === null || value === undefined) continue;\n\n // Handle nested operator objects: { status: { $ne: 'deleted' } }\n if (typeof value === 'object' && !Array.isArray(value)) {\n const prismaCondition: Record<string, unknown> = {};\n\n for (const [op, opValue] of Object.entries(value as Record<string, unknown>)) {\n if (op === '$exists') {\n // $exists: true → { not: null }, $exists: false → null\n result[field] = opValue ? { not: null } : null;\n continue;\n }\n\n const prismaOp = this.operatorMap[op];\n if (prismaOp) {\n prismaCondition[prismaOp] = opValue;\n }\n }\n\n if (Object.keys(prismaCondition).length > 0) {\n result[field] = prismaCondition;\n }\n } else {\n // Direct equality\n result[field] = value;\n }\n }\n\n return result;\n }\n\n private parseNumber(value: unknown, defaultValue: number): number {\n if (value === undefined || value === null) return defaultValue;\n const num = parseInt(String(value), 10);\n return Number.isNaN(num) ? defaultValue : Math.max(1, num);\n }\n\n private parseSort(value: unknown): Record<string, 1 | -1> | undefined {\n if (!value) return undefined;\n\n const sortStr = String(value);\n const result: Record<string, 1 | -1> = {};\n\n for (const field of sortStr.split(',')) {\n const trimmed = field.trim();\n if (!trimmed || !/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;\n\n if (trimmed.startsWith('-')) {\n result[trimmed.slice(1)] = -1;\n } else {\n result[trimmed] = 1;\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n }\n\n private parseSelect(value: unknown): Record<string, 0 | 1> | undefined {\n if (!value) return undefined;\n\n const result: Record<string, 0 | 1> = {};\n\n for (const field of String(value).split(',')) {\n const trimmed = field.trim();\n if (!trimmed || !/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;\n\n result[trimmed.startsWith('-') ? trimmed.slice(1) : trimmed] = trimmed.startsWith('-') ? 0 : 1;\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n }\n\n private parseFilters(query: Record<string, unknown>): Record<string, unknown> {\n const filters: Record<string, unknown> = {};\n\n const operators: Record<string, string> = {\n eq: '$eq', ne: '$ne', gt: '$gt', gte: '$gte',\n lt: '$lt', lte: '$lte', in: '$in', nin: '$nin',\n like: '$regex', contains: '$regex', exists: '$exists',\n };\n\n for (const [key, value] of Object.entries(query)) {\n if (RESERVED_QUERY_PARAMS.has(key) || value === undefined || value === null) continue;\n\n const match = key.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)(?:\\[([a-z]+)\\])?$/);\n if (!match) continue;\n\n const [, fieldName, operator] = match;\n if (!fieldName) continue;\n\n if (operator && operators[operator]) {\n if (!filters[fieldName]) filters[fieldName] = {};\n (filters[fieldName] as Record<string, unknown>)[operators[operator]] = this.coerceValue(value, operator);\n } else if (!operator) {\n filters[fieldName] = this.coerceValue(value);\n }\n }\n\n return filters;\n }\n\n private coerceValue(value: unknown, operator?: string): unknown {\n if (operator === 'in' || operator === 'nin') {\n if (Array.isArray(value)) return value.map(v => this.coerceValue(v));\n if (typeof value === 'string' && value.includes(',')) {\n return value.split(',').map(v => this.coerceValue(v.trim()));\n }\n return [this.coerceValue(value)];\n }\n\n if (operator === 'exists') {\n return String(value).toLowerCase() === 'true' || value === '1';\n }\n\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null') return null;\n\n if (typeof value === 'string') {\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== '') return num;\n }\n\n return value;\n }\n}\n\n/**\n * Prisma query options returned by toPrismaQuery\n */\nexport interface PrismaQueryOptions {\n where?: Record<string, unknown>;\n orderBy?: Array<Record<string, 'asc' | 'desc'>>;\n take?: number;\n skip?: number;\n select?: Record<string, boolean>;\n include?: Record<string, boolean>;\n}\n\n// ============================================================================\n// Prisma Adapter Options\n// ============================================================================\n\nexport interface PrismaAdapterOptions<TModel> {\n /** Prisma client instance */\n client: PrismaClientLike;\n /** Model name (e.g., 'user', 'product') */\n modelName: string;\n /** Repository instance implementing CRUD operations */\n repository: CrudRepository<TModel>;\n /** Optional: Prisma DMMF (Data Model Meta Format) for schema extraction */\n dmmf?: PrismaDmmf;\n /** Optional: Custom query parser (default: PrismaQueryParser) */\n queryParser?: PrismaQueryParser;\n /** Enable soft delete filtering (default: true) */\n softDeleteEnabled?: boolean;\n /** Field name for soft delete (default: 'deletedAt') */\n softDeleteField?: string;\n}\n\nexport class PrismaAdapter<TModel = unknown> implements DataAdapter<TModel> {\n readonly type = 'prisma' as const;\n readonly name: string;\n readonly repository: CrudRepository<TModel>;\n readonly queryParser: PrismaQueryParser;\n\n private client: PrismaClientLike;\n private modelName: string;\n private dmmf?: PrismaDmmf;\n private softDeleteEnabled: boolean;\n private softDeleteField: string;\n\n constructor(options: PrismaAdapterOptions<TModel>) {\n this.client = options.client;\n this.modelName = options.modelName;\n this.repository = options.repository;\n this.dmmf = options.dmmf;\n this.name = `prisma:${options.modelName}`;\n this.softDeleteEnabled = options.softDeleteEnabled ?? true;\n this.softDeleteField = options.softDeleteField ?? 'deletedAt';\n\n // Initialize query parser\n this.queryParser = options.queryParser ?? new PrismaQueryParser({\n softDeleteEnabled: this.softDeleteEnabled,\n softDeleteField: this.softDeleteField,\n });\n }\n\n /**\n * Parse URL query parameters and convert to Prisma query options\n */\n parseQuery(query: Record<string, unknown>, policyFilters?: Record<string, unknown>): PrismaQueryOptions {\n const parsed = this.queryParser.parse(query);\n return this.queryParser.toPrismaQuery(parsed, policyFilters);\n }\n\n /**\n * Apply policy filters to existing Prisma where clause\n * Used for multi-tenant, ownership, and other security filters\n */\n applyPolicyFilters(\n where: Record<string, unknown>,\n policyFilters: Record<string, unknown>\n ): Record<string, unknown> {\n return { ...where, ...policyFilters };\n }\n\n generateSchemas(options?: RouteSchemaOptions): OpenApiSchemas | null {\n // Extract schema from Prisma DMMF if available\n if (!this.dmmf) return null;\n\n try {\n const model = this.dmmf.datamodel?.models?.find(\n (m: DmmfModel) => m.name.toLowerCase() === this.modelName.toLowerCase()\n );\n\n if (!model) return null;\n\n const entitySchema = this.buildEntitySchema(model, options);\n const createBodySchema = this.buildCreateSchema(model, options);\n const updateBodySchema = this.buildUpdateSchema(model, options);\n\n return {\n entity: entitySchema,\n createBody: createBodySchema,\n updateBody: updateBodySchema,\n params: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n },\n required: ['id'],\n },\n listQuery: {\n type: 'object',\n properties: {\n page: { type: 'number', minimum: 1, description: 'Page number for pagination' },\n limit: { type: 'number', minimum: 1, maximum: 100, description: 'Items per page' },\n sort: { type: 'string', description: 'Sort field (e.g., \"name\", \"-createdAt\")' },\n // Note: Actual filtering requires custom query parser implementation\n // This is placeholder documentation only\n },\n },\n };\n } catch {\n // Schema generation is optional - fail silently\n return null;\n }\n }\n\n getSchemaMetadata(): SchemaMetadata | null {\n if (!this.dmmf) return null;\n\n try {\n const model = this.dmmf.datamodel?.models?.find(\n (m: DmmfModel) => m.name.toLowerCase() === this.modelName.toLowerCase()\n );\n\n if (!model) return null;\n\n const fields: Record<string, FieldMetadata> = {};\n\n for (const field of model.fields) {\n fields[field.name] = this.convertPrismaFieldToMetadata(field);\n }\n\n return {\n name: model.name,\n fields,\n indexes: model.uniqueIndexes?.map((idx: { fields: string[] }) => ({\n fields: idx.fields,\n unique: true,\n })),\n };\n } catch (err) {\n return null;\n }\n }\n\n async validate(data: unknown): Promise<ValidationResult> {\n // Prisma validates on write, so we do basic type checking here\n if (!data || typeof data !== 'object') {\n return {\n valid: false,\n errors: [{ field: 'root', message: 'Data must be an object' }],\n };\n }\n\n // Get required fields from DMMF\n if (this.dmmf) {\n try {\n const model = this.dmmf.datamodel?.models?.find(\n (m: DmmfModel) => m.name.toLowerCase() === this.modelName.toLowerCase()\n );\n\n if (model) {\n const requiredFields = model.fields.filter(\n (f: DmmfField) => f.isRequired && !f.hasDefaultValue && !f.isGenerated\n );\n\n const errors: Array<{ field: string; message: string }> = [];\n\n for (const field of requiredFields) {\n if (!(field.name in (data as Record<string, unknown>))) {\n errors.push({\n field: field.name,\n message: `${field.name} is required`,\n });\n }\n }\n\n if (errors.length > 0) {\n return { valid: false, errors };\n }\n }\n } catch (err) {\n // Validation failed, but we'll let Prisma handle it on write\n }\n }\n\n return { valid: true };\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n // Use findMany with take: 1 for database-agnostic health check\n // This works across all Prisma providers (SQL, MongoDB, etc.)\n // Prisma client delegates use camelCase (e.g., prisma.userProfile, not prisma.UserProfile)\n const delegateName = this.modelName.charAt(0).toLowerCase() + this.modelName.slice(1);\n const delegate = this.client[delegateName] as PrismaDelegate | undefined;\n if (!delegate) {\n return false;\n }\n await delegate.findMany({ take: 1 });\n return true;\n } catch (err) {\n return false;\n }\n }\n\n async close(): Promise<void> {\n try {\n await this.client.$disconnect();\n } catch (err) {\n // Already disconnected or error - ignore\n }\n }\n\n // ============================================================================\n // Private Helper Methods\n // ============================================================================\n\n private buildEntitySchema(model: DmmfModel, options?: RouteSchemaOptions): AnyRecord {\n const properties: Record<string, AnyRecord> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n // Skip internal fields unless explicitly included\n if (this.shouldSkipField(field, options)) continue;\n\n properties[field.name] = this.convertPrismaFieldToJsonSchema(field);\n\n if (field.isRequired && !field.hasDefaultValue) {\n required.push(field.name);\n }\n }\n\n return {\n type: 'object',\n properties,\n ...(required.length > 0 && { required }),\n };\n }\n\n private buildCreateSchema(model: DmmfModel, options?: RouteSchemaOptions): AnyRecord {\n const properties: Record<string, AnyRecord> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n // Skip auto-generated and relation fields for create\n if (field.isGenerated || field.relationName) continue;\n if (this.shouldSkipField(field, options)) continue;\n\n properties[field.name] = this.convertPrismaFieldToJsonSchema(field);\n\n if (field.isRequired && !field.hasDefaultValue) {\n required.push(field.name);\n }\n }\n\n return {\n type: 'object',\n properties,\n ...(required.length > 0 && { required }),\n };\n }\n\n private buildUpdateSchema(model: DmmfModel, options?: RouteSchemaOptions): AnyRecord {\n const properties: Record<string, AnyRecord> = {};\n\n for (const field of model.fields) {\n // Skip auto-generated, ID, and relation fields for update\n if (field.isGenerated || field.isId || field.relationName) continue;\n if (this.shouldSkipField(field, options)) continue;\n\n properties[field.name] = this.convertPrismaFieldToJsonSchema(field);\n }\n\n return {\n type: 'object',\n properties,\n };\n }\n\n private shouldSkipField(field: DmmfField, options?: RouteSchemaOptions): boolean {\n // Check if field is in excludeFields\n if (options?.excludeFields?.includes(field.name)) {\n return true;\n }\n\n // Skip internal Prisma fields\n if (field.name.startsWith('_')) {\n return true;\n }\n\n return false;\n }\n\n private convertPrismaFieldToJsonSchema(field: DmmfField): AnyRecord {\n const schema: AnyRecord = {};\n\n // Map Prisma types to JSON Schema types\n switch (field.type) {\n case 'String':\n schema.type = 'string';\n break;\n case 'Int':\n case 'BigInt':\n schema.type = 'integer';\n break;\n case 'Float':\n case 'Decimal':\n schema.type = 'number';\n break;\n case 'Boolean':\n schema.type = 'boolean';\n break;\n case 'DateTime':\n schema.type = 'string';\n schema.format = 'date-time';\n break;\n case 'Json':\n schema.type = 'object';\n break;\n default:\n // Enums and other types\n if (field.kind === 'enum') {\n schema.type = 'string';\n // Extract enum values from DMMF if available\n if (this.dmmf?.datamodel?.enums) {\n const enumDef = this.dmmf.datamodel.enums.find((e: DmmfEnum) => e.name === field.type);\n if (enumDef) {\n schema.enum = enumDef.values.map((v: DmmfEnumValue) => v.name);\n }\n }\n } else {\n schema.type = 'string';\n }\n }\n\n // Handle arrays\n if (field.isList) {\n return {\n type: 'array',\n items: schema,\n };\n }\n\n // Add description if available\n if (field.documentation) {\n schema.description = field.documentation;\n }\n\n return schema;\n }\n\n private convertPrismaFieldToMetadata(field: DmmfField): FieldMetadata {\n const metadata: FieldMetadata = {\n type: this.mapPrismaTypeToMetadataType(field.type, field.kind),\n required: field.isRequired,\n array: field.isList,\n };\n\n if (field.isUnique) {\n metadata.unique = true;\n }\n\n if (field.hasDefaultValue) {\n metadata.default = field.default;\n }\n\n if (field.documentation) {\n metadata.description = field.documentation;\n }\n\n if (field.relationName) {\n metadata.ref = field.type;\n }\n\n return metadata;\n }\n\n private mapPrismaTypeToMetadataType(\n type: string,\n kind: string\n ): FieldMetadata['type'] {\n if (kind === 'enum') return 'enum';\n\n switch (type) {\n case 'String':\n return 'string';\n case 'Int':\n case 'BigInt':\n case 'Float':\n case 'Decimal':\n return 'number';\n case 'Boolean':\n return 'boolean';\n case 'DateTime':\n return 'date';\n case 'Json':\n return 'object';\n default:\n return 'string';\n }\n }\n}\n\n/**\n * Factory function to create Prisma adapter\n *\n * @example\n * import { PrismaClient } from '@prisma/client';\n * import { createPrismaAdapter } from '@classytic/arc';\n *\n * const prisma = new PrismaClient();\n *\n * const userAdapter = createPrismaAdapter({\n * client: prisma,\n * modelName: 'user',\n * repository: userRepository,\n * dmmf: Prisma.dmmf, // Optional: for schema generation\n * });\n */\nexport function createPrismaAdapter<TModel>(\n options: PrismaAdapterOptions<TModel>\n): PrismaAdapter<TModel> {\n return new PrismaAdapter(options);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { D as CrudRepository, a as RepositoryLike, n as DataAdapter, o as SchemaMetadata, s as ValidationResult } from "./interface-Ch8HU9uM.mjs";
|
|
2
|
+
import { OpenApiSchemas, ParsedQuery, QueryParserInterface, RouteSchemaOptions } from "./types/index.mjs";
|
|
3
|
+
import { Model } from "mongoose";
|
|
4
|
+
|
|
5
|
+
//#region src/adapters/mongoose.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Options for creating a Mongoose adapter
|
|
8
|
+
*
|
|
9
|
+
* @typeParam TDoc - The document type (inferred or explicit)
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Options for creating a Mongoose adapter
|
|
13
|
+
*
|
|
14
|
+
* @typeParam TDoc - The document type (inferred or explicit)
|
|
15
|
+
*/
|
|
16
|
+
interface MongooseAdapterOptions<TDoc = unknown> {
|
|
17
|
+
/** Mongoose model instance — preserves document type for type safety */
|
|
18
|
+
model: Model<TDoc>;
|
|
19
|
+
/** Repository implementing CRUD operations - accepts any repository-like object */
|
|
20
|
+
repository: CrudRepository<TDoc> | RepositoryLike;
|
|
21
|
+
/**
|
|
22
|
+
* External schema generator plugin for OpenAPI docs.
|
|
23
|
+
* When provided, replaces the built-in basic type conversion.
|
|
24
|
+
* Receives the Mongoose model and schema options, must return OpenApiSchemas.
|
|
25
|
+
*
|
|
26
|
+
* @example MongoKit integration
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { buildCrudSchemasFromModel } from '@classytic/mongokit';
|
|
29
|
+
*
|
|
30
|
+
* createMongooseAdapter({
|
|
31
|
+
* model: JobModel,
|
|
32
|
+
* repository: jobRepository,
|
|
33
|
+
* schemaGenerator: (model, options) => buildCrudSchemasFromModel(model, options),
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions) => OpenApiSchemas;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Mongoose data adapter with proper type safety
|
|
41
|
+
*
|
|
42
|
+
* @typeParam TDoc - The document type
|
|
43
|
+
*/
|
|
44
|
+
declare class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {
|
|
45
|
+
readonly type: "mongoose";
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly model: Model<TDoc>;
|
|
48
|
+
readonly repository: CrudRepository<TDoc> | RepositoryLike;
|
|
49
|
+
private readonly schemaGenerator?;
|
|
50
|
+
constructor(options: MongooseAdapterOptions<TDoc>);
|
|
51
|
+
/**
|
|
52
|
+
* Get schema metadata from Mongoose model
|
|
53
|
+
*/
|
|
54
|
+
getSchemaMetadata(): SchemaMetadata;
|
|
55
|
+
/**
|
|
56
|
+
* Generate OpenAPI schemas from Mongoose model.
|
|
57
|
+
*
|
|
58
|
+
* If a `schemaGenerator` plugin was provided (e.g. MongoKit's buildCrudSchemasFromModel),
|
|
59
|
+
* it is used instead of the built-in basic conversion.
|
|
60
|
+
*/
|
|
61
|
+
generateSchemas(schemaOptions?: RouteSchemaOptions): OpenApiSchemas | null;
|
|
62
|
+
/**
|
|
63
|
+
* Extract relation metadata
|
|
64
|
+
*/
|
|
65
|
+
private extractRelations;
|
|
66
|
+
/**
|
|
67
|
+
* Convert Mongoose type to OpenAPI type
|
|
68
|
+
*/
|
|
69
|
+
private mongooseTypeToOpenApi;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create Mongoose adapter with flexible type acceptance.
|
|
73
|
+
* Accepts any repository with CRUD methods — no `as any` needed.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* // Object form (explicit)
|
|
78
|
+
* const adapter = createMongooseAdapter({
|
|
79
|
+
* model: ProductModel,
|
|
80
|
+
* repository: productRepository,
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* // Shorthand form (2-arg) — most common path
|
|
84
|
+
* const adapter = createMongooseAdapter(ProductModel, productRepository);
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function createMongooseAdapter<TDoc = unknown>(model: Model<TDoc>, repository: CrudRepository<TDoc> | RepositoryLike): DataAdapter<TDoc>;
|
|
88
|
+
declare function createMongooseAdapter<TDoc = unknown>(options: MongooseAdapterOptions<TDoc>): DataAdapter<TDoc>;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/adapters/prisma.d.ts
|
|
91
|
+
/** Prisma DMMF field shape */
|
|
92
|
+
interface DmmfField {
|
|
93
|
+
name: string;
|
|
94
|
+
type: string;
|
|
95
|
+
kind: string;
|
|
96
|
+
isList: boolean;
|
|
97
|
+
isRequired: boolean;
|
|
98
|
+
isUnique?: boolean;
|
|
99
|
+
isId?: boolean;
|
|
100
|
+
isGenerated?: boolean;
|
|
101
|
+
hasDefaultValue?: boolean;
|
|
102
|
+
default?: unknown;
|
|
103
|
+
documentation?: string;
|
|
104
|
+
relationName?: string;
|
|
105
|
+
}
|
|
106
|
+
/** Prisma DMMF enum value */
|
|
107
|
+
interface DmmfEnumValue {
|
|
108
|
+
name: string;
|
|
109
|
+
}
|
|
110
|
+
/** Prisma DMMF enum */
|
|
111
|
+
interface DmmfEnum {
|
|
112
|
+
name: string;
|
|
113
|
+
values: DmmfEnumValue[];
|
|
114
|
+
}
|
|
115
|
+
/** Prisma DMMF model shape */
|
|
116
|
+
interface DmmfModel {
|
|
117
|
+
name: string;
|
|
118
|
+
fields: DmmfField[];
|
|
119
|
+
uniqueIndexes?: Array<{
|
|
120
|
+
fields: string[];
|
|
121
|
+
}>;
|
|
122
|
+
}
|
|
123
|
+
/** Prisma DMMF datamodel */
|
|
124
|
+
interface DmmfDatamodel {
|
|
125
|
+
models: DmmfModel[];
|
|
126
|
+
enums?: DmmfEnum[];
|
|
127
|
+
}
|
|
128
|
+
/** Prisma DMMF root shape */
|
|
129
|
+
interface PrismaDmmf {
|
|
130
|
+
datamodel?: DmmfDatamodel;
|
|
131
|
+
}
|
|
132
|
+
/** Prisma client shape */
|
|
133
|
+
interface PrismaClientLike {
|
|
134
|
+
$disconnect(): Promise<void>;
|
|
135
|
+
[key: string]: unknown;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Options for PrismaQueryParser
|
|
139
|
+
*/
|
|
140
|
+
interface PrismaQueryParserOptions {
|
|
141
|
+
/** Maximum allowed limit value (default: 1000) */
|
|
142
|
+
maxLimit?: number;
|
|
143
|
+
/** Default limit for pagination (default: 20) */
|
|
144
|
+
defaultLimit?: number;
|
|
145
|
+
/** Enable soft delete filtering by default (default: true) */
|
|
146
|
+
softDeleteEnabled?: boolean;
|
|
147
|
+
/** Field name for soft delete (default: 'deletedAt') */
|
|
148
|
+
softDeleteField?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Prisma Query Parser - Converts URL parameters to Prisma query format
|
|
152
|
+
*
|
|
153
|
+
* Translates Arc's query format to Prisma's where/orderBy/take/skip structure.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const parser = new PrismaQueryParser();
|
|
158
|
+
*
|
|
159
|
+
* // URL: ?status=active&price[gte]=100&sort=-createdAt&page=2&limit=10
|
|
160
|
+
* const prismaQuery = parser.toPrismaQuery(parsedQuery);
|
|
161
|
+
* // Returns:
|
|
162
|
+
* // {
|
|
163
|
+
* // where: { status: 'active', price: { gte: 100 }, deletedAt: null },
|
|
164
|
+
* // orderBy: { createdAt: 'desc' },
|
|
165
|
+
* // take: 10,
|
|
166
|
+
* // skip: 10,
|
|
167
|
+
* // }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
declare class PrismaQueryParser implements QueryParserInterface {
|
|
171
|
+
private readonly maxLimit;
|
|
172
|
+
private readonly defaultLimit;
|
|
173
|
+
private readonly softDeleteEnabled;
|
|
174
|
+
private readonly softDeleteField;
|
|
175
|
+
/** Map Arc operators to Prisma operators */
|
|
176
|
+
private readonly operatorMap;
|
|
177
|
+
constructor(options?: PrismaQueryParserOptions);
|
|
178
|
+
/**
|
|
179
|
+
* Parse URL query parameters (delegates to ArcQueryParser format)
|
|
180
|
+
*/
|
|
181
|
+
parse(query: Record<string, unknown> | null | undefined): ParsedQuery;
|
|
182
|
+
/**
|
|
183
|
+
* Convert ParsedQuery to Prisma query options
|
|
184
|
+
*/
|
|
185
|
+
toPrismaQuery(parsed: ParsedQuery, policyFilters?: Record<string, unknown>): PrismaQueryOptions;
|
|
186
|
+
/**
|
|
187
|
+
* Translate Arc/MongoDB-style filters to Prisma where clause
|
|
188
|
+
*/
|
|
189
|
+
private translateFilters;
|
|
190
|
+
private parseNumber;
|
|
191
|
+
private parseSort;
|
|
192
|
+
private parseSelect;
|
|
193
|
+
private parseFilters;
|
|
194
|
+
private coerceValue;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Prisma query options returned by toPrismaQuery
|
|
198
|
+
*/
|
|
199
|
+
interface PrismaQueryOptions {
|
|
200
|
+
where?: Record<string, unknown>;
|
|
201
|
+
orderBy?: Array<Record<string, 'asc' | 'desc'>>;
|
|
202
|
+
take?: number;
|
|
203
|
+
skip?: number;
|
|
204
|
+
select?: Record<string, boolean>;
|
|
205
|
+
include?: Record<string, boolean>;
|
|
206
|
+
}
|
|
207
|
+
interface PrismaAdapterOptions<TModel> {
|
|
208
|
+
/** Prisma client instance */
|
|
209
|
+
client: PrismaClientLike;
|
|
210
|
+
/** Model name (e.g., 'user', 'product') */
|
|
211
|
+
modelName: string;
|
|
212
|
+
/** Repository instance implementing CRUD operations */
|
|
213
|
+
repository: CrudRepository<TModel>;
|
|
214
|
+
/** Optional: Prisma DMMF (Data Model Meta Format) for schema extraction */
|
|
215
|
+
dmmf?: PrismaDmmf;
|
|
216
|
+
/** Optional: Custom query parser (default: PrismaQueryParser) */
|
|
217
|
+
queryParser?: PrismaQueryParser;
|
|
218
|
+
/** Enable soft delete filtering (default: true) */
|
|
219
|
+
softDeleteEnabled?: boolean;
|
|
220
|
+
/** Field name for soft delete (default: 'deletedAt') */
|
|
221
|
+
softDeleteField?: string;
|
|
222
|
+
}
|
|
223
|
+
declare class PrismaAdapter<TModel = unknown> implements DataAdapter<TModel> {
|
|
224
|
+
readonly type: "prisma";
|
|
225
|
+
readonly name: string;
|
|
226
|
+
readonly repository: CrudRepository<TModel>;
|
|
227
|
+
readonly queryParser: PrismaQueryParser;
|
|
228
|
+
private client;
|
|
229
|
+
private modelName;
|
|
230
|
+
private dmmf?;
|
|
231
|
+
private softDeleteEnabled;
|
|
232
|
+
private softDeleteField;
|
|
233
|
+
constructor(options: PrismaAdapterOptions<TModel>);
|
|
234
|
+
/**
|
|
235
|
+
* Parse URL query parameters and convert to Prisma query options
|
|
236
|
+
*/
|
|
237
|
+
parseQuery(query: Record<string, unknown>, policyFilters?: Record<string, unknown>): PrismaQueryOptions;
|
|
238
|
+
/**
|
|
239
|
+
* Apply policy filters to existing Prisma where clause
|
|
240
|
+
* Used for multi-tenant, ownership, and other security filters
|
|
241
|
+
*/
|
|
242
|
+
applyPolicyFilters(where: Record<string, unknown>, policyFilters: Record<string, unknown>): Record<string, unknown>;
|
|
243
|
+
generateSchemas(options?: RouteSchemaOptions): OpenApiSchemas | null;
|
|
244
|
+
getSchemaMetadata(): SchemaMetadata | null;
|
|
245
|
+
validate(data: unknown): Promise<ValidationResult>;
|
|
246
|
+
healthCheck(): Promise<boolean>;
|
|
247
|
+
close(): Promise<void>;
|
|
248
|
+
private buildEntitySchema;
|
|
249
|
+
private buildCreateSchema;
|
|
250
|
+
private buildUpdateSchema;
|
|
251
|
+
private shouldSkipField;
|
|
252
|
+
private convertPrismaFieldToJsonSchema;
|
|
253
|
+
private convertPrismaFieldToMetadata;
|
|
254
|
+
private mapPrismaTypeToMetadataType;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Factory function to create Prisma adapter
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* import { PrismaClient } from '@prisma/client';
|
|
261
|
+
* import { createPrismaAdapter } from '@classytic/arc';
|
|
262
|
+
*
|
|
263
|
+
* const prisma = new PrismaClient();
|
|
264
|
+
*
|
|
265
|
+
* const userAdapter = createPrismaAdapter({
|
|
266
|
+
* client: prisma,
|
|
267
|
+
* modelName: 'user',
|
|
268
|
+
* repository: userRepository,
|
|
269
|
+
* dmmf: Prisma.dmmf, // Optional: for schema generation
|
|
270
|
+
* });
|
|
271
|
+
*/
|
|
272
|
+
declare function createPrismaAdapter<TModel>(options: PrismaAdapterOptions<TModel>): PrismaAdapter<TModel>;
|
|
273
|
+
//#endregion
|
|
274
|
+
export { PrismaQueryParserOptions as a, MongooseAdapterOptions as c, PrismaQueryParser as i, createMongooseAdapter as l, PrismaAdapterOptions as n, createPrismaAdapter as o, PrismaQueryOptions as r, MongooseAdapter as s, PrismaAdapter as t };
|
|
275
|
+
//# sourceMappingURL=prisma-Dg9GoVdj.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-Dg9GoVdj.d.mts","names":[],"sources":["../src/adapters/mongoose.ts","../src/adapters/prisma.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { i as CacheStore } from "./interface-CZe8IkMf.mjs";
|
|
2
|
+
import { FastifyPluginAsync } from "fastify";
|
|
3
|
+
|
|
4
|
+
//#region src/cache/QueryCache.d.ts
|
|
5
|
+
/** Metadata wrapper stored in CacheStore */
|
|
6
|
+
interface CacheEnvelope<T = unknown> {
|
|
7
|
+
data: T;
|
|
8
|
+
createdAt: number;
|
|
9
|
+
staleAfter: number;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
tags: string[];
|
|
12
|
+
}
|
|
13
|
+
interface QueryCacheConfig {
|
|
14
|
+
/** Seconds data is "fresh" (no revalidation). Default: 0 */
|
|
15
|
+
staleTime?: number;
|
|
16
|
+
/** Seconds stale data stays cached (SWR window). Default: 60 */
|
|
17
|
+
gcTime?: number;
|
|
18
|
+
/** Tags for group invalidation */
|
|
19
|
+
tags?: string[];
|
|
20
|
+
}
|
|
21
|
+
type CacheStatus = 'fresh' | 'stale' | 'miss';
|
|
22
|
+
interface CacheResult<T> {
|
|
23
|
+
data: T;
|
|
24
|
+
status: CacheStatus;
|
|
25
|
+
}
|
|
26
|
+
declare class QueryCache {
|
|
27
|
+
private readonly store;
|
|
28
|
+
constructor(store: CacheStore);
|
|
29
|
+
get<T>(key: string): Promise<CacheResult<T>>;
|
|
30
|
+
set<T>(key: string, data: T, config: QueryCacheConfig): Promise<void>;
|
|
31
|
+
invalidate(key: string): Promise<void>;
|
|
32
|
+
/** Get current version for a resource (defaults to 0 if not set) */
|
|
33
|
+
getResourceVersion(resource: string): Promise<number>;
|
|
34
|
+
/** Bump resource version — orphans all cached queries for this resource */
|
|
35
|
+
bumpResourceVersion(resource: string): Promise<void>;
|
|
36
|
+
/** Get current version for a tag */
|
|
37
|
+
getTagVersion(tag: string): Promise<number>;
|
|
38
|
+
/** Bump tag version — orphans all cached queries tagged with this tag */
|
|
39
|
+
bumpTagVersion(tag: string): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/cache/queryCachePlugin.d.ts
|
|
43
|
+
interface QueryCachePluginOptions {
|
|
44
|
+
/** CacheStore instance. Default: MemoryCacheStore with default options. */
|
|
45
|
+
store?: CacheStore;
|
|
46
|
+
/** Global defaults for staleTime/gcTime (seconds) */
|
|
47
|
+
defaults?: {
|
|
48
|
+
staleTime?: number;
|
|
49
|
+
gcTime?: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
interface QueryCacheDefaults {
|
|
53
|
+
staleTime: number;
|
|
54
|
+
gcTime: number;
|
|
55
|
+
}
|
|
56
|
+
/** Cross-resource invalidation rules collected from resource configs */
|
|
57
|
+
interface CrossResourceRule {
|
|
58
|
+
pattern: string;
|
|
59
|
+
tags: string[];
|
|
60
|
+
}
|
|
61
|
+
declare module 'fastify' {
|
|
62
|
+
interface FastifyInstance {
|
|
63
|
+
queryCache: QueryCache;
|
|
64
|
+
queryCacheConfig: QueryCacheDefaults;
|
|
65
|
+
/** Register cross-resource invalidation rules (called by defineResource) */
|
|
66
|
+
registerCacheInvalidationRule?(rule: CrossResourceRule): void;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
declare const queryCachePlugin: FastifyPluginAsync<QueryCachePluginOptions>;
|
|
70
|
+
//#endregion
|
|
71
|
+
export { CacheEnvelope as a, QueryCache as c, queryCachePlugin as i, QueryCacheConfig as l, QueryCacheDefaults as n, CacheResult as o, QueryCachePluginOptions as r, CacheStatus as s, CrossResourceRule as t };
|
|
72
|
+
//# sourceMappingURL=queryCachePlugin-7THaI5mt.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryCachePlugin-7THaI5mt.d.mts","names":[],"sources":["../src/cache/QueryCache.ts","../src/cache/queryCachePlugin.ts"],"mappings":";;;;;UAciB,aAAA;EACf,IAAA,EAAM,CAAA;EACN,SAAA;EACA,UAAA;EACA,SAAA;EACA,IAAA;AAAA;AAAA,UAGe,gBAAA;;EAEf,SAAA;EAAA;EAEA,MAAA;EAEA;EAAA,IAAA;AAAA;AAAA,KAGU,WAAA;AAAA,UAEK,WAAA;EACf,IAAA,EAAM,CAAA;EACN,MAAA,EAAQ,WAAA;AAAA;AAAA,cAGG,UAAA;EAAA,iBACM,KAAA;cAEL,KAAA,EAAO,UAAA;EAIb,GAAA,GAAA,CAAO,GAAA,WAAc,OAAA,CAAQ,WAAA,CAAY,CAAA;EAqBzC,GAAA,GAAA,CAAO,GAAA,UAAa,IAAA,EAAM,CAAA,EAAG,MAAA,EAAQ,gBAAA,GAAmB,OAAA;EAiBxD,UAAA,CAAW,GAAA,WAAc,OAAA;EAjDzB;EAsDA,kBAAA,CAAmB,QAAA,WAAmB,OAAA;EArDpC;EA2DF,mBAAA,CAAoB,QAAA,WAAmB,OAAA;EA3D1B;EAmEb,aAAA,CAAc,GAAA,WAAc,OAAA;EAhEb;EAsEf,cAAA,CAAe,GAAA,WAAc,OAAA;AAAA;;;UClFpB,uBAAA;EDAf;ECEA,KAAA,GAAQ,UAAA;EDAJ;ECEJ,QAAA;IACE,SAAA;IACA,MAAA;EAAA;AAAA;AAAA,UAIa,kBAAA;EACf,SAAA;EACA,MAAA;AAAA;;UAIe,iBAAA;EACf,OAAA;EACA,IAAA;AAAA;AAAA;EAAA,UAIU,eAAA;IACR,UAAA,EAAY,UAAA;IACZ,gBAAA,EAAkB,kBAAA;IDZC;ICcnB,6BAAA,EAA+B,IAAA,EAAM,iBAAA;EAAA;AAAA;AAAA,cA8D5B,gBAAA,EAAgB,kBAAA,CAAA,uBAAA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
|
|
2
|
+
import { i as versionKey, r as tagVersionKey } from "./keys-BqNejWup.mjs";
|
|
3
|
+
import { t as hasEvents } from "./typeGuards-DhMNLuvU.mjs";
|
|
4
|
+
import { t as MemoryCacheStore } from "./memory-cQgelFOj.mjs";
|
|
5
|
+
import fp from "fastify-plugin";
|
|
6
|
+
|
|
7
|
+
//#region src/cache/QueryCache.ts
|
|
8
|
+
var QueryCache = class {
|
|
9
|
+
store;
|
|
10
|
+
constructor(store) {
|
|
11
|
+
this.store = store;
|
|
12
|
+
}
|
|
13
|
+
async get(key) {
|
|
14
|
+
const envelope = await this.store.get(key);
|
|
15
|
+
if (!envelope || !envelope.createdAt) return {
|
|
16
|
+
data: void 0,
|
|
17
|
+
status: "miss"
|
|
18
|
+
};
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
if (now >= envelope.expiresAt) {
|
|
21
|
+
await this.store.delete(key);
|
|
22
|
+
return {
|
|
23
|
+
data: void 0,
|
|
24
|
+
status: "miss"
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (now < envelope.staleAfter) return {
|
|
28
|
+
data: envelope.data,
|
|
29
|
+
status: "fresh"
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
data: envelope.data,
|
|
33
|
+
status: "stale"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async set(key, data, config) {
|
|
37
|
+
const staleTimeMs = (config.staleTime ?? 0) * 1e3;
|
|
38
|
+
const totalTtl = staleTimeMs + (config.gcTime ?? 60) * 1e3;
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const envelope = {
|
|
41
|
+
data,
|
|
42
|
+
createdAt: now,
|
|
43
|
+
staleAfter: now + staleTimeMs,
|
|
44
|
+
expiresAt: now + totalTtl,
|
|
45
|
+
tags: config.tags ?? []
|
|
46
|
+
};
|
|
47
|
+
await this.store.set(key, envelope, { ttlMs: totalTtl });
|
|
48
|
+
}
|
|
49
|
+
async invalidate(key) {
|
|
50
|
+
await this.store.delete(key);
|
|
51
|
+
}
|
|
52
|
+
/** Get current version for a resource (defaults to 0 if not set) */
|
|
53
|
+
async getResourceVersion(resource) {
|
|
54
|
+
return await this.store.get(versionKey(resource)) ?? 0;
|
|
55
|
+
}
|
|
56
|
+
/** Bump resource version — orphans all cached queries for this resource */
|
|
57
|
+
async bumpResourceVersion(resource) {
|
|
58
|
+
const key = versionKey(resource);
|
|
59
|
+
const newVersion = Date.now();
|
|
60
|
+
await this.store.set(key, newVersion, { ttlMs: 1440 * 60 * 1e3 });
|
|
61
|
+
}
|
|
62
|
+
/** Get current version for a tag */
|
|
63
|
+
async getTagVersion(tag) {
|
|
64
|
+
return await this.store.get(tagVersionKey(tag)) ?? 0;
|
|
65
|
+
}
|
|
66
|
+
/** Bump tag version — orphans all cached queries tagged with this tag */
|
|
67
|
+
async bumpTagVersion(tag) {
|
|
68
|
+
const key = tagVersionKey(tag);
|
|
69
|
+
const newVersion = Date.now();
|
|
70
|
+
await this.store.set(key, newVersion, { ttlMs: 1440 * 60 * 1e3 });
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/cache/queryCachePlugin.ts
|
|
76
|
+
/**
|
|
77
|
+
* QueryCache Fastify Plugin
|
|
78
|
+
*
|
|
79
|
+
* Registers QueryCache on `fastify.queryCache` and wires automatic
|
|
80
|
+
* cache invalidation via CRUD events. Zero config for memory mode.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // Memory mode (default)
|
|
85
|
+
* await fastify.register(queryCachePlugin);
|
|
86
|
+
*
|
|
87
|
+
* // With Redis store
|
|
88
|
+
* await fastify.register(queryCachePlugin, {
|
|
89
|
+
* store: new RedisCacheStore({ client: redis, prefix: 'arc:qc:' }),
|
|
90
|
+
* defaults: { staleTime: 30, gcTime: 300 },
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
var queryCachePlugin_exports = /* @__PURE__ */ __exportAll({ queryCachePlugin: () => queryCachePlugin });
|
|
95
|
+
const CRUD_SUFFIXES = new Set([
|
|
96
|
+
"created",
|
|
97
|
+
"updated",
|
|
98
|
+
"deleted"
|
|
99
|
+
]);
|
|
100
|
+
const queryCachePluginImpl = async (fastify, opts = {}) => {
|
|
101
|
+
const store = opts.store ?? new MemoryCacheStore();
|
|
102
|
+
const queryCache = new QueryCache(store);
|
|
103
|
+
const defaults = {
|
|
104
|
+
staleTime: opts.defaults?.staleTime ?? 0,
|
|
105
|
+
gcTime: opts.defaults?.gcTime ?? 60
|
|
106
|
+
};
|
|
107
|
+
fastify.decorate("queryCache", queryCache);
|
|
108
|
+
fastify.decorate("queryCacheConfig", defaults);
|
|
109
|
+
const crossResourceRules = [];
|
|
110
|
+
fastify.decorate("registerCacheInvalidationRule", (rule) => {
|
|
111
|
+
crossResourceRules.push(rule);
|
|
112
|
+
});
|
|
113
|
+
fastify.addHook("onReady", async () => {
|
|
114
|
+
if (!hasEvents(fastify)) return;
|
|
115
|
+
await fastify.events.subscribe("*", async (event) => {
|
|
116
|
+
const type = event.type;
|
|
117
|
+
const dotIdx = type.lastIndexOf(".");
|
|
118
|
+
if (dotIdx === -1) return;
|
|
119
|
+
const suffix = type.slice(dotIdx + 1);
|
|
120
|
+
if (!CRUD_SUFFIXES.has(suffix)) return;
|
|
121
|
+
const resource = type.slice(0, dotIdx);
|
|
122
|
+
await queryCache.bumpResourceVersion(resource);
|
|
123
|
+
});
|
|
124
|
+
for (const rule of crossResourceRules) await fastify.events.subscribe(rule.pattern, async () => {
|
|
125
|
+
for (const tag of rule.tags) await queryCache.bumpTagVersion(tag);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
fastify.addHook("onClose", async () => {
|
|
129
|
+
if ("close" in store && typeof store.close === "function") await store.close();
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
const queryCachePlugin = fp(queryCachePluginImpl, {
|
|
133
|
+
name: "arc-query-cache",
|
|
134
|
+
fastify: "5.x"
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { queryCachePlugin_exports as n, QueryCache as r, queryCachePlugin as t };
|
|
139
|
+
//# sourceMappingURL=queryCachePlugin-DMBnp2Q0.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryCachePlugin-DMBnp2Q0.mjs","names":[],"sources":["../src/cache/QueryCache.ts","../src/cache/queryCachePlugin.ts"],"sourcesContent":["/**\r\n * QueryCache — TanStack Query-inspired server cache\r\n *\r\n * Wraps any CacheStore with:\r\n * - Freshness metadata (staleTime / gcTime envelope)\r\n * - Stale-while-revalidate status detection\r\n * - Version-based O(1) invalidation (no key scanning)\r\n * - Tag-based cross-resource invalidation\r\n */\r\n\r\nimport type { CacheStore } from './interface.js';\r\nimport { tagVersionKey, versionKey } from './keys.js';\r\n\r\n/** Metadata wrapper stored in CacheStore */\r\nexport interface CacheEnvelope<T = unknown> {\r\n data: T;\r\n createdAt: number;\r\n staleAfter: number;\r\n expiresAt: number;\r\n tags: string[];\r\n}\r\n\r\nexport interface QueryCacheConfig {\r\n /** Seconds data is \"fresh\" (no revalidation). Default: 0 */\r\n staleTime?: number;\r\n /** Seconds stale data stays cached (SWR window). Default: 60 */\r\n gcTime?: number;\r\n /** Tags for group invalidation */\r\n tags?: string[];\r\n}\r\n\r\nexport type CacheStatus = 'fresh' | 'stale' | 'miss';\r\n\r\nexport interface CacheResult<T> {\r\n data: T;\r\n status: CacheStatus;\r\n}\r\n\r\nexport class QueryCache {\r\n private readonly store: CacheStore;\r\n\r\n constructor(store: CacheStore) {\r\n this.store = store;\r\n }\r\n\r\n async get<T>(key: string): Promise<CacheResult<T>> {\r\n const envelope = await this.store.get(key) as CacheEnvelope<T> | undefined;\r\n\r\n if (!envelope || !envelope.createdAt) {\r\n return { data: undefined as T, status: 'miss' };\r\n }\r\n\r\n const now = Date.now();\r\n\r\n if (now >= envelope.expiresAt) {\r\n await this.store.delete(key);\r\n return { data: undefined as T, status: 'miss' };\r\n }\r\n\r\n if (now < envelope.staleAfter) {\r\n return { data: envelope.data, status: 'fresh' };\r\n }\r\n\r\n return { data: envelope.data, status: 'stale' };\r\n }\r\n\r\n async set<T>(key: string, data: T, config: QueryCacheConfig): Promise<void> {\r\n const staleTimeMs = (config.staleTime ?? 0) * 1000;\r\n const gcTimeMs = (config.gcTime ?? 60) * 1000;\r\n const totalTtl = staleTimeMs + gcTimeMs;\r\n const now = Date.now();\r\n\r\n const envelope: CacheEnvelope<T> = {\r\n data,\r\n createdAt: now,\r\n staleAfter: now + staleTimeMs,\r\n expiresAt: now + totalTtl,\r\n tags: config.tags ?? [],\r\n };\r\n\r\n await this.store.set(key, envelope, { ttlMs: totalTtl });\r\n }\r\n\r\n async invalidate(key: string): Promise<void> {\r\n await this.store.delete(key);\r\n }\r\n\r\n /** Get current version for a resource (defaults to 0 if not set) */\r\n async getResourceVersion(resource: string): Promise<number> {\r\n const ver = await this.store.get(versionKey(resource)) as number | undefined;\r\n return ver ?? 0;\r\n }\r\n\r\n /** Bump resource version — orphans all cached queries for this resource */\r\n async bumpResourceVersion(resource: string): Promise<void> {\r\n const key = versionKey(resource);\r\n const newVersion = Date.now();\r\n // Store version with a very long TTL (24h) — it's tiny data\r\n await this.store.set(key, newVersion, { ttlMs: 24 * 60 * 60 * 1000 });\r\n }\r\n\r\n /** Get current version for a tag */\r\n async getTagVersion(tag: string): Promise<number> {\r\n const ver = await this.store.get(tagVersionKey(tag)) as number | undefined;\r\n return ver ?? 0;\r\n }\r\n\r\n /** Bump tag version — orphans all cached queries tagged with this tag */\r\n async bumpTagVersion(tag: string): Promise<void> {\r\n const key = tagVersionKey(tag);\r\n const newVersion = Date.now();\r\n await this.store.set(key, newVersion, { ttlMs: 24 * 60 * 60 * 1000 });\r\n }\r\n}\r\n","/**\r\n * QueryCache Fastify Plugin\r\n *\r\n * Registers QueryCache on `fastify.queryCache` and wires automatic\r\n * cache invalidation via CRUD events. Zero config for memory mode.\r\n *\r\n * @example\r\n * ```typescript\r\n * // Memory mode (default)\r\n * await fastify.register(queryCachePlugin);\r\n *\r\n * // With Redis store\r\n * await fastify.register(queryCachePlugin, {\r\n * store: new RedisCacheStore({ client: redis, prefix: 'arc:qc:' }),\r\n * defaults: { staleTime: 30, gcTime: 300 },\r\n * });\r\n * ```\r\n */\r\n\r\nimport fp from 'fastify-plugin';\r\nimport type { FastifyInstance, FastifyPluginAsync } from 'fastify';\r\nimport { QueryCache, type QueryCacheConfig } from './QueryCache.js';\r\nimport type { CacheStore } from './interface.js';\r\nimport { MemoryCacheStore } from './memory.js';\r\nimport { hasEvents } from '../utils/typeGuards.js';\r\n\r\nexport interface QueryCachePluginOptions {\r\n /** CacheStore instance. Default: MemoryCacheStore with default options. */\r\n store?: CacheStore;\r\n /** Global defaults for staleTime/gcTime (seconds) */\r\n defaults?: {\r\n staleTime?: number;\r\n gcTime?: number;\r\n };\r\n}\r\n\r\nexport interface QueryCacheDefaults {\r\n staleTime: number;\r\n gcTime: number;\r\n}\r\n\r\n/** Cross-resource invalidation rules collected from resource configs */\r\nexport interface CrossResourceRule {\r\n pattern: string;\r\n tags: string[];\r\n}\r\n\r\ndeclare module 'fastify' {\r\n interface FastifyInstance {\r\n queryCache: QueryCache;\r\n queryCacheConfig: QueryCacheDefaults;\r\n /** Register cross-resource invalidation rules (called by defineResource) */\r\n registerCacheInvalidationRule?(rule: CrossResourceRule): void;\r\n }\r\n}\r\n\r\nconst CRUD_SUFFIXES = new Set(['created', 'updated', 'deleted']);\r\n\r\nconst queryCachePluginImpl: FastifyPluginAsync<QueryCachePluginOptions> = async (\r\n fastify: FastifyInstance,\r\n opts: QueryCachePluginOptions = {},\r\n) => {\r\n const store = opts.store ?? new MemoryCacheStore();\r\n const queryCache = new QueryCache(store);\r\n\r\n const defaults: QueryCacheDefaults = {\r\n staleTime: opts.defaults?.staleTime ?? 0,\r\n gcTime: opts.defaults?.gcTime ?? 60,\r\n };\r\n\r\n fastify.decorate('queryCache', queryCache);\r\n fastify.decorate('queryCacheConfig', defaults);\r\n\r\n // Collect cross-resource rules from defineResource calls\r\n const crossResourceRules: CrossResourceRule[] = [];\r\n fastify.decorate('registerCacheInvalidationRule', (rule: CrossResourceRule) => {\r\n crossResourceRules.push(rule);\r\n });\r\n\r\n // Wire event-driven invalidation after all resources are registered\r\n fastify.addHook('onReady', async () => {\r\n if (!hasEvents(fastify)) return;\r\n\r\n // Auto-invalidate on CRUD events (product.created → bump product version)\r\n await fastify.events.subscribe('*', async (event) => {\r\n const type = (event as { type: string }).type;\r\n const dotIdx = type.lastIndexOf('.');\r\n if (dotIdx === -1) return;\r\n\r\n const suffix = type.slice(dotIdx + 1);\r\n if (!CRUD_SUFFIXES.has(suffix)) return;\r\n\r\n const resource = type.slice(0, dotIdx);\r\n await queryCache.bumpResourceVersion(resource);\r\n });\r\n\r\n // Wire cross-resource tag invalidation\r\n for (const rule of crossResourceRules) {\r\n await fastify.events.subscribe(rule.pattern, async () => {\r\n for (const tag of rule.tags) {\r\n await queryCache.bumpTagVersion(tag);\r\n }\r\n });\r\n }\r\n });\r\n\r\n // Cleanup on close\r\n fastify.addHook('onClose', async () => {\r\n if ('close' in store && typeof store.close === 'function') {\r\n await store.close();\r\n }\r\n });\r\n};\r\n\r\nexport const queryCachePlugin = fp(queryCachePluginImpl, {\r\n name: 'arc-query-cache',\r\n fastify: '5.x',\r\n});\r\n"],"mappings":"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"}
|