@classytic/mongokit 3.5.2 → 3.5.4

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 CHANGED
@@ -1172,6 +1172,40 @@ const { crudSchemas } = buildCrudSchemasFromModel(UserModel, {
1172
1172
  // crudSchemas.listQuery - GET query validation
1173
1173
  ```
1174
1174
 
1175
+ #### Soft-required fields (draft-friendly bodies)
1176
+
1177
+ Some resources have fields that must be non-null in the DB but should be
1178
+ **optional in the HTTP body** — draft journal entries, multi-step wizards,
1179
+ or any state-machine resource that's filled out progressively. Marking a
1180
+ path `softRequired: true` keeps the Mongoose `required: true` invariant
1181
+ (null on save is still rejected) while excluding the field from the
1182
+ generated `createBody.required[]` array.
1183
+
1184
+ ```typescript
1185
+ // 1. Per-path flag (when you own the Mongoose schema)
1186
+ const JournalEntrySchema = new Schema({
1187
+ label: { type: String, required: true }, // hard required
1188
+ journalType: {
1189
+ type: String,
1190
+ required: true, // DB invariant — null rejected on save
1191
+ softRequired: true, // HTTP body — optional in create/update
1192
+ enum: ['sale', 'purchase', 'adjustment'],
1193
+ },
1194
+ date: { type: Date, required: true, softRequired: true },
1195
+ });
1196
+
1197
+ // 2. Per-build override (when the Mongoose model lives in an upstream package)
1198
+ const { createBody } = buildCrudSchemasFromModel(UpstreamModel, {
1199
+ softRequiredFields: ['journalType', 'date'],
1200
+ });
1201
+ // createBody.required → ['label'] (journalType + date excluded)
1202
+ // createBody.properties → still includes journalType + date (validated when present)
1203
+ ```
1204
+
1205
+ The DB-level `required: true` is unaffected — `repo.create()` with `null`
1206
+ for a soft-required field still throws a Mongoose ValidationError. The
1207
+ flag only changes what HTTP clients must send.
1208
+
1175
1209
  ### Complete Controller Example
1176
1210
 
1177
1211
  ```typescript
@@ -1,2 +1,2 @@
1
- import { a as create_d_exports, i as delete_d_exports, n as update_d_exports, o as aggregate_d_exports, r as read_d_exports } from "../index-BjXxDBgx.mjs";
1
+ import { a as create_d_exports, i as delete_d_exports, n as update_d_exports, o as aggregate_d_exports, r as read_d_exports } from "../index-BNpVzw_-.mjs";
2
2
  export { aggregate_d_exports as aggregate, create_d_exports as create, delete_d_exports as deleteActions, read_d_exports as read, update_d_exports as update };
@@ -1,4 +1,4 @@
1
- import { J as Plugin } from "../types-DK6ELT6o.mjs";
1
+ import { J as Plugin } from "../types-CkE3mOaW.mjs";
2
2
  import { ClientSession, PipelineStage } from "mongoose";
3
3
 
4
4
  //#region src/ai/types.d.ts
@@ -1,4 +1,4 @@
1
- import { $ as ReadPreferenceType, At as LookupOptions, D as GroupResult, V as ObjectId, W as OperationOptions, Z as PopulateSpec, _t as UpdateOptions, a as AnyDocument, b as DeleteResult, g as CreateOptions, gt as UpdateManyResult, pt as SortSpec, st as SelectSpec, vt as UpdateWithValidationResult, z as MinMaxResult } from "./types-DK6ELT6o.mjs";
1
+ import { $ as ReadPreferenceType, At as LookupOptions, D as GroupResult, V as ObjectId, W as OperationOptions, Z as PopulateSpec, _t as UpdateOptions, a as AnyDocument, b as DeleteResult, g as CreateOptions, gt as UpdateManyResult, pt as SortSpec, st as SelectSpec, vt as UpdateWithValidationResult, z as MinMaxResult } from "./types-CkE3mOaW.mjs";
2
2
  import { ClientSession, Model, PipelineStage } from "mongoose";
3
3
 
4
4
  //#region src/actions/aggregate.d.ts
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
- import { $ as ReadPreferenceType, A as InferDocument, At as LookupOptions, B as NonNullableFields, C as EventPayload, Ct as WithPlugins, D as GroupResult, Dt as IRequestContext, E as FieldRules, Et as IControllerResponse, F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, I as Logger, J as Plugin, K as PaginationResult, L as LookupPopulateOptions, M as JsonSchema, N as KeysOfType, O as HookMode, Ot as IResponseFormatter, P as KeysetPaginationOptions, Q as ReadOptions, R as LookupPopulateResult, S as EventHandlers, St as ValidatorDefinition, T as FieldPreset, Tt as IController, U as OffsetPaginationResult, V as ObjectId, W as OperationOptions, X as PluginType, Y as PluginFunction, Z as PopulateSpec, _ as CrudSchemas, _t as UpdateOptions, a as AnyDocument, at as RequiredBy, b as DeleteResult, bt as ValidationChainOptions, c as CacheOperationOptions, ct as SessionOptions, d as CacheableOptions, dt as SoftDeleteRepository, et as RepositoryContext, f as CascadeOptions, ft as SortDirection, g as CreateOptions, gt as UpdateManyResult, h as CreateInput, ht as UpdateInput, i as AllPluginMethods, it as RepositoryOptions, j as InferRawDoc, k as HttpError, kt as LookupBuilder, l as CacheOptions, lt as SoftDeleteFilterMode, m as CollationOptions, mt as Strict, n as AggregatePaginationOptions, nt as RepositoryInstance, o as AnyModel, ot as SchemaBuilderOptions, p as CascadeRelation, pt as SortSpec, q as PartialBy, r as AggregatePaginationResult, rt as RepositoryOperation, s as CacheAdapter, st as SelectSpec, t as AggregateOptions, tt as RepositoryEvent, u as CacheStats, ut as SoftDeleteOptions, v as DecodedCursor, vt as UpdateWithValidationResult, w as EventPhase, wt as WithTransactionOptions, x as DocField, xt as ValidationResult, y as DeepPartial, yt as UserContext, z as MinMaxResult } from "./types-DK6ELT6o.mjs";
2
- import { t as index_d_exports } from "./index-BjXxDBgx.mjs";
1
+ import { $ as ReadPreferenceType, A as InferDocument, At as LookupOptions, B as NonNullableFields, C as EventPayload, Ct as WithPlugins, D as GroupResult, Dt as IRequestContext, E as FieldRules, Et as IControllerResponse, F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, I as Logger, J as Plugin, K as PaginationResult, L as LookupPopulateOptions, M as JsonSchema, N as KeysOfType, O as HookMode, Ot as IResponseFormatter, P as KeysetPaginationOptions, Q as ReadOptions, R as LookupPopulateResult, S as EventHandlers, St as ValidatorDefinition, T as FieldPreset, Tt as IController, U as OffsetPaginationResult, V as ObjectId, W as OperationOptions, X as PluginType, Y as PluginFunction, Z as PopulateSpec, _ as CrudSchemas, _t as UpdateOptions, a as AnyDocument, at as RequiredBy, b as DeleteResult, bt as ValidationChainOptions, c as CacheOperationOptions, ct as SessionOptions, d as CacheableOptions, dt as SoftDeleteRepository, et as RepositoryContext, f as CascadeOptions, ft as SortDirection, g as CreateOptions, gt as UpdateManyResult, h as CreateInput, ht as UpdateInput, i as AllPluginMethods, it as RepositoryOptions, j as InferRawDoc, k as HttpError, kt as LookupBuilder, l as CacheOptions, lt as SoftDeleteFilterMode, m as CollationOptions, mt as Strict, n as AggregatePaginationOptions, nt as RepositoryInstance, o as AnyModel, ot as SchemaBuilderOptions, p as CascadeRelation, pt as SortSpec, q as PartialBy, r as AggregatePaginationResult, rt as RepositoryOperation, s as CacheAdapter, st as SelectSpec, t as AggregateOptions, tt as RepositoryEvent, u as CacheStats, ut as SoftDeleteOptions, v as DecodedCursor, vt as UpdateWithValidationResult, w as EventPhase, wt as WithTransactionOptions, x as DocField, xt as ValidationResult, y as DeepPartial, yt as UserContext, z as MinMaxResult } from "./types-CkE3mOaW.mjs";
2
+ import { t as index_d_exports } from "./index-BNpVzw_-.mjs";
3
3
  import { PaginationEngine } from "./pagination/PaginationEngine.mjs";
4
- import { A as dateSequentialId, B as AuditEntry, C as elasticSearchPlugin, D as PrefixedIdOptions, E as IdGenerator, F as CacheMethods, G as AuditTrailOptions, H as AuditQueryOptions, I as cachePlugin, J as auditLogPlugin, K as AuditTrailQuery, L as BatchOperationsMethods, M as prefixedId, N as sequentialId, O as SequentialIdOptions, P as cascadePlugin, R as BulkWriteResult, S as ElasticSearchOptions, T as DateSequentialIdOptions, U as AuditQueryResult, V as AuditOperation, W as AuditTrailMethods, X as aggregateHelpersPlugin, Y as AggregateHelpersMethods, _ as MongoOperationsMethods, a as uniqueField, b as methodRegistryPlugin, c as SubdocumentMethods, d as softDeletePlugin, f as ObservabilityOptions, g as multiTenantPlugin, h as MultiTenantOptions, i as requireField, j as getNextSequence, k as customIdPlugin, l as subdocumentPlugin, m as observabilityPlugin, n as blockIf, o as validationChainPlugin, p as OperationMetric, q as auditTrailPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as SoftDeleteMethods, v as mongoOperationsPlugin, w as CustomIdOptions, x as fieldFilterPlugin, z as batchOperationsPlugin } from "./validation-chain.plugin-DISOoL9A.mjs";
5
- import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, m as parseDuplicateKeyError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as createError, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as filterResponseData } from "./mongooseToJsonSchema-C78rAP7X.mjs";
4
+ import { A as dateSequentialId, B as AuditEntry, C as elasticSearchPlugin, D as PrefixedIdOptions, E as IdGenerator, F as CacheMethods, G as AuditTrailOptions, H as AuditQueryOptions, I as cachePlugin, J as auditLogPlugin, K as AuditTrailQuery, L as BatchOperationsMethods, M as prefixedId, N as sequentialId, O as SequentialIdOptions, P as cascadePlugin, R as BulkWriteResult, S as ElasticSearchOptions, T as DateSequentialIdOptions, U as AuditQueryResult, V as AuditOperation, W as AuditTrailMethods, X as aggregateHelpersPlugin, Y as AggregateHelpersMethods, _ as MongoOperationsMethods, a as uniqueField, b as methodRegistryPlugin, c as SubdocumentMethods, d as softDeletePlugin, f as ObservabilityOptions, g as multiTenantPlugin, h as MultiTenantOptions, i as requireField, j as getNextSequence, k as customIdPlugin, l as subdocumentPlugin, m as observabilityPlugin, n as blockIf, o as validationChainPlugin, p as OperationMetric, q as auditTrailPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as SoftDeleteMethods, v as mongoOperationsPlugin, w as CustomIdOptions, x as fieldFilterPlugin, z as batchOperationsPlugin } from "./validation-chain.plugin-BYMZB8dN.mjs";
5
+ import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, m as parseDuplicateKeyError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as createError, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as filterResponseData } from "./mongooseToJsonSchema-Dsx3YAAx.mjs";
6
6
  import * as _$mongoose from "mongoose";
7
7
  import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
8
8
 
package/dist/index.mjs CHANGED
@@ -2,9 +2,9 @@ import { a as warn, n as parseDuplicateKeyError, r as configureLogger, t as crea
2
2
  import { y as LookupBuilder } from "./update-AVfKWNGt.mjs";
3
3
  import { t as actions_exports } from "./actions/index.mjs";
4
4
  import { t as PaginationEngine } from "./PaginationEngine-DCs-zKwZ.mjs";
5
- import { A as aggregateHelpersPlugin, C as HOOK_PRIORITY, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, T as AggregationBuilder, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, w as Repository, x as cascadePlugin, y as prefixedId } from "./validation-chain.plugin-vxvcv1dg.mjs";
5
+ import { A as aggregateHelpersPlugin, C as HOOK_PRIORITY, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, T as AggregationBuilder, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, w as Repository, x as cascadePlugin, y as prefixedId } from "./validation-chain.plugin-KjPV_6_N.mjs";
6
6
  import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-reyDRzXf.mjs";
7
- import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-B6Qyl8BK.mjs";
7
+ import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-DpjhFfaO.mjs";
8
8
  import mongoose from "mongoose";
9
9
  //#region src/query/QueryParser.ts
10
10
  /**
@@ -154,6 +154,11 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
154
154
  const properties = {};
155
155
  const required = [];
156
156
  const paths = mongooseSchema.paths;
157
+ const softRequiredSet = new Set(options?.softRequiredFields ?? []);
158
+ const isSoftRequired = (name, schemaType) => {
159
+ if (softRequiredSet.has(name)) return true;
160
+ return schemaType?.options?.softRequired === true;
161
+ };
157
162
  const rootFields = /* @__PURE__ */ new Map();
158
163
  for (const [path, schemaType] of Object.entries(paths)) {
159
164
  if (path === "_id" || path === "__v") continue;
@@ -167,7 +172,7 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
167
172
  for (const [rootField, fieldPaths] of rootFields.entries()) if (fieldPaths.length === 1 && fieldPaths[0].path === rootField) {
168
173
  const schemaType = fieldPaths[0].schemaType;
169
174
  properties[rootField] = schemaTypeToJsonSchema(schemaType);
170
- if (schemaType.isRequired) required.push(rootField);
175
+ if (schemaType.isRequired && !isSoftRequired(rootField, schemaType)) required.push(rootField);
171
176
  } else {
172
177
  const nestedSchema = buildNestedJsonSchema(fieldPaths, rootField);
173
178
  properties[rootField] = nestedSchema.schema;
@@ -1,4 +1,4 @@
1
- import { T as FieldPreset, _ as CrudSchemas, k as HttpError, ot as SchemaBuilderOptions, s as CacheAdapter, xt as ValidationResult, yt as UserContext } from "./types-DK6ELT6o.mjs";
1
+ import { T as FieldPreset, _ as CrudSchemas, k as HttpError, ot as SchemaBuilderOptions, s as CacheAdapter, xt as ValidationResult, yt as UserContext } from "./types-CkE3mOaW.mjs";
2
2
  import mongoose, { Schema } from "mongoose";
3
3
 
4
4
  //#region src/utils/error.d.ts
@@ -1,4 +1,4 @@
1
- import { F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, P as KeysetPaginationOptions, U as OffsetPaginationResult, a as AnyDocument, n as AggregatePaginationOptions, r as AggregatePaginationResult } from "../types-DK6ELT6o.mjs";
1
+ import { F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, P as KeysetPaginationOptions, U as OffsetPaginationResult, a as AnyDocument, n as AggregatePaginationOptions, r as AggregatePaginationResult } from "../types-CkE3mOaW.mjs";
2
2
  import { Model } from "mongoose";
3
3
 
4
4
  //#region src/pagination/PaginationEngine.d.ts
@@ -1,2 +1,2 @@
1
- import { A as dateSequentialId, B as AuditEntry, C as elasticSearchPlugin, D as PrefixedIdOptions, E as IdGenerator, F as CacheMethods, G as AuditTrailOptions, H as AuditQueryOptions, I as cachePlugin, J as auditLogPlugin, K as AuditTrailQuery, L as BatchOperationsMethods, M as prefixedId, N as sequentialId, O as SequentialIdOptions, P as cascadePlugin, S as ElasticSearchOptions, T as DateSequentialIdOptions, U as AuditQueryResult, V as AuditOperation, W as AuditTrailMethods, X as aggregateHelpersPlugin, Y as AggregateHelpersMethods, _ as MongoOperationsMethods, a as uniqueField, b as methodRegistryPlugin, c as SubdocumentMethods, d as softDeletePlugin, f as ObservabilityOptions, g as multiTenantPlugin, h as MultiTenantOptions, i as requireField, j as getNextSequence, k as customIdPlugin, l as subdocumentPlugin, m as observabilityPlugin, n as blockIf, o as validationChainPlugin, p as OperationMetric, q as auditTrailPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as SoftDeleteMethods, v as mongoOperationsPlugin, w as CustomIdOptions, x as fieldFilterPlugin, y as MethodRegistryRepository, z as batchOperationsPlugin } from "../validation-chain.plugin-DISOoL9A.mjs";
1
+ import { A as dateSequentialId, B as AuditEntry, C as elasticSearchPlugin, D as PrefixedIdOptions, E as IdGenerator, F as CacheMethods, G as AuditTrailOptions, H as AuditQueryOptions, I as cachePlugin, J as auditLogPlugin, K as AuditTrailQuery, L as BatchOperationsMethods, M as prefixedId, N as sequentialId, O as SequentialIdOptions, P as cascadePlugin, S as ElasticSearchOptions, T as DateSequentialIdOptions, U as AuditQueryResult, V as AuditOperation, W as AuditTrailMethods, X as aggregateHelpersPlugin, Y as AggregateHelpersMethods, _ as MongoOperationsMethods, a as uniqueField, b as methodRegistryPlugin, c as SubdocumentMethods, d as softDeletePlugin, f as ObservabilityOptions, g as multiTenantPlugin, h as MultiTenantOptions, i as requireField, j as getNextSequence, k as customIdPlugin, l as subdocumentPlugin, m as observabilityPlugin, n as blockIf, o as validationChainPlugin, p as OperationMetric, q as auditTrailPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as SoftDeleteMethods, v as mongoOperationsPlugin, w as CustomIdOptions, x as fieldFilterPlugin, y as MethodRegistryRepository, z as batchOperationsPlugin } from "../validation-chain.plugin-BYMZB8dN.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, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, 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 aggregateHelpersPlugin, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, x as cascadePlugin, y as prefixedId } from "../validation-chain.plugin-vxvcv1dg.mjs";
1
+ import { A as aggregateHelpersPlugin, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, x as cascadePlugin, y as prefixedId } from "../validation-chain.plugin-KjPV_6_N.mjs";
2
2
  export { AuditTrailQuery, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -457,6 +457,27 @@ interface IResponseFormatter {
457
457
  }
458
458
  //#endregion
459
459
  //#region src/types.d.ts
460
+ /**
461
+ * MongoKit Type Definitions
462
+ *
463
+ * Production-grade types for MongoDB repository pattern with TypeScript
464
+ *
465
+ * @module @classytic/mongokit
466
+ */
467
+ /**
468
+ * Global declaration merge: adds `softRequired` to Mongoose's SchemaTypeOptions
469
+ * so consumers can annotate paths type-safely without importing anything.
470
+ *
471
+ * A path with `softRequired: true` keeps its DB-level `required: true`
472
+ * invariant but is excluded from the auto-generated CRUD body `required[]`
473
+ * array (see buildCrudSchemasFromModel).
474
+ */
475
+ declare module 'mongoose' {
476
+ interface SchemaTypeOptions<T, EnforcedDocType = any, THydratedDocumentType = HydratedDocument<EnforcedDocType>> {
477
+ /** mongokit: omit from auto-generated CRUD body `required` array. */
478
+ softRequired?: boolean;
479
+ }
480
+ }
460
481
  /** Read Preference Type for replica sets */
461
482
  type ReadPreferenceType = 'primary' | 'primaryPreferred' | 'secondary' | 'secondaryPreferred' | 'nearest' | (string & {});
462
483
  /** Re-export mongoose ObjectId */
@@ -684,6 +705,10 @@ type PaginationResult<T = unknown> = OffsetPaginationResult<T> | KeysetPaginatio
684
705
  interface SessionOptions {
685
706
  /** MongoDB session for transactions */
686
707
  session?: ClientSession;
708
+ /** Organization/tenant ID for multi-tenant plugin scoping */
709
+ organizationId?: string | ObjectId;
710
+ /** Extensible — plugins can read custom fields from options */
711
+ [key: string]: unknown;
687
712
  }
688
713
  /** Read options — session + readPreference for read-only operations */
689
714
  interface ReadOptions extends SessionOptions {
@@ -994,6 +1019,19 @@ interface SchemaBuilderOptions {
994
1019
  optionalOverrides?: Record<string, boolean>; /** Schema overrides */
995
1020
  schemaOverrides?: Record<string, unknown>;
996
1021
  };
1022
+ /**
1023
+ * Field names to mark as soft-required: they remain in the generated body
1024
+ * schema's `properties` (still validated when present) but are excluded
1025
+ * from the `required[]` array so the client may omit them.
1026
+ *
1027
+ * Combines with per-path `softRequired: true` Mongoose schema option.
1028
+ * Use this when the Mongoose model is owned by an upstream package and
1029
+ * you can't annotate the schema directly.
1030
+ *
1031
+ * The DB-level `required: true` invariant is unaffected — Mongoose still
1032
+ * rejects null on save. This flag only affects HTTP body validation.
1033
+ */
1034
+ softRequiredFields?: string[];
997
1035
  /** Update schema options */
998
1036
  update?: {
999
1037
  /** Fields to omit from update schema */omitFields?: string[]; /** Require at least one field to be provided (default: false) */
@@ -1,5 +1,5 @@
1
- import { Z as PopulateSpec, pt as SortSpec, st as SelectSpec } from "../types-DK6ELT6o.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-C78rAP7X.mjs";
1
+ import { Z as PopulateSpec, pt as SortSpec, st as SelectSpec } from "../types-CkE3mOaW.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-Dsx3YAAx.mjs";
3
3
 
4
4
  //#region src/utils/cache-keys.d.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
1
  import { r as configureLogger, t as createError } from "../error-Bpbi_NKo.mjs";
2
2
  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-reyDRzXf.mjs";
3
- import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-B6Qyl8BK.mjs";
3
+ import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-DpjhFfaO.mjs";
4
4
  export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSystemManagedFields, isFieldUpdateAllowed, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
@@ -1,4 +1,4 @@
1
- import { I as Logger, J as Plugin, St as ValidatorDefinition, T as FieldPreset, U as OffsetPaginationResult, V as ObjectId, Z as PopulateSpec, bt as ValidationChainOptions, et as RepositoryContext, f as CascadeOptions, l as CacheOptions, nt as RepositoryInstance, pt as SortSpec, st as SelectSpec, u as CacheStats, ut as SoftDeleteOptions } from "./types-DK6ELT6o.mjs";
1
+ import { I as Logger, J as Plugin, St as ValidatorDefinition, T as FieldPreset, U as OffsetPaginationResult, V as ObjectId, Z as PopulateSpec, bt as ValidationChainOptions, et as RepositoryContext, f as CascadeOptions, l as CacheOptions, nt as RepositoryInstance, pt as SortSpec, st as SelectSpec, u as CacheStats, ut as SoftDeleteOptions } from "./types-CkE3mOaW.mjs";
2
2
  import mongoose, { ClientSession } from "mongoose";
3
3
 
4
4
  //#region src/plugins/aggregate-helpers.plugin.d.ts
@@ -2431,6 +2431,34 @@ function cachePlugin(options) {
2431
2431
  }
2432
2432
  }, { priority: HOOK_PRIORITY.CACHE });
2433
2433
  /**
2434
+ * before:getOne - Same cache logic as getByQuery (single-doc)
2435
+ */
2436
+ repo.on("before:getOne", async (context) => {
2437
+ if (context.skipCache) return;
2438
+ try {
2439
+ const collectionVersion = await getVersion();
2440
+ const query = context.query || {};
2441
+ const key = byQueryKey(config.prefix, model, collectionVersion, query, {
2442
+ select: context.select,
2443
+ populate: context.populate
2444
+ });
2445
+ const cached = await config.adapter.get(key);
2446
+ if (cached !== null && cached !== void 0) {
2447
+ log(`Cache HIT for getOne: ${key}`);
2448
+ stats.hits++;
2449
+ context._cacheHit = true;
2450
+ context._cachedResult = cached;
2451
+ } else {
2452
+ log(`Cache MISS for getOne: ${key}`);
2453
+ stats.misses++;
2454
+ context._cacheKey = key;
2455
+ }
2456
+ } catch (e) {
2457
+ log(`Cache error for getOne:`, e);
2458
+ stats.errors++;
2459
+ }
2460
+ }, { priority: HOOK_PRIORITY.CACHE });
2461
+ /**
2434
2462
  * before:getAll - Check cache for list query
2435
2463
  * Runs at CACHE priority (200) — after policy hooks inject filters
2436
2464
  */
@@ -2526,6 +2554,25 @@ function cachePlugin(options) {
2526
2554
  }
2527
2555
  });
2528
2556
  /**
2557
+ * after:getOne - Cache the result (same as getByQuery)
2558
+ */
2559
+ repo.on("after:getOne", async (payload) => {
2560
+ const { context, result } = payload;
2561
+ if (context._cacheHit || context.skipCache || result === null) return;
2562
+ const collectionVersion = await getVersion();
2563
+ const query = context.query || {};
2564
+ const key = byQueryKey(config.prefix, model, collectionVersion, query, {
2565
+ select: context.select,
2566
+ populate: context.populate
2567
+ });
2568
+ try {
2569
+ await config.adapter.set(key, result, context.cacheTtl ?? config.queryTtl);
2570
+ stats.sets++;
2571
+ } catch (e) {
2572
+ log(`Failed to cache getOne:`, e);
2573
+ }
2574
+ });
2575
+ /**
2529
2576
  * after:getAll - Cache the result
2530
2577
  */
2531
2578
  repo.on("after:getAll", async (payload) => {
@@ -3193,8 +3240,10 @@ function fieldFilterPlugin(fieldPreset) {
3193
3240
  else context.select = presetSelect;
3194
3241
  };
3195
3242
  repo.on("before:getAll", applyFieldFiltering);
3243
+ repo.on("before:findAll", applyFieldFiltering);
3196
3244
  repo.on("before:getById", applyFieldFiltering);
3197
3245
  repo.on("before:getByQuery", applyFieldFiltering);
3246
+ repo.on("before:getOne", applyFieldFiltering);
3198
3247
  }
3199
3248
  };
3200
3249
  }
@@ -3480,12 +3529,14 @@ function multiTenantPlugin(options = {}) {
3480
3529
  const { tenantField = "organizationId", contextKey = "organizationId", required = true, skipOperations = [], skipWhen, resolveContext } = options;
3481
3530
  const filterOps = [
3482
3531
  "getAll",
3532
+ "findAll",
3483
3533
  "aggregatePaginate",
3484
3534
  "lookupPopulate"
3485
3535
  ];
3486
3536
  const queryReadOps = [
3487
3537
  "getById",
3488
3538
  "getByQuery",
3539
+ "getOne",
3489
3540
  "count",
3490
3541
  "exists",
3491
3542
  "getOrCreate",
@@ -3578,7 +3629,14 @@ const DEFAULT_OPS = [
3578
3629
  "delete",
3579
3630
  "getById",
3580
3631
  "getByQuery",
3632
+ "getOne",
3581
3633
  "getAll",
3634
+ "findAll",
3635
+ "getOrCreate",
3636
+ "count",
3637
+ "exists",
3638
+ "distinct",
3639
+ "aggregate",
3582
3640
  "aggregatePaginate",
3583
3641
  "lookupPopulate"
3584
3642
  ];
@@ -3733,7 +3791,7 @@ function softDeletePlugin(options = {}) {
3733
3791
  context.softDeleted = true;
3734
3792
  }
3735
3793
  }, { priority: HOOK_PRIORITY.POLICY });
3736
- repo.on("before:getAll", (context) => {
3794
+ const injectDeleteFilterToFilters = (context) => {
3737
3795
  if (options.soft !== false) {
3738
3796
  const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
3739
3797
  if (Object.keys(deleteFilter).length > 0) context.filters = {
@@ -3741,53 +3799,8 @@ function softDeletePlugin(options = {}) {
3741
3799
  ...deleteFilter
3742
3800
  };
3743
3801
  }
3744
- }, { priority: HOOK_PRIORITY.POLICY });
3745
- repo.on("before:getById", (context) => {
3746
- if (options.soft !== false) {
3747
- const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
3748
- if (Object.keys(deleteFilter).length > 0) context.query = {
3749
- ...context.query || {},
3750
- ...deleteFilter
3751
- };
3752
- }
3753
- }, { priority: HOOK_PRIORITY.POLICY });
3754
- repo.on("before:getByQuery", (context) => {
3755
- if (options.soft !== false) {
3756
- const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
3757
- if (Object.keys(deleteFilter).length > 0) context.query = {
3758
- ...context.query || {},
3759
- ...deleteFilter
3760
- };
3761
- }
3762
- }, { priority: HOOK_PRIORITY.POLICY });
3763
- repo.on("before:count", (context) => {
3764
- if (options.soft !== false) {
3765
- const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
3766
- if (Object.keys(deleteFilter).length > 0) context.query = {
3767
- ...context.query || {},
3768
- ...deleteFilter
3769
- };
3770
- }
3771
- }, { priority: HOOK_PRIORITY.POLICY });
3772
- repo.on("before:exists", (context) => {
3773
- if (options.soft !== false) {
3774
- const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
3775
- if (Object.keys(deleteFilter).length > 0) context.query = {
3776
- ...context.query || {},
3777
- ...deleteFilter
3778
- };
3779
- }
3780
- }, { priority: HOOK_PRIORITY.POLICY });
3781
- repo.on("before:getOrCreate", (context) => {
3782
- if (options.soft !== false) {
3783
- const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
3784
- if (Object.keys(deleteFilter).length > 0) context.query = {
3785
- ...context.query || {},
3786
- ...deleteFilter
3787
- };
3788
- }
3789
- }, { priority: HOOK_PRIORITY.POLICY });
3790
- repo.on("before:distinct", (context) => {
3802
+ };
3803
+ const injectDeleteFilterToQuery = (context) => {
3791
3804
  if (options.soft !== false) {
3792
3805
  const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
3793
3806
  if (Object.keys(deleteFilter).length > 0) context.query = {
@@ -3795,7 +3808,23 @@ function softDeletePlugin(options = {}) {
3795
3808
  ...deleteFilter
3796
3809
  };
3797
3810
  }
3798
- }, { priority: HOOK_PRIORITY.POLICY });
3811
+ };
3812
+ for (const op of [
3813
+ "getAll",
3814
+ "findAll",
3815
+ "aggregatePaginate",
3816
+ "lookupPopulate"
3817
+ ]) repo.on(`before:${op}`, injectDeleteFilterToFilters, { priority: HOOK_PRIORITY.POLICY });
3818
+ for (const op of [
3819
+ "getById",
3820
+ "getOne",
3821
+ "getByQuery",
3822
+ "count",
3823
+ "exists",
3824
+ "getOrCreate",
3825
+ "distinct",
3826
+ "aggregate"
3827
+ ]) repo.on(`before:${op}`, injectDeleteFilterToQuery, { priority: HOOK_PRIORITY.POLICY });
3799
3828
  repo.on("before:updateMany", (context) => {
3800
3829
  if (options.soft !== false) {
3801
3830
  const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/mongokit",
3
- "version": "3.5.2",
3
+ "version": "3.5.4",
4
4
  "description": "Production-grade MongoDB repositories with zero dependencies - smart pagination, events, and plugins",
5
5
  "type": "module",
6
6
  "sideEffects": false,