@classytic/mongokit 3.18.0 → 3.20.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 (49) hide show
  1. package/README.md +66 -0
  2. package/dist/{PaginationEngine-BcvaXBQ-.mjs → PaginationEngine-BT1ywu9k.mjs} +327 -6
  3. package/dist/PaginationEngine-DYpOD2wY.d.mts +102 -0
  4. package/dist/{Repository-B01VW_fU.mjs → Repository-dbbgOGjK.mjs} +85 -11
  5. package/dist/actions/index.d.mts +1 -1
  6. package/dist/actions/index.mjs +2 -2
  7. package/dist/adapter/index.d.mts +10 -2
  8. package/dist/adapter/index.mjs +1 -1
  9. package/dist/{adapter-DYEMkLwK.mjs → adapter-CW1EL0fF.mjs} +10 -1
  10. package/dist/ai/index.d.mts +1 -2
  11. package/dist/ai/index.mjs +3 -2
  12. package/dist/better-auth/index.d.mts +0 -1
  13. package/dist/better-auth/index.mjs +2 -2
  14. package/dist/create-repository-0TkQhcv-.d.mts +1757 -0
  15. package/dist/create-repository-CJATiAKX.mjs +29 -0
  16. package/dist/{id-resolution-CnN0YA8t.mjs → id-resolution-P2OzcyRl.mjs} +10 -5
  17. package/dist/{index-fi91ycQc.d.mts → index-Cg3SWugF.d.mts} +1 -3
  18. package/dist/index.d.mts +11 -1666
  19. package/dist/index.mjs +51 -37
  20. package/dist/lock/index.d.mts +0 -1
  21. package/dist/{mongooseToJsonSchema-BdQJU39w.d.mts → mongooseToJsonSchema-D2CyAMIC.d.mts} +9 -5
  22. package/dist/{mongooseToJsonSchema-CBEQmeEa.mjs → mongooseToJsonSchema-khdzVo8x.mjs} +1 -1
  23. package/dist/multi-tenant.plugin-CTsWz0XK.d.mts +486 -0
  24. package/dist/pagination/PaginationEngine.d.mts +1 -102
  25. package/dist/pagination/PaginationEngine.mjs +1 -1
  26. package/dist/plugins/index.d.mts +3 -3
  27. package/dist/plugins/index.mjs +2 -1
  28. package/dist/query/primitives/coercion.d.mts +1 -1
  29. package/dist/query/primitives/coercion.mjs +1 -1
  30. package/dist/query/primitives/geo.d.mts +1 -1
  31. package/dist/query/primitives/indexes.d.mts +1 -1
  32. package/dist/testkit/index.d.mts +128 -0
  33. package/dist/testkit/index.mjs +139 -0
  34. package/dist/{validation-chain.plugin-DJtC0KJ-.mjs → timestamp.plugin-nG9Z1JEk.mjs} +5 -1056
  35. package/dist/{types-CU3SEDbD.d.mts → types-DNFMofHy.d.mts} +51 -22
  36. package/dist/{update-CvCMv5Cl.mjs → update-1Lq9OMD2.mjs} +5 -5
  37. package/dist/utils/index.d.mts +1 -1
  38. package/dist/utils/index.mjs +5 -5
  39. package/dist/validation-chain.plugin-Dn_wZYLA.mjs +1056 -0
  40. package/dist/{validation-chain.plugin-BpMfy18N.d.mts → validation-chain.plugin-buy3uvyk.d.mts} +2 -487
  41. package/package.json +16 -7
  42. package/dist/cursor-_SbWVAn-.mjs +0 -254
  43. package/dist/sort-Ca1BPfvE.mjs +0 -72
  44. /package/dist/{coercion-TRMmR49h.d.mts → coercion-BjdPX3YF.d.mts} +0 -0
  45. /package/dist/{error-BTeepSNL.mjs → error-BuByZwKU.mjs} +0 -0
  46. /package/dist/{field-selection-CQW3WBiK.mjs → field-selection-FSQFLpsU.mjs} +0 -0
  47. /package/dist/{indexes-CgOF7Wjd.d.mts → indexes-DIjMpYES.d.mts} +0 -0
  48. /package/dist/{logger-B8wf-Mtb.mjs → logger-K7dPmu2U.mjs} +0 -0
  49. /package/dist/{chunk-CfYAbeIz.mjs → rolldown-runtime-D7D4PA-g.mjs} +0 -0
@@ -1,103 +1,2 @@
1
- import { A as KeysetPaginationOptions, U as PaginationConfig, V as OffsetPaginationOptions, i as AnyDocument, n as AggregatePaginationOptions } from "../types-CU3SEDbD.mjs";
2
- import { Model } from "mongoose";
3
- import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
4
-
5
- //#region src/pagination/PaginationEngine.d.ts
6
- /**
7
- * Internal pagination config with required values
8
- */
9
- interface ResolvedPaginationConfig {
10
- defaultLimit: number;
11
- maxLimit: number;
12
- maxPage: number;
13
- deepPageThreshold: number;
14
- cursorVersion: number;
15
- minCursorVersion: number;
16
- strictKeysetSortFields: string[] | undefined;
17
- useEstimatedCount: boolean;
18
- }
19
- /**
20
- * Production-grade pagination engine for MongoDB
21
- * Supports offset, keyset (cursor), and aggregate pagination
22
- */
23
- declare class PaginationEngine<TDoc = AnyDocument> {
24
- readonly Model: Model<TDoc>;
25
- readonly config: ResolvedPaginationConfig;
26
- /**
27
- * Lazily-cached schema index snapshot used by stream() to decide whether
28
- * to emit the "missing compound index" warning. Computed on first use.
29
- */
30
- private _cachedSchemaIndexes;
31
- /**
32
- * Create a new pagination engine
33
- *
34
- * @param Model - Mongoose model to paginate
35
- * @param config - Pagination configuration
36
- */
37
- constructor(Model: Model<TDoc, any, any, any>, config?: PaginationConfig);
38
- /** Memoized schema index lookup — avoids re-walking schema on every stream(). */
39
- private _getSchemaIndexes;
40
- /**
41
- * Offset-based pagination using skip/limit
42
- * Best for small datasets and when users need random page access
43
- * O(n) performance - slower for deep pages
44
- *
45
- * @param options - Pagination options
46
- * @returns Pagination result with total count
47
- *
48
- * @example
49
- * const result = await engine.paginate({
50
- * filters: { status: 'active' },
51
- * sort: { createdAt: -1 },
52
- * page: 1,
53
- * limit: 20
54
- * });
55
- * console.log(result.data, result.total, result.hasNext);
56
- */
57
- paginate(options?: OffsetPaginationOptions): Promise<OffsetPaginationResult<TDoc>>;
58
- /**
59
- * Keyset (cursor-based) pagination for high-performance streaming
60
- * Best for large datasets, infinite scroll, real-time feeds
61
- * O(1) performance - consistent speed regardless of position
62
- *
63
- * @param options - Pagination options (sort is required)
64
- * @returns Pagination result with next cursor
65
- *
66
- * @example
67
- * // First page
68
- * const page1 = await engine.stream({
69
- * sort: { createdAt: -1 },
70
- * limit: 20
71
- * });
72
- *
73
- * // Next page using cursor
74
- * const page2 = await engine.stream({
75
- * sort: { createdAt: -1 },
76
- * after: page1.next,
77
- * limit: 20
78
- * });
79
- */
80
- stream(options: KeysetPaginationOptions): Promise<KeysetPaginationResult<TDoc>>;
81
- /**
82
- * Aggregate pipeline with pagination
83
- * Best for complex queries requiring aggregation stages
84
- * Uses $facet to combine results and count in single query
85
- *
86
- * @param options - Aggregation options
87
- * @returns Pagination result with total count
88
- *
89
- * @example
90
- * const result = await engine.aggregatePaginate({
91
- * pipeline: [
92
- * { $match: { status: 'active' } },
93
- * { $group: { _id: '$category', count: { $sum: 1 } } },
94
- * { $sort: { count: -1 } }
95
- * ],
96
- * page: 1,
97
- * limit: 20
98
- * });
99
- */
100
- aggregatePaginate(options?: AggregatePaginationOptions): Promise<AggregatePaginationResult<TDoc>>;
101
- }
102
- //#endregion
1
+ import { t as PaginationEngine } from "../PaginationEngine-DYpOD2wY.mjs";
103
2
  export { PaginationEngine };
@@ -1,2 +1,2 @@
1
- import { t as PaginationEngine } from "../PaginationEngine-BcvaXBQ-.mjs";
1
+ import { t as PaginationEngine } from "../PaginationEngine-BT1ywu9k.mjs";
2
2
  export { PaginationEngine };
@@ -1,7 +1,7 @@
1
- import { G as Plugin } from "../types-CU3SEDbD.mjs";
2
- import { $ as AuditEntry, B as IdGenerator, C as LeasePluginOptions, D as elasticSearchPlugin, E as ElasticSearchOptions, F as MultiTenantOptions, G as getNextSequence, H as SequentialIdOptions, J as CacheAdapter, K as prefixedId, L as multiTenantPlugin, M as auditLogPlugin, N as AggregateHelpersMethods, O as cascadePlugin, P as aggregateHelpersPlugin, Q as cachePlugin, R as CustomIdOptions, S as LeaseMethods, T as fieldFilterPlugin, U as customIdPlugin, V as PrefixedIdOptions, W as dateSequentialId, X as RepositoryCacheHandle, Y as CacheOptions, Z as RepositoryCachePluginOptions, _ as observabilityPlugin, a as uniqueField, at as AuditTrailQuery, b as MethodRegistryRepository, c as TenantContext, d as SubdocumentMethods, et as AuditOperation, f as subdocumentPlugin, g as OperationMetric, h as ObservabilityOptions, i as requireField, it as AuditTrailOptions, j as batchOperationsPlugin, k as BatchOperationsMethods, l as TenantStore, m as softDeletePlugin, n as blockIf, nt as AuditQueryResult, o as validationChainPlugin, ot as auditTrailPlugin, p as SoftDeleteMethods, q as sequentialId, r as immutableField, rt as AuditTrailMethods, s as timestampPlugin, t as autoInject, tt as AuditQueryOptions, u as createTenantContext, v as MongoOperationsMethods, w as leasePlugin, x as methodRegistryPlugin, y as mongoOperationsPlugin, z as DateSequentialIdOptions } from "../validation-chain.plugin-BpMfy18N.mjs";
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";
3
4
  import { ChangeLogStore } from "@classytic/repo-core/sync";
4
-
5
5
  //#region src/plugins/change-log.plugin.d.ts
6
6
  interface ChangeLogPluginOptions {
7
7
  /** Durable feed implementing the repo-core/sync contract. */
@@ -1,4 +1,5 @@
1
- import { A as auditLogPlugin, C as getNextSequence, D as batchOperationsPlugin, E as cachePlugin, O as AuditTrailQuery, S as dateSequentialId, T as sequentialId, _ as softDeletePlugin, a as uniqueField, b as methodRegistryPlugin, c as subdocumentPlugin, d as leasePlugin, f as fieldFilterPlugin, g as timestampPlugin, h as aggregateHelpersPlugin, i as requireField, k as auditTrailPlugin, 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, w as prefixedId, x as customIdPlugin, y as multiTenantPlugin } from "../validation-chain.plugin-DJtC0KJ-.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-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";
2
3
  //#region src/plugins/change-log.plugin.ts
3
4
  const PLUGIN_NAME = "changeLog";
4
5
  function isSkipped(context) {
@@ -1,2 +1,2 @@
1
- import { a as coerceHeuristic, i as coerceFieldValue, n as SchemaPathsLike, o as coerceToType, r as buildFieldTypeMap, s as normalizeMongooseType, t as FieldType } from "../../coercion-TRMmR49h.mjs";
1
+ import { a as coerceHeuristic, i as coerceFieldValue, n as SchemaPathsLike, o as coerceToType, r as buildFieldTypeMap, s as normalizeMongooseType, t as FieldType } from "../../coercion-BjdPX3YF.mjs";
2
2
  export { FieldType, SchemaPathsLike, buildFieldTypeMap, coerceFieldValue, coerceHeuristic, coerceToType, normalizeMongooseType };
@@ -1,4 +1,4 @@
1
- import { r as isObjectIdInstance } from "../../id-resolution-CnN0YA8t.mjs";
1
+ import { r as isObjectIdInstance } from "../../id-resolution-P2OzcyRl.mjs";
2
2
  import mongoose from "mongoose";
3
3
  //#region src/query/primitives/coercion.ts
4
4
  /**
@@ -14,7 +14,7 @@
14
14
  * `[nearSphere]`, or `[geoWithin]` operator on a field.
15
15
  */
16
16
  /** Geo operators recognized by the parser */
17
- declare const GEO_OPERATORS: readonly ["near", "nearSphere", "geoWithin", "withinRadius"];
17
+ declare const GEO_OPERATORS: readonly ['near', 'nearSphere', 'geoWithin', 'withinRadius'];
18
18
  type GeoOperator = (typeof GEO_OPERATORS)[number];
19
19
  /**
20
20
  * True iff `operator` is one of the geo operators we handle.
@@ -1,2 +1,2 @@
1
- import { n as SchemaIndexes, r as extractSchemaIndexes, t as IndexableSchema } from "../../indexes-CgOF7Wjd.mjs";
1
+ import { n as SchemaIndexes, r as extractSchemaIndexes, t as IndexableSchema } from "../../indexes-DIjMpYES.mjs";
2
2
  export { IndexableSchema, SchemaIndexes, extractSchemaIndexes };
@@ -0,0 +1,128 @@
1
+ import { a as Repository, n as CreateRepositoryConfig } from "../create-repository-0TkQhcv-.mjs";
2
+ import { Connection, Model, Schema } from "mongoose";
3
+ import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server";
4
+ //#region src/testkit/index.d.ts
5
+ interface MongoMemoryOptions {
6
+ /**
7
+ * Boot a single-node replica set instead of a standalone mongod. Required for
8
+ * any code that opens a `session.startTransaction()` — a standalone server
9
+ * rejects it with error 263. Slightly slower to start (~2-3s). Default false.
10
+ */
11
+ replset?: boolean;
12
+ /** Database name baked into the generated URI. Default `"mongokit-test"`. */
13
+ dbName?: string;
14
+ /**
15
+ * Use this URI instead of spinning an in-memory server (real MongoDB, CI
16
+ * replica set). Falls back to `process.env.MONGODB_URI`. When either is set,
17
+ * `stop()` is a no-op — the caller owns the server's lifecycle.
18
+ */
19
+ uri?: string;
20
+ }
21
+ interface MongoMemoryHandle {
22
+ /** Connection string for the running (or external) server. */
23
+ uri: string;
24
+ /** The in-memory server; `undefined` when an external `uri` is used. */
25
+ server?: MongoMemoryServer | MongoMemoryReplSet;
26
+ /** Stop the in-memory server. No-op for an external URI. Idempotent. */
27
+ stop(): Promise<void>;
28
+ }
29
+ /**
30
+ * Start an ephemeral MongoDB (or resolve an external one) and return its URI +
31
+ * a `stop()` teardown. Prefer `createTestConnection` / `createTestRepository`
32
+ * unless you need the server without a mongoose connection.
33
+ */
34
+ declare function createMongoMemory(options?: MongoMemoryOptions): Promise<MongoMemoryHandle>;
35
+ interface TestConnection {
36
+ /** An isolated mongoose connection (never the global `mongoose.connection`). */
37
+ connection: Connection;
38
+ uri: string;
39
+ /** Empty every collection on this connection — call in `beforeEach`. */
40
+ clear(): Promise<void>;
41
+ /** Close the connection and stop the server. Idempotent. */
42
+ close(): Promise<void>;
43
+ }
44
+ /** Drop every document in every collection on a connection (models preserved). */
45
+ declare function clearCollections(connection: Connection): Promise<void>;
46
+ /**
47
+ * Start (or resolve) a server AND open an isolated mongoose connection over it.
48
+ * Each call gets its own connection — model registries are per-connection, so
49
+ * repeated boots in one process never collide.
50
+ */
51
+ declare function createTestConnection(options?: MongoMemoryOptions): Promise<TestConnection>;
52
+ /**
53
+ * Scoped harness: start a connection, run `fn`, and tear down in a `finally`
54
+ * even if `fn` throws.
55
+ *
56
+ * ```ts
57
+ * await withMongoMemory(async ({ connection }) => {
58
+ * const repo = createRepository(connection.model("User", userSchema));
59
+ * // ... assertions ...
60
+ * });
61
+ * ```
62
+ */
63
+ declare function withMongoMemory<T>(fn: (ctx: TestConnection) => Promise<T> | T, options?: MongoMemoryOptions): Promise<T>;
64
+ interface CreateTestRepositoryOptions<TDoc> extends MongoMemoryOptions {
65
+ /** Model / collection name (unique per test to avoid cross-file collisions). */
66
+ name: string;
67
+ /** Mongoose schema for the model. */
68
+ schema: Schema<TDoc>;
69
+ /** mongokit `createRepository` config — tenant, softDelete, plugins, cache… */
70
+ config?: CreateRepositoryConfig;
71
+ }
72
+ interface TestRepository<TDoc> {
73
+ /** A live mongokit repository over an in-memory collection. */
74
+ repository: Repository<TDoc>;
75
+ /** The underlying mongoose model (bound to the isolated connection). */
76
+ model: Model<TDoc>;
77
+ connection: Connection;
78
+ uri: string;
79
+ /** Empty this repository's collection. */
80
+ clear(): Promise<void>;
81
+ /** Close the connection and stop the server. Idempotent. */
82
+ close(): Promise<void>;
83
+ }
84
+ /**
85
+ * The fast path for testing a mongokit repository: spins an in-memory server,
86
+ * opens an isolated connection, registers the model on it, builds indexes, and
87
+ * returns a ready `Repository` (plus `clear()` / `close()`).
88
+ *
89
+ * ```ts
90
+ * const t = await createTestRepository({ name: "Order", schema: orderSchema,
91
+ * config: { softDelete: true } });
92
+ * await t.repository.create({ total: 10 });
93
+ * await t.close();
94
+ * ```
95
+ */
96
+ declare function createTestRepository<TDoc>(options: CreateTestRepositoryOptions<TDoc>): Promise<TestRepository<TDoc>>;
97
+ /** The context a module receives from the Mongo backend (live connection + URI). */
98
+ interface MongoTestContext {
99
+ connection: Connection;
100
+ uri: string;
101
+ }
102
+ /**
103
+ * A backend usable by `@classytic/arc-testkit`'s `bootModuleApp`. Structurally
104
+ * a `TestBackend<MongoTestContext>` — no import of arc-testkit, so mongokit
105
+ * never depends on it (one-way: arc-testkit consumes this by shape).
106
+ */
107
+ interface MongoMemoryBackend {
108
+ setup(): Promise<{
109
+ ctx: MongoTestContext;
110
+ teardown: () => Promise<void>;
111
+ }>;
112
+ }
113
+ /**
114
+ * A real in-memory Mongo backend for arc-testkit:
115
+ *
116
+ * ```ts
117
+ * import { bootModuleApp } from "@classytic/arc-testkit";
118
+ * import { mongoMemoryBackend } from "@classytic/mongokit/testkit";
119
+ *
120
+ * const t = await bootModuleApp(
121
+ * ({ connection }) => [createAccountingModule({ connection, permissions })],
122
+ * { backend: mongoMemoryBackend() },
123
+ * );
124
+ * ```
125
+ */
126
+ declare function mongoMemoryBackend(options?: MongoMemoryOptions): MongoMemoryBackend;
127
+ //#endregion
128
+ export { CreateTestRepositoryOptions, MongoMemoryBackend, MongoMemoryHandle, MongoMemoryOptions, MongoTestContext, TestConnection, TestRepository, clearCollections, createMongoMemory, createTestConnection, createTestRepository, mongoMemoryBackend, withMongoMemory };
@@ -0,0 +1,139 @@
1
+ import { t as createRepository } from "../create-repository-CJATiAKX.mjs";
2
+ import { createConnection } from "mongoose";
3
+ //#region src/testkit/index.ts
4
+ function idempotentStop(server) {
5
+ let stopped = false;
6
+ return async () => {
7
+ if (stopped) return;
8
+ stopped = true;
9
+ await server.stop().catch(() => void 0);
10
+ };
11
+ }
12
+ /**
13
+ * Start an ephemeral MongoDB (or resolve an external one) and return its URI +
14
+ * a `stop()` teardown. Prefer `createTestConnection` / `createTestRepository`
15
+ * unless you need the server without a mongoose connection.
16
+ */
17
+ async function createMongoMemory(options = {}) {
18
+ const externalUri = options.uri ?? process.env.MONGODB_URI;
19
+ if (externalUri) return {
20
+ uri: externalUri,
21
+ stop: async () => void 0
22
+ };
23
+ const { MongoMemoryServer, MongoMemoryReplSet } = await import("mongodb-memory-server");
24
+ const dbName = options.dbName ?? "mongokit-test";
25
+ if (options.replset) {
26
+ const rs = await MongoMemoryReplSet.create({ replSet: { count: 1 } });
27
+ return {
28
+ uri: rs.getUri(dbName),
29
+ server: rs,
30
+ stop: idempotentStop(rs)
31
+ };
32
+ }
33
+ const server = await MongoMemoryServer.create();
34
+ return {
35
+ uri: server.getUri(dbName),
36
+ server,
37
+ stop: idempotentStop(server)
38
+ };
39
+ }
40
+ /** Drop every document in every collection on a connection (models preserved). */
41
+ async function clearCollections(connection) {
42
+ const collections = await connection.db?.collections();
43
+ if (!collections) return;
44
+ await Promise.all(collections.map((c) => c.deleteMany({})));
45
+ }
46
+ /**
47
+ * Start (or resolve) a server AND open an isolated mongoose connection over it.
48
+ * Each call gets its own connection — model registries are per-connection, so
49
+ * repeated boots in one process never collide.
50
+ */
51
+ async function createTestConnection(options = {}) {
52
+ const handle = await createMongoMemory(options);
53
+ const connection = await createConnection(handle.uri).asPromise();
54
+ let closed = false;
55
+ return {
56
+ connection,
57
+ uri: handle.uri,
58
+ clear: () => clearCollections(connection),
59
+ close: async () => {
60
+ if (closed) return;
61
+ closed = true;
62
+ await connection.close().catch(() => void 0);
63
+ await handle.stop();
64
+ }
65
+ };
66
+ }
67
+ /**
68
+ * Scoped harness: start a connection, run `fn`, and tear down in a `finally`
69
+ * even if `fn` throws.
70
+ *
71
+ * ```ts
72
+ * await withMongoMemory(async ({ connection }) => {
73
+ * const repo = createRepository(connection.model("User", userSchema));
74
+ * // ... assertions ...
75
+ * });
76
+ * ```
77
+ */
78
+ async function withMongoMemory(fn, options = {}) {
79
+ const ctx = await createTestConnection(options);
80
+ try {
81
+ return await fn(ctx);
82
+ } finally {
83
+ await ctx.close();
84
+ }
85
+ }
86
+ /**
87
+ * The fast path for testing a mongokit repository: spins an in-memory server,
88
+ * opens an isolated connection, registers the model on it, builds indexes, and
89
+ * returns a ready `Repository` (plus `clear()` / `close()`).
90
+ *
91
+ * ```ts
92
+ * const t = await createTestRepository({ name: "Order", schema: orderSchema,
93
+ * config: { softDelete: true } });
94
+ * await t.repository.create({ total: 10 });
95
+ * await t.close();
96
+ * ```
97
+ */
98
+ async function createTestRepository(options) {
99
+ const tc = await createTestConnection(options);
100
+ const model = tc.connection.model(options.name, options.schema);
101
+ await model.init();
102
+ return {
103
+ repository: createRepository(model, options.config),
104
+ model,
105
+ connection: tc.connection,
106
+ uri: tc.uri,
107
+ clear: async () => {
108
+ await model.deleteMany({});
109
+ },
110
+ close: tc.close
111
+ };
112
+ }
113
+ /**
114
+ * A real in-memory Mongo backend for arc-testkit:
115
+ *
116
+ * ```ts
117
+ * import { bootModuleApp } from "@classytic/arc-testkit";
118
+ * import { mongoMemoryBackend } from "@classytic/mongokit/testkit";
119
+ *
120
+ * const t = await bootModuleApp(
121
+ * ({ connection }) => [createAccountingModule({ connection, permissions })],
122
+ * { backend: mongoMemoryBackend() },
123
+ * );
124
+ * ```
125
+ */
126
+ function mongoMemoryBackend(options = {}) {
127
+ return { async setup() {
128
+ const tc = await createTestConnection(options);
129
+ return {
130
+ ctx: {
131
+ connection: tc.connection,
132
+ uri: tc.uri
133
+ },
134
+ teardown: tc.close
135
+ };
136
+ } };
137
+ }
138
+ //#endregion
139
+ export { clearCollections, createMongoMemory, createTestConnection, createTestRepository, mongoMemoryBackend, withMongoMemory };