@classytic/mongokit 3.8.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { n as isObjectIdInstance } from "./id-resolution-e4BT9zAV.mjs";
2
2
  import mongoose from "mongoose";
3
+ import { applyFieldRules, collectFieldsToOmit } from "@classytic/repo-core/schema";
3
4
  //#region src/utils/memory-cache.ts
4
5
  /**
5
6
  * Creates an in-memory cache adapter
@@ -51,7 +52,7 @@ function createMemoryCache(maxEntries = 1e3) {
51
52
  expiresAt: Date.now() + ttl * 1e3
52
53
  });
53
54
  },
54
- async del(key) {
55
+ async delete(key) {
55
56
  cache.delete(key);
56
57
  },
57
58
  async clear(pattern) {
@@ -68,25 +69,22 @@ function createMemoryCache(maxEntries = 1e3) {
68
69
  //#endregion
69
70
  //#region src/utils/mongooseToJsonSchema.ts
70
71
  /**
71
- * Mongoose to JSON Schema Converter with Field Rules
72
+ * Mongoose → JSON Schema converter.
72
73
  *
73
- * Generates Fastify JSON schemas from Mongoose models with declarative field rules.
74
+ * The **mongoose-specific** half of mongokit's CRUD-schema pipeline. Walks
75
+ * `Model.schema.paths` (accurate type info, including subdocuments, arrays,
76
+ * Maps, enums, length/min/max validators) and emits the four portable
77
+ * `CrudSchemas` fragments defined at `@classytic/repo-core/schema`.
74
78
  *
75
- * Field Rules (options.fieldRules):
76
- * - immutable: Field cannot be updated (omitted from update schema)
77
- * - immutableAfterCreate: Alias for immutable
78
- * - systemManaged: System-only field (omitted from create/update)
79
- * - optional: Remove from required array
80
- *
81
- * Additional Options:
82
- * - strictAdditionalProperties: Set to true to add "additionalProperties: false" to schemas
83
- * This makes Fastify reject unknown fields at validation level (default: false for backward compatibility)
84
- * - update.requireAtLeastOne: Set to true to add "minProperties: 1" to update schema
85
- * This prevents empty update payloads (default: false)
79
+ * Policy — `fieldRules` (immutable / systemManaged / optional) and the
80
+ * `create.omitFields` / `update.omitFields` lists — lives in repo-core and
81
+ * is shared across every kit. Every other kit (sqlitekit's
82
+ * `drizzleToJsonSchema`, prismakit's `prismaToJsonSchema`, ...) reuses the
83
+ * same helpers so a switch of storage backend is a one-line import swap.
86
84
  *
87
85
  * @example
88
86
  * buildCrudSchemasFromModel(Model, {
89
- * strictAdditionalProperties: true, // Reject unknown fields
87
+ * strictAdditionalProperties: true,
90
88
  * fieldRules: {
91
89
  * organizationId: { immutable: true },
92
90
  * status: { systemManaged: true },
@@ -94,9 +92,9 @@ function createMemoryCache(maxEntries = 1e3) {
94
92
  * create: { omitFields: ['verifiedAt'] },
95
93
  * update: {
96
94
  * omitFields: ['customerId'],
97
- * requireAtLeastOne: true // Reject empty updates
98
- * }
99
- * })
95
+ * requireAtLeastOne: true,
96
+ * },
97
+ * });
100
98
  */
101
99
  function isMongooseSchema(value) {
102
100
  return value instanceof mongoose.Schema;
@@ -134,89 +132,6 @@ function buildCrudSchemasFromModel(mongooseModel, options = {}) {
134
132
  return buildCrudSchemasFromMongooseSchema(mongooseModel.schema, options);
135
133
  }
136
134
  /**
137
- * Collect fields to omit from a generated schema based on field rules and
138
- * explicit omit lists. This is the single source of truth for "which fields
139
- * should NOT appear in the create/update body" — called by both the
140
- * path-based and tree-based schema builders.
141
- */
142
- function collectFieldsToOmit(options, purpose) {
143
- const result = new Set([
144
- "createdAt",
145
- "updatedAt",
146
- "__v"
147
- ]);
148
- const rules = options?.fieldRules || {};
149
- for (const [field, rule] of Object.entries(rules)) {
150
- if (rule.systemManaged) result.add(field);
151
- if (purpose === "update" && (rule.immutable || rule.immutableAfterCreate)) result.add(field);
152
- }
153
- const explicit = purpose === "create" ? options?.create?.omitFields : options?.update?.omitFields;
154
- if (explicit) for (const f of explicit) result.add(f);
155
- return result;
156
- }
157
- /**
158
- * Apply omissions + optional-overrides to a built JSON schema in-place.
159
- * Removes properties and adjusts the `required` array.
160
- */
161
- function applyFieldRules(schema, fieldsToOmit, options) {
162
- for (const field of fieldsToOmit) {
163
- if (schema.properties?.[field]) delete schema.properties[field];
164
- if (schema.required) schema.required = schema.required.filter((k) => k !== field);
165
- }
166
- const rules = options?.fieldRules || {};
167
- for (const [field, rule] of Object.entries(rules)) if (rule.optional && schema.required) schema.required = schema.required.filter((k) => k !== field);
168
- }
169
- /**
170
- * Get fields that are immutable (cannot be updated).
171
- * Delegates to `collectFieldsToOmit` internally.
172
- */
173
- function getImmutableFields(options = {}) {
174
- const immutable = [];
175
- const rules = options?.fieldRules || {};
176
- for (const [field, rule] of Object.entries(rules)) if (rule.immutable || rule.immutableAfterCreate) immutable.push(field);
177
- for (const f of options?.update?.omitFields || []) if (!immutable.includes(f)) immutable.push(f);
178
- return immutable;
179
- }
180
- /**
181
- * Get fields that are system-managed (cannot be set by users).
182
- */
183
- function getSystemManagedFields(options = {}) {
184
- const systemManaged = [];
185
- const rules = options?.fieldRules || {};
186
- for (const [field, rule] of Object.entries(rules)) if (rule.systemManaged) systemManaged.push(field);
187
- return systemManaged;
188
- }
189
- /**
190
- * Check if field is allowed in update
191
- */
192
- function isFieldUpdateAllowed(fieldName, options = {}) {
193
- const immutableFields = getImmutableFields(options);
194
- const systemManagedFields = getSystemManagedFields(options);
195
- return !immutableFields.includes(fieldName) && !systemManagedFields.includes(fieldName);
196
- }
197
- /**
198
- * Validate update body against field rules
199
- */
200
- function validateUpdateBody(body = {}, options = {}) {
201
- const violations = [];
202
- const immutableFields = getImmutableFields(options);
203
- const systemManagedFields = getSystemManagedFields(options);
204
- Object.keys(body).forEach((field) => {
205
- if (immutableFields.includes(field)) violations.push({
206
- field,
207
- reason: "Field is immutable"
208
- });
209
- else if (systemManagedFields.includes(field)) violations.push({
210
- field,
211
- reason: "Field is system-managed"
212
- });
213
- });
214
- return {
215
- valid: violations.length === 0,
216
- violations
217
- };
218
- }
219
- /**
220
135
  * Build JSON schema from Mongoose schema.paths (accurate type information)
221
136
  */
222
137
  function buildJsonSchemaFromPaths(mongooseSchema, options) {
@@ -228,6 +143,9 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
228
143
  if (softRequiredSet.has(name)) return true;
229
144
  return schemaType?.options?.softRequired === true;
230
145
  };
146
+ const hasDefault = (schemaType) => {
147
+ return schemaType?.options?.default !== void 0;
148
+ };
231
149
  const rootFields = /* @__PURE__ */ new Map();
232
150
  for (const [path, schemaType] of Object.entries(paths)) {
233
151
  if (path === "__v") continue;
@@ -246,7 +164,7 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
246
164
  for (const [rootField, fieldPaths] of rootFields.entries()) if (fieldPaths.length === 1 && fieldPaths[0].path === rootField) {
247
165
  const schemaType = fieldPaths[0].schemaType;
248
166
  properties[rootField] = schemaTypeToJsonSchema(schemaType, options);
249
- if (schemaType.isRequired && !isSoftRequired(rootField, schemaType)) required.push(rootField);
167
+ if (schemaType.isRequired && !isSoftRequired(rootField, schemaType) && !hasDefault(schemaType)) required.push(rootField);
250
168
  } else {
251
169
  const nestedSchema = buildNestedJsonSchema(fieldPaths, rootField, options);
252
170
  properties[rootField] = nestedSchema.schema;
@@ -631,4 +549,4 @@ function buildJsonSchemaForQuery(_tree, options) {
631
549
  return basePagination;
632
550
  }
633
551
  //#endregion
634
- export { isFieldUpdateAllowed as a, getSystemManagedFields as i, buildCrudSchemasFromMongooseSchema as n, validateUpdateBody as o, getImmutableFields as r, createMemoryCache as s, buildCrudSchemasFromModel as t };
552
+ export { buildCrudSchemasFromMongooseSchema as n, createMemoryCache as r, buildCrudSchemasFromModel as t };
@@ -1,5 +1,6 @@
1
- import { A as HttpError, St as ValidationResult, T as FieldPreset, _ as CrudSchemas, bt as UserContext, s as CacheAdapter, st as SchemaBuilderOptions } from "./types-D57SZJeE.mjs";
1
+ import { O as HttpError, _t as UserContext, s as CacheAdapter, w as FieldPreset } from "./types-BUS0Zj8y.mjs";
2
2
  import mongoose, { Schema } from "mongoose";
3
+ import { CrudSchemas, SchemaBuilderOptions } from "@classytic/repo-core/schema";
3
4
 
4
5
  //#region src/utils/error.d.ts
5
6
  /**
@@ -15,6 +16,23 @@ import mongoose, { Schema } from "mongoose";
15
16
  * throw createError(403, 'Access denied');
16
17
  */
17
18
  declare function createError(status: number, message: string): HttpError;
19
+ /**
20
+ * Boolean classifier: true when `err` is an authoritative MongoDB duplicate
21
+ * key error — and ONLY then. Used by arc's outbox / idempotency adapters to
22
+ * distinguish "write already landed (idempotent no-op)" from "transient DB
23
+ * error (must retry)".
24
+ *
25
+ * Deliberately narrow — matches only:
26
+ * - `code === 11000`
27
+ * - `codeName === 'DuplicateKey'`
28
+ *
29
+ * Does NOT match `err.name === 'MongoServerError'`, which is also true for
30
+ * WriteConflict (112), NotWritablePrimary (10107), ExceededTimeLimit, and
31
+ * every other server-side error. Treating those as duplicate keys would
32
+ * cause arc's outbox to silently swallow transactional retries and lose
33
+ * events.
34
+ */
35
+ declare function isDuplicateKeyError(err: unknown): boolean;
18
36
  /** Options for parseDuplicateKeyError */
19
37
  interface ParseDuplicateKeyOptions {
20
38
  /**
@@ -161,22 +179,5 @@ declare function buildCrudSchemasFromMongooseSchema(mongooseSchema: Schema, opti
161
179
  * Build CRUD schemas from Mongoose model
162
180
  */
163
181
  declare function buildCrudSchemasFromModel(mongooseModel: mongoose.Model<unknown>, options?: SchemaBuilderOptions): CrudSchemas;
164
- /**
165
- * Get fields that are immutable (cannot be updated).
166
- * Delegates to `collectFieldsToOmit` internally.
167
- */
168
- declare function getImmutableFields(options?: SchemaBuilderOptions): string[];
169
- /**
170
- * Get fields that are system-managed (cannot be set by users).
171
- */
172
- declare function getSystemManagedFields(options?: SchemaBuilderOptions): string[];
173
- /**
174
- * Check if field is allowed in update
175
- */
176
- declare function isFieldUpdateAllowed(fieldName: string, options?: SchemaBuilderOptions): boolean;
177
- /**
178
- * Validate update body against field rules
179
- */
180
- declare function validateUpdateBody(body?: Record<string, unknown>, options?: SchemaBuilderOptions): ValidationResult;
181
182
  //#endregion
182
- export { isFieldUpdateAllowed as a, configureLogger as c, getFieldsForUser as d, getMongooseProjection as f, getSystemManagedFields as i, createFieldPreset as l, parseDuplicateKeyError as m, buildCrudSchemasFromMongooseSchema as n, validateUpdateBody as o, createError as p, getImmutableFields as r, createMemoryCache as s, buildCrudSchemasFromModel as t, filterResponseData as u };
183
+ export { createFieldPreset as a, getMongooseProjection as c, parseDuplicateKeyError as d, configureLogger as i, createError as l, buildCrudSchemasFromMongooseSchema as n, filterResponseData as o, createMemoryCache as r, getFieldsForUser as s, buildCrudSchemasFromModel as t, isDuplicateKeyError as u };
@@ -1,4 +1,4 @@
1
- import { F as KeysetPaginationOptions, I as KeysetPaginationResult, K as PaginationConfig, U as OffsetPaginationOptions, W as OffsetPaginationResult, a as AnyDocument, n as AggregatePaginationOptions, r as AggregatePaginationResult } from "../types-D57SZJeE.mjs";
1
+ import { B as OffsetPaginationOptions, M as KeysetPaginationOptions, N as KeysetPaginationResult, U as PaginationConfig, V as OffsetPaginationResult, a as AnyDocument, n as AggregatePaginationOptions, r as AggregatePaginationResult } from "../types-BUS0Zj8y.mjs";
2
2
  import { Model } from "mongoose";
3
3
 
4
4
  //#region src/pagination/PaginationEngine.d.ts
@@ -1,2 +1,2 @@
1
- import { t as PaginationEngine } from "../PaginationEngine-C2_tAvAD.mjs";
1
+ import { t as PaginationEngine } from "../PaginationEngine-B93fLCcO.mjs";
2
2
  export { PaginationEngine };
@@ -1,2 +1,2 @@
1
- import { $ as aggregateHelpersPlugin, A as PrefixedIdOptions, B as BatchOperationsMethods, C as methodRegistryPlugin, D as CustomIdOptions, E as elasticSearchPlugin, F as prefixedId, G as AuditQueryOptions, H as batchOperationsPlugin, I as sequentialId, J as AuditTrailOptions, K as AuditQueryResult, L as cascadePlugin, M as customIdPlugin, N as dateSequentialId, O as DateSequentialIdOptions, P as getNextSequence, Q as AggregateHelpersMethods, R as CacheMethods, S as MethodRegistryRepository, T as ElasticSearchOptions, U as AuditEntry, W as AuditOperation, X as auditTrailPlugin, Y as AuditTrailQuery, Z as auditLogPlugin, _ as observabilityPlugin, a as uniqueField, b as MongoOperationsMethods, c as TenantContext, d as SubdocumentMethods, f as subdocumentPlugin, g as OperationMetric, h as ObservabilityOptions, i as requireField, j as SequentialIdOptions, k as IdGenerator, l as TenantStore, m as softDeletePlugin, n as blockIf, o as validationChainPlugin, p as SoftDeleteMethods, q as AuditTrailMethods, r as immutableField, s as timestampPlugin, t as autoInject, u as createTenantContext, v as MultiTenantOptions, w as fieldFilterPlugin, x as mongoOperationsPlugin, y as multiTenantPlugin, z as cachePlugin } from "../validation-chain.plugin-wGF34Ym-.mjs";
1
+ import { $ as aggregateHelpersPlugin, A as PrefixedIdOptions, B as BatchOperationsMethods, C as methodRegistryPlugin, D as CustomIdOptions, E as elasticSearchPlugin, F as prefixedId, G as AuditQueryOptions, H as batchOperationsPlugin, I as sequentialId, J as AuditTrailOptions, K as AuditQueryResult, L as cascadePlugin, M as customIdPlugin, N as dateSequentialId, O as DateSequentialIdOptions, P as getNextSequence, Q as AggregateHelpersMethods, R as CacheMethods, S as MethodRegistryRepository, T as ElasticSearchOptions, U as AuditEntry, W as AuditOperation, X as auditTrailPlugin, Y as AuditTrailQuery, Z as auditLogPlugin, _ as observabilityPlugin, a as uniqueField, b as MongoOperationsMethods, c as TenantContext, d as SubdocumentMethods, f as subdocumentPlugin, g as OperationMetric, h as ObservabilityOptions, i as requireField, j as SequentialIdOptions, k as IdGenerator, l as TenantStore, m as softDeletePlugin, n as blockIf, o as validationChainPlugin, p as SoftDeleteMethods, q as AuditTrailMethods, r as immutableField, s as timestampPlugin, t as autoInject, u as createTenantContext, v as MultiTenantOptions, w as fieldFilterPlugin, x as mongoOperationsPlugin, y as multiTenantPlugin, z as cachePlugin } from "../validation-chain.plugin-JkipjuOR.mjs";
2
2
  export { type AggregateHelpersMethods, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type CacheMethods, type CustomIdOptions, type DateSequentialIdOptions, type ElasticSearchOptions, type IdGenerator, type MethodRegistryRepository, type MongoOperationsMethods, type MultiTenantOptions, type ObservabilityOptions, type OperationMetric, type PrefixedIdOptions, type SequentialIdOptions, type SoftDeleteMethods, type SubdocumentMethods, type TenantContext, type TenantStore, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -1,2 +1,2 @@
1
- import { A as AuditTrailQuery, C as cachePlugin, M as auditLogPlugin, N as aggregateHelpersPlugin, S as cascadePlugin, _ as customIdPlugin, a as uniqueField, b as prefixedId, c as createTenantContext, d as observabilityPlugin, f as multiTenantPlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, j as auditTrailPlugin, k as batchOperationsPlugin, l as subdocumentPlugin, m as methodRegistryPlugin, n as blockIf, o as validationChainPlugin, p as mongoOperationsPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as softDeletePlugin, v as dateSequentialId, x as sequentialId, y as getNextSequence } from "../validation-chain.plugin-DwoOOWse.mjs";
1
+ import { A as AuditTrailQuery, C as cachePlugin, M as auditLogPlugin, N as aggregateHelpersPlugin, S as cascadePlugin, _ as customIdPlugin, a as uniqueField, b as prefixedId, c as createTenantContext, d as observabilityPlugin, f as multiTenantPlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, j as auditTrailPlugin, k as batchOperationsPlugin, l as subdocumentPlugin, m as methodRegistryPlugin, n as blockIf, o as validationChainPlugin, p as mongoOperationsPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as softDeletePlugin, v as dateSequentialId, x as sequentialId, y as getNextSequence } from "../validation-chain.plugin-Ccu6irtx.mjs";
2
2
  export { AuditTrailQuery, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -1,5 +1,8 @@
1
1
  import * as _$mongoose from "mongoose";
2
2
  import { ClientSession, Document, Model, PipelineStage, PopulateOptions, Types } from "mongoose";
3
+ import * as _$_classytic_repo_core_repository0 from "@classytic/repo-core/repository";
4
+ import { DeleteResult, LookupPopulateResult, UpdateManyResult } from "@classytic/repo-core/repository";
5
+ import { ValidationResult } from "@classytic/repo-core/schema";
3
6
 
4
7
  //#region src/query/LookupBuilder.d.ts
5
8
  interface LookupOptions {
@@ -865,11 +868,30 @@ interface AggregateOptions extends ReadOptions {
865
868
  maxPipelineStages?: number;
866
869
  }
867
870
  /** Lookup populate options */
871
+ /**
872
+ * Mongokit's `lookupPopulate` options — kit-native superset of the
873
+ * portable `LookupPopulateOptions` from repo-core. Widens two slots:
874
+ *
875
+ * - `lookups`: accepts either the portable `LookupSpec[]` (works on
876
+ * every kit) or mongokit's `LookupOptions[]` (adds `pipeline` /
877
+ * `let` / `sanitize` for mongo-correlated joins).
878
+ * - `select`: accepts mongoose's space-separated string form
879
+ * (`'name email'`) in addition to the portable array / inclusion
880
+ * map. Kept for arc + existing controller convenience.
881
+ *
882
+ * For cross-kit code: type the variable as
883
+ * `import('@classytic/repo-core/repository').LookupPopulateOptions<TDoc>`
884
+ * — every kit's `lookupPopulate` accepts that shape.
885
+ */
868
886
  interface LookupPopulateOptions extends ReadOptions {
869
887
  /** MongoDB query filters */
870
888
  filters?: Record<string, unknown>;
871
- /** Lookup configurations for $lookup joins */
872
- lookups: LookupOptions[];
889
+ /**
890
+ * Portable `LookupSpec[]` (cross-kit) or mongokit-native
891
+ * `LookupOptions[]` (with `pipeline` / `let` / `sanitize`). The
892
+ * portable shape is a structural subset, so callers can mix.
893
+ */
894
+ lookups: readonly _$_classytic_repo_core_repository0.LookupSpec[] | LookupOptions[];
873
895
  /** Sort specification */
874
896
  sort?: SortSpec | string;
875
897
  /** Page number (offset mode, 1-indexed) */
@@ -885,38 +907,13 @@ interface LookupPopulateOptions extends ReadOptions {
885
907
  /** Count strategy for offset pagination */
886
908
  countStrategy?: 'exact' | 'estimated' | 'none';
887
909
  }
888
- /** Lookup populate result */
889
- interface LookupPopulateResult<T = unknown> {
890
- data: T[];
891
- total: number;
892
- page?: number;
893
- limit: number;
894
- next?: string | null;
895
- hasMore?: boolean;
896
- }
897
- /** Delete result */
898
- interface DeleteResult {
899
- success: boolean;
900
- message: string;
901
- id?: string;
902
- soft?: boolean;
903
- count?: number;
904
- }
905
- /** Update many result */
906
- interface UpdateManyResult {
907
- matchedCount: number;
908
- modifiedCount: number;
909
- }
910
- /** Validation result */
911
- interface ValidationResult {
912
- valid: boolean;
913
- violations?: Array<{
914
- field: string;
915
- reason: string;
916
- }>;
917
- message?: string;
918
- }
919
- /** Update with validation result */
910
+ /**
911
+ * Union emitted by the validation-chain plugin. Keeps the violation shape
912
+ * from `ValidationResult` without re-exporting the type — consumers that
913
+ * need `ValidationResult`, `CrudSchemas`, `JsonSchema`, `SchemaBuilderOptions`,
914
+ * `FieldRule` or `FieldRules` import them straight from
915
+ * `@classytic/repo-core/schema`.
916
+ */
920
917
  type UpdateWithValidationResult<T> = {
921
918
  success: true;
922
919
  data: T;
@@ -1046,13 +1043,13 @@ interface RepositoryInstance {
1046
1043
  _buildContext(operation: string, options: Record<string, unknown> | object): Promise<RepositoryContext>;
1047
1044
  _handleError(error: Error): HttpError;
1048
1045
  update(id: string | ObjectId, data: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
1049
- aggregate(pipeline: PipelineStage[], options?: Record<string, unknown>): Promise<unknown[]>;
1046
+ aggregatePipeline(pipeline: PipelineStage[], options?: Record<string, unknown>): Promise<unknown[]>;
1050
1047
  getByQuery(query: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
1051
1048
  _executeQuery<T>(buildQuery: (Model: Model<any>) => Promise<T>): Promise<T>;
1052
1049
  [key: string]: unknown;
1053
1050
  }
1054
1051
  /** Repository operation names */
1055
- type RepositoryOperation = 'create' | 'createMany' | 'update' | 'updateMany' | 'findOneAndUpdate' | 'delete' | 'deleteMany' | 'restore' | 'getById' | 'getByQuery' | 'getOne' | 'getAll' | 'findAll' | 'getOrCreate' | 'count' | 'exists' | 'distinct' | 'aggregate' | 'aggregatePaginate' | 'lookupPopulate' | 'bulkWrite';
1052
+ type RepositoryOperation = 'create' | 'createMany' | 'update' | 'updateMany' | 'findOneAndUpdate' | 'delete' | 'deleteMany' | 'restore' | 'getById' | 'getByQuery' | 'getOne' | 'getAll' | 'findAll' | 'getOrCreate' | 'count' | 'exists' | 'distinct' | 'aggregate' | 'aggregatePaginate' | 'aggregatePipeline' | 'aggregatePipelinePaginate' | 'lookupPopulate' | 'bulkWrite';
1056
1053
  /** Event lifecycle phases */
1057
1054
  type EventPhase = 'before' | 'after' | 'error';
1058
1055
  /** Repository event names (generated from template literals) */
@@ -1090,96 +1087,6 @@ interface FieldPreset {
1090
1087
  /** Additional fields for admins */
1091
1088
  admin?: string[];
1092
1089
  }
1093
- /** Field rules for schema building */
1094
- interface FieldRules {
1095
- [fieldName: string]: {
1096
- /** Field cannot be updated */immutable?: boolean; /** Alias for immutable */
1097
- immutableAfterCreate?: boolean; /** System-only field (omitted from create/update) */
1098
- systemManaged?: boolean; /** Remove from required array */
1099
- optional?: boolean;
1100
- };
1101
- }
1102
- /** Schema builder options */
1103
- interface SchemaBuilderOptions {
1104
- /** Field rules for create/update */
1105
- fieldRules?: FieldRules;
1106
- /** Strict additional properties (default: false) */
1107
- strictAdditionalProperties?: boolean;
1108
- /** Date format: 'date' | 'datetime' */
1109
- dateAs?: 'date' | 'datetime';
1110
- /** Create schema options */
1111
- create?: {
1112
- /** Fields to omit from create schema */omitFields?: string[]; /** Override required status */
1113
- requiredOverrides?: Record<string, boolean>; /** Override optional status */
1114
- optionalOverrides?: Record<string, boolean>; /** Schema overrides */
1115
- schemaOverrides?: Record<string, unknown>;
1116
- };
1117
- /**
1118
- * Field names to mark as soft-required: they remain in the generated body
1119
- * schema's `properties` (still validated when present) but are excluded
1120
- * from the `required[]` array so the client may omit them.
1121
- *
1122
- * Combines with per-path `softRequired: true` Mongoose schema option.
1123
- * Use this when the Mongoose model is owned by an upstream package and
1124
- * you can't annotate the schema directly.
1125
- *
1126
- * The DB-level `required: true` invariant is unaffected — Mongoose still
1127
- * rejects null on save. This flag only affects HTTP body validation.
1128
- */
1129
- softRequiredFields?: string[];
1130
- /** Update schema options */
1131
- update?: {
1132
- /** Fields to omit from update schema */omitFields?: string[]; /** Require at least one field to be provided (default: false) */
1133
- requireAtLeastOne?: boolean;
1134
- };
1135
- /** Query schema options */
1136
- query?: {
1137
- /** Filterable fields */filterableFields?: Record<string, {
1138
- type: string;
1139
- } | unknown>;
1140
- };
1141
- /**
1142
- * Emit OpenAPI vendor extensions (`x-*` keywords like `x-ref` for populated
1143
- * ObjectId fields). Default: `false`.
1144
- *
1145
- * Why opt-in: Ajv's strict mode THROWS on any `x-*` keyword it doesn't
1146
- * recognize (`strict mode: unknown keyword: "x-ref"`). Consumers validating
1147
- * request bodies with Ajv strict need a keyword-clean schema; consumers
1148
- * generating OpenAPI documentation want the vendor extensions. Default is
1149
- * OFF so Ajv-strict-mode consumers are safe out of the box. Turn ON to
1150
- * include `x-ref` etc. in the output (typically when feeding the schema
1151
- * into a docgen tool like @fastify/swagger or redocly).
1152
- */
1153
- openApiExtensions?: boolean;
1154
- }
1155
- /** JSON Schema type */
1156
- interface JsonSchema {
1157
- type: string;
1158
- properties?: Record<string, unknown>;
1159
- required?: string[];
1160
- additionalProperties?: boolean | unknown;
1161
- items?: unknown;
1162
- enum?: string[];
1163
- format?: string;
1164
- pattern?: string;
1165
- minProperties?: number;
1166
- maxProperties?: number;
1167
- minLength?: number;
1168
- maxLength?: number;
1169
- minimum?: number;
1170
- maximum?: number;
1171
- }
1172
- /** CRUD schemas result - framework-agnostic JSON schemas */
1173
- interface CrudSchemas {
1174
- /** JSON Schema for create request body */
1175
- createBody: JsonSchema;
1176
- /** JSON Schema for update request body */
1177
- updateBody: JsonSchema;
1178
- /** JSON Schema for route params (id validation) */
1179
- params: JsonSchema;
1180
- /** JSON Schema for list/query parameters */
1181
- listQuery: JsonSchema;
1182
- }
1183
1090
  /** Decoded cursor */
1184
1091
  interface DecodedCursor {
1185
1092
  /** Primary sort field value (rehydrated) — legacy compat */
@@ -1281,15 +1188,20 @@ interface MinMaxResult {
1281
1188
  max: unknown;
1282
1189
  }
1283
1190
  /**
1284
- * Cache adapter interface - bring your own cache implementation
1285
- * Works with Redis, Memcached, in-memory, or any key-value store
1191
+ * Cache adapter interface — bring your own cache implementation.
1192
+ * Works with Redis, Memcached, in-memory, or any key-value store.
1193
+ *
1194
+ * Shape is aligned with `@classytic/repo-core/cache#CacheAdapter` and arc's
1195
+ * `CacheStore` so one adapter implementation satisfies every consumer. The
1196
+ * `delete` method is named for consistency with `Map.delete` / `Set.delete` /
1197
+ * `MinimalRepo.delete(id)` — your Redis wrapper translates to `redis.del`.
1286
1198
  *
1287
1199
  * @example Redis implementation:
1288
1200
  * ```typescript
1289
1201
  * const redisCache: CacheAdapter = {
1290
1202
  * async get(key) { return JSON.parse(await redis.get(key) || 'null'); },
1291
1203
  * async set(key, value, ttl) { await redis.setex(key, ttl, JSON.stringify(value)); },
1292
- * async del(key) { await redis.del(key); },
1204
+ * async delete(key) { await redis.del(key); },
1293
1205
  * async clear(pattern) {
1294
1206
  * const keys = await redis.keys(pattern || '*');
1295
1207
  * if (keys.length) await redis.del(...keys);
@@ -1302,8 +1214,8 @@ interface CacheAdapter {
1302
1214
  get<T = unknown>(key: string): Promise<T | null>;
1303
1215
  /** Set value with TTL in seconds */
1304
1216
  set<T = unknown>(key: string, value: T, ttl: number): Promise<void>;
1305
- /** Delete single key */
1306
- del(key: string): Promise<void>;
1217
+ /** Delete a single key. No-op when the key doesn't exist. */
1218
+ delete(key: string): Promise<void>;
1307
1219
  /** Clear keys matching pattern (optional, used for bulk invalidation) */
1308
1220
  clear?(pattern?: string): Promise<void>;
1309
1221
  }
@@ -1550,4 +1462,4 @@ type AllPluginMethods<TDoc> = {
1550
1462
  */
1551
1463
  type WithPlugins<TDoc, TRepo extends RepositoryInstance = RepositoryInstance> = TRepo & AllPluginMethods<TDoc>;
1552
1464
  //#endregion
1553
- export { ReadOptions as $, HttpError as A, LookupBuilder as At, MinMaxResult as B, EventPayload as C, ValidatorDefinition as Ct, FindOneAndUpdateOptions as D, IControllerResponse as Dt, FieldRules as E, IController as Et, KeysetPaginationOptions as F, OperationOptions as G, ObjectId as H, KeysetPaginationResult as I, PartialBy as J, PaginationConfig as K, Logger as L, InferRawDoc as M, JsonSchema as N, GroupResult as O, IRequestContext as Ot, KeysOfType as P, PopulateSpec as Q, LookupPopulateOptions as R, EventHandlers as S, ValidationResult as St, FieldPreset as T, WithTransactionOptions as Tt, OffsetPaginationOptions as U, NonNullableFields as V, OffsetPaginationResult as W, PluginFunction as X, Plugin as Y, PluginType as Z, CrudSchemas as _, UpdateManyResult as _t, AnyDocument as a, RepositoryOptions as at, DeleteResult as b, UserContext as bt, CacheOperationOptions as c, SelectSpec as ct, CacheableOptions as d, SoftDeleteOptions as dt, ReadPreferenceType as et, CascadeOptions as f, SoftDeleteRepository as ft, CreateOptions as g, UpdateInput as gt, CreateInput as h, Strict as ht, AllPluginMethods as i, RepositoryOperation as it, InferDocument as j, LookupOptions as jt, HookMode as k, IResponseFormatter as kt, CacheOptions as l, SessionOptions as lt, CollationOptions as m, SortSpec as mt, AggregatePaginationOptions as n, RepositoryEvent as nt, AnyModel as o, RequiredBy as ot, CascadeRelation as p, SortDirection as pt, PaginationResult as q, AggregatePaginationResult as r, RepositoryInstance as rt, CacheAdapter as s, SchemaBuilderOptions as st, AggregateOptions as t, RepositoryContext as tt, CacheStats as u, SoftDeleteFilterMode as ut, DecodedCursor as v, UpdateOptions as vt, EventPhase as w, WithPlugins as wt, DocField as x, ValidationChainOptions as xt, DeepPartial as y, UpdateWithValidationResult as yt, LookupPopulateResult as z };
1465
+ export { RepositoryContext as $, InferRawDoc as A, OffsetPaginationOptions as B, EventPhase as C, IControllerResponse as Ct, HookMode as D, LookupOptions as Dt, GroupResult as E, LookupBuilder as Et, LookupPopulateOptions as F, PartialBy as G, OperationOptions as H, LookupPopulateResult as I, PluginType as J, Plugin as K, MinMaxResult as L, KeysetPaginationOptions as M, KeysetPaginationResult as N, HttpError as O, Logger as P, ReadPreferenceType as Q, NonNullableFields as R, EventPayload as S, IController as St, FindOneAndUpdateOptions as T, IResponseFormatter as Tt, PaginationConfig as U, OffsetPaginationResult as V, PaginationResult as W, PrioritizedHook as X, PopulateSpec as Y, ReadOptions as Z, DecodedCursor as _, UserContext as _t, AnyDocument as a, SelectSpec as at, DocField as b, WithPlugins as bt, CacheOperationOptions as c, SoftDeleteOptions as ct, CacheableOptions as d, SortSpec as dt, RepositoryEvent as et, CascadeOptions as f, Strict as ft, CreateOptions as g, UpdateWithValidationResult as gt, CreateInput as h, UpdateOptions as ht, AllPluginMethods as i, RequiredBy as it, KeysOfType as j, InferDocument as k, CacheOptions as l, SoftDeleteRepository as lt, CollationOptions as m, UpdateManyResult as mt, AggregatePaginationOptions as n, RepositoryOperation as nt, AnyModel as o, SessionOptions as ot, CascadeRelation as p, UpdateInput as pt, PluginFunction as q, AggregatePaginationResult as r, RepositoryOptions as rt, CacheAdapter as s, SoftDeleteFilterMode as st, AggregateOptions as t, RepositoryInstance as tt, CacheStats as u, SortDirection as ut, DeepPartial as v, ValidationChainOptions as vt, FieldPreset as w, IRequestContext as wt, EventHandlers as x, WithTransactionOptions as xt, DeleteResult as y, ValidatorDefinition as yt, ObjectId as z };
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
2
  import { r as warn } from "./logger-DJ7egMnd.mjs";
3
- import { t as createError } from "./error-Lw9GEAro.mjs";
3
+ import { t as createError } from "./error-CzYwrfUP.mjs";
4
4
  //#region src/query/LookupBuilder.ts
5
5
  /** Stages that are never valid inside a $lookup pipeline */
6
6
  const BLOCKED_PIPELINE_STAGES = [
@@ -544,7 +544,11 @@ async function deleteById(Model, id, options = {}) {
544
544
  _id: id,
545
545
  ...options.query
546
546
  };
547
- if (!await Model.findOneAndDelete(query).session(options.session ?? null)) throw createError(404, "Document not found");
547
+ if (!await Model.findOneAndDelete(query).session(options.session ?? null)) return {
548
+ success: false,
549
+ message: "Document not found",
550
+ id: String(id)
551
+ };
548
552
  return {
549
553
  success: true,
550
554
  message: "Deleted successfully",
@@ -566,11 +570,17 @@ async function deleteMany(Model, query, options = {}) {
566
570
  */
567
571
  async function deleteByQuery(Model, query, options = {}) {
568
572
  const document = await Model.findOneAndDelete(query).session(options.session ?? null);
569
- if (!document && options.throwOnNotFound !== false) throw createError(404, "Document not found");
573
+ if (!document) {
574
+ if (options.throwOnNotFound === true) throw createError(404, "Document not found");
575
+ return {
576
+ success: false,
577
+ message: "Document not found"
578
+ };
579
+ }
570
580
  return {
571
581
  success: true,
572
582
  message: "Deleted successfully",
573
- ...document ? { id: String(document._id) } : {}
583
+ id: String(document._id)
574
584
  };
575
585
  }
576
586
  /**
@@ -650,7 +660,7 @@ async function getById(Model, id, options = {}) {
650
660
  if (options.session) query.session(options.session);
651
661
  if (options.readPreference) query.read(options.readPreference);
652
662
  const document = await query.exec();
653
- if (!document && options.throwOnNotFound !== false) throw createError(404, "Document not found");
663
+ if (!document && options.throwOnNotFound === true) throw createError(404, "Document not found");
654
664
  return document;
655
665
  }
656
666
  /**
@@ -670,7 +680,7 @@ async function getByQuery(Model, query, options = {}) {
670
680
  if (options.session) mongoQuery.session(options.session);
671
681
  if (options.readPreference) mongoQuery.read(options.readPreference);
672
682
  const document = await mongoQuery.exec();
673
- if (!document && options.throwOnNotFound !== false) throw createError(404, "Document not found");
683
+ if (!document && options.throwOnNotFound === true) throw createError(404, "Document not found");
674
684
  return document;
675
685
  }
676
686
  /**
@@ -760,15 +770,13 @@ async function update(Model, id, data, options = {}) {
760
770
  _id: id,
761
771
  ...options.query
762
772
  };
763
- const document = await Model.findOneAndUpdate(query, data, {
773
+ return await Model.findOneAndUpdate(query, data, {
764
774
  returnDocument: "after",
765
775
  runValidators: true,
766
776
  session: options.session,
767
777
  ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {},
768
778
  ...options.arrayFilters ? { arrayFilters: options.arrayFilters } : {}
769
- }).select(options.select || "").populate(parsePopulate(options.populate)).lean(options.lean ?? false);
770
- if (!document) throw createError(404, "Document not found");
771
- return document;
779
+ }).select(options.select || "").populate(parsePopulate(options.populate)).lean(options.lean ?? false) ?? null;
772
780
  }
773
781
  /**
774
782
  * Update with query constraints (optimized)
@@ -825,9 +833,17 @@ async function updateWithValidation(Model, id, data, validationOptions = {}, opt
825
833
  }
826
834
  };
827
835
  }
836
+ const updated = await update(Model, id, data, options);
837
+ if (!updated) return {
838
+ success: false,
839
+ error: {
840
+ code: 404,
841
+ message: "Document not found"
842
+ }
843
+ };
828
844
  return {
829
845
  success: true,
830
- data: await update(Model, id, data, options)
846
+ data: updated
831
847
  };
832
848
  }
833
849
  /**
@@ -858,7 +874,7 @@ async function updateByQuery(Model, query, data, options = {}) {
858
874
  ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {},
859
875
  ...options.arrayFilters ? { arrayFilters: options.arrayFilters } : {}
860
876
  }).select(options.select || "").populate(parsePopulate(options.populate)).lean(options.lean ?? false);
861
- if (!document && options.throwOnNotFound !== false) throw createError(404, "Document not found");
877
+ if (!document && options.throwOnNotFound === true) throw createError(404, "Document not found");
862
878
  return document;
863
879
  }
864
880
  /**
@@ -1,5 +1,5 @@
1
- import { Q as PopulateSpec, ct as SelectSpec, mt as SortSpec } from "../types-D57SZJeE.mjs";
2
- import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as createError, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as filterResponseData } from "../mongooseToJsonSchema-BZaKOXXI.mjs";
1
+ import { Y as PopulateSpec, at as SelectSpec, dt as SortSpec } from "../types-BUS0Zj8y.mjs";
2
+ import { a as createFieldPreset, c as getMongooseProjection, i as configureLogger, l as createError, n as buildCrudSchemasFromMongooseSchema, o as filterResponseData, r as createMemoryCache, s as getFieldsForUser, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-t-MoVleF.mjs";
3
3
 
4
4
  //#region src/utils/cache-keys.d.ts
5
5
  /**
@@ -146,4 +146,4 @@ declare function getSchemaIdType(schema?: SchemaLike | null): IdType;
146
146
  */
147
147
  declare function isValidIdForType(id: unknown, idType: IdType): boolean;
148
148
  //#endregion
149
- export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSchemaIdType, getSystemManagedFields, isFieldUpdateAllowed, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
149
+ export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, versionKey };
@@ -1,6 +1,6 @@
1
1
  import { t as configureLogger } from "../logger-DJ7egMnd.mjs";
2
- import { t as createError } from "../error-Lw9GEAro.mjs";
2
+ import { t as createError } from "../error-CzYwrfUP.mjs";
3
3
  import { n as isObjectIdInstance, r as isValidIdForType, t as getSchemaIdType } from "../id-resolution-e4BT9zAV.mjs";
4
4
  import { a as byIdKey, c as listQueryKey, i as getMongooseProjection, l as modelPattern, n as filterResponseData, o as byQueryKey, r as getFieldsForUser, s as listPattern, t as createFieldPreset, u as versionKey } from "../field-selection-dI0m4RBs.mjs";
5
- import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-DUwXPStK.mjs";
6
- export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSchemaIdType, getSystemManagedFields, isFieldUpdateAllowed, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
5
+ import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-D1sdCOmj.mjs";
6
+ export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, versionKey };