@classytic/mongokit 3.34.0 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,15 +3,25 @@ import { Document, Model } from "mongoose";
3
3
  import { SchemaBuilderOptions, SchemaGenerator } from "@classytic/repo-core/schema";
4
4
  import { AdapterRepositoryInput, AdapterSchemaContext, AdapterValidationResult, DataAdapter, OpenApiSchemas, RepositoryLike, SchemaMetadata } from "@classytic/repo-core/adapter";
5
5
  //#region src/adapter/mongoose-adapter.d.ts
6
- /**
7
- * Options for creating a Mongoose adapter.
8
- *
9
- * `TDoc` is auto-inferred from the Mongoose model — no explicit type
10
- * needed in most call sites.
11
- */
12
- interface MongooseAdapterOptions<TDoc = unknown> {
13
- /** Mongoose model instance — preserves document type for type safety. */
14
- model: Model<TDoc>;
6
+ interface MongooseAdapterOptions<TDoc = unknown, TModelDoc = unknown> {
7
+ /**
8
+ * The Mongoose model. Still a real `Model`, so a Schema or a hand-built
9
+ * object literal is a COMPILE error — but its document type is INDEPENDENT of
10
+ * `TDoc`.
11
+ *
12
+ * `Model<T>` is invariant in `T`, so `Model<TDoc>` here forced every caller
13
+ * whose repository speaks a lean entity (string ids) while its schema-inferred
14
+ * model carries hydrated documents to cast: the two describe one collection
15
+ * and can never unify. The adapter reads the model for `modelName` and
16
+ * `.schema.paths` metadata only, never for document typing.
17
+ *
18
+ * Decoupling the two parameters removes a static check, so the constructor
19
+ * restores it at RUNTIME by comparing this model against the one the
20
+ * repository owns — see `assertModelMatchesRepository`. That check is
21
+ * strictly stronger: it also rejects two DIFFERENT collections that happen to
22
+ * share a document shape, which no type could distinguish.
23
+ */
24
+ model: Model<TModelDoc>;
15
25
  /**
16
26
  * Repository implementing CRUD operations.
17
27
  *
@@ -58,13 +68,19 @@ interface MongooseAdapterOptions<TDoc = unknown> {
58
68
  * Mongoose data adapter — implements the `DataAdapter<TDoc>` contract from
59
69
  * `@classytic/repo-core/adapter`.
60
70
  */
61
- declare class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {
71
+ declare class MongooseAdapter<TDoc = unknown, TModelDoc = unknown> implements DataAdapter<TDoc> {
62
72
  readonly type: "mongoose";
63
73
  readonly name: string;
64
- readonly model: Model<TDoc>;
74
+ /**
75
+ * The model as given. Its document type is NOT `TDoc` — a lean repository
76
+ * legitimately pairs with a hydrated schema-inferred model, so claiming
77
+ * `Model<TDoc>` here was an assertion the code could not honour. Read it for
78
+ * `modelName` and `.schema`; narrow it yourself if you need document typing.
79
+ */
80
+ readonly model: Model<unknown>;
65
81
  readonly repository: RepositoryLike<TDoc>;
66
82
  private readonly schemaGenerator?;
67
- constructor(options: MongooseAdapterOptions<TDoc>);
83
+ constructor(options: MongooseAdapterOptions<TDoc, TModelDoc>);
68
84
  /**
69
85
  * Lightweight path-existence check for hosts that infer absent fields
70
86
  * (e.g. arc's `defineResource()` infers `tenantField: false` when the
@@ -159,8 +175,8 @@ declare class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {
159
175
  * const adapter = createMongooseAdapter(ProductModel, productRepository);
160
176
  * ```
161
177
  */
162
- declare function createMongooseAdapter<TDoc = unknown>(model: Model<TDoc>, repository: AdapterRepositoryInput<TDoc>): DataAdapter<TDoc>;
163
- declare function createMongooseAdapter<TDoc = unknown>(options: MongooseAdapterOptions<TDoc>): DataAdapter<TDoc>;
178
+ declare function createMongooseAdapter<TDoc = unknown, TModelDoc = unknown>(model: Model<TModelDoc>, repository: AdapterRepositoryInput<TDoc>): DataAdapter<TDoc>;
179
+ declare function createMongooseAdapter<TDoc = unknown, TModelDoc = unknown>(options: MongooseAdapterOptions<TDoc, TModelDoc>): DataAdapter<TDoc>;
164
180
  //#endregion
165
181
  //#region src/adapter/types.d.ts
166
182
  /**
@@ -1,2 +1,2 @@
1
- import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-BitHg-CN.mjs";
1
+ import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-Bfqz8yHE.mjs";
2
2
  export { MongooseAdapter, createMongooseAdapter, isMongooseModel };
@@ -1,5 +1,5 @@
1
1
  import { n as warn } from "./logger-K7dPmu2U.mjs";
2
- import { t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-DKbnKduN.mjs";
2
+ import { t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-waUVQwK4.mjs";
3
3
  import { matchesRecordFilter } from "@classytic/repo-core/filter";
4
4
  import { mergeFieldRuleConstraints } from "@classytic/repo-core/schema";
5
5
  import { asRepositoryLike, isRepository } from "@classytic/repo-core/adapter";
@@ -65,18 +65,60 @@ function formatInvalidRepositoryError(repository, model) {
65
65
  ].join("\n");
66
66
  }
67
67
  /**
68
+ * Options for creating a Mongoose adapter.
69
+ *
70
+ * `TDoc` is auto-inferred from the Mongoose model — no explicit type
71
+ * needed in most call sites.
72
+ */
73
+ /**
74
+ * The model and the repository must name the SAME collection.
75
+ *
76
+ * Decoupling their document types (so a lean repository pairs with a hydrated
77
+ * schema-inferred model) removes the static cross-check that `Model<TDoc>` +
78
+ * `AdapterRepositoryInput<TDoc>` gave. This restores it where it can actually be
79
+ * decided — at construction, against `Repository.Model`, which every kit-native
80
+ * repository carries. It is strictly stronger than the type was: two different
81
+ * collections sharing a document shape were always statically indistinguishable.
82
+ *
83
+ * Silent when the repository does not expose a model (a BYO/mock repository) —
84
+ * absence of an answer is not a negative answer, and refusing there would break
85
+ * every legitimate custom repository.
86
+ */
87
+ function assertModelMatchesRepository(model, repository) {
88
+ const repoModel = repository?.Model;
89
+ const repoModelName = typeof repoModel?.modelName === "string" ? repoModel.modelName : void 0;
90
+ if (repoModelName === void 0 || repoModelName === model.modelName) return;
91
+ throw new TypeError([
92
+ `MongooseAdapter: model/repository mismatch — model is '${model.modelName}' but the repository is bound to '${repoModelName}'.`,
93
+ "",
94
+ "The adapter reads schema metadata from the MODEL (arc infers a missing",
95
+ "`tenantField` from it) and reads/writes through the REPOSITORY. Mismatched,",
96
+ "the tenant probe answers about the wrong collection and queries are either",
97
+ "filtered to zero rows or left unscoped — neither of which errors.",
98
+ "",
99
+ "Fix: pass the model that repository was constructed with."
100
+ ].join("\n"));
101
+ }
102
+ /**
68
103
  * Mongoose data adapter — implements the `DataAdapter<TDoc>` contract from
69
104
  * `@classytic/repo-core/adapter`.
70
105
  */
71
106
  var MongooseAdapter = class {
72
107
  type = "mongoose";
73
108
  name;
109
+ /**
110
+ * The model as given. Its document type is NOT `TDoc` — a lean repository
111
+ * legitimately pairs with a hydrated schema-inferred model, so claiming
112
+ * `Model<TDoc>` here was an assertion the code could not honour. Read it for
113
+ * `modelName` and `.schema`; narrow it yourself if you need document typing.
114
+ */
74
115
  model;
75
116
  repository;
76
117
  schemaGenerator;
77
118
  constructor(options) {
78
119
  if (!isMongooseModel(options.model)) throw new TypeError(formatInvalidModelError(options.model));
79
120
  if (!isRepository(options.repository)) throw new TypeError(formatInvalidRepositoryError(options.repository, options.model));
121
+ assertModelMatchesRepository(options.model, options.repository);
80
122
  this.model = options.model;
81
123
  this.repository = asRepositoryLike(options.repository);
82
124
  this.schemaGenerator = options.schemaGenerator === false ? void 0 : options.schemaGenerator ?? buildCrudSchemasFromModel;
@@ -1,5 +1,5 @@
1
1
  import { n as Repository } from "../Repository-BArvU32x.mjs";
2
- import { n as createMongooseAdapter } from "../adapter-BitHg-CN.mjs";
2
+ import { n as createMongooseAdapter } from "../adapter-Bfqz8yHE.mjs";
3
3
  import { asReadOnlyRepo } from "@classytic/repo-core/repository";
4
4
  import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@classytic/repo-core/better-auth";
5
5
  //#region src/better-auth/index.ts
package/dist/index.mjs CHANGED
@@ -15,7 +15,7 @@ import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForU
15
15
  import { buildFieldTypeMap, coerceFieldValue, coerceHeuristic } from "./query/primitives/coercion.mjs";
16
16
  import { extractSchemaIndexes } from "./query/primitives/indexes.mjs";
17
17
  import { n as createMemoryCache, t as toPlain } from "./to-plain-vksRrLtc.mjs";
18
- import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-DKbnKduN.mjs";
18
+ import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-waUVQwK4.mjs";
19
19
  import { runChunkedPurge } from "@classytic/repo-core/repository";
20
20
  import { isControlParam } from "@classytic/repo-core/query-parser";
21
21
  //#region src/actions/cascade-references.ts
@@ -474,9 +474,37 @@ function stripDefaultsDeep(node) {
474
474
  delete obj.default;
475
475
  for (const value of Object.values(obj)) stripDefaultsDeep(value);
476
476
  }
477
+ /**
478
+ * Recursively delete every `required` keyword from a JSON-schema subtree
479
+ * (properties, array items, nested subdocuments).
480
+ *
481
+ * PATCH semantics mean every field is optional at EVERY nesting depth, not
482
+ * just the root — a caller may send a partial subdocument-array item (e.g.
483
+ * `variants: [{ sku, isActive }]` to toggle a flag) without re-supplying
484
+ * every field the CREATE schema requires for that item. The one-line
485
+ * `delete clone.required` below only stripped the top-level key; a
486
+ * DocumentArray's `items` schema carries its OWN `required` (built by
487
+ * `subSchemaToJsonSchema`), which survived untouched — so a partial nested
488
+ * item still failed AJV's `required` check before the request ever reached
489
+ * the repository (real defect: PATCHing a product to add ONE variant with
490
+ * no `variationAttributes` 400'd on `variants/0` missing `attributes`, even
491
+ * though `attributes: {}` is a valid, already-persisted value). Same fix
492
+ * shape as `stripDefaultsDeep` above, for the identical reason: an UPDATE
493
+ * schema's partial-field semantics belong to the whole tree, not its root.
494
+ */
495
+ function stripRequiredDeep(node) {
496
+ if (Array.isArray(node)) {
497
+ for (const item of node) stripRequiredDeep(item);
498
+ return;
499
+ }
500
+ if (!node || typeof node !== "object") return;
501
+ const obj = node;
502
+ delete obj.required;
503
+ for (const value of Object.values(obj)) stripRequiredDeep(value);
504
+ }
477
505
  function buildJsonSchemaForUpdate(createJson, options) {
478
506
  const clone = JSON.parse(JSON.stringify(createJson));
479
- delete clone.required;
507
+ stripRequiredDeep(clone);
480
508
  const fieldsToOmit = collectFieldsToOmit(options, "update");
481
509
  applyFieldRules(clone, fieldsToOmit, options);
482
510
  stripDefaultsDeep(clone);
@@ -4,5 +4,5 @@ import { t as configureLogger } from "../logger-K7dPmu2U.mjs";
4
4
  import { t as createError } from "../error-BuByZwKU.mjs";
5
5
  import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "../field-selection-FSQFLpsU.mjs";
6
6
  import { n as createMemoryCache, t as toPlain } from "../to-plain-vksRrLtc.mjs";
7
- import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-DKbnKduN.mjs";
7
+ import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-waUVQwK4.mjs";
8
8
  export { DEFAULT_ID_CHUNK, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, collectIds, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, idChunks, isObjectIdInstance, isValidIdForType, keysetFilter, narrowToIds, selectKeysetChunk, toObjectId, toPlain };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/mongokit",
3
- "version": "3.34.0",
3
+ "version": "3.36.0",
4
4
  "description": "Production-grade MongoDB repositories - smart pagination, events, and plugins. Built on @classytic/repo-core.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -122,7 +122,7 @@
122
122
  },
123
123
  "homepage": "https://github.com/classytic/mongokit#readme",
124
124
  "peerDependencies": {
125
- "@classytic/repo-core": ">=0.23.0",
125
+ "@classytic/repo-core": ">=0.25.0",
126
126
  "mongodb-memory-server": ">=10.4.3",
127
127
  "mongoose": ">=9.4.1"
128
128
  },
@@ -174,7 +174,7 @@
174
174
  "@better-auth/mongo-adapter": "^1.6.29",
175
175
  "@biomejs/biome": "^2.5.8",
176
176
  "@classytic/dev-tools": "^0.2.0",
177
- "@classytic/repo-core": "^0.23.0",
177
+ "@classytic/repo-core": "^0.25.0",
178
178
  "@types/node": "^26.2.0",
179
179
  "@vitest/coverage-v8": "^4.1.10",
180
180
  "ajv": "^8.20.0",