@classytic/arc 2.9.1 → 2.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +20 -91
  2. package/dist/{BaseController-Vu2yc56T.mjs → BaseController-DVNKvoX4.mjs} +154 -170
  3. package/dist/{ResourceRegistry-Dq3_zBQP.mjs → ResourceRegistry-CcN2LVrc.mjs} +1 -1
  4. package/dist/actionPermissions-TUVR3uiZ.mjs +22 -0
  5. package/dist/adapters/index.d.mts +3 -3
  6. package/dist/adapters/index.mjs +2 -2
  7. package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
  8. package/dist/audit/index.d.mts +38 -3
  9. package/dist/audit/index.mjs +54 -22
  10. package/dist/auth/index.d.mts +2 -2
  11. package/dist/auth/index.mjs +3 -3
  12. package/dist/cache/index.d.mts +17 -15
  13. package/dist/cache/index.mjs +16 -15
  14. package/dist/{caching-CjybdRwx.mjs → caching-3h93rkJM.mjs} +8 -3
  15. package/dist/cli/commands/describe.mjs +1 -1
  16. package/dist/cli/commands/docs.mjs +2 -2
  17. package/dist/cli/commands/init.mjs +1 -1
  18. package/dist/cli/commands/introspect.mjs +1 -1
  19. package/dist/context/index.d.mts +58 -0
  20. package/dist/context/index.mjs +2 -0
  21. package/dist/core/index.d.mts +2 -2
  22. package/dist/core/index.mjs +3 -4
  23. package/dist/{defineResource-C__jkwvs.mjs → core-3MWJosCH.mjs} +174 -94
  24. package/dist/{createActionRouter-DH1YFL9m.mjs → createActionRouter-C8UUB3Px.mjs} +1 -1
  25. package/dist/{createApp-CBJUJKGP.mjs → createApp-BwnEAO2h.mjs} +53 -19
  26. package/dist/docs/index.d.mts +1 -1
  27. package/dist/docs/index.mjs +2 -2
  28. package/dist/{elevation-DxQ6ACbt.mjs → elevation-Dci0AYLT.mjs} +2 -2
  29. package/dist/errorHandler-2ii4RIYr.d.mts +114 -0
  30. package/dist/{errorHandler-CZDW4EXS.mjs → errorHandler-CSxe7KIM.mjs} +1 -1
  31. package/dist/{eventPlugin-Dl7MoVWH.mjs → eventPlugin-ByU4Cv0e.mjs} +1 -1
  32. package/dist/{eventPlugin-BxvaCIZF.d.mts → eventPlugin-D1ThQ1Pp.d.mts} +1 -1
  33. package/dist/events/index.d.mts +8 -5
  34. package/dist/events/index.mjs +87 -52
  35. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  36. package/dist/events/transports/redis.d.mts +1 -1
  37. package/dist/factory/index.d.mts +1 -1
  38. package/dist/factory/index.mjs +1 -1
  39. package/dist/{types-DZi1aYhm.d.mts → fields-C8Y0XLAu.d.mts} +122 -2
  40. package/dist/hooks/index.d.mts +1 -1
  41. package/dist/idempotency/index.d.mts +5 -2
  42. package/dist/idempotency/index.mjs +46 -37
  43. package/dist/{interface-YrWsmKqE.d.mts → index-BGbpGVyM.d.mts} +2107 -2756
  44. package/dist/{index-CtGKT0lf.d.mts → index-BziRPS4H.d.mts} +81 -7
  45. package/dist/{index-C-xjcA6F.d.mts → index-C_Noptz-.d.mts} +284 -409
  46. package/dist/{index-Cibkchnx.d.mts → index-EqQN6p0W.d.mts} +3 -3
  47. package/dist/index.d.mts +6 -219
  48. package/dist/index.mjs +10 -131
  49. package/dist/integrations/event-gateway.d.mts +1 -1
  50. package/dist/integrations/event-gateway.mjs +1 -1
  51. package/dist/integrations/index.d.mts +1 -1
  52. package/dist/integrations/mcp/index.d.mts +2 -2
  53. package/dist/integrations/mcp/index.mjs +1 -1
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/interface-yhyb_pLY.d.mts +77 -0
  57. package/dist/logger/index.d.mts +81 -0
  58. package/dist/{logger-CDjpjySd.mjs → logger/index.mjs} +1 -6
  59. package/dist/{memory-BFAYkf8H.mjs → memory-DqI-449b.mjs} +23 -8
  60. package/dist/middleware/index.d.mts +109 -0
  61. package/dist/middleware/index.mjs +70 -0
  62. package/dist/multipartBody-CUQGVlM_.mjs +123 -0
  63. package/dist/{openapi-CXuTG1M9.mjs → openapi-DpNpqBmo.mjs} +9 -7
  64. package/dist/org/index.d.mts +2 -2
  65. package/dist/permissions/index.d.mts +3 -4
  66. package/dist/permissions/index.mjs +5 -5
  67. package/dist/{permissions-oNZawnkR.mjs → permissions-wkqRwicB.mjs} +315 -397
  68. package/dist/pipe-CGJxqDGx.mjs +62 -0
  69. package/dist/pipeline/index.d.mts +62 -0
  70. package/dist/pipeline/index.mjs +53 -0
  71. package/dist/plugins/index.d.mts +23 -3
  72. package/dist/plugins/index.mjs +9 -11
  73. package/dist/plugins/response-cache.mjs +1 -1
  74. package/dist/plugins/tracing-entry.mjs +1 -1
  75. package/dist/presets/filesUpload.d.mts +3 -3
  76. package/dist/presets/filesUpload.mjs +255 -1
  77. package/dist/presets/index.d.mts +1 -1
  78. package/dist/presets/index.mjs +2 -2
  79. package/dist/presets/multiTenant.d.mts +1 -1
  80. package/dist/presets/multiTenant.mjs +43 -9
  81. package/dist/presets/search.d.mts +91 -4
  82. package/dist/presets/search.mjs +1 -1
  83. package/dist/{presets-hM4WhNWY.mjs → presets-CrwOvuXI.mjs} +1 -1
  84. package/dist/{queryCachePlugin-DbUVroUG.mjs → queryCachePlugin-ChLNZvFT.mjs} +9 -9
  85. package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-Dumka73q.d.mts} +1 -1
  86. package/dist/{queryParser-Cs-6SHQK.mjs → queryParser-NR__Qiju.mjs} +69 -2
  87. package/dist/{redis-stream-Bz-4q96t.d.mts → redis-stream-bkO88VHx.d.mts} +1 -1
  88. package/dist/registry/index.d.mts +1 -1
  89. package/dist/registry/index.mjs +1 -1
  90. package/dist/{requestContext-DYtmNpm5.mjs → requestContext-C38GskNt.mjs} +1 -1
  91. package/dist/{resourceToTools-C3cWymnW.mjs → resourceToTools-BhF3JV5p.mjs} +8 -3
  92. package/dist/scope/index.d.mts +2 -2
  93. package/dist/scope/index.mjs +2 -2
  94. package/dist/{sse-CJpt7LGI.mjs → sse-D8UeDwis.mjs} +1 -1
  95. package/dist/{store-helpers-DFiZl5TL.mjs → store-helpers-DYYUQbQN.mjs} +4 -0
  96. package/dist/testing/index.d.mts +6 -5
  97. package/dist/testing/index.mjs +17 -10
  98. package/dist/types/index.d.mts +5 -5
  99. package/dist/types/index.mjs +1 -31
  100. package/dist/types-CDnTEpga.mjs +27 -0
  101. package/dist/{types-CoSzA-s-.d.mts → types-CVKBssX5.d.mts} +1 -1
  102. package/dist/{types-CunEX4UX.d.mts → types-CVdgPXBW.d.mts} +20 -7
  103. package/dist/utils/index.d.mts +277 -3
  104. package/dist/utils/index.mjs +4 -5
  105. package/dist/{utils-B7FuRr9w.mjs → utils-LMwVidKy.mjs} +303 -2
  106. package/dist/{versioning-Cm8qoFDg.mjs → versioning-B6mimogM.mjs} +3 -5
  107. package/dist/versioning-CeUXHfjw.d.mts +117 -0
  108. package/package.json +31 -18
  109. package/skills/arc/SKILL.md +8 -12
  110. package/skills/arc/references/production.md +0 -41
  111. package/dist/circuitBreaker-CvXkjfrW.d.mts +0 -206
  112. package/dist/circuitBreaker-l18oRgL5.mjs +0 -284
  113. package/dist/core-DNncu0xF.mjs +0 -34
  114. package/dist/dynamic/index.d.mts +0 -93
  115. package/dist/dynamic/index.mjs +0 -122
  116. package/dist/errorHandler-DixGcttC.d.mts +0 -218
  117. package/dist/fields-BC7zcmI9.d.mts +0 -121
  118. package/dist/filesUpload-q8oHt--L.mjs +0 -377
  119. package/dist/interface-DplgQO2e.d.mts +0 -54
  120. package/dist/policies/index.d.mts +0 -425
  121. package/dist/policies/index.mjs +0 -318
  122. package/dist/rpc/index.d.mts +0 -90
  123. package/dist/rpc/index.mjs +0 -248
  124. /package/dist/{EventTransport-CqZ8FyM_.d.mts → EventTransport-CfVEGaEl.d.mts} +0 -0
  125. /package/dist/{applyPermissionResult-bqGpo9ML.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
  126. /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
  127. /package/dist/{elevation-B6S5csVA.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
  128. /package/dist/{errors-CqWnSqM-.mjs → errors-BqdUDja_.mjs} +0 -0
  129. /package/dist/{fields-CU6FlaDV.mjs → fields-CTMWOUDt.mjs} +0 -0
  130. /package/dist/{keys-qcD-TVJl.mjs → keys-nWQGUTu1.mjs} +0 -0
  131. /package/dist/{types-ZUu_h0jp.mjs → types-D57iXYb8.mjs} +0 -0
  132. /package/dist/{types-BD85MlEK.d.mts → types-tgR4Pt8F.d.mts} +0 -0
@@ -1,3 +1,3 @@
1
- import { a as RelationMetadata, c as ValidationResult, i as FieldMetadata, o as RepositoryLike, r as DataAdapter, s as SchemaMetadata, t as AdapterFactory } from "../interface-YrWsmKqE.mjs";
2
- import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter } from "../index-CtGKT0lf.mjs";
3
- export { AdapterFactory, DataAdapter, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createMongooseAdapter, createPrismaAdapter };
1
+ import { _n as RepositoryLike, fn as AdapterFactory, gn as RelationMetadata, hn as FieldMetadata, mn as DataAdapter, vn as SchemaMetadata, yn as ValidationResult } from "../index-BGbpGVyM.mjs";
2
+ import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, d as DrizzleAdapterOptions, f as createDrizzleAdapter, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter, u as DrizzleAdapter } from "../index-BziRPS4H.mjs";
3
+ export { AdapterFactory, DataAdapter, DrizzleAdapter, DrizzleAdapterOptions, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createDrizzleAdapter, createMongooseAdapter, createPrismaAdapter };
@@ -1,2 +1,2 @@
1
- import { a as createMongooseAdapter, i as MongooseAdapter, n as PrismaQueryParser, r as createPrismaAdapter, t as PrismaAdapter } from "../adapters-BBqAVvPK.mjs";
2
- export { MongooseAdapter, PrismaAdapter, PrismaQueryParser, createMongooseAdapter, createPrismaAdapter };
1
+ import { a as createMongooseAdapter, i as MongooseAdapter, n as PrismaQueryParser, o as DrizzleAdapter, r as createPrismaAdapter, s as createDrizzleAdapter, t as PrismaAdapter } from "../adapters-BXY4i-hw.mjs";
2
+ export { DrizzleAdapter, MongooseAdapter, PrismaAdapter, PrismaQueryParser, createDrizzleAdapter, createMongooseAdapter, createPrismaAdapter };
@@ -1,4 +1,44 @@
1
- import { h as SYSTEM_FIELDS, m as RESERVED_QUERY_PARAMS } from "./constants-Cxde4rpC.mjs";
1
+ import { h as SYSTEM_FIELDS, m as RESERVED_QUERY_PARAMS } from "./constants-BhY1OHoH.mjs";
2
+ //#region src/adapters/field-rule-helpers.ts
3
+ /**
4
+ * Merge constraint-style `fieldRules` into an `OpenApiSchemas` bag in place.
5
+ *
6
+ * Operates on the three schema slots that carry property maps — `createBody`,
7
+ * `updateBody`, `response`. `listQuery` and `params` are skipped (their
8
+ * constraint vocabulary is owned by the kit's query parser).
9
+ *
10
+ * Existing constraints on a property always win — the merge only fills in
11
+ * gaps. Adapters that already walk `fieldRules` during base-schema assembly
12
+ * can call this helper for free (the checks are no-ops when constraints
13
+ * already exist).
14
+ */
15
+ function mergeFieldRuleConstraints(schemas, schemaOptions) {
16
+ if (!schemas || typeof schemas !== "object") return;
17
+ const rules = schemaOptions?.fieldRules;
18
+ if (!rules || Object.keys(rules).length === 0) return;
19
+ for (const slot of [
20
+ "createBody",
21
+ "updateBody",
22
+ "response"
23
+ ]) {
24
+ const slotSchema = schemas[slot];
25
+ if (!slotSchema || typeof slotSchema !== "object") continue;
26
+ const properties = slotSchema.properties;
27
+ if (!properties) continue;
28
+ for (const [field, rule] of Object.entries(rules)) {
29
+ const prop = properties[field];
30
+ if (!prop || typeof prop !== "object") continue;
31
+ if (rule.minLength != null && prop.minLength == null) prop.minLength = rule.minLength;
32
+ if (rule.maxLength != null && prop.maxLength == null) prop.maxLength = rule.maxLength;
33
+ if (rule.min != null && prop.minimum == null) prop.minimum = rule.min;
34
+ if (rule.max != null && prop.maximum == null) prop.maximum = rule.max;
35
+ if (rule.pattern != null && prop.pattern == null) prop.pattern = rule.pattern;
36
+ if (rule.enum != null && prop.enum == null) prop.enum = rule.enum;
37
+ if (rule.description != null && prop.description == null) prop.description = rule.description;
38
+ }
39
+ }
40
+ }
41
+ //#endregion
2
42
  //#region src/adapters/types.ts
3
43
  /**
4
44
  * Check if value is a Mongoose model
@@ -13,6 +53,168 @@ function isRepository(value) {
13
53
  return typeof value === "object" && value !== null && "getAll" in value && "getById" in value && "create" in value && "update" in value && "delete" in value;
14
54
  }
15
55
  //#endregion
56
+ //#region src/adapters/drizzle.ts
57
+ const DRIZZLE_COLUMNS_SYMBOL = Symbol.for("drizzle:Columns");
58
+ function getColumns(table) {
59
+ const cols = table[DRIZZLE_COLUMNS_SYMBOL];
60
+ if (!cols || typeof cols !== "object") return {};
61
+ return cols;
62
+ }
63
+ function columnToJsonSchema(column) {
64
+ const { dataType, columnType, enumValues, length } = column;
65
+ if (dataType === "date") return {
66
+ type: "string",
67
+ format: "date-time"
68
+ };
69
+ if (dataType === "boolean") return { type: "boolean" };
70
+ if (dataType === "json") return {
71
+ type: "object",
72
+ additionalProperties: true
73
+ };
74
+ if (dataType === "buffer") return {
75
+ type: "string",
76
+ contentEncoding: "base64"
77
+ };
78
+ if (dataType === "number" || dataType === "bigint") return { type: columnType === "SQLiteInteger" ? "integer" : "number" };
79
+ if (dataType === "string") {
80
+ const result = { type: "string" };
81
+ if (Array.isArray(enumValues) && enumValues.length > 0) result.enum = [...enumValues];
82
+ if (typeof length === "number" && length > 0) result.maxLength = length;
83
+ return result;
84
+ }
85
+ return {};
86
+ }
87
+ function columnToFieldMetadata(column) {
88
+ const { dataType, enumValues } = column;
89
+ const meta = {
90
+ type: (dataType && {
91
+ number: "number",
92
+ bigint: "number",
93
+ string: "string",
94
+ date: "date",
95
+ boolean: "boolean",
96
+ json: "object",
97
+ buffer: "object"
98
+ }[dataType]) ?? (enumValues?.length ? "enum" : "object"),
99
+ required: !!column.notNull && !column.hasDefault
100
+ };
101
+ if (enumValues?.length) meta.enum = [...enumValues];
102
+ if (typeof column.length === "number") meta.maxLength = column.length;
103
+ return meta;
104
+ }
105
+ var DrizzleAdapter = class {
106
+ type = "drizzle";
107
+ name;
108
+ table;
109
+ repository;
110
+ schemaGenerator;
111
+ constructor(options) {
112
+ if (!options.table || typeof options.table !== "object") throw new TypeError("DrizzleAdapter: Invalid table. Expected a Drizzle table created with sqliteTable / pgTable / mysqlTable.");
113
+ if (!isRepository(options.repository)) throw new TypeError("DrizzleAdapter: Invalid repository. Expected an object implementing MinimalRepo (getAll / getById / create / update / delete).");
114
+ this.table = options.table;
115
+ this.repository = options.repository;
116
+ this.schemaGenerator = options.schemaGenerator;
117
+ this.name = options.name ?? "DrizzleAdapter";
118
+ }
119
+ /**
120
+ * Introspect Drizzle columns into arc's schema metadata shape.
121
+ */
122
+ getSchemaMetadata() {
123
+ const columns = getColumns(this.table);
124
+ const fields = {};
125
+ const indexes = [];
126
+ for (const [name, column] of Object.entries(columns)) {
127
+ fields[name] = columnToFieldMetadata(column);
128
+ if (column.primary) indexes.push({
129
+ fields: [name],
130
+ unique: true
131
+ });
132
+ }
133
+ return {
134
+ name: this.name,
135
+ fields,
136
+ ...indexes.length > 0 ? { indexes } : {}
137
+ };
138
+ }
139
+ /**
140
+ * Generate OpenAPI schemas. Delegates to the user-provided
141
+ * `schemaGenerator` when available (strongly recommended — that's where
142
+ * field rules, omit lists, and param-type narrowing live). The built-in
143
+ * fallback emits a permissive entity + CRUD body shape so routes still
144
+ * register when no generator is provided.
145
+ *
146
+ * After the kit generator runs, arc merges constraint-style field rules
147
+ * (`minLength`, `maxLength`, `min`, `max`, `pattern`, `enum`, `description`)
148
+ * into the resulting property schemas so sqlitekit / pgkit behave
149
+ * identically to mongoose here — rule-driven AJV constraints apply
150
+ * regardless of backend.
151
+ */
152
+ generateSchemas(schemaOptions, context) {
153
+ try {
154
+ if (this.schemaGenerator) {
155
+ const generated = this.schemaGenerator(this.table, schemaOptions, context);
156
+ mergeFieldRuleConstraints(generated, schemaOptions);
157
+ return generated;
158
+ }
159
+ const columns = getColumns(this.table);
160
+ if (Object.keys(columns).length === 0) return null;
161
+ const entityProperties = {};
162
+ const inputProperties = {};
163
+ const inputRequired = [];
164
+ const updateProperties = {};
165
+ const fieldRules = schemaOptions?.fieldRules ?? {};
166
+ const readonlySet = new Set(schemaOptions?.readonlyFields ?? []);
167
+ const optionalSet = new Set(schemaOptions?.optionalFields ?? []);
168
+ const blocked = new Set([
169
+ ...Object.entries(fieldRules).filter(([, rules]) => rules.systemManaged || rules.hidden).map(([field]) => field),
170
+ ...schemaOptions?.excludeFields ?? [],
171
+ ...schemaOptions?.hiddenFields ?? []
172
+ ]);
173
+ for (const [fieldName, column] of Object.entries(columns)) {
174
+ const schema = columnToJsonSchema(column);
175
+ entityProperties[fieldName] = schema;
176
+ if (blocked.has(fieldName)) continue;
177
+ if (column.primary && column.columnType === "SQLiteInteger") continue;
178
+ if (!readonlySet.has(fieldName)) {
179
+ inputProperties[fieldName] = schema;
180
+ if (!!column.notNull && !column.hasDefault && !optionalSet.has(fieldName)) inputRequired.push(fieldName);
181
+ updateProperties[fieldName] = schema;
182
+ }
183
+ }
184
+ return {
185
+ createBody: {
186
+ type: "object",
187
+ properties: inputProperties,
188
+ required: inputRequired.length > 0 ? inputRequired : void 0,
189
+ additionalProperties: true
190
+ },
191
+ updateBody: {
192
+ type: "object",
193
+ properties: updateProperties,
194
+ additionalProperties: true
195
+ },
196
+ response: {
197
+ type: "object",
198
+ properties: entityProperties,
199
+ additionalProperties: true
200
+ }
201
+ };
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ async healthCheck() {
207
+ return typeof this.repository.getAll === "function";
208
+ }
209
+ };
210
+ /**
211
+ * Factory — preferred construction style for symmetry with
212
+ * `createMongooseAdapter` / `createPrismaAdapter`.
213
+ */
214
+ function createDrizzleAdapter(options) {
215
+ return new DrizzleAdapter(options);
216
+ }
217
+ //#endregion
16
218
  //#region src/adapters/mongoose.ts
17
219
  /**
18
220
  * Mongoose data adapter with proper type safety
@@ -27,7 +229,7 @@ var MongooseAdapter = class {
27
229
  schemaGenerator;
28
230
  constructor(options) {
29
231
  if (!isMongooseModel(options.model)) throw new TypeError("MongooseAdapter: Invalid model. Expected Mongoose Model instance.\nUsage: createMongooseAdapter({ model: YourModel, repository: yourRepo })");
30
- if (!isRepository(options.repository)) throw new TypeError("MongooseAdapter: Invalid repository. Expected CrudRepository instance.\nUsage: createMongooseAdapter({ model: YourModel, repository: yourRepo })");
232
+ if (!isRepository(options.repository)) throw new TypeError("MongooseAdapter: Invalid repository. Expected StandardRepo instance.\nUsage: createMongooseAdapter({ model: YourModel, repository: yourRepo })");
31
233
  this.model = options.model;
32
234
  this.repository = options.repository;
33
235
  this.schemaGenerator = options.schemaGenerator;
@@ -73,7 +275,11 @@ var MongooseAdapter = class {
73
275
  */
74
276
  generateSchemas(schemaOptions, context) {
75
277
  try {
76
- if (this.schemaGenerator) return this.schemaGenerator(this.model, schemaOptions, context);
278
+ if (this.schemaGenerator) {
279
+ const generated = this.schemaGenerator(this.model, schemaOptions, context);
280
+ mergeFieldRuleConstraints(generated, schemaOptions);
281
+ return generated;
282
+ }
77
283
  const paths = this.model.schema.paths;
78
284
  const properties = {};
79
285
  const required = [];
@@ -242,43 +448,6 @@ function createMongooseAdapter(modelOrOptions, repository) {
242
448
  //#endregion
243
449
  //#region src/adapters/prisma.ts
244
450
  /**
245
- * Prisma Adapter - PostgreSQL/MySQL/SQLite Implementation
246
- *
247
- * @experimental This adapter is implemented but has no integration tests yet.
248
- * Use in production at your own risk. The Mongoose adapter is the recommended
249
- * and battle-tested path.
250
- *
251
- * Bridges Prisma Client with Arc's DataAdapter interface.
252
- * Supports Prisma 5+ with all database providers.
253
- *
254
- * Implemented features:
255
- * - Schema generation (OpenAPI docs from DMMF)
256
- * - Health checks (database connectivity)
257
- * - Query parsing (URL params → Prisma where/orderBy)
258
- * - Policy filter translation
259
- * - Soft delete preset support
260
- *
261
- * Known gaps:
262
- * - No integration test coverage
263
- * - Multi-tenant isolation relies on caller-provided policyFilters (no auto-enforcement)
264
- *
265
- * @example
266
- * ```typescript
267
- * import { PrismaClient, Prisma } from '@prisma/client';
268
- * import { createPrismaAdapter, PrismaQueryParser } from '@classytic/arc/adapters';
269
- *
270
- * const prisma = new PrismaClient();
271
- *
272
- * const userAdapter = createPrismaAdapter({
273
- * client: prisma,
274
- * modelName: 'user',
275
- * repository: new UserRepository(prisma),
276
- * dmmf: Prisma.dmmf, // For schema generation
277
- * queryParser: new PrismaQueryParser(), // Optional: custom parser
278
- * });
279
- * ```
280
- */
281
- /**
282
451
  * Prisma Query Parser - Converts URL parameters to Prisma query format
283
452
  *
284
453
  * Translates Arc's query format to Prisma's where/orderBy/take/skip structure.
@@ -723,4 +892,4 @@ function createPrismaAdapter(options) {
723
892
  return new PrismaAdapter(options);
724
893
  }
725
894
  //#endregion
726
- export { createMongooseAdapter as a, MongooseAdapter as i, PrismaQueryParser as n, createPrismaAdapter as r, PrismaAdapter as t };
895
+ export { createMongooseAdapter as a, MongooseAdapter as i, PrismaQueryParser as n, DrizzleAdapter as o, createPrismaAdapter as r, createDrizzleAdapter as s, PrismaAdapter as t };
@@ -1,5 +1,5 @@
1
- import { o as RepositoryLike } from "../interface-YrWsmKqE.mjs";
2
- import { i as UserBase } from "../types-DZi1aYhm.mjs";
1
+ import { _n as RepositoryLike } from "../index-BGbpGVyM.mjs";
2
+ import { d as UserBase } from "../fields-C8Y0XLAu.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/audit/stores/interface.d.ts
@@ -59,6 +59,15 @@ interface AuditStore {
59
59
  log(entry: AuditEntry): Promise<void>;
60
60
  /** Query audit logs (optional - not all stores support querying) */
61
61
  query?(options: AuditQueryOptions): Promise<AuditEntry[]>;
62
+ /**
63
+ * Purge entries older than `cutoff`, return count deleted. Optional —
64
+ * stores that don't support deletion (append-only emitters like Kafka,
65
+ * S3 archivers) simply omit this method and are skipped by
66
+ * `fastify.audit.purge(...)`. Mongo-backed repositories can also rely
67
+ * on a server-side TTL index instead of calling this; the method is
68
+ * the DB-agnostic escape hatch.
69
+ */
70
+ purgeOlderThan?(cutoff: Date): Promise<number>;
62
71
  /** Close/cleanup (optional) */
63
72
  close?(): Promise<void>;
64
73
  }
@@ -104,6 +113,22 @@ interface AuditPluginOptions {
104
113
  * to every store.
105
114
  */
106
115
  customStores?: AuditStore[];
116
+ /**
117
+ * Retention policy — optional. Entries older than `maxAgeMs` are purged
118
+ * on a timer (`purgeIntervalMs`, default 24h). Stores that implement
119
+ * `purgeOlderThan` participate; append-only stores are skipped.
120
+ *
121
+ * Apps on MongoDB can instead declare a TTL index on the audit
122
+ * collection's `timestamp` field — server-side TTL is cheaper than a
123
+ * periodic delete. Both approaches coexist: `fastify.audit.purge(...)`
124
+ * is always available for manual / cron-driven purges.
125
+ *
126
+ * Set `purgeIntervalMs: 0` to skip the timer (manual purge only).
127
+ */
128
+ retention?: {
129
+ /** Max entry age in ms. Entries with `timestamp < now - maxAgeMs` are purged. */maxAgeMs: number; /** Interval between purges in ms. Default 86_400_000 (24h). 0 disables the timer. */
130
+ purgeIntervalMs?: number;
131
+ };
107
132
  /**
108
133
  * Automatically audit CRUD operations via the hook system (default: true when enabled).
109
134
  * When enabled, create/update/delete operations are auto-logged without manual calls.
@@ -167,10 +192,19 @@ interface AuditLogger {
167
192
  custom: (resource: string, documentId: string, action: string, data?: Record<string, unknown>, context?: AuditContext) => Promise<void>;
168
193
  /** Query audit logs (if stores support it) */
169
194
  query: (options: AuditQueryOptions) => Promise<AuditEntry[]>;
195
+ /**
196
+ * Purge audit entries older than `cutoff` across every registered store.
197
+ * Returns the total number of entries deleted. Stores that don't support
198
+ * deletion (append-only emitters) are skipped silently.
199
+ */
200
+ purge: (cutoff: Date) => Promise<number>;
170
201
  }
171
202
  declare const auditPlugin: FastifyPluginAsync<AuditPluginOptions>;
172
203
  declare const _default: FastifyPluginAsync<AuditPluginOptions>;
173
204
  //#endregion
205
+ //#region src/audit/repository-audit-adapter.d.ts
206
+ declare function repositoryAsAuditStore(repository: RepositoryLike): AuditStore;
207
+ //#endregion
174
208
  //#region src/audit/stores/memory.d.ts
175
209
  interface MemoryAuditStoreOptions {
176
210
  /** Maximum entries to keep (default: 1000) */
@@ -183,6 +217,7 @@ declare class MemoryAuditStore implements AuditStore {
183
217
  constructor(options?: MemoryAuditStoreOptions);
184
218
  log(entry: AuditEntry): Promise<void>;
185
219
  query(options?: AuditQueryOptions): Promise<AuditEntry[]>;
220
+ purgeOlderThan(cutoff: Date): Promise<number>;
186
221
  close(): Promise<void>;
187
222
  /** Get all entries (for testing) */
188
223
  getAll(): AuditEntry[];
@@ -190,4 +225,4 @@ declare class MemoryAuditStore implements AuditStore {
190
225
  clear(): void;
191
226
  }
192
227
  //#endregion
193
- export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions, MemoryAuditStore, type MemoryAuditStoreOptions, _default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry };
228
+ export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions, MemoryAuditStore, type MemoryAuditStoreOptions, _default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry, repositoryAsAuditStore };
@@ -1,12 +1,13 @@
1
1
  import fp from "fastify-plugin";
2
+ import { and, anyOf, eq, gte, lt, lte } from "@classytic/repo-core/filter";
2
3
  //#region src/audit/repository-audit-adapter.ts
3
4
  function repositoryAsAuditStore(repository) {
5
+ const idField = repository.idField ?? "_id";
4
6
  return {
5
7
  name: "repository",
6
8
  async log(entry) {
7
9
  const doc = {
8
- _id: entry.id,
9
- id: entry.id,
10
+ [idField]: entry.id,
10
11
  resource: entry.resource,
11
12
  documentId: entry.documentId,
12
13
  action: entry.action,
@@ -23,29 +24,34 @@ function repositoryAsAuditStore(repository) {
23
24
  };
24
25
  await repository.create(doc);
25
26
  },
27
+ async purgeOlderThan(cutoff) {
28
+ if (!repository.deleteMany) return 0;
29
+ return (await repository.deleteMany(lt("timestamp", cutoff))).deletedCount ?? 0;
30
+ },
26
31
  async query(opts = {}) {
27
- if (!repository.findAll) throw new Error("auditPlugin: repository.findAll is required for query(). mongokit ≥3.6 implements it; other kits should match.");
28
- const filter = {};
29
- if (opts.resource) filter.resource = opts.resource;
30
- if (opts.documentId) filter.documentId = opts.documentId;
31
- if (opts.userId) filter.userId = opts.userId;
32
- if (opts.organizationId) filter.organizationId = opts.organizationId;
32
+ if (!repository.getAll) throw new Error("auditPlugin: repository.getAll is required for query(). It's on repo-core's MinimalRepo floor every kit (mongokit, sqlitekit, custom) implements it.");
33
+ const clauses = [];
34
+ if (opts.resource) clauses.push(eq("resource", opts.resource));
35
+ if (opts.documentId) clauses.push(eq("documentId", opts.documentId));
36
+ if (opts.userId) clauses.push(eq("userId", opts.userId));
37
+ if (opts.organizationId) clauses.push(eq("organizationId", opts.organizationId));
33
38
  if (opts.action) {
34
39
  const actions = Array.isArray(opts.action) ? opts.action : [opts.action];
35
- filter.action = actions.length === 1 ? actions[0] : { $in: actions };
36
- }
37
- if (opts.from || opts.to) {
38
- const range = {};
39
- if (opts.from) range.$gte = opts.from;
40
- if (opts.to) range.$lte = opts.to;
41
- filter.timestamp = range;
40
+ clauses.push(actions.length === 1 ? eq("action", actions[0]) : anyOf("action", actions));
42
41
  }
43
- return (await repository.findAll(filter, {
42
+ if (opts.from) clauses.push(gte("timestamp", opts.from));
43
+ if (opts.to) clauses.push(lte("timestamp", opts.to));
44
+ const filters = clauses.length > 0 ? and(...clauses) : void 0;
45
+ const limit = opts.limit ?? 100;
46
+ const page = Math.floor((opts.offset ?? 0) / limit) + 1;
47
+ const result = await repository.getAll({
48
+ ...filters ? { filters } : {},
44
49
  sort: { timestamp: -1 },
45
- skip: opts.offset ?? 0,
46
- limit: opts.limit ?? 100
47
- })).map((d) => ({
48
- id: String(d._id ?? d.id ?? ""),
50
+ page,
51
+ limit
52
+ });
53
+ return (Array.isArray(result) ? result : result.docs ?? []).map((d) => ({
54
+ id: String(d[idField] ?? ""),
49
55
  resource: d.resource ?? "",
50
56
  documentId: d.documentId ?? "",
51
57
  action: d.action ?? "create",
@@ -145,6 +151,11 @@ var MemoryAuditStore = class {
145
151
  results = results.slice(offset, offset + limit);
146
152
  return results;
147
153
  }
154
+ async purgeOlderThan(cutoff) {
155
+ const before = this.entries.length;
156
+ this.entries = this.entries.filter((e) => e.timestamp >= cutoff);
157
+ return before - this.entries.length;
158
+ }
148
159
  async close() {
149
160
  this.entries = [];
150
161
  }
@@ -199,6 +210,11 @@ const auditPlugin = async (fastify, opts = {}) => {
199
210
  async query(options) {
200
211
  for (const store of stores) if (store.query) return store.query(options);
201
212
  return [];
213
+ },
214
+ async purge(cutoff) {
215
+ let total = 0;
216
+ for (const store of stores) if (store.purgeOlderThan) total += await store.purgeOlderThan(cutoff);
217
+ return total;
202
218
  }
203
219
  };
204
220
  fastify.decorate("audit", audit);
@@ -223,7 +239,22 @@ const auditPlugin = async (fastify, opts = {}) => {
223
239
  request.auditContext.duration = Math.round(reply.elapsedTime);
224
240
  }
225
241
  });
242
+ const retention = opts.retention;
243
+ let retentionTimer = null;
244
+ if (retention) {
245
+ const interval = retention.purgeIntervalMs ?? 864e5;
246
+ if (interval > 0) {
247
+ retentionTimer = setInterval(() => {
248
+ const cutoff = new Date(Date.now() - retention.maxAgeMs);
249
+ audit.purge(cutoff).catch((err) => {
250
+ fastify.log?.warn?.({ err }, "audit retention purge failed");
251
+ });
252
+ }, interval);
253
+ retentionTimer.unref?.();
254
+ }
255
+ }
226
256
  fastify.addHook("onClose", async () => {
257
+ if (retentionTimer) clearInterval(retentionTimer);
227
258
  await Promise.all(stores.map((store) => store.close?.()));
228
259
  });
229
260
  const autoAuditConfig = opts.autoAudit ?? true;
@@ -317,7 +348,8 @@ function createNoopLogger() {
317
348
  delete: noop,
318
349
  restore: noop,
319
350
  custom: noop,
320
- query: async () => []
351
+ query: async () => [],
352
+ purge: async () => 0
321
353
  };
322
354
  }
323
355
  var auditPlugin_default = fp(auditPlugin, {
@@ -326,4 +358,4 @@ var auditPlugin_default = fp(auditPlugin, {
326
358
  dependencies: ["arc-core"]
327
359
  });
328
360
  //#endregion
329
- export { MemoryAuditStore, auditPlugin_default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry };
361
+ export { MemoryAuditStore, auditPlugin_default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry, repositoryAsAuditStore };
@@ -1,5 +1,5 @@
1
- import { v as AuthHelpers, y as AuthPluginOptions } from "../interface-YrWsmKqE.mjs";
2
- import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
1
+ import { Ut as AuthHelpers, Wt as AuthPluginOptions } from "../index-BGbpGVyM.mjs";
2
+ import { c as PermissionCheck } from "../fields-C8Y0XLAu.mjs";
3
3
  import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
4
4
  import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-D-oNWHz3.mjs";
5
5
  import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest } from "fastify";
@@ -1,6 +1,6 @@
1
- import { n as normalizeRoles, t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
- import { t as ArcError } from "../errors-CqWnSqM-.mjs";
3
- import { h as requireTeamMembership, l as requireOrgMembership, u as requireOrgRole } from "../permissions-oNZawnkR.mjs";
1
+ import { n as normalizeRoles, t as getUserRoles } from "../types-D57iXYb8.mjs";
2
+ import { t as ArcError } from "../errors-BqdUDja_.mjs";
3
+ import { _ as requireTeamMembership, m as requireOrgRole, p as requireOrgMembership } from "../permissions-wkqRwicB.mjs";
4
4
  import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi--rdY15Ld.mjs";
5
5
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
6
6
  import fp from "fastify-plugin";
@@ -1,5 +1,5 @@
1
- import { i as CacheStore, n as CacheSetOptions, r as CacheStats, t as CacheLogger } from "../interface-DplgQO2e.mjs";
2
- import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-CnTZZTC5.mjs";
1
+ import { n as CacheStats, r as CacheStore, t as CacheLogger } from "../interface-yhyb_pLY.mjs";
2
+ import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-Dumka73q.mjs";
3
3
 
4
4
  //#region src/cache/keys.d.ts
5
5
  /**
@@ -24,8 +24,8 @@ declare function hashParams(params: Record<string, unknown>): string;
24
24
  //#endregion
25
25
  //#region src/cache/memory.d.ts
26
26
  interface MemoryCacheStoreOptions {
27
- /** Default TTL in milliseconds (default: 60_000) */
28
- defaultTtlMs?: number;
27
+ /** Default TTL in seconds (default: 60) */
28
+ defaultTtlSeconds?: number;
29
29
  /** Hard upper bound for entries (default: 1000) */
30
30
  maxEntries?: number;
31
31
  /** Background cleanup interval in milliseconds (default: 30_000) */
@@ -59,7 +59,7 @@ interface MemoryCacheStoreOptions {
59
59
  declare class MemoryCacheStore<TValue = unknown> implements CacheStore<TValue> {
60
60
  readonly name = "memory-cache";
61
61
  private readonly cache;
62
- private readonly defaultTtlMs;
62
+ private readonly defaultTtlSeconds;
63
63
  private readonly maxEntries;
64
64
  private readonly maxEntryBytes;
65
65
  private readonly maxMemoryBytes;
@@ -72,9 +72,9 @@ declare class MemoryCacheStore<TValue = unknown> implements CacheStore<TValue> {
72
72
  private _evictions;
73
73
  constructor(options?: MemoryCacheStoreOptions);
74
74
  get(key: string): Promise<TValue | undefined>;
75
- set(key: string, value: TValue, options?: CacheSetOptions): Promise<void>;
75
+ set(key: string, value: TValue, ttlSeconds?: number): Promise<void>;
76
76
  delete(key: string): Promise<void>;
77
- clear(): Promise<void>;
77
+ clear(pattern?: string): Promise<void>;
78
78
  close(): Promise<void>;
79
79
  stats(): CacheStats;
80
80
  private removeEntry;
@@ -112,8 +112,8 @@ interface RedisCacheStoreOptions {
112
112
  client: RedisCacheClient;
113
113
  /** Key prefix for namespacing (default: 'arc:cache:') */
114
114
  prefix?: string;
115
- /** Default TTL in milliseconds (default: 60_000) */
116
- defaultTtlMs?: number;
115
+ /** Default TTL in seconds (default: 60) */
116
+ defaultTtlSeconds?: number;
117
117
  /** Maximum serialized entry size in bytes. Oversized entries are skipped. */
118
118
  maxEntryBytes?: number;
119
119
  }
@@ -126,17 +126,19 @@ declare class RedisCacheStore<TValue = unknown> implements CacheStore<TValue> {
126
126
  readonly name = "redis-cache";
127
127
  private readonly client;
128
128
  private readonly prefix;
129
- private readonly defaultTtlMs;
129
+ private readonly defaultTtlSeconds;
130
130
  private readonly maxEntryBytes;
131
131
  private _hits;
132
132
  private _misses;
133
133
  constructor(options: RedisCacheStoreOptions);
134
134
  get(key: string): Promise<TValue | undefined>;
135
- set(key: string, value: TValue, options?: CacheSetOptions): Promise<void>;
135
+ set(key: string, value: TValue, ttlSeconds?: number): Promise<void>;
136
136
  delete(key: string): Promise<void>;
137
- clear(): Promise<void>;
138
- /** Delete all keys matching `this.prefix + prefix + *`. Returns count deleted. */
139
- deleteByPrefix(prefix: string): Promise<number>;
137
+ /**
138
+ * Invalidate keys. Pass a glob pattern to delete a subset (`user:*:v2`);
139
+ * omit to clear every key under this store's prefix.
140
+ */
141
+ clear(pattern?: string): Promise<void>;
140
142
  stats(): CacheStats;
141
143
  private scanAndDelete;
142
144
  private withPrefix;
@@ -212,4 +214,4 @@ interface UpstashRedisLike {
212
214
  */
213
215
  declare function upstashAsCacheClient(client: UpstashRedisLike): RedisCacheClient;
214
216
  //#endregion
215
- export { type CacheEnvelope, type CacheLogger, type CacheResult, type CacheSetOptions, type CacheStats, type CacheStatus, type CacheStore, type CrossResourceRule, type IoredisLike, MemoryCacheStore, type MemoryCacheStoreOptions, QueryCache, type QueryCacheConfig, type QueryCacheDefaults, type QueryCachePluginOptions, type RedisCacheClient, RedisCacheStore, type RedisCacheStoreOptions, type RedisPipeline, type UpstashRedisLike, buildQueryKey, hashParams, ioredisAsCacheClient, queryCachePlugin, tagVersionKey, upstashAsCacheClient, versionKey };
217
+ export { type CacheEnvelope, type CacheLogger, type CacheResult, type CacheStats, type CacheStatus, type CacheStore, type CrossResourceRule, type IoredisLike, MemoryCacheStore, type MemoryCacheStoreOptions, QueryCache, type QueryCacheConfig, type QueryCacheDefaults, type QueryCachePluginOptions, type RedisCacheClient, RedisCacheStore, type RedisCacheStoreOptions, type RedisPipeline, type UpstashRedisLike, buildQueryKey, hashParams, ioredisAsCacheClient, queryCachePlugin, tagVersionKey, upstashAsCacheClient, versionKey };