@classytic/mongokit 3.20.0 → 3.22.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.
Files changed (31) hide show
  1. package/dist/{PaginationEngine-DYpOD2wY.d.mts → PaginationEngine-CYRTLW6s.d.mts} +1 -1
  2. package/dist/{Repository-dbbgOGjK.mjs → Repository-CaDAJY98.mjs} +100 -14
  3. package/dist/actions/index.d.mts +1 -1
  4. package/dist/adapter/index.d.mts +17 -10
  5. package/dist/adapter/index.mjs +1 -1
  6. package/dist/{adapter-CW1EL0fF.mjs → adapter-rTPLUrAV.mjs} +2 -1
  7. package/dist/ai/index.d.mts +1 -1
  8. package/dist/better-auth/index.mjs +2 -2
  9. package/dist/{create-repository-CJATiAKX.mjs → create-repository-BFVYm48p.mjs} +2 -2
  10. package/dist/{create-repository-0TkQhcv-.d.mts → create-repository-DXa3nMG6.d.mts} +95 -4
  11. package/dist/{index-Cg3SWugF.d.mts → index-CENL7LRb.d.mts} +1 -1
  12. package/dist/index.d.mts +8 -8
  13. package/dist/index.mjs +7 -6
  14. package/dist/memory-cache-D3wyN_2w.mjs +98 -0
  15. package/dist/{mongooseToJsonSchema-khdzVo8x.mjs → mongooseToJsonSchema-CVnrKFDq.mjs} +1 -67
  16. package/dist/{mongooseToJsonSchema-D2CyAMIC.d.mts → mongooseToJsonSchema-ChFKfZts.d.mts} +31 -2
  17. package/dist/{multi-tenant.plugin-CTsWz0XK.d.mts → multi-tenant.plugin-CFzMOuZP.d.mts} +1 -1
  18. package/dist/pagination/PaginationEngine.d.mts +1 -1
  19. package/dist/plugins/index.d.mts +4 -4
  20. package/dist/plugins/index.mjs +3 -3
  21. package/dist/testkit/index.d.mts +1 -1
  22. package/dist/testkit/index.mjs +1 -1
  23. package/dist/{timestamp.plugin-nG9Z1JEk.mjs → timestamp.plugin-Ch0O6DPX.mjs} +1 -1
  24. package/dist/{types-DNFMofHy.d.mts → types-BnRqAJVa.d.mts} +8 -0
  25. package/dist/usage/index.d.mts +23 -0
  26. package/dist/usage/index.mjs +67 -0
  27. package/dist/utils/index.d.mts +2 -2
  28. package/dist/utils/index.mjs +3 -2
  29. package/dist/{validation-chain.plugin-buy3uvyk.d.mts → validation-chain.plugin-DDQeEyk5.d.mts} +63 -2
  30. package/dist/{validation-chain.plugin-Dn_wZYLA.mjs → validation-chain.plugin-RxOzGuQy.mjs} +221 -2
  31. package/package.json +178 -184
@@ -1,4 +1,4 @@
1
- import { C as FieldPreset, gt as UserContext } from "./types-DNFMofHy.mjs";
1
+ import { C as FieldPreset, gt as UserContext } from "./types-BnRqAJVa.mjs";
2
2
  import mongoose, { Schema } from "mongoose";
3
3
  import { CacheAdapter } from "@classytic/repo-core/cache";
4
4
  import { CrudSchemas, SchemaBuilderOptions } from "@classytic/repo-core/schema";
@@ -249,6 +249,35 @@ interface LoggerConfig {
249
249
  */
250
250
  declare function configureLogger(config: Partial<LoggerConfig> | false): void;
251
251
  //#endregion
252
+ //#region src/utils/to-plain.d.ts
253
+ /**
254
+ * Convert a hydrated Mongoose (sub)document to a plain object — the
255
+ * spread-safety primitive for domain verbs that rebuild embedded lines.
256
+ *
257
+ * **The footgun this closes:** spreading a hydrated subdocument
258
+ * (`{ ...line, consumedQuantity: x }`) spreads its Mongoose INTERNAL
259
+ * properties (`$__`, `_doc`, ...), not its schema fields. The rebuilt
260
+ * object then casts to garbage on write — required fields land
261
+ * `undefined`, and because `findOneAndUpdate` does not run validators
262
+ * by default, the corrupt lines persist silently. This has bitten every
263
+ * package that maps over embedded arrays (`components`, `operations`,
264
+ * `revisions`) to produce an updated copy.
265
+ *
266
+ * Rule of thumb: **any `{ ...subdoc }` in a repository verb goes
267
+ * through `toPlain` first.**
268
+ *
269
+ * ```ts
270
+ * const updated = lines.map((line) => ({
271
+ * ...toPlain(line),
272
+ * reservedQuantity: reserved.get(line.itemSku) ?? 0,
273
+ * }));
274
+ * ```
275
+ *
276
+ * Non-documents pass through unchanged, so it is safe to apply
277
+ * defensively over values that may or may not be hydrated.
278
+ */
279
+ declare function toPlain<T>(value: T): T;
280
+ //#endregion
252
281
  //#region src/utils/memory-cache.d.ts
253
282
  /**
254
283
  * Creates an in-memory cache adapter
@@ -295,4 +324,4 @@ declare function buildCrudSchemasFromMongooseSchema(mongooseSchema: Schema, opti
295
324
  */
296
325
  declare function buildCrudSchemasFromModel(mongooseModel: mongoose.Model<unknown>, options?: SchemaBuilderOptions): CrudSchemasFramework;
297
326
  //#endregion
298
- export { IdType as a, isObjectIdInstance as c, filterResponseData as d, getFieldsForUser as f, parseDuplicateKeyError as g, isDuplicateKeyError as h, configureLogger as i, isValidIdForType as l, createError as m, buildCrudSchemasFromMongooseSchema as n, getSchemaIdType as o, getMongooseProjection as p, createMemoryCache as r, idVariants as s, buildCrudSchemasFromModel as t, createFieldPreset as u };
327
+ export { parseDuplicateKeyError as _, configureLogger as a, idVariants as c, createFieldPreset as d, filterResponseData as f, isDuplicateKeyError as g, createError as h, toPlain as i, isObjectIdInstance as l, getMongooseProjection as m, buildCrudSchemasFromMongooseSchema as n, IdType as o, getFieldsForUser as p, createMemoryCache as r, getSchemaIdType as s, buildCrudSchemasFromModel as t, isValidIdForType as u };
@@ -1,4 +1,4 @@
1
- import { B as ObjectId, G as Plugin, Q as RepositoryContext } from "./types-DNFMofHy.mjs";
1
+ import { B as ObjectId, G as Plugin, Q as RepositoryContext } from "./types-BnRqAJVa.mjs";
2
2
  import mongoose from "mongoose";
3
3
  import { CacheAdapter as CacheAdapter$1, CacheOptions, RepositoryCacheHandle, RepositoryCachePluginOptions, cachePlugin } from "@classytic/repo-core/cache";
4
4
  import { TenantConfig } from "@classytic/repo-core/tenant";
@@ -1,2 +1,2 @@
1
- import { t as PaginationEngine } from "../PaginationEngine-DYpOD2wY.mjs";
1
+ import { t as PaginationEngine } from "../PaginationEngine-CYRTLW6s.mjs";
2
2
  export { PaginationEngine };
@@ -1,6 +1,6 @@
1
- import { G as Plugin } from "../types-DNFMofHy.mjs";
2
- import { C as AuditTrailMethods, E as auditTrailPlugin, S as AuditQueryResult, T as AuditTrailQuery, _ as RepositoryCachePluginOptions, a as DateSequentialIdOptions, b as AuditOperation, c as SequentialIdOptions, d as getNextSequence, f as prefixedId, g as RepositoryCacheHandle, h as CacheOptions, i as CustomIdOptions, l as customIdPlugin, m as CacheAdapter, o as IdGenerator, p as sequentialId, r as multiTenantPlugin, s as PrefixedIdOptions, t as MultiTenantOptions, u as dateSequentialId, v as cachePlugin, w as AuditTrailOptions, x as AuditQueryOptions, y as AuditEntry } from "../multi-tenant.plugin-CTsWz0XK.mjs";
3
- import { C as LeasePluginOptions, D as elasticSearchPlugin, E as ElasticSearchOptions, M as auditLogPlugin, N as AggregateHelpersMethods, O as cascadePlugin, P as aggregateHelpersPlugin, S as LeaseMethods, T as fieldFilterPlugin, _ as observabilityPlugin, a as uniqueField, b as MethodRegistryRepository, c as TenantContext, d as SubdocumentMethods, f as subdocumentPlugin, g as OperationMetric, h as ObservabilityOptions, i as requireField, j as batchOperationsPlugin, k as BatchOperationsMethods, l as TenantStore, m as softDeletePlugin, n as blockIf, o as validationChainPlugin, p as SoftDeleteMethods, r as immutableField, s as timestampPlugin, t as autoInject, u as createTenantContext, v as MongoOperationsMethods, w as leasePlugin, x as methodRegistryPlugin, y as mongoOperationsPlugin } from "../validation-chain.plugin-buy3uvyk.mjs";
1
+ import { G as Plugin } from "../types-BnRqAJVa.mjs";
2
+ import { C as AuditTrailMethods, E as auditTrailPlugin, S as AuditQueryResult, T as AuditTrailQuery, _ as RepositoryCachePluginOptions, a as DateSequentialIdOptions, b as AuditOperation, c as SequentialIdOptions, d as getNextSequence, f as prefixedId, g as RepositoryCacheHandle, h as CacheOptions, i as CustomIdOptions, l as customIdPlugin, m as CacheAdapter, o as IdGenerator, p as sequentialId, r as multiTenantPlugin, s as PrefixedIdOptions, t as MultiTenantOptions, u as dateSequentialId, v as cachePlugin, w as AuditTrailOptions, x as AuditQueryOptions, y as AuditEntry } from "../multi-tenant.plugin-CFzMOuZP.mjs";
3
+ import { A as fieldFilterPlugin, C as MongoOperationsMethods, D as LeaseMethods, E as methodRegistryPlugin, I as batchOperationsPlugin, L as auditLogPlugin, M as elasticSearchPlugin, N as cascadePlugin, O as LeasePluginOptions, P as BatchOperationsMethods, R as AggregateHelpersMethods, S as observabilityPlugin, T as MethodRegistryRepository, _ as immutableStatesPlugin, a as uniqueField, b as ObservabilityOptions, c as TenantContext, d as SubdocumentMethods, f as subdocumentPlugin, g as ImmutableStatesPluginOptions, h as ImmutableClaimView, i as requireField, j as ElasticSearchOptions, k as leasePlugin, l as TenantStore, m as softDeletePlugin, n as blockIf, o as validationChainPlugin, p as SoftDeleteMethods, r as immutableField, s as timestampPlugin, t as autoInject, u as createTenantContext, v as AppendOnlyPluginOptions, w as mongoOperationsPlugin, x as OperationMetric, y as appendOnlyPlugin, z as aggregateHelpersPlugin } from "../validation-chain.plugin-DDQeEyk5.mjs";
4
4
  import { ChangeLogStore } from "@classytic/repo-core/sync";
5
5
  //#region src/plugins/change-log.plugin.d.ts
6
6
  interface ChangeLogPluginOptions {
@@ -15,4 +15,4 @@ interface ChangeLogPluginOptions {
15
15
  }
16
16
  declare function changeLogPlugin(options: ChangeLogPluginOptions): Plugin;
17
17
  //#endregion
18
- export { type AggregateHelpersMethods, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type CacheAdapter, type CacheOptions, type ChangeLogPluginOptions, type CustomIdOptions, type DateSequentialIdOptions, type ElasticSearchOptions, type IdGenerator, type LeaseMethods, type LeasePluginOptions, type MethodRegistryRepository, type MongoOperationsMethods, type MultiTenantOptions, type ObservabilityOptions, type OperationMetric, type PrefixedIdOptions, type RepositoryCacheHandle, type RepositoryCachePluginOptions, type SequentialIdOptions, type SoftDeleteMethods, type SubdocumentMethods, type TenantContext, type TenantStore, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, changeLogPlugin, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
18
+ export { type AggregateHelpersMethods, type AppendOnlyPluginOptions, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type CacheAdapter, type CacheOptions, type ChangeLogPluginOptions, type CustomIdOptions, type DateSequentialIdOptions, type ElasticSearchOptions, type IdGenerator, type ImmutableClaimView, type ImmutableStatesPluginOptions, type LeaseMethods, type LeasePluginOptions, type MethodRegistryRepository, type MongoOperationsMethods, type MultiTenantOptions, type ObservabilityOptions, type OperationMetric, type PrefixedIdOptions, type RepositoryCacheHandle, type RepositoryCachePluginOptions, type SequentialIdOptions, type SoftDeleteMethods, type SubdocumentMethods, type TenantContext, type TenantStore, aggregateHelpersPlugin, appendOnlyPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, changeLogPlugin, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, immutableStatesPlugin, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -1,5 +1,5 @@
1
- import { a as methodRegistryPlugin, c as getNextSequence, d as cachePlugin, f as batchOperationsPlugin, h as auditLogPlugin, i as multiTenantPlugin, l as prefixedId, m as auditTrailPlugin, n as softDeletePlugin, o as customIdPlugin, p as AuditTrailQuery, s as dateSequentialId, t as timestampPlugin, u as sequentialId } from "../timestamp.plugin-nG9Z1JEk.mjs";
2
- import { a as uniqueField, c as subdocumentPlugin, d as leasePlugin, f as fieldFilterPlugin, h as aggregateHelpersPlugin, i as requireField, l as observabilityPlugin, m as cascadePlugin, n as blockIf, o as validationChainPlugin, p as elasticSearchPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as mongoOperationsPlugin } from "../validation-chain.plugin-Dn_wZYLA.mjs";
1
+ import { a as methodRegistryPlugin, c as getNextSequence, d as cachePlugin, f as batchOperationsPlugin, h as auditLogPlugin, i as multiTenantPlugin, l as prefixedId, m as auditTrailPlugin, n as softDeletePlugin, o as customIdPlugin, p as AuditTrailQuery, s as dateSequentialId, t as timestampPlugin, u as sequentialId } from "../timestamp.plugin-Ch0O6DPX.mjs";
2
+ import { _ as aggregateHelpersPlugin, a as uniqueField, c as subdocumentPlugin, d as observabilityPlugin, f as mongoOperationsPlugin, g as cascadePlugin, h as elasticSearchPlugin, i as requireField, l as immutableStatesPlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as leasePlugin, r as immutableField, s as createTenantContext, t as autoInject, u as appendOnlyPlugin } from "../validation-chain.plugin-RxOzGuQy.mjs";
3
3
  //#region src/plugins/change-log.plugin.ts
4
4
  const PLUGIN_NAME = "changeLog";
5
5
  function isSkipped(context) {
@@ -70,4 +70,4 @@ function changeLogPlugin(options) {
70
70
  };
71
71
  }
72
72
  //#endregion
73
- export { AuditTrailQuery, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, changeLogPlugin, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
73
+ export { AuditTrailQuery, aggregateHelpersPlugin, appendOnlyPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, changeLogPlugin, createTenantContext, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, immutableStatesPlugin, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -1,4 +1,4 @@
1
- import { a as Repository, n as CreateRepositoryConfig } from "../create-repository-0TkQhcv-.mjs";
1
+ import { a as Repository, n as CreateRepositoryConfig } from "../create-repository-DXa3nMG6.mjs";
2
2
  import { Connection, Model, Schema } from "mongoose";
3
3
  import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server";
4
4
  //#region src/testkit/index.d.ts
@@ -1,4 +1,4 @@
1
- import { t as createRepository } from "../create-repository-CJATiAKX.mjs";
1
+ import { t as createRepository } from "../create-repository-BFVYm48p.mjs";
2
2
  import { createConnection } from "mongoose";
3
3
  //#region src/testkit/index.ts
4
4
  function idempotentStop(server) {
@@ -1,4 +1,4 @@
1
- import { l as OP_REGISTRY, s as ALL_OPERATIONS, t as HOOK_PRIORITY } from "./Repository-dbbgOGjK.mjs";
1
+ import { l as OP_REGISTRY, s as ALL_OPERATIONS, t as HOOK_PRIORITY } from "./Repository-CaDAJY98.mjs";
2
2
  import { n as warn } from "./logger-K7dPmu2U.mjs";
3
3
  import { t as createError } from "./error-BuByZwKU.mjs";
4
4
  import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
@@ -1101,6 +1101,14 @@ interface SessionOptions {
1101
1101
  * ```
1102
1102
  */
1103
1103
  bypassTenant?: boolean;
1104
+ /**
1105
+ * Per-call escape hatch for `appendOnlyPlugin` — deliberate
1106
+ * maintenance on an immutable-facts collection (backfill repair,
1107
+ * migration). Audited via `after:append-only-bypass`; grep for this
1108
+ * flag the same way you grep `bypassTenant` / `mode: 'hard'`. NOT a
1109
+ * substitute for access control — gate it at the host layer.
1110
+ */
1111
+ bypassAppendOnly?: boolean;
1104
1112
  /** Extensible — plugins can read custom fields from options */
1105
1113
  [key: string]: unknown;
1106
1114
  }
@@ -0,0 +1,23 @@
1
+ import { Connection } from "mongoose";
2
+ import { UsageStore } from "@classytic/repo-core/usage";
3
+ //#region src/usage/index.d.ts
4
+ /** Adapter-construction options. */
5
+ interface MongoUsageStoreOptions {
6
+ /**
7
+ * Mongoose connection. Defaults to `mongoose.connection` (the
8
+ * default global connection). Apps using a named connection pass
9
+ * it explicitly.
10
+ */
11
+ connection?: Connection;
12
+ /** Collection name. Default `usage_counters`. */
13
+ collectionName?: string;
14
+ /** Override the Mongoose model name. Default `MongoUsageCounter`. */
15
+ modelName?: string;
16
+ }
17
+ /**
18
+ * Build a Mongo-backed usage store. Idempotent — the underlying model
19
+ * is registered once per `(connection, modelName)` pair.
20
+ */
21
+ declare function createMongoUsageStore(options?: MongoUsageStoreOptions): UsageStore;
22
+ //#endregion
23
+ export { MongoUsageStoreOptions, createMongoUsageStore };
@@ -0,0 +1,67 @@
1
+ import { n as isDuplicateKeyError } from "../error-BuByZwKU.mjs";
2
+ import mongoose, { Schema } from "mongoose";
3
+ //#region src/usage/index.ts
4
+ /**
5
+ * Build a Mongo-backed usage store. Idempotent — the underlying model
6
+ * is registered once per `(connection, modelName)` pair.
7
+ */
8
+ function createMongoUsageStore(options = {}) {
9
+ const { connection, collectionName = "usage_counters", modelName = "MongoUsageCounter" } = options;
10
+ const model = getOrCreateModel(connection ?? mongoose, modelName, collectionName);
11
+ return {
12
+ name: "mongo",
13
+ async increment(bucket, amount) {
14
+ const filter = {
15
+ actor: bucket.actor,
16
+ period: bucket.period,
17
+ kind: bucket.kind
18
+ };
19
+ try {
20
+ await model.updateOne(filter, { $inc: { count: amount } }, { upsert: true });
21
+ } catch (err) {
22
+ if (!isDuplicateKeyError(err)) throw err;
23
+ await model.updateOne(filter, { $inc: { count: amount } }, { upsert: true });
24
+ }
25
+ },
26
+ async summary(actor, period) {
27
+ const rows = await model.find({
28
+ actor,
29
+ period
30
+ }).select("kind count").lean();
31
+ const out = {};
32
+ for (const row of rows) out[row.kind] = row.count;
33
+ return out;
34
+ }
35
+ };
36
+ }
37
+ function getOrCreateModel(conn, modelName, collectionName) {
38
+ const existing = "models" in conn ? conn.models[modelName] : void 0;
39
+ if (existing) return existing;
40
+ const schema = new Schema({
41
+ actor: {
42
+ type: String,
43
+ required: true
44
+ },
45
+ period: {
46
+ type: String,
47
+ required: true
48
+ },
49
+ kind: {
50
+ type: String,
51
+ required: true
52
+ },
53
+ count: {
54
+ type: Number,
55
+ required: true,
56
+ default: 0
57
+ }
58
+ }, { versionKey: false });
59
+ schema.index({
60
+ actor: 1,
61
+ period: 1,
62
+ kind: 1
63
+ }, { unique: true });
64
+ return conn.model(modelName, schema, collectionName);
65
+ }
66
+ //#endregion
67
+ export { createMongoUsageStore };
@@ -1,2 +1,2 @@
1
- import { a as IdType, c as isObjectIdInstance, d as filterResponseData, f as getFieldsForUser, i as configureLogger, l as isValidIdForType, m as createError, n as buildCrudSchemasFromMongooseSchema, o as getSchemaIdType, p as getMongooseProjection, r as createMemoryCache, t as buildCrudSchemasFromModel, u as createFieldPreset } from "../mongooseToJsonSchema-D2CyAMIC.mjs";
2
- export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType };
1
+ import { a as configureLogger, d as createFieldPreset, f as filterResponseData, h as createError, i as toPlain, l as isObjectIdInstance, m as getMongooseProjection, n as buildCrudSchemasFromMongooseSchema, o as IdType, p as getFieldsForUser, r as createMemoryCache, s as getSchemaIdType, t as buildCrudSchemasFromModel, u as isValidIdForType } from "../mongooseToJsonSchema-ChFKfZts.mjs";
2
+ export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, toPlain };
@@ -2,5 +2,6 @@ import { i as isValidIdForType, r as isObjectIdInstance, t as getSchemaIdType }
2
2
  import { t as configureLogger } from "../logger-K7dPmu2U.mjs";
3
3
  import { t as createError } from "../error-BuByZwKU.mjs";
4
4
  import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "../field-selection-FSQFLpsU.mjs";
5
- import { n as buildCrudSchemasFromMongooseSchema, r as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-khdzVo8x.mjs";
6
- export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType };
5
+ import { n as toPlain, t as createMemoryCache } from "../memory-cache-D3wyN_2w.mjs";
6
+ import { n as buildCrudSchemasFromMongooseSchema, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-CVnrKFDq.mjs";
7
+ export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getMongooseProjection, getSchemaIdType, isObjectIdInstance, isValidIdForType, toPlain };
@@ -1,4 +1,4 @@
1
- import { B as ObjectId, C as FieldPreset, G as Plugin, J as PopulateSpec, Q as RepositoryContext, _t as ValidationChainOptions, et as RepositoryInstance, it as SelectSpec, j as Logger, l as CascadeOptions, st as SoftDeleteOptions, ut as SortSpec, vt as ValidatorDefinition } from "./types-DNFMofHy.mjs";
1
+ import { B as ObjectId, C as FieldPreset, G as Plugin, J as PopulateSpec, Q as RepositoryContext, _t as ValidationChainOptions, et as RepositoryInstance, it as SelectSpec, j as Logger, l as CascadeOptions, st as SoftDeleteOptions, ut as SortSpec, vt as ValidatorDefinition } from "./types-BnRqAJVa.mjs";
2
2
  import { BulkWriteResult } from "@classytic/repo-core/repository";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { OffsetPaginationResult } from "@classytic/repo-core/pagination";
@@ -517,6 +517,67 @@ interface ObservabilityOptions {
517
517
  }
518
518
  declare function observabilityPlugin(options: ObservabilityOptions): Plugin;
519
519
  //#endregion
520
+ //#region src/plugins/append-only.plugin.d.ts
521
+ interface AppendOnlyPluginOptions {
522
+ /**
523
+ * Mutating ops to permit besides `create` / `createMany`. Use for a
524
+ * domain's ONE legitimate mutation path (e.g. GDPR `deleteMany`) —
525
+ * every allowed op still flows through the rest of the plugin stack
526
+ * (tenant scope, audit) as usual.
527
+ */
528
+ allow?: readonly string[] | undefined;
529
+ /**
530
+ * Hook priority for the fence. Default `HOOK_PRIORITY.POLICY` (100).
531
+ * Ledger repos that want the append-only refusal to win over other
532
+ * policy errors (e.g. a missing-tenant throw) register BELOW policy —
533
+ * flow's StockEvent uses `50` so the violation always reads
534
+ * "append-only", never "missing organizationId".
535
+ */
536
+ priority?: number | undefined;
537
+ }
538
+ declare function appendOnlyPlugin(options?: AppendOnlyPluginOptions): Plugin;
539
+ //#endregion
540
+ //#region src/plugins/immutable-states.plugin.d.ts
541
+ /** The slice of a `before:claim` context `allowClaim` predicates see. */
542
+ interface ImmutableClaimView {
543
+ id: unknown;
544
+ transition: {
545
+ from?: unknown | readonly unknown[];
546
+ to?: unknown;
547
+ field?: string;
548
+ where?: Record<string, unknown>;
549
+ };
550
+ /** Operator-form patch (`{ $set: {...}, ... }`). */
551
+ data: Record<string, unknown> | undefined;
552
+ }
553
+ interface ImmutableStatesPluginOptions {
554
+ /** States in which the document is frozen (e.g. `['posted']`). */
555
+ states: readonly string[];
556
+ /** State field on the doc — dotted paths supported. Default `'state'`. */
557
+ field?: string | undefined;
558
+ /**
559
+ * Context flag the OWNING ENGINE's verbs set to pass the guard
560
+ * (private handshake — see module doc). Default `'_immutableInternal'`.
561
+ */
562
+ internalFlag?: string | undefined;
563
+ /** Tenant field used to scope state lookups (mirrors the repo's scope). */
564
+ tenantField?: string | undefined;
565
+ /**
566
+ * Exempt specific claim shapes that legitimately touch frozen rows
567
+ * (audit-trail stamps like ledger's reverse-mark). Return `true` to
568
+ * allow. Runs only after the claim was determined to reach a frozen
569
+ * state — keep the predicate a strict fingerprint.
570
+ */
571
+ allowClaim?: ((view: ImmutableClaimView) => boolean) | undefined;
572
+ /** Domain error to throw. Default: 403 `IMMUTABLE_STATE_VIOLATION`. */
573
+ errorFactory?: ((ctx: {
574
+ id: unknown;
575
+ operation: string;
576
+ state?: string | undefined;
577
+ }) => Error) | undefined;
578
+ }
579
+ declare function immutableStatesPlugin(options: ImmutableStatesPluginOptions): Plugin;
580
+ //#endregion
520
581
  //#region src/plugins/soft-delete.plugin.d.ts
521
582
  /**
522
583
  * Soft delete plugin
@@ -759,4 +820,4 @@ declare function immutableField(field: string): ValidatorDefinition;
759
820
  */
760
821
  declare function uniqueField(field: string, errorMessage?: string): ValidatorDefinition;
761
822
  //#endregion
762
- export { BulkWriteResult as A, LeasePluginOptions as C, elasticSearchPlugin as D, ElasticSearchOptions as E, auditLogPlugin as M, AggregateHelpersMethods as N, cascadePlugin as O, aggregateHelpersPlugin as P, LeaseMethods as S, fieldFilterPlugin as T, observabilityPlugin as _, uniqueField as a, MethodRegistryRepository as b, TenantContext as c, SubdocumentMethods as d, subdocumentPlugin as f, OperationMetric as g, ObservabilityOptions as h, requireField as i, batchOperationsPlugin as j, BatchOperationsMethods as k, TenantStore as l, softDeletePlugin as m, blockIf as n, validationChainPlugin as o, SoftDeleteMethods as p, immutableField as r, timestampPlugin as s, autoInject as t, createTenantContext as u, MongoOperationsMethods as v, leasePlugin as w, methodRegistryPlugin as x, mongoOperationsPlugin as y };
823
+ export { fieldFilterPlugin as A, MongoOperationsMethods as C, LeaseMethods as D, methodRegistryPlugin as E, BulkWriteResult as F, batchOperationsPlugin as I, auditLogPlugin as L, elasticSearchPlugin as M, cascadePlugin as N, LeasePluginOptions as O, BatchOperationsMethods as P, AggregateHelpersMethods as R, observabilityPlugin as S, MethodRegistryRepository as T, immutableStatesPlugin as _, uniqueField as a, ObservabilityOptions as b, TenantContext as c, SubdocumentMethods as d, subdocumentPlugin as f, ImmutableStatesPluginOptions as g, ImmutableClaimView as h, requireField as i, ElasticSearchOptions as j, leasePlugin as k, TenantStore as l, softDeletePlugin as m, blockIf as n, validationChainPlugin as o, SoftDeleteMethods as p, immutableField as r, timestampPlugin as s, autoInject as t, createTenantContext as u, AppendOnlyPluginOptions as v, mongoOperationsPlugin as w, OperationMetric as x, appendOnlyPlugin as y, aggregateHelpersPlugin as z };
@@ -1,9 +1,10 @@
1
- import { s as ALL_OPERATIONS } from "./Repository-dbbgOGjK.mjs";
1
+ import { l as OP_REGISTRY, s as ALL_OPERATIONS } from "./Repository-CaDAJY98.mjs";
2
2
  import { _ as upsert } from "./update-1Lq9OMD2.mjs";
3
3
  import { n as warn } from "./logger-K7dPmu2U.mjs";
4
4
  import { t as createError } from "./error-BuByZwKU.mjs";
5
5
  import { r as getFieldsForUser } from "./field-selection-FSQFLpsU.mjs";
6
6
  import mongoose from "mongoose";
7
+ import { HOOK_PRIORITY } from "@classytic/repo-core/hooks";
7
8
  import { AsyncLocalStorage } from "node:async_hooks";
8
9
  //#region src/plugins/aggregate-helpers.plugin.ts
9
10
  /**
@@ -782,6 +783,224 @@ function observabilityPlugin(options) {
782
783
  };
783
784
  }
784
785
  //#endregion
786
+ //#region src/plugins/append-only.plugin.ts
787
+ /**
788
+ * Append-Only Plugin
789
+ *
790
+ * Turns a repository into an immutable-facts ledger: inserts land,
791
+ * everything else is refused at POLICY priority. The enforcement
792
+ * layer for event-sourced planes — flow's `StockEvent`,
793
+ * manufacturing's `ProductionEvent`, audit sinks, outbox archives —
794
+ * where a mutable row would silently corrupt the audit/time-travel
795
+ * story that the whole collection exists to provide.
796
+ *
797
+ * Grep-proof by construction: hand-fenced `update()`/`delete()`
798
+ * overrides only cover the verbs someone remembered to override; this
799
+ * plugin derives coverage from `OP_REGISTRY`, so every mutating op —
800
+ * including `claim`, `claimVersion`, `restore`, `bulkWrite`, and any
801
+ * op added to the registry later — is refused automatically.
802
+ *
803
+ * Allowed by default: `create`, `createMany` (appends). `getOrCreate`
804
+ * is registry-classified as a read and stays available — it can only
805
+ * INSERT or return the existing row, never mutate one, which is
806
+ * append-safe by definition (idempotency stores rely on it).
807
+ * Refused by default: `update`, `findOneAndUpdate`, `updateMany`,
808
+ * `delete`, `deleteMany`, `restore`, `claim`, `claimVersion`,
809
+ * `bulkWrite` (wholesale — mixed insert/update batches are ambiguous;
810
+ * append via `createMany` instead). Composed methods from other
811
+ * plugins (`incrementIfBelow`, lease ops) decompose into these
812
+ * registry ops internally, so the fence catches them too.
813
+ *
814
+ * TTL indexes are unaffected (server-side GC never touches the repo
815
+ * layer) — TTL-expiring an idempotency ledger still works. Raw
816
+ * `Model.*` driver calls bypass every plugin, this one included —
817
+ * the org-wide "never drop to Model.deleteOne" rule is what covers
818
+ * that hole, same as for tenant scope.
819
+ *
820
+ * **Escape hatches** (both audited via `after:append-only-bypass`):
821
+ * - Plugin-level `allow` — ops the DOMAIN legitimately needs, wired
822
+ * once at construction (e.g. `allow: ['deleteMany']` for a
823
+ * GDPR-erasure path the host gates behind admin roles).
824
+ * - Per-call `bypassAppendOnly: true` — deliberate one-off
825
+ * maintenance (backfill repair, migration), grep-auditable at the
826
+ * call site.
827
+ *
828
+ * `skipPlugins` is deliberately IGNORED — same policy as tenant scope
829
+ * and soft delete: integrity fences don't have a convenience bypass.
830
+ *
831
+ * @example
832
+ * const events = new Repository(StockEventModel, [
833
+ * multiTenantPlugin({ ... }),
834
+ * appendOnlyPlugin(),
835
+ * ]);
836
+ * await events.create({ ... }); // OK
837
+ * await events.update(id, { ... }); // throws 405 APPEND_ONLY_VIOLATION
838
+ * await events.deleteMany({ ... }, { bypassAppendOnly: true }); // audited bypass
839
+ */
840
+ const ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["create", "createMany"]);
841
+ function appendOnlyPlugin(options = {}) {
842
+ const allowed = /* @__PURE__ */ new Set([...ALWAYS_ALLOWED, ...options.allow ?? []]);
843
+ return {
844
+ name: "appendOnly",
845
+ apply(repo) {
846
+ const modelName = repo.Model?.modelName ?? "model";
847
+ for (const [op, meta] of Object.entries(OP_REGISTRY)) {
848
+ if (!meta.mutates || allowed.has(op)) continue;
849
+ repo.on(`before:${op}`, (context) => {
850
+ if (context.bypassAppendOnly === true) {
851
+ repo.emit("after:append-only-bypass", {
852
+ operation: op,
853
+ context
854
+ });
855
+ return;
856
+ }
857
+ throw createError(405, `${modelName} is append-only: '${op}' is not permitted. Rows on this collection are immutable facts (event ledger). Append a compensating row instead; for deliberate maintenance pass bypassAppendOnly: true (audited via 'after:append-only-bypass').`, {
858
+ code: "APPEND_ONLY_VIOLATION",
859
+ meta: {
860
+ operation: op,
861
+ model: modelName
862
+ }
863
+ });
864
+ }, { priority: options.priority ?? HOOK_PRIORITY.POLICY });
865
+ }
866
+ }
867
+ };
868
+ }
869
+ //#endregion
870
+ //#region src/plugins/immutable-states.plugin.ts
871
+ /** Ops this plugin has an explicit strategy for (see module doc). */
872
+ const STRATEGY_OPS = /* @__PURE__ */ new Set([
873
+ "create",
874
+ "createMany",
875
+ "update",
876
+ "delete",
877
+ "claimVersion",
878
+ "restore",
879
+ "findOneAndUpdate",
880
+ "updateMany",
881
+ "deleteMany",
882
+ "bulkWrite",
883
+ "claim"
884
+ ]);
885
+ function immutableStatesPlugin(options) {
886
+ const field = options.field ?? "state";
887
+ const internalFlag = options.internalFlag ?? "_immutableInternal";
888
+ const frozen = new Set(options.states);
889
+ const throwViolation = (ctx) => {
890
+ if (options.errorFactory) throw options.errorFactory(ctx);
891
+ throw createError(403, `Document ${String(ctx.id)} is in immutable state${ctx.state !== void 0 ? ` '${ctx.state}'` : ""} — '${ctx.operation}' is not permitted. Frozen rows change only through the owning engine's transition verbs.`, {
892
+ code: "IMMUTABLE_STATE_VIOLATION",
893
+ meta: { ...ctx }
894
+ });
895
+ };
896
+ return {
897
+ name: "immutableStates",
898
+ apply(repo) {
899
+ const unhandled = Object.entries(OP_REGISTRY).filter(([op, meta]) => meta.mutates && !STRATEGY_OPS.has(op)).map(([op]) => op);
900
+ if (unhandled.length > 0) throw createError(500, `immutableStatesPlugin has no strategy for mutating op(s): ${unhandled.join(", ")}. Mongokit added write ops this plugin predates — extend the plugin before using it.`, {
901
+ code: "IMMUTABLE_STATES_UNHANDLED_OPS",
902
+ meta: { unhandled }
903
+ });
904
+ const Model = repo.Model;
905
+ const getState = (doc) => field.split(".").reduce((acc, key) => acc?.[key], doc);
906
+ const scoped = (base, ctx) => {
907
+ const q = { ...base };
908
+ if (options.tenantField && ctx.query && options.tenantField in ctx.query) q[options.tenantField] = ctx.query[options.tenantField];
909
+ return q;
910
+ };
911
+ /** Raw-Model read — never re-enters the repo hook pipeline. */
912
+ const readStateById = async (ctx) => {
913
+ if (ctx.id === void 0 || ctx.id === null) return void 0;
914
+ const doc = await Model.findOne(scoped({ _id: ctx.id }, ctx)).select({ [field]: 1 }).lean();
915
+ return doc ? getState(doc) : void 0;
916
+ };
917
+ const guardById = (op) => async (ctx) => {
918
+ if (ctx[internalFlag]) return;
919
+ const state = await readStateById(ctx);
920
+ if (state !== void 0 && frozen.has(state)) throwViolation({
921
+ id: ctx.id,
922
+ operation: op,
923
+ state
924
+ });
925
+ };
926
+ for (const op of [
927
+ "update",
928
+ "delete",
929
+ "claimVersion",
930
+ "restore"
931
+ ]) repo.on(`before:${op}`, guardById(op));
932
+ repo.on("before:findOneAndUpdate", async (ctx) => {
933
+ if (ctx[internalFlag]) return;
934
+ const filter = ctx.query ?? {};
935
+ const doc = await Model.findOne(filter).select({
936
+ [field]: 1,
937
+ _id: 1
938
+ }).lean();
939
+ if (!doc) return;
940
+ const state = getState(doc);
941
+ if (state !== void 0 && frozen.has(state)) throwViolation({
942
+ id: doc._id,
943
+ operation: "findOneAndUpdate",
944
+ state
945
+ });
946
+ });
947
+ const guardMany = (op) => async (ctx) => {
948
+ if (ctx[internalFlag]) return;
949
+ const filter = ctx.query ?? {};
950
+ const hit = await Model.exists({ $and: [filter, { [field]: { $in: [...frozen] } }] });
951
+ if (hit) throwViolation({
952
+ id: hit._id,
953
+ operation: op,
954
+ state: void 0
955
+ });
956
+ };
957
+ repo.on("before:updateMany", guardMany("updateMany"));
958
+ repo.on("before:deleteMany", guardMany("deleteMany"));
959
+ repo.on("before:bulkWrite", (ctx) => {
960
+ if (ctx[internalFlag]) return;
961
+ throwViolation({
962
+ id: void 0,
963
+ operation: "bulkWrite",
964
+ state: void 0
965
+ });
966
+ });
967
+ repo.on("before:claim", async (rawCtx) => {
968
+ const ctx = rawCtx;
969
+ if (ctx[internalFlag]) return;
970
+ const t = ctx.transition;
971
+ if (!t) return;
972
+ if ((t.field ?? "status") === field) {
973
+ const fromSpec = t.from;
974
+ if (!(Array.isArray(fromSpec) ? fromSpec.some((f) => frozen.has(String(f))) : frozen.has(String(fromSpec)))) return;
975
+ if (options.allowClaim?.({
976
+ id: ctx.id,
977
+ transition: t,
978
+ data: ctx.data
979
+ })) return;
980
+ throwViolation({
981
+ id: ctx.id,
982
+ operation: "claim",
983
+ state: void 0
984
+ });
985
+ }
986
+ const state = await readStateById(ctx);
987
+ if (state !== void 0 && frozen.has(state)) {
988
+ if (options.allowClaim?.({
989
+ id: ctx.id,
990
+ transition: t,
991
+ data: ctx.data
992
+ })) return;
993
+ throwViolation({
994
+ id: ctx.id,
995
+ operation: "claim",
996
+ state
997
+ });
998
+ }
999
+ });
1000
+ }
1001
+ };
1002
+ }
1003
+ //#endregion
785
1004
  //#region src/plugins/subdocument.plugin.ts
786
1005
  /**
787
1006
  * Subdocument plugin for managing nested arrays
@@ -1053,4 +1272,4 @@ function uniqueField(field, errorMessage) {
1053
1272
  };
1054
1273
  }
1055
1274
  //#endregion
1056
- export { uniqueField as a, subdocumentPlugin as c, leasePlugin as d, fieldFilterPlugin as f, aggregateHelpersPlugin as h, requireField as i, observabilityPlugin as l, cascadePlugin as m, blockIf as n, validationChainPlugin as o, elasticSearchPlugin as p, immutableField as r, createTenantContext as s, autoInject as t, mongoOperationsPlugin as u };
1275
+ export { aggregateHelpersPlugin as _, uniqueField as a, subdocumentPlugin as c, observabilityPlugin as d, mongoOperationsPlugin as f, cascadePlugin as g, elasticSearchPlugin as h, requireField as i, immutableStatesPlugin as l, fieldFilterPlugin as m, blockIf as n, validationChainPlugin as o, leasePlugin as p, immutableField as r, createTenantContext as s, autoInject as t, appendOnlyPlugin as u };