@classytic/mongokit 3.11.1 → 3.12.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.
- package/README.md +2 -2
- package/dist/{PaginationEngine-CvDFfVXT.mjs → PaginationEngine-BDaAsyPn.mjs} +1 -1
- package/dist/actions/index.d.mts +1 -1
- package/dist/actions/index.mjs +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/ai/index.mjs +3 -3
- package/dist/{index-dNTmpgK8.d.mts → index-D9cORR-o.d.mts} +1 -1
- package/dist/index.d.mts +16 -11
- package/dist/index.mjs +5 -5
- package/dist/{mongooseToJsonSchema-D1sdCOmj.mjs → mongooseToJsonSchema-B187ufOP.mjs} +67 -19
- package/dist/{mongooseToJsonSchema-DXxjFkLB.d.mts → mongooseToJsonSchema-rtkdbBhf.d.mts} +9 -2
- package/dist/pagination/PaginationEngine.d.mts +2 -1
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +1 -1
- package/dist/{types-Dc2aDVUi.d.mts → types-DXscZnLu.d.mts} +49 -94
- package/dist/{update-DEJYcgfV.mjs → update-CDxPtpsk.mjs} +144 -19
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +2 -2
- package/dist/{validation-chain.plugin-Dllv-cml.mjs → validation-chain.plugin-BicIgzzt.mjs} +26 -178
- package/dist/{validation-chain.plugin-B0VSdcwa.d.mts → validation-chain.plugin-CH-xPoKM.d.mts} +17 -19
- package/package.json +8 -5
- /package/dist/{logger-DJ7egMnd.mjs → logger-8-VS1za6.mjs} +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Production-grade MongoDB repository pattern for Node.js. Zero runtime deps — M
|
|
|
9
9
|
npm install @classytic/mongokit @classytic/repo-core mongoose
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Requires Mongoose `>=9.4.1`, `@classytic/repo-core` `>=0.
|
|
12
|
+
Requires Mongoose `>=9.4.1`, `@classytic/repo-core` `>=0.3.0`, Node.js `>=22`.
|
|
13
13
|
|
|
14
14
|
> **Swap-able with sqlitekit.** Mongokit implements the `StandardRepo<TDoc>` contract from `@classytic/repo-core/repository`. Controller code written against the contract runs unchanged on [@classytic/sqlitekit](https://www.npmjs.com/package/@classytic/sqlitekit) — both kits share an identical conformance suite.
|
|
15
15
|
|
|
@@ -180,7 +180,7 @@ Sketch:
|
|
|
180
180
|
```ts
|
|
181
181
|
// host: src/outbox/wire.ts
|
|
182
182
|
import { wireOutbox, MongoOutboxStore } from './outbox-recipe.js';
|
|
183
|
-
import { RedisEventTransport } from '@classytic/arc/events';
|
|
183
|
+
import { RedisEventTransport } from '@classytic/arc/events/redis';
|
|
184
184
|
|
|
185
185
|
const store = new MongoOutboxStore({ connection: mongoose.connection, name: 'outbox' });
|
|
186
186
|
const transport = new RedisEventTransport({ url: process.env.REDIS_URL });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as warn } from "./logger-
|
|
1
|
+
import { r as warn } from "./logger-8-VS1za6.mjs";
|
|
2
2
|
import { t as createError } from "./error-B00pI7dV.mjs";
|
|
3
3
|
import { n as encodeCursor, r as resolveCursorFilter } from "./cursor-J84rR6wG.mjs";
|
|
4
4
|
import { r as validateKeysetSort, t as getPrimaryField } from "./sort-CrZjTK25.mjs";
|
package/dist/actions/index.d.mts
CHANGED
|
@@ -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-
|
|
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-D9cORR-o.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 };
|
package/dist/actions/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "../chunk-CfYAbeIz.mjs";
|
|
2
|
-
import { g as create_exports, i as update_exports, p as delete_exports, u as read_exports, v as aggregate_exports } from "../update-
|
|
2
|
+
import { g as create_exports, i as update_exports, p as delete_exports, u as read_exports, v as aggregate_exports } from "../update-CDxPtpsk.mjs";
|
|
3
3
|
//#region src/actions/index.ts
|
|
4
4
|
var actions_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
aggregate: () => aggregate_exports,
|
package/dist/ai/index.d.mts
CHANGED
package/dist/ai/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as warn } from "../logger-
|
|
1
|
+
import { r as warn } from "../logger-8-VS1za6.mjs";
|
|
2
2
|
//#region src/ai/url-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* URL policy helpers for the vector plugin media pipeline.
|
|
@@ -45,7 +45,7 @@ function matchesOriginEntry(origin, entry, url) {
|
|
|
45
45
|
if (wildcardMatch) {
|
|
46
46
|
const [, entryProtocol, suffix] = wildcardMatch;
|
|
47
47
|
if (url.protocol.toLowerCase() !== entryProtocol.toLowerCase()) return false;
|
|
48
|
-
return url.hostname.toLowerCase().endsWith(
|
|
48
|
+
return url.hostname.toLowerCase().endsWith(`.${suffix.toLowerCase()}`);
|
|
49
49
|
}
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
@@ -137,7 +137,7 @@ async function withVectorErrorHints(op, ctx) {
|
|
|
137
137
|
} catch (err) {
|
|
138
138
|
const hint = classifyVectorSearchError(err, ctx);
|
|
139
139
|
if (hint.code === "UNKNOWN") throw err;
|
|
140
|
-
const message = `[mongokit:vector] ${hint.summary}\n
|
|
140
|
+
const message = `[mongokit:vector] ${hint.summary}\n${hint.hints.map((h) => ` • ${h}`).join("\n")}`;
|
|
141
141
|
const wrapped = new Error(message, { cause: err });
|
|
142
142
|
wrapped.code = hint.code;
|
|
143
143
|
throw wrapped;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as FindOneAndUpdateOptions, Dt as LookupOptions, L as MinMaxResult, O as GroupResult, V as OperationOptions, X as ReadPreferenceType, ft as UpdateManyResult, g as CreateOptions, ht as UpdateWithValidationResult, i as AnyDocument, lt as SortSpec, pt as UpdateOptions, q as PopulateSpec, rt as SelectSpec, x as DeleteResult, z as ObjectId } from "./types-DXscZnLu.mjs";
|
|
2
2
|
import { Model, PipelineStage } from "mongoose";
|
|
3
3
|
|
|
4
4
|
//#region src/actions/aggregate.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { t as index_d_exports } from "./index-
|
|
1
|
+
import { $ as RepositoryInstance, A as InferDocument, B as OffsetPaginationOptions, C as EventHandlers, Ct as IControllerResponse, D as FindOneAndUpdateOptions, Dt as LookupOptions, E as FieldPreset, Et as LookupBuilder, F as LookupPopulateOptions, G as PluginFunction, H as PaginationConfig, I as LookupPopulateResult, J as PrioritizedHook, K as PluginType, L as MinMaxResult, M as KeysOfType, N as KeysetPaginationOptions, O as GroupResult, P as Logger, Q as RepositoryEvent, R as NonNullableFields, S as DocField, St as IController, T as EventPhase, Tt as IResponseFormatter, U as PartialBy, V as OperationOptions, W as Plugin, X as ReadPreferenceType, Y as ReadOptions, Z as RepositoryContext, _ as CursorPayload, _t as ValidationChainOptions, a as AnyModel, at as SoftDeleteFilterMode, b as DeleteManyResult, bt as WithPlugins, c as CacheOperationOptions, ct as SortDirection, d as CacheableOptions, dt as UpdateInput, et as RepositoryOperation, f as CascadeOptions, ft as UpdateManyResult, g as CreateOptions, gt as UserContext, h as CreateInput, ht as UpdateWithValidationResult, i as AnyDocument, it as SessionOptions, j as InferRawDoc, k as HookMode, l as CacheOptions, lt as SortSpec, m as CollationOptions, mt as UpdatePatch, n as AggregatePaginationOptions, nt as RequiredBy, o as BasePaginationOptions, ot as SoftDeleteOptions, p as CascadeRelation, pt as UpdateOptions, q as PopulateSpec, r as AllPluginMethods, rt as SelectSpec, s as CacheAdapter, st as SoftDeleteRepository, t as AggregateOptions, tt as RepositoryOptions, u as CacheStats, ut as Strict, v as DecodedCursor, vt as ValidatorDefinition, w as EventPayload, wt as IRequestContext, x as DeleteResult, xt as WithTransactionOptions, y as DeepPartial, yt as ValueType, z as ObjectId } from "./types-DXscZnLu.mjs";
|
|
2
|
+
import { t as index_d_exports } from "./index-D9cORR-o.mjs";
|
|
3
3
|
import { PaginationEngine } from "./pagination/PaginationEngine.mjs";
|
|
4
|
-
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, T as ElasticSearchOptions, U as AuditEntry, V as BulkWriteResult, 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-
|
|
4
|
+
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, T as ElasticSearchOptions, U as AuditEntry, V as BulkWriteResult, 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-CH-xPoKM.mjs";
|
|
5
5
|
import { n as SchemaPathsLike, t as FieldType$1 } from "./coercion-oM8G4b6m.mjs";
|
|
6
6
|
import { n as SchemaIndexes } from "./indexes-C26GNZRU.mjs";
|
|
7
|
-
import { a as createFieldPreset, c as getMongooseProjection, d as parseDuplicateKeyError, i as configureLogger, l as createError, n as buildCrudSchemasFromMongooseSchema, o as filterResponseData, r as createMemoryCache, s as getFieldsForUser, t as buildCrudSchemasFromModel, u as isDuplicateKeyError } from "./mongooseToJsonSchema-
|
|
7
|
+
import { a as createFieldPreset, c as getMongooseProjection, d as parseDuplicateKeyError, i as configureLogger, l as createError, n as buildCrudSchemasFromMongooseSchema, o as filterResponseData, r as createMemoryCache, s as getFieldsForUser, t as buildCrudSchemasFromModel, u as isDuplicateKeyError } from "./mongooseToJsonSchema-rtkdbBhf.mjs";
|
|
8
8
|
import * as _$mongoose from "mongoose";
|
|
9
9
|
import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
|
|
10
10
|
import { AggPaginationRequest, AggRequest, AggResult, RepositoryBase } from "@classytic/repo-core/repository";
|
|
11
11
|
import { UpdateInput as UpdateInput$1 } from "@classytic/repo-core/update";
|
|
12
|
-
import { OffsetPaginationResultCore } from "@classytic/repo-core/pagination";
|
|
12
|
+
import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult, OffsetPaginationResultCore } from "@classytic/repo-core/pagination";
|
|
13
|
+
import { HttpError } from "@classytic/repo-core/errors";
|
|
13
14
|
|
|
14
15
|
//#region src/operations.d.ts
|
|
15
16
|
/**
|
|
@@ -1210,12 +1211,16 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1210
1211
|
* Merge policy-hook-injected filters (multi-tenant scope,
|
|
1211
1212
|
* soft-delete) into the pre-aggregate `filter` slot of the request.
|
|
1212
1213
|
* Plugins speak mongo-query shape (`context.query` / `context.filters`);
|
|
1213
|
-
*
|
|
1214
|
+
* the caller's `req.filter` may be either Filter IR or a mongo query.
|
|
1214
1215
|
*
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1216
|
+
* Each part is compiled through `compileFilterToMongo` BEFORE the
|
|
1217
|
+
* `$and` merge — otherwise an IR node like `eq('status', 'paid')`
|
|
1218
|
+
* (`{ op, field, value }`) gets nested inside `$and` as a literal,
|
|
1219
|
+
* `compileFilterToMongo` of the merged value finds no top-level `op`
|
|
1220
|
+
* (so `isFilter` is false), and Mongo receives the IR object as a
|
|
1221
|
+
* raw query clause that matches no real document. Symptom: aggregates
|
|
1222
|
+
* silently return 0 rows whenever a callerpasses IR + a policy
|
|
1223
|
+
* plugin is installed.
|
|
1219
1224
|
*/
|
|
1220
1225
|
private _injectPolicyScopeIntoAgg;
|
|
1221
1226
|
/**
|
|
@@ -1472,4 +1477,4 @@ declare function isTransactionUnsupported(error: Error): boolean;
|
|
|
1472
1477
|
*/
|
|
1473
1478
|
declare function createRepository<TDoc>(Model: _$mongoose.Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions): Repository<TDoc>;
|
|
1474
1479
|
//#endregion
|
|
1475
|
-
export { ALL_OPERATIONS, type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions,
|
|
1480
|
+
export { ALL_OPERATIONS, type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BasePaginationOptions, type BatchOperationsMethods, type BulkWriteResult, type CacheAdapter, type CacheMethods, type CacheOperationOptions, type CacheOptions, type CacheStats, type CacheableOptions, type CascadeOptions, type CascadeRelation, type CollationOptions, type CreateInput, type CreateOptions, type CursorPayload, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type DocField, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldType, type FilterQuery, type FindOneAndUpdateOptions, type GroupResult, HOOK_PRIORITY, type HookMode, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type KeysOfType, type KeysetPaginationOptions, type Logger, LookupBuilder, type LookupOptions, type LookupPopulateOptions, type LookupPopulateResult, MUTATING_OPERATIONS, type MinMaxResult, type MongoOperationsMethods, type MultiTenantOptions, type NonNullableFields, OP_REGISTRY, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OperationDescriptor, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PolicyKey, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, type PrioritizedHook, QueryParser, type QueryParserOptions, READ_OPERATIONS, type ReadOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaLike, type SearchMode, type SelectSpec, type SequentialIdOptions, type SessionOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type TenantContext, type TenantStore, type UpdateInput, type UpdateManyResult, type UpdateOptions, type UpdatePatch, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidatorDefinition, type ValueType, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getMongooseProjection, getNextSequence, immutableField, isDuplicateKeyError, isTransactionUnsupported, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin, withTransaction };
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { b as LookupBuilder } from "./update-CDxPtpsk.mjs";
|
|
2
|
+
import { r as warn, t as configureLogger } from "./logger-8-VS1za6.mjs";
|
|
3
3
|
import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-B00pI7dV.mjs";
|
|
4
4
|
import { t as actions_exports } from "./actions/index.mjs";
|
|
5
|
-
import { A as AuditTrailQuery, C as cachePlugin, D as withTransaction, E as isTransactionUnsupported, F as MUTATING_OPERATIONS, I as OP_REGISTRY, L as READ_OPERATIONS, M as auditLogPlugin, N as aggregateHelpersPlugin, O as AggregationBuilder, P as ALL_OPERATIONS, R as operationsByPolicyKey, S as cascadePlugin, T as Repository, _ 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, w as HOOK_PRIORITY, x as sequentialId, y as getNextSequence } from "./validation-chain.plugin-
|
|
6
|
-
import { t as PaginationEngine } from "./PaginationEngine-
|
|
5
|
+
import { A as AuditTrailQuery, C as cachePlugin, D as withTransaction, E as isTransactionUnsupported, F as MUTATING_OPERATIONS, I as OP_REGISTRY, L as READ_OPERATIONS, M as auditLogPlugin, N as aggregateHelpersPlugin, O as AggregationBuilder, P as ALL_OPERATIONS, R as operationsByPolicyKey, S as cascadePlugin, T as Repository, _ 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, w as HOOK_PRIORITY, x as sequentialId, y as getNextSequence } from "./validation-chain.plugin-BicIgzzt.mjs";
|
|
6
|
+
import { t as PaginationEngine } from "./PaginationEngine-BDaAsyPn.mjs";
|
|
7
7
|
import { isGeoOperator, parseGeoFilter } from "./query/primitives/geo.mjs";
|
|
8
8
|
import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-dI0m4RBs.mjs";
|
|
9
9
|
import { buildFieldTypeMap, coerceFieldValue, coerceHeuristic } from "./query/primitives/coercion.mjs";
|
|
10
10
|
import { extractSchemaIndexes } from "./query/primitives/indexes.mjs";
|
|
11
|
-
import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-
|
|
11
|
+
import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-B187ufOP.mjs";
|
|
12
12
|
//#region src/query/QueryParser.ts
|
|
13
13
|
/**
|
|
14
14
|
* Top-level query parameters that mongokit handles outside the filter
|
|
@@ -110,9 +110,11 @@ function isObjectIdType(t) {
|
|
|
110
110
|
*/
|
|
111
111
|
function buildCrudSchemasFromMongooseSchema(mongooseSchema, options = {}) {
|
|
112
112
|
const jsonCreate = buildJsonSchemaFromPaths(mongooseSchema, options);
|
|
113
|
+
const jsonUpdate = buildJsonSchemaForUpdate(jsonCreate, options);
|
|
114
|
+
const jsonResponse = buildJsonSchemaForResponse(mongooseSchema, options);
|
|
113
115
|
return {
|
|
114
116
|
createBody: jsonCreate,
|
|
115
|
-
updateBody:
|
|
117
|
+
updateBody: jsonUpdate,
|
|
116
118
|
params: {
|
|
117
119
|
type: "object",
|
|
118
120
|
properties: { id: {
|
|
@@ -121,11 +123,18 @@ function buildCrudSchemasFromMongooseSchema(mongooseSchema, options = {}) {
|
|
|
121
123
|
} },
|
|
122
124
|
required: ["id"]
|
|
123
125
|
},
|
|
124
|
-
listQuery: buildJsonSchemaForQuery(mongooseSchema?.obj || {}, options)
|
|
126
|
+
listQuery: buildJsonSchemaForQuery(mongooseSchema?.obj || {}, options),
|
|
127
|
+
response: jsonResponse
|
|
125
128
|
};
|
|
126
129
|
}
|
|
127
130
|
/**
|
|
128
|
-
* Build CRUD schemas from Mongoose model
|
|
131
|
+
* Build CRUD schemas from Mongoose model.
|
|
132
|
+
*
|
|
133
|
+
* Satisfies the canonical `SchemaGenerator<Model<unknown>>` contract from
|
|
134
|
+
* `@classytic/repo-core/schema`, so arc's `MongooseAdapter.schemaGenerator`
|
|
135
|
+
* field accepts this function directly with no glue. Future Mongo-shaped
|
|
136
|
+
* kits implementing the same contract are interchangeable at the adapter
|
|
137
|
+
* boundary.
|
|
129
138
|
*/
|
|
130
139
|
function buildCrudSchemasFromModel(mongooseModel, options = {}) {
|
|
131
140
|
if (!mongooseModel?.schema) throw new Error("Invalid mongoose model");
|
|
@@ -249,10 +258,7 @@ function introspectArrayItems(schemaType, options = {}) {
|
|
|
249
258
|
const declaredType = schemaType?.options?.type;
|
|
250
259
|
if (Array.isArray(declaredType) && declaredType.length > 0) {
|
|
251
260
|
const inner = declaredType[0];
|
|
252
|
-
if (inner === mongoose.Schema.Types.Mixed) return {
|
|
253
|
-
type: "object",
|
|
254
|
-
additionalProperties: true
|
|
255
|
-
};
|
|
261
|
+
if (inner === mongoose.Schema.Types.Mixed) return { additionalProperties: true };
|
|
256
262
|
return jsonTypeFor(inner, options, /* @__PURE__ */ new WeakSet());
|
|
257
263
|
}
|
|
258
264
|
return {
|
|
@@ -328,6 +334,7 @@ function schemaTypeToJsonSchema(schemaType, builderOptions = {}) {
|
|
|
328
334
|
if (ofDef !== void 0 && ofDef !== null) result.additionalProperties = jsonTypeFor(ofDef, {}, /* @__PURE__ */ new WeakSet());
|
|
329
335
|
else result.additionalProperties = true;
|
|
330
336
|
} else if (hasInnerSchema(schemaType)) return subSchemaToJsonSchema(schemaType.schema, builderOptions);
|
|
337
|
+
else if (instance === "Mixed") result.additionalProperties = true;
|
|
331
338
|
else {
|
|
332
339
|
result.type = "object";
|
|
333
340
|
result.additionalProperties = true;
|
|
@@ -335,6 +342,7 @@ function schemaTypeToJsonSchema(schemaType, builderOptions = {}) {
|
|
|
335
342
|
if (options.default === null && typeof result.type === "string") {
|
|
336
343
|
result.type = [result.type, "null"];
|
|
337
344
|
result.default = null;
|
|
345
|
+
if (Array.isArray(result.enum) && !result.enum.includes(null)) result.enum = [...result.enum, null];
|
|
338
346
|
}
|
|
339
347
|
if (typeof options.description === "string" && options.description.length > 0) result.description = options.description;
|
|
340
348
|
if (typeof options.title === "string" && options.title.length > 0) result.title = options.title;
|
|
@@ -348,10 +356,7 @@ function jsonTypeFor(def, options, seen) {
|
|
|
348
356
|
if (Array.isArray(def)) {
|
|
349
357
|
if (def[0] === mongoose.Schema.Types.Mixed) return {
|
|
350
358
|
type: "array",
|
|
351
|
-
items: {
|
|
352
|
-
type: "object",
|
|
353
|
-
additionalProperties: true
|
|
354
|
-
}
|
|
359
|
+
items: { additionalProperties: true }
|
|
355
360
|
};
|
|
356
361
|
return {
|
|
357
362
|
type: "array",
|
|
@@ -368,10 +373,7 @@ function jsonTypeFor(def, options, seen) {
|
|
|
368
373
|
const inner = typedDef.type[0] !== void 0 ? typedDef.type[0] : String;
|
|
369
374
|
if (inner === mongoose.Schema.Types.Mixed) return {
|
|
370
375
|
type: "array",
|
|
371
|
-
items: {
|
|
372
|
-
type: "object",
|
|
373
|
-
additionalProperties: true
|
|
374
|
-
}
|
|
376
|
+
items: { additionalProperties: true }
|
|
375
377
|
};
|
|
376
378
|
return {
|
|
377
379
|
type: "array",
|
|
@@ -407,10 +409,7 @@ function jsonTypeFor(def, options, seen) {
|
|
|
407
409
|
type: "object",
|
|
408
410
|
additionalProperties: jsonTypeFor(typedDef.of || String, options, seen)
|
|
409
411
|
};
|
|
410
|
-
if (typedDef.type === mongoose.Schema.Types.Mixed) return {
|
|
411
|
-
type: "object",
|
|
412
|
-
additionalProperties: true
|
|
413
|
-
};
|
|
412
|
+
if (typedDef.type === mongoose.Schema.Types.Mixed) return { additionalProperties: true };
|
|
414
413
|
if (typedDef.type === Object) {
|
|
415
414
|
if (isPlainObject(typedDef) && Object.keys(typedDef).some((k) => k !== "type")) {
|
|
416
415
|
const nested = {};
|
|
@@ -514,6 +513,55 @@ function buildJsonSchemaForUpdate(createJson, options) {
|
|
|
514
513
|
if (options?.update?.requireAtLeastOne === true) clone.minProperties = 1;
|
|
515
514
|
return clone;
|
|
516
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Build the response-shape JSON Schema.
|
|
518
|
+
*
|
|
519
|
+
* Walks every schema path INCLUDING server-set fields (`createdAt`,
|
|
520
|
+
* `updatedAt`, `_id`, immutable / readonly fields) because those ARE
|
|
521
|
+
* returned to clients. Only `fieldRules[field].hidden: true` fields are
|
|
522
|
+
* stripped — passwords, secrets, internal scoring.
|
|
523
|
+
*
|
|
524
|
+
* `additionalProperties: true` so virtuals, computed fields, and any
|
|
525
|
+
* non-schema-declared keys flow through. Strict response validation is
|
|
526
|
+
* opt-in via `options.strictAdditionalProperties` (rare; most hosts want
|
|
527
|
+
* lean responses but don't want to break virtuals).
|
|
528
|
+
*
|
|
529
|
+
* No `required` array — response schemas are *documentation* of what
|
|
530
|
+
* MAY be returned. Marking fields required would force every endpoint
|
|
531
|
+
* that occasionally returns sparse data (lookup-by-id with selected
|
|
532
|
+
* fields, partial projections) to fail validation.
|
|
533
|
+
*/
|
|
534
|
+
function buildJsonSchemaForResponse(mongooseSchema, options) {
|
|
535
|
+
const fullSchema = buildJsonSchemaFromPaths(mongooseSchema, options);
|
|
536
|
+
const responseSchema = buildJsonSchemaFromPaths(mongooseSchema, {
|
|
537
|
+
...options,
|
|
538
|
+
fieldRules: stripCreateOnlyFieldRulesForResponse(options.fieldRules),
|
|
539
|
+
create: void 0
|
|
540
|
+
});
|
|
541
|
+
applyFieldRules(responseSchema, collectFieldsToOmit(options, "response"), options);
|
|
542
|
+
responseSchema.required = void 0;
|
|
543
|
+
if (options?.strictAdditionalProperties !== true) responseSchema.additionalProperties = true;
|
|
544
|
+
if (fullSchema.title && !responseSchema.title) responseSchema.title = fullSchema.title;
|
|
545
|
+
return responseSchema;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Strip request-side-only field-rule flags (`systemManaged`, `immutable`,
|
|
549
|
+
* `immutableAfterCreate`, `optional`) from a fieldRules map so the
|
|
550
|
+
* response builder doesn't accidentally hide server-set fields.
|
|
551
|
+
*
|
|
552
|
+
* Keeps `hidden` (response-relevant), `nullable`, `description`,
|
|
553
|
+
* constraint flags (`minLength`/`maxLength`/`min`/`max`/`pattern`/`enum`)
|
|
554
|
+
* — they all describe response shape, not request acceptance.
|
|
555
|
+
*/
|
|
556
|
+
function stripCreateOnlyFieldRulesForResponse(rules) {
|
|
557
|
+
if (!rules) return rules;
|
|
558
|
+
const result = {};
|
|
559
|
+
for (const [field, rule] of Object.entries(rules)) {
|
|
560
|
+
const { systemManaged: _sm, immutable: _i, immutableAfterCreate: _iac, optional: _o, ...rest } = rule;
|
|
561
|
+
result[field] = rest;
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
517
565
|
function buildJsonSchemaForQuery(_tree, options) {
|
|
518
566
|
const basePagination = {
|
|
519
567
|
type: "object",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as FieldPreset, gt as UserContext, s as CacheAdapter } from "./types-DXscZnLu.mjs";
|
|
2
2
|
import mongoose, { Schema } from "mongoose";
|
|
3
3
|
import { CrudSchemas, SchemaBuilderOptions } from "@classytic/repo-core/schema";
|
|
4
|
+
import { HttpError } from "@classytic/repo-core/errors";
|
|
4
5
|
|
|
5
6
|
//#region src/utils/error.d.ts
|
|
6
7
|
/** Optional structured fields for createError. */
|
|
@@ -205,7 +206,13 @@ type CrudSchemasFramework = CrudSchemas & Record<string, unknown>;
|
|
|
205
206
|
*/
|
|
206
207
|
declare function buildCrudSchemasFromMongooseSchema(mongooseSchema: Schema, options?: SchemaBuilderOptions): CrudSchemasFramework;
|
|
207
208
|
/**
|
|
208
|
-
* Build CRUD schemas from Mongoose model
|
|
209
|
+
* Build CRUD schemas from Mongoose model.
|
|
210
|
+
*
|
|
211
|
+
* Satisfies the canonical `SchemaGenerator<Model<unknown>>` contract from
|
|
212
|
+
* `@classytic/repo-core/schema`, so arc's `MongooseAdapter.schemaGenerator`
|
|
213
|
+
* field accepts this function directly with no glue. Future Mongo-shaped
|
|
214
|
+
* kits implementing the same contract are interchangeable at the adapter
|
|
215
|
+
* boundary.
|
|
209
216
|
*/
|
|
210
217
|
declare function buildCrudSchemasFromModel(mongooseModel: mongoose.Model<unknown>, options?: SchemaBuilderOptions): CrudSchemasFramework;
|
|
211
218
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as OffsetPaginationOptions, H as PaginationConfig, N as KeysetPaginationOptions, i as AnyDocument, n as AggregatePaginationOptions } from "../types-DXscZnLu.mjs";
|
|
2
2
|
import { Model } from "mongoose";
|
|
3
|
+
import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
3
4
|
|
|
4
5
|
//#region src/pagination/PaginationEngine.d.ts
|
|
5
6
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as PaginationEngine } from "../PaginationEngine-
|
|
1
|
+
import { t as PaginationEngine } from "../PaginationEngine-BDaAsyPn.mjs";
|
|
2
2
|
export { PaginationEngine };
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -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-
|
|
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-CH-xPoKM.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 };
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -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-
|
|
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-BicIgzzt.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,9 +1,12 @@
|
|
|
1
|
+
import * as _$_classytic_repo_core_filter0 from "@classytic/repo-core/filter";
|
|
2
|
+
import { Filter } from "@classytic/repo-core/filter";
|
|
1
3
|
import * as _$mongoose from "mongoose";
|
|
2
4
|
import { Document, Model, PipelineStage, PopulateOptions, Types } from "mongoose";
|
|
3
|
-
import * as _$_classytic_repo_core_filter0 from "@classytic/repo-core/filter";
|
|
4
5
|
import * as _$_classytic_repo_core_repository0 from "@classytic/repo-core/repository";
|
|
5
6
|
import { DeleteManyResult, DeleteResult, LookupPopulateResult, UpdateManyResult } from "@classytic/repo-core/repository";
|
|
6
7
|
import { ValidationResult } from "@classytic/repo-core/schema";
|
|
8
|
+
import { AnyPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
9
|
+
import { HttpError } from "@classytic/repo-core/errors";
|
|
7
10
|
|
|
8
11
|
//#region src/query/LookupBuilder.d.ts
|
|
9
12
|
interface LookupOptions {
|
|
@@ -17,8 +20,15 @@ interface LookupOptions {
|
|
|
17
20
|
as?: string;
|
|
18
21
|
/** Whether to unwrap array to single object */
|
|
19
22
|
single?: boolean;
|
|
20
|
-
/**
|
|
21
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Field selection on the joined collection (shorthand for `$project`).
|
|
25
|
+
* Three accepted shapes — all map to a single `$project` stage:
|
|
26
|
+
* - `string` — CSV form (`'name,price'`); leading `-` marks an exclusion.
|
|
27
|
+
* - `readonly string[]` — array form (`['name', 'price']` / `['-status']`);
|
|
28
|
+
* mirrors `repo-core/lookup` `LookupSpec.select` for cross-kit parity.
|
|
29
|
+
* - `Record<string, 0 | 1>` — explicit Mongo projection map.
|
|
30
|
+
*/
|
|
31
|
+
select?: string | readonly string[] | Record<string, 0 | 1>;
|
|
22
32
|
/** Additional pipeline to run on the joined collection */
|
|
23
33
|
pipeline?: PipelineStage[];
|
|
24
34
|
/** Optional let variables for pipeline */
|
|
@@ -31,6 +41,18 @@ interface LookupOptions {
|
|
|
31
41
|
};
|
|
32
42
|
/** Sanitize pipeline stages (default: true). Set false only for trusted server-side pipelines */
|
|
33
43
|
sanitize?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Joined-side filter, compiled into a `$match` stage inside the lookup
|
|
46
|
+
* pipeline AFTER the auto-generated join correlation. Mirrors
|
|
47
|
+
* `repo-core/lookup/types.ts#LookupSpec.where` and sqlitekit's
|
|
48
|
+
* predicate-on-the-JOIN behavior, so cross-kit code that narrows the
|
|
49
|
+
* foreign side (`where: eq('status', 'active')`) gets identical rows
|
|
50
|
+
* from both backends.
|
|
51
|
+
*
|
|
52
|
+
* Accepts Filter IR or a plain mongo query — `compileFilterToMongo`
|
|
53
|
+
* routes each correctly.
|
|
54
|
+
*/
|
|
55
|
+
where?: Filter | Record<string, unknown>;
|
|
34
56
|
}
|
|
35
57
|
/**
|
|
36
58
|
* Fluent builder for MongoDB $lookup aggregation stage
|
|
@@ -307,7 +329,7 @@ interface IController<TDoc> {
|
|
|
307
329
|
* }
|
|
308
330
|
* ```
|
|
309
331
|
*/
|
|
310
|
-
list(context: IRequestContext): Promise<IControllerResponse<
|
|
332
|
+
list(context: IRequestContext): Promise<IControllerResponse<AnyPaginationResult<TDoc>>>;
|
|
311
333
|
/**
|
|
312
334
|
* Get single resource by ID
|
|
313
335
|
*
|
|
@@ -457,7 +479,7 @@ interface IResponseFormatter {
|
|
|
457
479
|
* Format paginated response
|
|
458
480
|
* @param result - Pagination result from Repository.getAll()
|
|
459
481
|
*/
|
|
460
|
-
paginated<T>(result:
|
|
482
|
+
paginated<T>(result: AnyPaginationResult<T>): IControllerResponse<AnyPaginationResult<T>>;
|
|
461
483
|
}
|
|
462
484
|
//#endregion
|
|
463
485
|
//#region src/types.d.ts
|
|
@@ -732,63 +754,6 @@ interface AggregatePaginationOptions {
|
|
|
732
754
|
/** Read preference for replica sets (e.g. 'secondaryPreferred') */
|
|
733
755
|
readPreference?: ReadPreferenceType;
|
|
734
756
|
}
|
|
735
|
-
/** Offset pagination result */
|
|
736
|
-
interface OffsetPaginationResult<T = unknown> {
|
|
737
|
-
/** Pagination method used */
|
|
738
|
-
method: 'offset';
|
|
739
|
-
/** Array of documents */
|
|
740
|
-
docs: T[];
|
|
741
|
-
/** Current page number */
|
|
742
|
-
page: number;
|
|
743
|
-
/** Documents per page */
|
|
744
|
-
limit: number;
|
|
745
|
-
/** Total document count */
|
|
746
|
-
total: number;
|
|
747
|
-
/** Total page count */
|
|
748
|
-
pages: number;
|
|
749
|
-
/** Whether next page exists */
|
|
750
|
-
hasNext: boolean;
|
|
751
|
-
/** Whether previous page exists */
|
|
752
|
-
hasPrev: boolean;
|
|
753
|
-
/** Performance warning for deep pagination */
|
|
754
|
-
warning?: string;
|
|
755
|
-
}
|
|
756
|
-
/** Keyset pagination result */
|
|
757
|
-
interface KeysetPaginationResult<T = unknown> {
|
|
758
|
-
/** Pagination method used */
|
|
759
|
-
method: 'keyset';
|
|
760
|
-
/** Array of documents */
|
|
761
|
-
docs: T[];
|
|
762
|
-
/** Documents per page */
|
|
763
|
-
limit: number;
|
|
764
|
-
/** Whether more documents exist */
|
|
765
|
-
hasMore: boolean;
|
|
766
|
-
/** Cursor token for next page */
|
|
767
|
-
next: string | null;
|
|
768
|
-
}
|
|
769
|
-
/** Aggregate pagination result */
|
|
770
|
-
interface AggregatePaginationResult<T = unknown> {
|
|
771
|
-
/** Pagination method used */
|
|
772
|
-
method: 'aggregate';
|
|
773
|
-
/** Array of documents */
|
|
774
|
-
docs: T[];
|
|
775
|
-
/** Current page number */
|
|
776
|
-
page: number;
|
|
777
|
-
/** Documents per page */
|
|
778
|
-
limit: number;
|
|
779
|
-
/** Total document count */
|
|
780
|
-
total: number;
|
|
781
|
-
/** Total page count */
|
|
782
|
-
pages: number;
|
|
783
|
-
/** Whether next page exists */
|
|
784
|
-
hasNext: boolean;
|
|
785
|
-
/** Whether previous page exists */
|
|
786
|
-
hasPrev: boolean;
|
|
787
|
-
/** Performance warning for deep pagination */
|
|
788
|
-
warning?: string;
|
|
789
|
-
}
|
|
790
|
-
/** Union type for all pagination results */
|
|
791
|
-
type PaginationResult<T = unknown> = OffsetPaginationResult<T> | KeysetPaginationResult<T> | AggregatePaginationResult<T>;
|
|
792
757
|
/** Session-only options — shared base for lightweight operations */
|
|
793
758
|
interface SessionOptions {
|
|
794
759
|
/** MongoDB session for transactions */
|
|
@@ -1136,6 +1101,27 @@ interface FieldPreset {
|
|
|
1136
1101
|
/** Additional fields for admins */
|
|
1137
1102
|
admin?: string[];
|
|
1138
1103
|
}
|
|
1104
|
+
/** Value type identifier for cursor serialization */
|
|
1105
|
+
type ValueType = 'date' | 'objectid' | 'boolean' | 'number' | 'string' | 'null' | 'unknown';
|
|
1106
|
+
/** Cursor payload */
|
|
1107
|
+
interface CursorPayload {
|
|
1108
|
+
/** Primary sort field value (legacy single-field) */
|
|
1109
|
+
v: string | number | boolean | null;
|
|
1110
|
+
/** Value type identifier (legacy single-field) */
|
|
1111
|
+
t: ValueType;
|
|
1112
|
+
/** Document ID */
|
|
1113
|
+
id: string;
|
|
1114
|
+
/** ID type identifier */
|
|
1115
|
+
idType: ValueType;
|
|
1116
|
+
/** Sort specification */
|
|
1117
|
+
sort: SortSpec;
|
|
1118
|
+
/** Cursor version */
|
|
1119
|
+
ver: number;
|
|
1120
|
+
/** Compound sort field values (multi-field keyset) */
|
|
1121
|
+
vals?: Record<string, string | number | boolean | null>;
|
|
1122
|
+
/** Compound sort value types */
|
|
1123
|
+
types?: Record<string, ValueType>;
|
|
1124
|
+
}
|
|
1139
1125
|
/** Decoded cursor */
|
|
1140
1126
|
interface DecodedCursor {
|
|
1141
1127
|
/** Primary sort field value (rehydrated) — legacy compat */
|
|
@@ -1349,37 +1335,6 @@ interface CascadeOptions {
|
|
|
1349
1335
|
/** Logger for cascade operations */
|
|
1350
1336
|
logger?: Logger;
|
|
1351
1337
|
}
|
|
1352
|
-
/** HTTP Error with status code */
|
|
1353
|
-
interface HttpError extends Error {
|
|
1354
|
-
status: number;
|
|
1355
|
-
/**
|
|
1356
|
-
* Stable machine-readable error code. Hosts can switch on this in catch
|
|
1357
|
-
* blocks instead of grepping the message string. Currently emitted for:
|
|
1358
|
-
* - `SEARCH_NOT_CONFIGURED` — search requested on a repo with no text
|
|
1359
|
-
* index AND no `searchMode: 'regex' | 'auto'` + `searchFields` config.
|
|
1360
|
-
*/
|
|
1361
|
-
code?: string;
|
|
1362
|
-
/**
|
|
1363
|
-
* Free-form structured metadata for diagnostics. Pairs with `code` so
|
|
1364
|
-
* hosts can render a clearer message in their own UI without parsing
|
|
1365
|
-
* the human-readable `message` string. Safe for logs.
|
|
1366
|
-
*/
|
|
1367
|
-
meta?: Record<string, unknown>;
|
|
1368
|
-
validationErrors?: Array<{
|
|
1369
|
-
validator: string;
|
|
1370
|
-
error: string;
|
|
1371
|
-
}>;
|
|
1372
|
-
/**
|
|
1373
|
-
* Structured metadata for duplicate-key (E11000) errors. Safe to surface in
|
|
1374
|
-
* logs/audit. Includes the offending field names and a boolean-only mirror
|
|
1375
|
-
* of whether each field had a duplicate value — never the value itself
|
|
1376
|
-
* unless `parseDuplicateKeyError` was called with `{ exposeValues: true }`.
|
|
1377
|
-
*/
|
|
1378
|
-
duplicate?: {
|
|
1379
|
-
fields: string[]; /** Only populated when parseDuplicateKeyError was called with exposeValues:true. */
|
|
1380
|
-
values?: Record<string, unknown>;
|
|
1381
|
-
};
|
|
1382
|
-
}
|
|
1383
1338
|
/**
|
|
1384
1339
|
* Combines all plugin method types into a single type
|
|
1385
1340
|
* Useful when you're using all plugins and want full type safety
|
|
@@ -1510,4 +1465,4 @@ type AllPluginMethods<TDoc> = {
|
|
|
1510
1465
|
*/
|
|
1511
1466
|
type WithPlugins<TDoc, TRepo extends RepositoryInstance = RepositoryInstance> = TRepo & AllPluginMethods<TDoc>;
|
|
1512
1467
|
//#endregion
|
|
1513
|
-
export {
|
|
1468
|
+
export { RepositoryInstance as $, InferDocument as A, OffsetPaginationOptions as B, EventHandlers as C, IControllerResponse as Ct, FindOneAndUpdateOptions as D, LookupOptions as Dt, FieldPreset as E, LookupBuilder as Et, LookupPopulateOptions as F, PluginFunction as G, PaginationConfig as H, LookupPopulateResult as I, PrioritizedHook as J, PluginType as K, MinMaxResult as L, KeysOfType as M, KeysetPaginationOptions as N, GroupResult as O, Logger as P, RepositoryEvent as Q, NonNullableFields as R, DocField as S, IController as St, EventPhase as T, IResponseFormatter as Tt, PartialBy as U, OperationOptions as V, Plugin as W, ReadPreferenceType as X, ReadOptions as Y, RepositoryContext as Z, CursorPayload as _, ValidationChainOptions as _t, AnyModel as a, SoftDeleteFilterMode as at, DeleteManyResult as b, WithPlugins as bt, CacheOperationOptions as c, SortDirection as ct, CacheableOptions as d, UpdateInput as dt, RepositoryOperation as et, CascadeOptions as f, UpdateManyResult as ft, CreateOptions as g, UserContext as gt, CreateInput as h, UpdateWithValidationResult as ht, AnyDocument as i, SessionOptions as it, InferRawDoc as j, HookMode as k, CacheOptions as l, SortSpec as lt, CollationOptions as m, UpdatePatch as mt, AggregatePaginationOptions as n, RequiredBy as nt, BasePaginationOptions as o, SoftDeleteOptions as ot, CascadeRelation as p, UpdateOptions as pt, PopulateSpec as q, AllPluginMethods as r, SelectSpec as rt, CacheAdapter as s, SoftDeleteRepository as st, AggregateOptions as t, RepositoryOptions as tt, CacheStats as u, Strict as ut, DecodedCursor as v, ValidatorDefinition as vt, EventPayload as w, IRequestContext as wt, DeleteResult as x, WithTransactionOptions as xt, DeepPartial as y, ValueType as yt, ObjectId as z };
|
|
@@ -1,6 +1,103 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
-
import { r as warn } from "./logger-
|
|
2
|
+
import { r as warn } from "./logger-8-VS1za6.mjs";
|
|
3
3
|
import { t as createError } from "./error-B00pI7dV.mjs";
|
|
4
|
+
import { isFilter } from "@classytic/repo-core/filter";
|
|
5
|
+
//#region src/filter/compile.ts
|
|
6
|
+
/**
|
|
7
|
+
* Compile a Filter IR node to a MongoDB query object. Returns `{}`
|
|
8
|
+
* when the node is `TRUE` (or an empty `and`) so callers can merge
|
|
9
|
+
* with other scopes without special-casing.
|
|
10
|
+
*
|
|
11
|
+
* The `input` is typed as `unknown` so callers can pass either a
|
|
12
|
+
* Filter IR node or an already-built Mongo query and we dispatch
|
|
13
|
+
* appropriately. This keeps the portable API ergonomic for apps that
|
|
14
|
+
* write Mongo queries directly today.
|
|
15
|
+
*/
|
|
16
|
+
function compileFilterToMongo(input) {
|
|
17
|
+
if (!input) return {};
|
|
18
|
+
if (!isFilter(input)) return input;
|
|
19
|
+
return compile(input);
|
|
20
|
+
}
|
|
21
|
+
function compile(filter) {
|
|
22
|
+
switch (filter.op) {
|
|
23
|
+
case "true": return {};
|
|
24
|
+
case "false": return { $expr: false };
|
|
25
|
+
case "eq": return { [filter.field]: filter.value };
|
|
26
|
+
case "ne": return filter.value === null ? { [filter.field]: { $ne: null } } : { $and: [{ [filter.field]: { $ne: filter.value } }, { [filter.field]: { $ne: null } }] };
|
|
27
|
+
case "gt": return { [filter.field]: { $gt: filter.value } };
|
|
28
|
+
case "gte": return { [filter.field]: { $gte: filter.value } };
|
|
29
|
+
case "lt": return { [filter.field]: { $lt: filter.value } };
|
|
30
|
+
case "lte": return { [filter.field]: { $lte: filter.value } };
|
|
31
|
+
case "in":
|
|
32
|
+
if (filter.values.length === 0) return { $expr: false };
|
|
33
|
+
return { [filter.field]: { $in: [...filter.values] } };
|
|
34
|
+
case "nin":
|
|
35
|
+
if (filter.values.length === 0) return {};
|
|
36
|
+
return { [filter.field]: { $nin: [...filter.values] } };
|
|
37
|
+
case "exists": return filter.exists ? { [filter.field]: { $ne: null } } : { [filter.field]: null };
|
|
38
|
+
case "like": return { [filter.field]: likeToRegex(filter.pattern, filter.caseSensitivity) };
|
|
39
|
+
case "regex": return { [filter.field]: { $regex: filter.pattern } };
|
|
40
|
+
case "and": {
|
|
41
|
+
if (filter.children.length === 0) return {};
|
|
42
|
+
const parts = filter.children.map(compile);
|
|
43
|
+
if (parts.length === 1) return parts[0];
|
|
44
|
+
return { $and: parts };
|
|
45
|
+
}
|
|
46
|
+
case "or": {
|
|
47
|
+
if (filter.children.length === 0) return { $expr: false };
|
|
48
|
+
const parts = filter.children.map(compile);
|
|
49
|
+
if (parts.length === 1) return parts[0];
|
|
50
|
+
return { $or: parts };
|
|
51
|
+
}
|
|
52
|
+
case "not": return { $nor: [compile(filter.child)] };
|
|
53
|
+
case "raw": throw new Error("mongokit/filter: `raw` is a SQL escape hatch and has no safe MongoDB translation. Pass a Mongo query object directly to the filter slot instead.");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Translate a SQL-style LIKE pattern to a MongoDB regex.
|
|
58
|
+
*
|
|
59
|
+
* SQL semantics:
|
|
60
|
+
* - `%` → match zero or more characters (`.*`)
|
|
61
|
+
* - `_` → match exactly one character (`.`)
|
|
62
|
+
* - `\%` → literal percent (escape via backslash)
|
|
63
|
+
* - `\_` → literal underscore
|
|
64
|
+
* - `\\` → literal backslash
|
|
65
|
+
* - everything else is literal — regex metacharacters get escaped.
|
|
66
|
+
*
|
|
67
|
+
* The naive implementation `pattern.replace(/%/g, '.*')` is wrong:
|
|
68
|
+
* it also expands escaped `\%` into a wildcard. We walk the pattern
|
|
69
|
+
* char-by-char, honoring backslash escapes, so `like('notes', '50\\% off')`
|
|
70
|
+
* matches the literal string `50% off` instead of `50<anything> off`.
|
|
71
|
+
*
|
|
72
|
+
* Anchored with `^` / `$` so LIKE matches the whole field value, the
|
|
73
|
+
* same way sqlitekit's `LIKE` semantically does.
|
|
74
|
+
*/
|
|
75
|
+
function likeToRegex(pattern, caseSensitivity) {
|
|
76
|
+
let out = "^";
|
|
77
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
78
|
+
const ch = pattern[i];
|
|
79
|
+
if (ch === "\\" && i + 1 < pattern.length) {
|
|
80
|
+
const next = pattern[i + 1];
|
|
81
|
+
if (next === "%" || next === "_" || next === "\\") {
|
|
82
|
+
out += escapeRegexChar(next);
|
|
83
|
+
i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (ch === "%") out += ".*";
|
|
88
|
+
else if (ch === "_") out += ".";
|
|
89
|
+
else out += escapeRegexChar(ch);
|
|
90
|
+
}
|
|
91
|
+
out += "$";
|
|
92
|
+
return caseSensitivity === "sensitive" ? { $regex: out } : {
|
|
93
|
+
$regex: out,
|
|
94
|
+
$options: "i"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function escapeRegexChar(ch) {
|
|
98
|
+
return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
4
101
|
//#region src/query/LookupBuilder.ts
|
|
5
102
|
/** Stages that are never valid inside a $lookup pipeline */
|
|
6
103
|
const BLOCKED_PIPELINE_STAGES = [
|
|
@@ -191,28 +288,24 @@ var LookupBuilder = class LookupBuilder {
|
|
|
191
288
|
const builder = new LookupBuilder(lookup.from).localField(lookup.localField).foreignField(lookup.foreignField);
|
|
192
289
|
if (lookup.as) builder.as(lookup.as);
|
|
193
290
|
if (lookup.single) builder.single(lookup.single);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
]);
|
|
291
|
+
const stages = [];
|
|
292
|
+
const appendCallerStages = (raw) => {
|
|
293
|
+
if (raw.length === 0) return;
|
|
294
|
+
stages.push(...LookupBuilder.sanitizePipeline(raw));
|
|
295
|
+
};
|
|
296
|
+
const compiledWhere = lookup.where ? compileFilterToMongo(lookup.where) : void 0;
|
|
297
|
+
const hasWhere = !!compiledWhere && Object.keys(compiledWhere).length > 0;
|
|
298
|
+
if (!!(lookup.select || lookup.pipeline || hasWhere)) {
|
|
299
|
+
stages.push({ $match: { $expr: { $eq: [`$${lookup.foreignField}`, "$$lookupJoinVal"] } } });
|
|
300
|
+
if (hasWhere && compiledWhere) appendCallerStages([{ $match: compiledWhere }]);
|
|
301
|
+
if (lookup.pipeline) appendCallerStages(lookup.pipeline);
|
|
302
|
+
if (lookup.select) stages.push({ $project: compileSelectToProjection(lookup.select) });
|
|
303
|
+
builder.pipeline(stages);
|
|
208
304
|
builder.let({
|
|
209
305
|
lookupJoinVal: `$${lookup.localField}`,
|
|
210
306
|
...lookup.let || {}
|
|
211
307
|
});
|
|
212
308
|
builder.sanitize(false);
|
|
213
|
-
} else if (lookup.pipeline) {
|
|
214
|
-
builder.pipeline(lookup.pipeline);
|
|
215
|
-
if (lookup.let) builder.let(lookup.let);
|
|
216
309
|
} else if (lookup.let) builder.let(lookup.let);
|
|
217
310
|
return builder.build();
|
|
218
311
|
});
|
|
@@ -281,6 +374,38 @@ var LookupBuilder = class LookupBuilder {
|
|
|
281
374
|
}
|
|
282
375
|
};
|
|
283
376
|
/**
|
|
377
|
+
* Normalize the three accepted `LookupOptions.select` shapes into a single
|
|
378
|
+
* Mongo `$project` map.
|
|
379
|
+
*
|
|
380
|
+
* - `string` → CSV form, leading `-` excludes (`'name,-status'`)
|
|
381
|
+
* - `readonly string[]` → array form, leading `-` excludes (`['-status']`)
|
|
382
|
+
* - `Record<string, 0|1>` → already a projection map, copied as-is
|
|
383
|
+
*
|
|
384
|
+
* The array form mirrors `repo-core`'s `LookupSpec.select` so cross-kit
|
|
385
|
+
* callers (`select: ['name']`) compile identically on mongokit and sqlitekit.
|
|
386
|
+
*/
|
|
387
|
+
function compileSelectToProjection(select) {
|
|
388
|
+
if (typeof select === "string") {
|
|
389
|
+
const projection = {};
|
|
390
|
+
for (const field of select.split(",").map((f) => f.trim())) {
|
|
391
|
+
if (!field) continue;
|
|
392
|
+
if (field.startsWith("-")) projection[field.substring(1)] = 0;
|
|
393
|
+
else projection[field] = 1;
|
|
394
|
+
}
|
|
395
|
+
return projection;
|
|
396
|
+
}
|
|
397
|
+
if (Array.isArray(select)) {
|
|
398
|
+
const projection = {};
|
|
399
|
+
for (const field of select) {
|
|
400
|
+
if (!field) continue;
|
|
401
|
+
if (field.startsWith("-")) projection[field.substring(1)] = 0;
|
|
402
|
+
else projection[field] = 1;
|
|
403
|
+
}
|
|
404
|
+
return projection;
|
|
405
|
+
}
|
|
406
|
+
return { ...select };
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
284
409
|
* Performance Guidelines for $lookup at Scale:
|
|
285
410
|
*
|
|
286
411
|
* 1. **Index Requirements** (Critical for millions of records):
|
|
@@ -918,4 +1043,4 @@ async function pullFromArray(Model, id, field, value, options = {}) {
|
|
|
918
1043
|
return update(Model, id, { $pull: { [field]: value } }, options);
|
|
919
1044
|
}
|
|
920
1045
|
//#endregion
|
|
921
|
-
export { upsert as _, count as a, LookupBuilder as b, getByQuery as c, deleteById as d, deleteByQuery as f, create_exports as g, createMany as h, update_exports as i, getOrCreate as l, create as m, update as n, exists as o, delete_exports as p, updateByQuery as r, getById as s, findOneAndUpdate as t, read_exports as u, aggregate_exports as v, distinct as y };
|
|
1046
|
+
export { upsert as _, count as a, LookupBuilder as b, getByQuery as c, deleteById as d, deleteByQuery as f, create_exports as g, createMany as h, update_exports as i, getOrCreate as l, create as m, update as n, exists as o, delete_exports as p, updateByQuery as r, getById as s, findOneAndUpdate as t, read_exports as u, aggregate_exports as v, compileFilterToMongo as x, distinct as y };
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
1
|
+
import { lt as SortSpec, q as PopulateSpec, rt as SelectSpec } from "../types-DXscZnLu.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-rtkdbBhf.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/utils/cache-keys.d.ts
|
|
5
5
|
/**
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as configureLogger } from "../logger-
|
|
1
|
+
import { t as configureLogger } from "../logger-8-VS1za6.mjs";
|
|
2
2
|
import { t as createError } from "../error-B00pI7dV.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 { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-
|
|
5
|
+
import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-B187ufOP.mjs";
|
|
6
6
|
export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, versionKey };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
import {
|
|
1
|
+
import { _ as upsert, a as count, b as LookupBuilder, c as getByQuery, d as deleteById, f as deleteByQuery, h as createMany, l as getOrCreate, m as create, n as update, o as exists, r as updateByQuery, s as getById, t as findOneAndUpdate, x as compileFilterToMongo, y as distinct } from "./update-CDxPtpsk.mjs";
|
|
2
|
+
import { n as debug, r as warn } from "./logger-8-VS1za6.mjs";
|
|
3
3
|
import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-B00pI7dV.mjs";
|
|
4
|
-
import { t as PaginationEngine } from "./PaginationEngine-
|
|
4
|
+
import { t as PaginationEngine } from "./PaginationEngine-BDaAsyPn.mjs";
|
|
5
5
|
import { hasNearOperator, rewriteNearForCount } from "./query/primitives/geo.mjs";
|
|
6
6
|
import { r as isValidIdForType, t as getSchemaIdType } from "./id-resolution-e4BT9zAV.mjs";
|
|
7
7
|
import { a as byIdKey, c as listQueryKey, l as modelPattern, o as byQueryKey, r as getFieldsForUser, u as versionKey } from "./field-selection-dI0m4RBs.mjs";
|
|
8
|
-
import mongoose from "mongoose";
|
|
9
8
|
import { isFilter } from "@classytic/repo-core/filter";
|
|
9
|
+
import mongoose from "mongoose";
|
|
10
10
|
import { HOOK_PRIORITY } from "@classytic/repo-core/hooks";
|
|
11
11
|
import { RepositoryBase, validatePluginOrder } from "@classytic/repo-core/repository";
|
|
12
12
|
import { compileUpdateSpecToMongo, isUpdateSpec } from "@classytic/repo-core/update";
|
|
@@ -728,102 +728,6 @@ function batchOperationsPlugin() {
|
|
|
728
728
|
};
|
|
729
729
|
}
|
|
730
730
|
//#endregion
|
|
731
|
-
//#region src/filter/compile.ts
|
|
732
|
-
/**
|
|
733
|
-
* Compile a Filter IR node to a MongoDB query object. Returns `{}`
|
|
734
|
-
* when the node is `TRUE` (or an empty `and`) so callers can merge
|
|
735
|
-
* with other scopes without special-casing.
|
|
736
|
-
*
|
|
737
|
-
* The `input` is typed as `unknown` so callers can pass either a
|
|
738
|
-
* Filter IR node or an already-built Mongo query and we dispatch
|
|
739
|
-
* appropriately. This keeps the portable API ergonomic for apps that
|
|
740
|
-
* write Mongo queries directly today.
|
|
741
|
-
*/
|
|
742
|
-
function compileFilterToMongo(input) {
|
|
743
|
-
if (!input) return {};
|
|
744
|
-
if (!isFilter(input)) return input;
|
|
745
|
-
return compile(input);
|
|
746
|
-
}
|
|
747
|
-
function compile(filter) {
|
|
748
|
-
switch (filter.op) {
|
|
749
|
-
case "true": return {};
|
|
750
|
-
case "false": return { $expr: false };
|
|
751
|
-
case "eq": return { [filter.field]: filter.value };
|
|
752
|
-
case "ne": return filter.value === null ? { [filter.field]: { $ne: null } } : { $and: [{ [filter.field]: { $ne: filter.value } }, { [filter.field]: { $ne: null } }] };
|
|
753
|
-
case "gt": return { [filter.field]: { $gt: filter.value } };
|
|
754
|
-
case "gte": return { [filter.field]: { $gte: filter.value } };
|
|
755
|
-
case "lt": return { [filter.field]: { $lt: filter.value } };
|
|
756
|
-
case "lte": return { [filter.field]: { $lte: filter.value } };
|
|
757
|
-
case "in":
|
|
758
|
-
if (filter.values.length === 0) return { $expr: false };
|
|
759
|
-
return { [filter.field]: { $in: [...filter.values] } };
|
|
760
|
-
case "nin":
|
|
761
|
-
if (filter.values.length === 0) return {};
|
|
762
|
-
return { [filter.field]: { $nin: [...filter.values] } };
|
|
763
|
-
case "exists": return filter.exists ? { [filter.field]: { $ne: null } } : { [filter.field]: null };
|
|
764
|
-
case "like": return { [filter.field]: likeToRegex(filter.pattern, filter.caseSensitivity) };
|
|
765
|
-
case "regex": return { [filter.field]: { $regex: filter.pattern } };
|
|
766
|
-
case "and": {
|
|
767
|
-
if (filter.children.length === 0) return {};
|
|
768
|
-
const parts = filter.children.map(compile);
|
|
769
|
-
if (parts.length === 1) return parts[0];
|
|
770
|
-
return { $and: parts };
|
|
771
|
-
}
|
|
772
|
-
case "or": {
|
|
773
|
-
if (filter.children.length === 0) return { $expr: false };
|
|
774
|
-
const parts = filter.children.map(compile);
|
|
775
|
-
if (parts.length === 1) return parts[0];
|
|
776
|
-
return { $or: parts };
|
|
777
|
-
}
|
|
778
|
-
case "not": return { $nor: [compile(filter.child)] };
|
|
779
|
-
case "raw": throw new Error("mongokit/filter: `raw` is a SQL escape hatch and has no safe MongoDB translation. Pass a Mongo query object directly to the filter slot instead.");
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Translate a SQL-style LIKE pattern to a MongoDB regex.
|
|
784
|
-
*
|
|
785
|
-
* SQL semantics:
|
|
786
|
-
* - `%` → match zero or more characters (`.*`)
|
|
787
|
-
* - `_` → match exactly one character (`.`)
|
|
788
|
-
* - `\%` → literal percent (escape via backslash)
|
|
789
|
-
* - `\_` → literal underscore
|
|
790
|
-
* - `\\` → literal backslash
|
|
791
|
-
* - everything else is literal — regex metacharacters get escaped.
|
|
792
|
-
*
|
|
793
|
-
* The naive implementation `pattern.replace(/%/g, '.*')` is wrong:
|
|
794
|
-
* it also expands escaped `\%` into a wildcard. We walk the pattern
|
|
795
|
-
* char-by-char, honoring backslash escapes, so `like('notes', '50\\% off')`
|
|
796
|
-
* matches the literal string `50% off` instead of `50<anything> off`.
|
|
797
|
-
*
|
|
798
|
-
* Anchored with `^` / `$` so LIKE matches the whole field value, the
|
|
799
|
-
* same way sqlitekit's `LIKE` semantically does.
|
|
800
|
-
*/
|
|
801
|
-
function likeToRegex(pattern, caseSensitivity) {
|
|
802
|
-
let out = "^";
|
|
803
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
804
|
-
const ch = pattern[i];
|
|
805
|
-
if (ch === "\\" && i + 1 < pattern.length) {
|
|
806
|
-
const next = pattern[i + 1];
|
|
807
|
-
if (next === "%" || next === "_" || next === "\\") {
|
|
808
|
-
out += escapeRegexChar(next);
|
|
809
|
-
i++;
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
if (ch === "%") out += ".*";
|
|
814
|
-
else if (ch === "_") out += ".";
|
|
815
|
-
else out += escapeRegexChar(ch);
|
|
816
|
-
}
|
|
817
|
-
out += "$";
|
|
818
|
-
return caseSensitivity === "sensitive" ? { $regex: out } : {
|
|
819
|
-
$regex: out,
|
|
820
|
-
$options: "i"
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
function escapeRegexChar(ch) {
|
|
824
|
-
return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
825
|
-
}
|
|
826
|
-
//#endregion
|
|
827
731
|
//#region src/actions/aggregate-ir/normalize.ts
|
|
828
732
|
/**
|
|
829
733
|
* Normalize `AggRequest['groupBy']` into a readonly string array.
|
|
@@ -1626,32 +1530,6 @@ function createTxBoundRepo(outer, session) {
|
|
|
1626
1530
|
}
|
|
1627
1531
|
//#endregion
|
|
1628
1532
|
//#region src/Repository.ts
|
|
1629
|
-
/**
|
|
1630
|
-
* Repository Pattern - Data Access Layer
|
|
1631
|
-
*
|
|
1632
|
-
* Event-driven, plugin-based abstraction for MongoDB operations
|
|
1633
|
-
* Inspired by Meta & Stripe's repository patterns
|
|
1634
|
-
*
|
|
1635
|
-
* @example
|
|
1636
|
-
* ```typescript
|
|
1637
|
-
* const userRepo = new Repository(UserModel, [
|
|
1638
|
-
* timestampPlugin(),
|
|
1639
|
-
* softDeletePlugin(),
|
|
1640
|
-
* ]);
|
|
1641
|
-
*
|
|
1642
|
-
* // Create
|
|
1643
|
-
* const user = await userRepo.create({ name: 'John', email: 'john@example.com' });
|
|
1644
|
-
*
|
|
1645
|
-
* // Read with pagination
|
|
1646
|
-
* const users = await userRepo.getAll({ page: 1, limit: 20, filters: { status: 'active' } });
|
|
1647
|
-
*
|
|
1648
|
-
* // Update
|
|
1649
|
-
* const updated = await userRepo.update(user._id, { name: 'John Doe' });
|
|
1650
|
-
*
|
|
1651
|
-
* // Delete
|
|
1652
|
-
* await userRepo.delete(user._id);
|
|
1653
|
-
* ```
|
|
1654
|
-
*/
|
|
1655
1533
|
function ensureLookupProjectionIncludesCursorFields(projection, sort) {
|
|
1656
1534
|
if (!projection || !sort) return projection;
|
|
1657
1535
|
if (!Object.values(projection).some((value) => value === 1)) return projection;
|
|
@@ -2300,7 +2178,7 @@ var Repository = class extends RepositoryBase {
|
|
|
2300
2178
|
if (context.softDeleted) {
|
|
2301
2179
|
const result = {
|
|
2302
2180
|
acknowledged: true,
|
|
2303
|
-
deletedCount: 0,
|
|
2181
|
+
deletedCount: context.softDeletedCount ?? 0,
|
|
2304
2182
|
soft: true
|
|
2305
2183
|
};
|
|
2306
2184
|
await this._emitHook("after:deleteMany", {
|
|
@@ -2488,19 +2366,31 @@ var Repository = class extends RepositoryBase {
|
|
|
2488
2366
|
* Merge policy-hook-injected filters (multi-tenant scope,
|
|
2489
2367
|
* soft-delete) into the pre-aggregate `filter` slot of the request.
|
|
2490
2368
|
* Plugins speak mongo-query shape (`context.query` / `context.filters`);
|
|
2491
|
-
*
|
|
2369
|
+
* the caller's `req.filter` may be either Filter IR or a mongo query.
|
|
2492
2370
|
*
|
|
2493
|
-
*
|
|
2494
|
-
*
|
|
2495
|
-
*
|
|
2496
|
-
*
|
|
2371
|
+
* Each part is compiled through `compileFilterToMongo` BEFORE the
|
|
2372
|
+
* `$and` merge — otherwise an IR node like `eq('status', 'paid')`
|
|
2373
|
+
* (`{ op, field, value }`) gets nested inside `$and` as a literal,
|
|
2374
|
+
* `compileFilterToMongo` of the merged value finds no top-level `op`
|
|
2375
|
+
* (so `isFilter` is false), and Mongo receives the IR object as a
|
|
2376
|
+
* raw query clause that matches no real document. Symptom: aggregates
|
|
2377
|
+
* silently return 0 rows whenever a callerpasses IR + a policy
|
|
2378
|
+
* plugin is installed.
|
|
2497
2379
|
*/
|
|
2498
2380
|
_injectPolicyScopeIntoAgg(req, context) {
|
|
2499
2381
|
const scopeCandidates = [];
|
|
2500
2382
|
for (const candidate of [context.filters, context.query]) if (candidate && typeof candidate === "object" && Object.keys(candidate).length > 0) scopeCandidates.push(candidate);
|
|
2501
2383
|
if (scopeCandidates.length === 0) return req;
|
|
2502
|
-
const parts = [
|
|
2503
|
-
|
|
2384
|
+
const parts = [];
|
|
2385
|
+
for (const c of scopeCandidates) {
|
|
2386
|
+
const compiled = compileFilterToMongo(c);
|
|
2387
|
+
if (Object.keys(compiled).length > 0) parts.push(compiled);
|
|
2388
|
+
}
|
|
2389
|
+
if (req.filter) {
|
|
2390
|
+
const compiled = compileFilterToMongo(req.filter);
|
|
2391
|
+
if (Object.keys(compiled).length > 0) parts.push(compiled);
|
|
2392
|
+
}
|
|
2393
|
+
if (parts.length === 0) return req;
|
|
2504
2394
|
const merged = parts.length === 1 ? parts[0] : { $and: parts };
|
|
2505
2395
|
return {
|
|
2506
2396
|
...req,
|
|
@@ -4330,49 +4220,6 @@ function mongoOperationsPlugin() {
|
|
|
4330
4220
|
//#endregion
|
|
4331
4221
|
//#region src/plugins/multi-tenant.plugin.ts
|
|
4332
4222
|
/**
|
|
4333
|
-
* Multi-Tenant Plugin
|
|
4334
|
-
*
|
|
4335
|
-
* Automatically injects tenant isolation filters into all queries.
|
|
4336
|
-
* Ensures data isolation by adding organizationId (or custom tenant field)
|
|
4337
|
-
* to every read and write operation.
|
|
4338
|
-
*
|
|
4339
|
-
* Uses HOOK_PRIORITY.POLICY (100) to ensure tenant filters are injected
|
|
4340
|
-
* BEFORE cache keys are computed (HOOK_PRIORITY.CACHE = 200).
|
|
4341
|
-
*
|
|
4342
|
-
* @example
|
|
4343
|
-
* ```typescript
|
|
4344
|
-
* // Basic — scopes every operation by organizationId from context
|
|
4345
|
-
* const repo = new Repository(Invoice, [
|
|
4346
|
-
* multiTenantPlugin({ tenantField: 'organizationId' }),
|
|
4347
|
-
* ]);
|
|
4348
|
-
*
|
|
4349
|
-
* const invoices = await repo.getAll(
|
|
4350
|
-
* { filters: { status: 'paid' } },
|
|
4351
|
-
* { organizationId: 'org_123' }
|
|
4352
|
-
* );
|
|
4353
|
-
* // Actual query: { status: 'paid', organizationId: 'org_123' }
|
|
4354
|
-
*
|
|
4355
|
-
* // Super admin bypass — skip scoping based on context
|
|
4356
|
-
* const repo = new Repository(Invoice, [
|
|
4357
|
-
* multiTenantPlugin({
|
|
4358
|
-
* tenantField: 'organizationId',
|
|
4359
|
-
* skipWhen: (context) => context.role === 'superadmin',
|
|
4360
|
-
* }),
|
|
4361
|
-
* ]);
|
|
4362
|
-
*
|
|
4363
|
-
* // Admin sees all orgs
|
|
4364
|
-
* await repo.getAll({ page: 1, limit: 10 }, { role: 'superadmin' });
|
|
4365
|
-
*
|
|
4366
|
-
* // Automatic context — resolve tenant from AsyncLocalStorage
|
|
4367
|
-
* const repo = new Repository(Invoice, [
|
|
4368
|
-
* multiTenantPlugin({
|
|
4369
|
-
* tenantField: 'organizationId',
|
|
4370
|
-
* resolveContext: () => asyncLocalStorage.getStore()?.tenantId,
|
|
4371
|
-
* }),
|
|
4372
|
-
* ]);
|
|
4373
|
-
* ```
|
|
4374
|
-
*/
|
|
4375
|
-
/**
|
|
4376
4223
|
* True when the op's policy target already has `tenantField` set by the
|
|
4377
4224
|
* caller. Used to decide whether the plugin can safely skip injecting a
|
|
4378
4225
|
* tenant scope rather than throwing on a missing context.
|
|
@@ -4719,8 +4566,9 @@ function softDeletePlugin(options = {}) {
|
|
|
4719
4566
|
...context.query || {},
|
|
4720
4567
|
...deleteFilter
|
|
4721
4568
|
};
|
|
4722
|
-
await repo.Model.updateMany(finalQuery, { $set: { [deletedField]: /* @__PURE__ */ new Date() } }, { session: context.session });
|
|
4569
|
+
const updateRes = await repo.Model.updateMany(finalQuery, { $set: { [deletedField]: /* @__PURE__ */ new Date() } }, { session: context.session });
|
|
4723
4570
|
context.softDeleted = true;
|
|
4571
|
+
context.softDeletedCount = updateRes?.modifiedCount ?? 0;
|
|
4724
4572
|
}
|
|
4725
4573
|
}, { priority: HOOK_PRIORITY$1.POLICY });
|
|
4726
4574
|
if (addRestoreMethod) {
|
package/dist/{validation-chain.plugin-B0VSdcwa.d.mts → validation-chain.plugin-CH-xPoKM.d.mts}
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as RepositoryInstance, E as FieldPreset, P as Logger, W as Plugin, Z as RepositoryContext, _t as ValidationChainOptions, f as CascadeOptions, l as CacheOptions, lt as SortSpec, ot as SoftDeleteOptions, q as PopulateSpec, rt as SelectSpec, u as CacheStats, vt as ValidatorDefinition, z as ObjectId } from "./types-DXscZnLu.mjs";
|
|
2
2
|
import mongoose from "mongoose";
|
|
3
3
|
import { BulkWriteResult } from "@classytic/repo-core/repository";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
+
import { OffsetPaginationResult } from "@classytic/repo-core/pagination";
|
|
6
|
+
import { TenantConfig } from "@classytic/repo-core/tenant";
|
|
5
7
|
|
|
6
8
|
//#region src/plugins/aggregate-helpers.plugin.d.ts
|
|
7
9
|
/**
|
|
@@ -753,13 +755,20 @@ interface MongoOperationsMethods<TDoc> {
|
|
|
753
755
|
}
|
|
754
756
|
//#endregion
|
|
755
757
|
//#region src/plugins/multi-tenant.plugin.d.ts
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
758
|
+
/**
|
|
759
|
+
* Static tenant fields are picked from `@classytic/repo-core/tenant` so the
|
|
760
|
+
* vocabulary (`tenantField`, `contextKey`, `required`, `fieldType`) stays
|
|
761
|
+
* locked to the org-wide canonical source. Mongokit-specific runtime
|
|
762
|
+
* callbacks (`skipWhen`, `resolveContext`, `skipOperations`,
|
|
763
|
+
* `allowDataInjection`) extend on top — those genuinely differ from
|
|
764
|
+
* sqlitekit's runtime shape because mongokit's `RepositoryContext` carries
|
|
765
|
+
* Mongoose-specific bits the resolver may inspect.
|
|
766
|
+
*
|
|
767
|
+
* Mongokit defaults `fieldType` to `'string'` (not repo-core's `'objectId'`)
|
|
768
|
+
* for back-compat — pre-3.x stored tenant ids as raw strings; flipping the
|
|
769
|
+
* default would silently change cast behavior for existing apps.
|
|
770
|
+
*/
|
|
771
|
+
interface MultiTenantOptions extends Pick<TenantConfig, 'tenantField' | 'contextKey' | 'required' | 'fieldType'> {
|
|
763
772
|
/** Operations to skip tenant injection (e.g., for admin/system queries) */
|
|
764
773
|
skipOperations?: string[];
|
|
765
774
|
/**
|
|
@@ -784,17 +793,6 @@ interface MultiTenantOptions {
|
|
|
784
793
|
* ```
|
|
785
794
|
*/
|
|
786
795
|
resolveContext?: () => string | undefined;
|
|
787
|
-
/**
|
|
788
|
-
* How to store/query the tenant ID.
|
|
789
|
-
*
|
|
790
|
-
* - `'string'` (default): inject the tenant ID as-is (raw string).
|
|
791
|
-
* - `'objectId'`: cast to `mongoose.Types.ObjectId` before injecting.
|
|
792
|
-
*
|
|
793
|
-
* Choose `'objectId'` when the schema declares the tenant field as
|
|
794
|
-
* `Schema.Types.ObjectId` (e.g., Flow, accounting). This enables
|
|
795
|
-
* `$lookup` and `.populate()` against the referenced collection.
|
|
796
|
-
*/
|
|
797
|
-
fieldType?: 'string' | 'objectId';
|
|
798
796
|
/**
|
|
799
797
|
* When `true` (default), bypass the `required` throw if the operation's
|
|
800
798
|
* policy target already carries the tenant field. This lets hosts that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/mongokit",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.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,
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
},
|
|
94
94
|
"homepage": "https://github.com/classytic/mongokit#readme",
|
|
95
95
|
"peerDependencies": {
|
|
96
|
-
"
|
|
97
|
-
"
|
|
96
|
+
"@classytic/repo-core": ">=0.3.0",
|
|
97
|
+
"mongoose": ">=9.4.1"
|
|
98
98
|
},
|
|
99
99
|
"engines": {
|
|
100
100
|
"node": ">=22"
|
|
@@ -115,10 +115,12 @@
|
|
|
115
115
|
"lint:fix": "biome check src/ --write",
|
|
116
116
|
"format": "biome format src/ --write",
|
|
117
117
|
"check": "biome ci src/ --diagnostic-level=error",
|
|
118
|
+
"push": "classytic-push",
|
|
118
119
|
"prepublishOnly": "npm run check && npm run build && npm run typecheck && npm run typecheck:tests && npm test",
|
|
119
120
|
"publish:dry": "npm publish --dry-run --access public",
|
|
120
121
|
"publish:npm": "npm publish --access public",
|
|
121
|
-
"release": "
|
|
122
|
+
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
123
|
+
"release": "npm run push -- main && npm run release:tag && npm publish",
|
|
122
124
|
"release:patch": "npm version patch && npm run release",
|
|
123
125
|
"release:minor": "npm version minor && npm run release",
|
|
124
126
|
"release:major": "npm version major && npm run release"
|
|
@@ -126,7 +128,8 @@
|
|
|
126
128
|
"devDependencies": {
|
|
127
129
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
128
130
|
"@biomejs/biome": "^2.4.10",
|
|
129
|
-
"@classytic/
|
|
131
|
+
"@classytic/dev-tools": "^0.2.0",
|
|
132
|
+
"@classytic/repo-core": "^0.3.0",
|
|
130
133
|
"@types/node": "^22.0.0",
|
|
131
134
|
"@vitest/coverage-v8": "^3.2.4",
|
|
132
135
|
"ajv": "^8.18.0",
|
|
File without changes
|