@classytic/repo-core 0.8.1 → 0.9.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 (71) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/adapter/types.d.mts +0 -1
  3. package/dist/adapter/widen.d.mts +0 -1
  4. package/dist/aggregate/normalize.d.mts +0 -1
  5. package/dist/better-auth/index.mjs +1 -1
  6. package/dist/cache/engine.d.mts +0 -1
  7. package/dist/cache/memory-adapter.d.mts +0 -1
  8. package/dist/cache/memory-adapter.mjs +13 -0
  9. package/dist/cache/plugin/context.mjs +1 -1
  10. package/dist/cache/plugin/index.d.mts +3 -4
  11. package/dist/cache/plugin/index.mjs +1 -1
  12. package/dist/cache/timeout-adapter.d.mts +0 -1
  13. package/dist/context/index.d.mts +1 -1
  14. package/dist/context/types.d.mts +0 -1
  15. package/dist/errors/contract.d.mts +0 -1
  16. package/dist/errors/create-error.d.mts +0 -1
  17. package/dist/errors/duplicate-key.d.mts +0 -1
  18. package/dist/errors/schema.d.mts +27 -27
  19. package/dist/errors/types.d.mts +11 -11
  20. package/dist/events/emit.d.mts +0 -1
  21. package/dist/filter/builders.d.mts +2 -3
  22. package/dist/filter/builders.mjs +1 -1
  23. package/dist/filter/from-record.d.mts +0 -1
  24. package/dist/filter/from-record.mjs +2 -2
  25. package/dist/filter/guard.d.mts +0 -1
  26. package/dist/filter/guard.mjs +1 -1
  27. package/dist/filter/index.d.mts +1 -1
  28. package/dist/filter/index.mjs +1 -1
  29. package/dist/filter/match.d.mts +0 -1
  30. package/dist/filter/scope.d.mts +0 -1
  31. package/dist/filter/walk.d.mts +0 -1
  32. package/dist/hooks/engine.d.mts +0 -1
  33. package/dist/hooks/events.d.mts +51 -51
  34. package/dist/hooks/priority.d.mts +8 -4
  35. package/dist/hooks/priority.mjs +9 -0
  36. package/dist/lookup/index.d.mts +1 -1
  37. package/dist/lookup/types.d.mts +0 -1
  38. package/dist/operations/registry.d.mts +0 -1
  39. package/dist/pagination/canonical.d.mts +0 -1
  40. package/dist/pagination/cursor.d.mts +0 -1
  41. package/dist/pagination/keyset.d.mts +0 -1
  42. package/dist/pagination/offset.d.mts +0 -1
  43. package/dist/plugins/tenant-helpers.d.mts +0 -1
  44. package/dist/query-parser/coerce.d.mts +1 -2
  45. package/dist/query-parser/coerce.mjs +2 -2
  46. package/dist/query-parser/parse-url.d.mts +0 -1
  47. package/dist/query-parser/parse-url.mjs +2 -2
  48. package/dist/query-parser/reserved.mjs +1 -1
  49. package/dist/query-parser/types.d.mts +0 -1
  50. package/dist/repository/archive.d.mts +0 -1
  51. package/dist/repository/base.d.mts +0 -1
  52. package/dist/repository/distribution.d.mts +0 -1
  53. package/dist/repository/options.d.mts +1 -1
  54. package/dist/repository/plugin-types.d.mts +0 -1
  55. package/dist/repository/purge.d.mts +0 -1
  56. package/dist/repository/types.d.mts +38 -10
  57. package/dist/schema/field-rules.d.mts +0 -1
  58. package/dist/schema/field-rules.mjs +1 -1
  59. package/dist/schema/generator.d.mts +0 -1
  60. package/dist/schema/types.d.mts +14 -6
  61. package/dist/sync/index.d.mts +1 -1
  62. package/dist/sync/index.mjs +2 -0
  63. package/dist/tenant/resolve.d.mts +1 -2
  64. package/dist/testing/conformance.d.mts +0 -1
  65. package/dist/testing/conformance.mjs +1 -1
  66. package/dist/testing/lock-conformance.d.mts +0 -1
  67. package/dist/testing/types.d.mts +1 -2
  68. package/dist/update/builders.d.mts +0 -1
  69. package/dist/update/compile.d.mts +0 -1
  70. package/dist/update/guard.d.mts +0 -1
  71. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,42 @@ All notable changes to `@classytic/repo-core` are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.9.0] - 2026-07-11
8
+
9
+ ### Added — `StandardRepo.getByIds` (batch point-read)
10
+
11
+ - **`getByIds?(ids: readonly string[], options?: FindAllOptions): Promise<Map<string, TDoc>>`**
12
+ added to `StandardRepo` — the `$in` / `IN (...)` counterpart to `getById`. The N+1
13
+ killer for callers resolving many ids in one tick (order-line snapshotting, dashboard
14
+ row enrichment, reference expansion).
15
+
16
+ **Contract:**
17
+ - Kit de-duplicates ids before the driver round-trip.
18
+ - Ids with no matching doc are ABSENT from the returned `Map` — never throw on a miss.
19
+ - Structurally invalid ids are treated as misses (one malformed id must never fail the
20
+ batch).
21
+ - Tenant scoping, soft-delete, and hooks apply exactly as they would to `getById`.
22
+ - Empty input → empty Map, no round-trip.
23
+ - Optional: kits adopt incrementally. Callers feature-detect
24
+ (`repo.getByIds ?? per-id fallback`) until their kit floor ships it.
25
+ - Sizing is backend-bound (Mongo: 16 MB query document); keep batches ≤ ~10k ids and
26
+ chunk beyond.
27
+
28
+ - **`events.ts` clarification** — `getByIds` has no `BEFORE_GET_BY_IDS` event by design.
29
+ It routes through `findAll`, so observers see the batch as `before:findAll` / `after:findAll`
30
+ with an `$in` filter. The comment guards against accidentally adding a separate event.
31
+
32
+ - **New type-level contract test** (`tests/unit/repository/get-by-ids-contract.test.ts`) —
33
+ pins the `getByIds` shape for kits implementing it: `Map<string, TDoc>` keyed by
34
+ stringified id, `readonly string[]` input. Verifies that kits without `getByIds` still
35
+ conform (the member is optional).
36
+
37
+ ### Changed
38
+
39
+ - `ChangesPage.changes` type: `ReadonlyArray<ChangeEntry<TDoc>>` → `readonly ChangeEntry<TDoc>[]`
40
+ (canonical form — functionally identical).
41
+ - devDeps: `tsdown ^0.21.8 → ^0.22.5`, `typescript ^6.0.3 → ^7.0.2`.
42
+
7
43
  ## [0.8.1] - 2026-07-11
8
44
 
9
45
  ### Docs — `DataAdapter.close()` ownership rule
@@ -1,6 +1,5 @@
1
1
  import { DeleteResult, MinimalRepo, StandardRepo } from "../repository/types.mjs";
2
2
  import { SchemaBuilderOptions } from "../schema/types.mjs";
3
-
4
3
  //#region src/adapter/types.d.ts
5
4
  /**
6
5
  * Cross-kit repository contract.
@@ -1,5 +1,4 @@
1
1
  import { AdapterRepositoryInput, RepositoryLike } from "./types.mjs";
2
-
3
2
  //#region src/adapter/widen.d.ts
4
3
  /**
5
4
  * Widen a permissive `AdapterRepositoryInput<TDoc>` to the strict
@@ -1,5 +1,4 @@
1
1
  import { AggRequest } from "../repository/types.mjs";
2
-
3
2
  //#region src/aggregate/normalize.d.ts
4
3
  /**
5
4
  * Normalize `AggRequest['groupBy']` into a readonly string array.
@@ -53,7 +53,7 @@ function pluralizeBetterAuthCollection(name) {
53
53
  */
54
54
  function resolveBetterAuthCollections(options = {}) {
55
55
  const { plugins = [], extraCollections = [], usePlural = false, modelOverrides = {} } = options;
56
- const pluginSet = new Set(["core", ...plugins]);
56
+ const pluginSet = /* @__PURE__ */ new Set(["core", ...plugins]);
57
57
  const collected = [];
58
58
  for (const key of pluginSet) for (const name of BA_COLLECTIONS_BY_PLUGIN[key]) collected.push(name);
59
59
  for (const name of extraCollections) collected.push(name);
@@ -1,6 +1,5 @@
1
1
  import { CacheReadResult, ResolvedCacheOptions } from "./options.mjs";
2
2
  import { CacheAdapter } from "./types.mjs";
3
-
4
3
  //#region src/cache/engine.d.ts
5
4
  interface CacheEngineOptions {
6
5
  /** Cache key namespace prefix. Default: `'rc'`. */
@@ -1,5 +1,4 @@
1
1
  import { CacheAdapter } from "./types.mjs";
2
-
3
2
  //#region src/cache/memory-adapter.d.ts
4
3
  /** Minimal in-memory `Map`-backed adapter with per-key TTL + prefix invalidation. */
5
4
  declare function createMemoryCacheAdapter(): CacheAdapter;
@@ -36,6 +36,13 @@ function createMemoryCacheAdapter() {
36
36
  const prefix = pattern.endsWith("*") ? pattern.slice(0, -1) : pattern;
37
37
  for (const key of store.keys()) if (key.startsWith(prefix)) store.delete(key);
38
38
  },
39
+ /**
40
+ * Atomic add-to-set — `O(M)` per call where M is the number of
41
+ * new members. Stores the value as `Set<string>` internally for
42
+ * `O(1)` membership checks; `get` exposes it back as an array for
43
+ * cross-adapter portability. JS's single-thread guarantee makes
44
+ * the read-mutate-write race-free without locks.
45
+ */
39
46
  addToSet(key, members, ttlSeconds = 60) {
40
47
  const existing = readUnexpired(key);
41
48
  let set;
@@ -55,6 +62,12 @@ function createMemoryCacheAdapter() {
55
62
  }
56
63
  return added;
57
64
  },
65
+ /**
66
+ * Atomic increment — JS's single-threaded execution model makes
67
+ * this race-free without locks. Mirrors Redis's `INCRBY key by` +
68
+ * `EXPIRE key ttlSeconds NX`: TTL is applied only on key creation,
69
+ * existing keys keep their original expiry.
70
+ */
58
71
  increment(key, by = 1, ttlSeconds = 60) {
59
72
  const existing = readUnexpired(key);
60
73
  const next = (existing && typeof existing.value === "number" && Number.isFinite(existing.value) ? existing.value : 0) + by;
@@ -99,7 +99,7 @@ function stripAggOperationalSlots(value) {
99
99
  return rest;
100
100
  }
101
101
  /** Aggregate ops carry their cache slot inside the AggRequest IR. */
102
- const AGGREGATE_OPS = new Set(["aggregate", "aggregatePaginate"]);
102
+ const AGGREGATE_OPS = /* @__PURE__ */ new Set(["aggregate", "aggregatePaginate"]);
103
103
  /**
104
104
  * Extract per-call `CacheOptions` from a hook context. Three valid
105
105
  * locations covered (kit-dependent):
@@ -3,12 +3,11 @@ import { CacheOptions, CacheReadResult } from "../options.mjs";
3
3
  import { CacheAdapter } from "../types.mjs";
4
4
  import { CacheEngine } from "../engine.mjs";
5
5
  import { DEFAULT_SHAPE_KEYS_BY_OP } from "./context.mjs";
6
-
7
6
  //#region src/cache/plugin/index.d.ts
8
7
  /** Default read ops the plugin caches. Kits may override per resource. */
9
- declare const DEFAULT_CACHEABLE_OPS: readonly ["getById", "getAll", "getOne", "getByQuery", "count", "exists", "distinct", "aggregate", "aggregatePaginate"];
8
+ declare const DEFAULT_CACHEABLE_OPS: readonly ['getById', 'getAll', 'getOne', 'getByQuery', 'count', 'exists', 'distinct', 'aggregate', 'aggregatePaginate'];
10
9
  /** Default mutating ops that bump version + invalidate tags on success. */
11
- declare const DEFAULT_INVALIDATING_OPS: readonly ["create", "createMany", "update", "updateMany", "findOneAndUpdate", "upsert", "delete", "deleteMany", "restore", "claim", "claimVersion", "increment", "bulkWrite"];
10
+ declare const DEFAULT_INVALIDATING_OPS: readonly ['create', 'createMany', 'update', 'updateMany', 'findOneAndUpdate', 'upsert', 'delete', 'deleteMany', 'restore', 'claim', 'claimVersion', 'increment', 'bulkWrite'];
12
11
  /** Observability callbacks — pipe to metrics, traces, or stdout. */
13
12
  interface LogCallbacks {
14
13
  onHit?: (key: string, op: string, ageSeconds: number) => void;
@@ -83,4 +82,4 @@ interface RepositoryCacheHandle {
83
82
  */
84
83
  declare function cachePlugin(options: RepositoryCachePluginOptions): Plugin;
85
84
  //#endregion
86
- export { DEFAULT_CACHEABLE_OPS, DEFAULT_INVALIDATING_OPS, LogCallbacks, RepositoryCacheHandle, RepositoryCachePluginOptions, cachePlugin };
85
+ export { DEFAULT_CACHEABLE_OPS, DEFAULT_INVALIDATING_OPS, DEFAULT_SHAPE_KEYS_BY_OP, LogCallbacks, RepositoryCacheHandle, RepositoryCachePluginOptions, cachePlugin };
@@ -75,4 +75,4 @@ function cachePlugin(options) {
75
75
  };
76
76
  }
77
77
  //#endregion
78
- export { DEFAULT_CACHEABLE_OPS, DEFAULT_INVALIDATING_OPS, cachePlugin };
78
+ export { DEFAULT_CACHEABLE_OPS, DEFAULT_INVALIDATING_OPS, DEFAULT_SHAPE_KEYS_BY_OP, cachePlugin };
@@ -1,5 +1,4 @@
1
1
  import { CacheAdapter } from "./types.mjs";
2
-
3
2
  //#region src/cache/timeout-adapter.d.ts
4
3
  declare class CacheTimeoutError extends Error {
5
4
  readonly op: string;
@@ -1,2 +1,2 @@
1
1
  import { RepositoryContext } from "./types.mjs";
2
- export { type RepositoryContext };
2
+ export type { RepositoryContext };
@@ -1,5 +1,4 @@
1
1
  import { Filter } from "../filter/types.mjs";
2
-
3
2
  //#region src/context/types.d.ts
4
3
  /** The canonical lifecycle-hook context. */
5
4
  interface RepositoryContext {
@@ -1,5 +1,4 @@
1
1
  import { ErrorCode, ErrorContract } from "./types.mjs";
2
-
3
2
  //#region src/errors/contract.d.ts
4
3
  /**
5
4
  * Map an HTTP status code to the canonical {@link ErrorCode}. Used as a
@@ -1,5 +1,4 @@
1
1
  import { HttpError } from "./types.mjs";
2
-
3
2
  //#region src/errors/create-error.d.ts
4
3
  /**
5
4
  * Create an error with an HTTP status code attached.
@@ -1,5 +1,4 @@
1
1
  import { DuplicateKeyMeta, HttpError } from "./types.mjs";
2
-
3
2
  //#region src/errors/duplicate-key.d.ts
4
3
  /** Predicate shape kits implement and repositories expose as `isDuplicateKeyError`. */
5
4
  type IsDuplicateKeyErrorFn = (err: unknown) => boolean;
@@ -17,24 +17,24 @@
17
17
  * {@link ./types.ts}.
18
18
  */
19
19
  declare const errorDetailSchema: {
20
- readonly type: "object";
20
+ readonly type: 'object';
21
21
  readonly properties: {
22
22
  readonly path: {
23
- readonly type: "string";
23
+ readonly type: 'string';
24
24
  readonly description: "Dot-path to the offending field, e.g. 'lines.0.quantity'.";
25
25
  };
26
26
  readonly code: {
27
- readonly type: "string";
27
+ readonly type: 'string';
28
28
  };
29
29
  readonly message: {
30
- readonly type: "string";
30
+ readonly type: 'string';
31
31
  };
32
32
  readonly meta: {
33
- readonly type: "object";
34
- readonly description: "Non-PII per-detail diagnostics (safe to log + return).";
33
+ readonly type: 'object';
34
+ readonly description: 'Non-PII per-detail diagnostics (safe to log + return).';
35
35
  };
36
36
  };
37
- readonly required: readonly ["code", "message"];
37
+ readonly required: readonly ['code', 'message'];
38
38
  };
39
39
  /**
40
40
  * Canonical error response. Mirrors `ErrorContract` in {@link ./types.ts}.
@@ -48,54 +48,54 @@ declare const errorDetailSchema: {
48
48
  * a separate path from success — HTTP status discriminates.
49
49
  */
50
50
  declare const errorContractSchema: {
51
- readonly type: "object";
51
+ readonly type: 'object';
52
52
  readonly properties: {
53
53
  readonly code: {
54
- readonly type: "string";
54
+ readonly type: 'string';
55
55
  readonly description: "Hierarchical machine-readable code (e.g. 'arc.not_found').";
56
56
  };
57
57
  readonly message: {
58
- readonly type: "string";
59
- readonly description: "Human-readable, safe-for-client message.";
58
+ readonly type: 'string';
59
+ readonly description: 'Human-readable, safe-for-client message.';
60
60
  };
61
61
  readonly status: {
62
- readonly type: "integer";
63
- readonly description: "Suggested HTTP status code (hosts may override).";
62
+ readonly type: 'integer';
63
+ readonly description: 'Suggested HTTP status code (hosts may override).';
64
64
  };
65
65
  readonly details: {
66
- readonly type: "array";
67
- readonly description: "Field-scoped structured details (validation failures, duplicate keys, multi-code domain errors).";
66
+ readonly type: 'array';
67
+ readonly description: 'Field-scoped structured details (validation failures, duplicate keys, multi-code domain errors).';
68
68
  readonly items: {
69
- readonly type: "object";
69
+ readonly type: 'object';
70
70
  readonly properties: {
71
71
  readonly path: {
72
- readonly type: "string";
72
+ readonly type: 'string';
73
73
  readonly description: "Dot-path to the offending field, e.g. 'lines.0.quantity'.";
74
74
  };
75
75
  readonly code: {
76
- readonly type: "string";
76
+ readonly type: 'string';
77
77
  };
78
78
  readonly message: {
79
- readonly type: "string";
79
+ readonly type: 'string';
80
80
  };
81
81
  readonly meta: {
82
- readonly type: "object";
83
- readonly description: "Non-PII per-detail diagnostics (safe to log + return).";
82
+ readonly type: 'object';
83
+ readonly description: 'Non-PII per-detail diagnostics (safe to log + return).';
84
84
  };
85
85
  };
86
- readonly required: readonly ["code", "message"];
86
+ readonly required: readonly ['code', 'message'];
87
87
  };
88
88
  };
89
89
  readonly correlationId: {
90
- readonly type: "string";
91
- readonly description: "Request id for support lookups.";
90
+ readonly type: 'string';
91
+ readonly description: 'Request id for support lookups.';
92
92
  };
93
93
  readonly meta: {
94
- readonly type: "object";
95
- readonly description: "Non-PII diagnostics (safe to log + return).";
94
+ readonly type: 'object';
95
+ readonly description: 'Non-PII diagnostics (safe to log + return).';
96
96
  };
97
97
  };
98
- readonly required: readonly ["code", "message"];
98
+ readonly required: readonly ['code', 'message'];
99
99
  };
100
100
  //#endregion
101
101
  export { errorContractSchema, errorDetailSchema };
@@ -125,17 +125,17 @@ interface ErrorDetail {
125
125
  * codes on the wire so existing client switches keep working.
126
126
  */
127
127
  declare const ERROR_CODES: {
128
- readonly VALIDATION: "validation_error";
129
- readonly NOT_FOUND: "not_found";
130
- readonly CONFLICT: "conflict";
131
- readonly UNAUTHORIZED: "unauthorized";
132
- readonly FORBIDDEN: "forbidden";
133
- readonly RATE_LIMITED: "rate_limited";
134
- readonly IDEMPOTENCY_CONFLICT: "idempotency_conflict";
135
- readonly PRECONDITION_FAILED: "precondition_failed";
136
- readonly INTERNAL: "internal_error";
137
- readonly UNAVAILABLE: "service_unavailable";
138
- readonly TIMEOUT: "timeout";
128
+ readonly VALIDATION: 'validation_error';
129
+ readonly NOT_FOUND: 'not_found';
130
+ readonly CONFLICT: 'conflict';
131
+ readonly UNAUTHORIZED: 'unauthorized';
132
+ readonly FORBIDDEN: 'forbidden';
133
+ readonly RATE_LIMITED: 'rate_limited';
134
+ readonly IDEMPOTENCY_CONFLICT: 'idempotency_conflict';
135
+ readonly PRECONDITION_FAILED: 'precondition_failed';
136
+ readonly INTERNAL: 'internal_error';
137
+ readonly UNAVAILABLE: 'service_unavailable';
138
+ readonly TIMEOUT: 'timeout';
139
139
  };
140
140
  type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
141
141
  //#endregion
@@ -1,6 +1,5 @@
1
1
  import { EventMeta, RepositoryEventPublisher } from "./types.mjs";
2
2
  import { RepositoryBase } from "../repository/base.mjs";
3
-
4
3
  //#region src/events/emit.d.ts
5
4
  /** Construction-time event wiring. */
6
5
  interface RepositoryEventsOptions {
@@ -1,5 +1,4 @@
1
1
  import { Filter, FilterEq, FilterExists, FilterFalse, FilterGt, FilterGte, FilterIn, FilterLike, FilterLt, FilterLte, FilterNe, FilterNin, FilterRaw, FilterRegex, FilterTrue } from "./types.mjs";
2
-
3
2
  //#region src/filter/builders.d.ts
4
3
  declare const eq: (field: string, value: unknown) => FilterEq;
5
4
  declare const ne: (field: string, value: unknown) => FilterNe;
@@ -11,7 +10,7 @@ declare const lte: (field: string, value: unknown) => FilterLte;
11
10
  declare const in_: (field: string, values: readonly unknown[]) => FilterIn;
12
11
  /** Non-membership (`$nin` / `NOT IN (...)`). Aliased as `noneOf`. */
13
12
  declare const nin: (field: string, values: readonly unknown[]) => FilterNin;
14
- declare const like: (field: string, pattern: string, caseSensitivity?: "sensitive" | "insensitive") => FilterLike;
13
+ declare const like: (field: string, pattern: string, caseSensitivity?: 'sensitive' | 'insensitive') => FilterLike;
15
14
  declare const regex: (field: string, pattern: string, flags?: string) => FilterRegex;
16
15
  declare const exists: (field: string, present?: boolean) => FilterExists;
17
16
  /**
@@ -57,4 +56,4 @@ declare function isNotNull(field: string): FilterExists;
57
56
  */
58
57
  declare function raw(sql: string, params?: readonly unknown[]): FilterRaw;
59
58
  //#endregion
60
- export { FALSE, TRUE, and, between, contains, endsWith, eq, exists, gt, gte, iEq, in_, isNotNull, isNull, like, lt, lte, ne, nin, not, or, raw, regex, startsWith };
59
+ export { FALSE, type FilterRaw, TRUE, and, in_ as anyOf, in_, between, contains, endsWith, eq, exists, gt, gte, iEq, not as invert, not, isNotNull, isNull, like, lt, lte, ne, nin, nin as noneOf, or, raw, regex, startsWith };
@@ -169,4 +169,4 @@ function escapeLikePattern(value) {
169
169
  return value.replace(/[%_]/g, (c) => `\\${c}`);
170
170
  }
171
171
  //#endregion
172
- export { FALSE, TRUE, and, between, contains, endsWith, eq, exists, gt, gte, iEq, in_, isNotNull, isNull, like, lt, lte, ne, nin, not, or, raw, regex, startsWith };
172
+ export { FALSE, TRUE, and, in_ as anyOf, in_, between, contains, endsWith, eq, exists, gt, gte, iEq, not as invert, not, isNotNull, isNull, like, lt, lte, ne, nin, nin as noneOf, or, raw, regex, startsWith };
@@ -1,5 +1,4 @@
1
1
  import { Filter } from "./types.mjs";
2
-
3
2
  //#region src/filter/from-record.d.ts
4
3
  /**
5
4
  * Convert a plain record (Mongo-style query object) into a Filter IR
@@ -1,4 +1,4 @@
1
- import { TRUE, and, contains, endsWith, eq, gt, gte, in_, isNotNull, isNull, like, lt, lte, ne, nin, startsWith } from "./builders.mjs";
1
+ import { TRUE, and, anyOf as in_, contains, endsWith, eq, gt, gte, isNotNull, isNull, like, lt, lte, ne, nin, startsWith } from "./builders.mjs";
2
2
  //#region src/filter/from-record.ts
3
3
  /**
4
4
  * Plain-record → Filter IR conversion — the canonical normalizer every
@@ -40,7 +40,7 @@ function isOperatorObject(value) {
40
40
  if (keys.length === 0) return false;
41
41
  return keys.every((k) => KNOWN_OPS.has(k));
42
42
  }
43
- const KNOWN_OPS = new Set([
43
+ const KNOWN_OPS = /* @__PURE__ */ new Set([
44
44
  "eq",
45
45
  "ne",
46
46
  "gt",
@@ -1,5 +1,4 @@
1
1
  import { Filter } from "./types.mjs";
2
-
3
2
  //#region src/filter/guard.d.ts
4
3
  /**
5
4
  * True when `value` is a Filter IR node.
@@ -1,5 +1,5 @@
1
1
  //#region src/filter/guard.ts
2
- const FILTER_OPS = new Set([
2
+ const FILTER_OPS = /* @__PURE__ */ new Set([
3
3
  "eq",
4
4
  "ne",
5
5
  "gt",
@@ -1,5 +1,5 @@
1
1
  import { Filter, FilterAnd, FilterEq, FilterExists, FilterFalse, FilterGt, FilterGte, FilterIn, FilterLike, FilterLt, FilterLte, FilterNe, FilterNin, FilterNot, FilterOp, FilterOr, FilterRaw, FilterRegex, FilterTrue } from "./types.mjs";
2
- import { FALSE, TRUE, and, between, contains, endsWith, eq, exists, gt, gte, iEq, in_, isNotNull, isNull, like, lt, lte, ne, nin, not, or, raw, regex, startsWith } from "./builders.mjs";
2
+ import { FALSE, TRUE, and, anyOf as in_, between, contains, endsWith, eq, exists, gt, gte, iEq, invert as not, isNotNull, isNull, like, lt, lte, ne, nin, or, raw, regex, startsWith } from "./builders.mjs";
3
3
  import { recordToFilter } from "./from-record.mjs";
4
4
  import { isFilter } from "./guard.mjs";
5
5
  import { asPredicate, matchFilter } from "./match.mjs";
@@ -1,6 +1,6 @@
1
1
  import { isFilter } from "./guard.mjs";
2
2
  import { collectFields, mapFilter, walkFilter } from "./walk.mjs";
3
- import { FALSE, TRUE, and, between, contains, endsWith, eq, exists, gt, gte, iEq, in_, isNotNull, isNull, like, lt, lte, ne, nin, not, or, raw, regex, startsWith } from "./builders.mjs";
3
+ import { FALSE, TRUE, and, anyOf as in_, between, contains, endsWith, eq, exists, gt, gte, iEq, invert as not, isNotNull, isNull, like, lt, lte, ne, nin, or, raw, regex, startsWith } from "./builders.mjs";
4
4
  import { recordToFilter } from "./from-record.mjs";
5
5
  import { asPredicate, matchFilter } from "./match.mjs";
6
6
  import { SCOPE_ANY, buildTenantScope, mergeScope } from "./scope.mjs";
@@ -1,5 +1,4 @@
1
1
  import { Filter } from "./types.mjs";
2
-
3
2
  //#region src/filter/match.d.ts
4
3
  /** Evaluate a filter against a single document. Returns true iff the doc matches. */
5
4
  declare function matchFilter(doc: unknown, filter: Filter): boolean;
@@ -1,5 +1,4 @@
1
1
  import { Filter } from "./types.mjs";
2
-
3
2
  //#region src/filter/scope.d.ts
4
3
  /**
5
4
  * Inject a tenant-scope predicate into an existing filter.
@@ -1,5 +1,4 @@
1
1
  import { Filter } from "./types.mjs";
2
-
3
2
  //#region src/filter/walk.d.ts
4
3
  /**
5
4
  * Depth-first visit of every node. Visitor returns `false` to stop descent
@@ -1,5 +1,4 @@
1
1
  import { HookListener, HookMode, PrioritizedHook } from "./types.mjs";
2
-
3
2
  //#region src/hooks/engine.d.ts
4
3
  /** The default priority assigned when a user omits one on `on(...)`. */
5
4
  declare const DEFAULT_LISTENER_PRIORITY = 500;
@@ -37,57 +37,57 @@
37
37
  * ```
38
38
  */
39
39
  declare const HOOK_EVENTS: {
40
- readonly BEFORE_CREATE: "before:create";
41
- readonly AFTER_CREATE: "after:create";
42
- readonly ERROR_CREATE: "error:create";
43
- readonly BEFORE_UPDATE: "before:update";
44
- readonly AFTER_UPDATE: "after:update";
45
- readonly ERROR_UPDATE: "error:update";
46
- readonly BEFORE_DELETE: "before:delete";
47
- readonly AFTER_DELETE: "after:delete";
48
- readonly ERROR_DELETE: "error:delete";
49
- readonly BEFORE_GET_BY_ID: "before:getById";
50
- readonly AFTER_GET_BY_ID: "after:getById";
51
- readonly ERROR_GET_BY_ID: "error:getById";
52
- readonly BEFORE_GET_ALL: "before:getAll";
53
- readonly AFTER_GET_ALL: "after:getAll";
54
- readonly ERROR_GET_ALL: "error:getAll";
55
- readonly BEFORE_CREATE_MANY: "before:createMany";
56
- readonly AFTER_CREATE_MANY: "after:createMany";
57
- readonly ERROR_CREATE_MANY: "error:createMany";
58
- readonly BEFORE_UPDATE_MANY: "before:updateMany";
59
- readonly AFTER_UPDATE_MANY: "after:updateMany";
60
- readonly ERROR_UPDATE_MANY: "error:updateMany";
61
- readonly BEFORE_DELETE_MANY: "before:deleteMany";
62
- readonly AFTER_DELETE_MANY: "after:deleteMany";
63
- readonly ERROR_DELETE_MANY: "error:deleteMany";
64
- readonly BEFORE_FIND_ONE_AND_UPDATE: "before:findOneAndUpdate";
65
- readonly AFTER_FIND_ONE_AND_UPDATE: "after:findOneAndUpdate";
66
- readonly ERROR_FIND_ONE_AND_UPDATE: "error:findOneAndUpdate";
67
- readonly BEFORE_RESTORE: "before:restore";
68
- readonly AFTER_RESTORE: "after:restore";
69
- readonly ERROR_RESTORE: "error:restore";
70
- readonly BEFORE_GET_BY_QUERY: "before:getByQuery";
71
- readonly AFTER_GET_BY_QUERY: "after:getByQuery";
72
- readonly ERROR_GET_BY_QUERY: "error:getByQuery";
73
- readonly BEFORE_GET_ONE: "before:getOne";
74
- readonly AFTER_GET_ONE: "after:getOne";
75
- readonly ERROR_GET_ONE: "error:getOne";
76
- readonly BEFORE_FIND_ALL: "before:findAll";
77
- readonly AFTER_FIND_ALL: "after:findAll";
78
- readonly ERROR_FIND_ALL: "error:findAll";
79
- readonly BEFORE_GET_OR_CREATE: "before:getOrCreate";
80
- readonly AFTER_GET_OR_CREATE: "after:getOrCreate";
81
- readonly ERROR_GET_OR_CREATE: "error:getOrCreate";
82
- readonly BEFORE_COUNT: "before:count";
83
- readonly AFTER_COUNT: "after:count";
84
- readonly ERROR_COUNT: "error:count";
85
- readonly BEFORE_EXISTS: "before:exists";
86
- readonly AFTER_EXISTS: "after:exists";
87
- readonly ERROR_EXISTS: "error:exists";
88
- readonly BEFORE_DISTINCT: "before:distinct";
89
- readonly AFTER_DISTINCT: "after:distinct";
90
- readonly ERROR_DISTINCT: "error:distinct";
40
+ readonly BEFORE_CREATE: 'before:create';
41
+ readonly AFTER_CREATE: 'after:create';
42
+ readonly ERROR_CREATE: 'error:create';
43
+ readonly BEFORE_UPDATE: 'before:update';
44
+ readonly AFTER_UPDATE: 'after:update';
45
+ readonly ERROR_UPDATE: 'error:update';
46
+ readonly BEFORE_DELETE: 'before:delete';
47
+ readonly AFTER_DELETE: 'after:delete';
48
+ readonly ERROR_DELETE: 'error:delete';
49
+ readonly BEFORE_GET_BY_ID: 'before:getById';
50
+ readonly AFTER_GET_BY_ID: 'after:getById';
51
+ readonly ERROR_GET_BY_ID: 'error:getById';
52
+ readonly BEFORE_GET_ALL: 'before:getAll';
53
+ readonly AFTER_GET_ALL: 'after:getAll';
54
+ readonly ERROR_GET_ALL: 'error:getAll';
55
+ readonly BEFORE_CREATE_MANY: 'before:createMany';
56
+ readonly AFTER_CREATE_MANY: 'after:createMany';
57
+ readonly ERROR_CREATE_MANY: 'error:createMany';
58
+ readonly BEFORE_UPDATE_MANY: 'before:updateMany';
59
+ readonly AFTER_UPDATE_MANY: 'after:updateMany';
60
+ readonly ERROR_UPDATE_MANY: 'error:updateMany';
61
+ readonly BEFORE_DELETE_MANY: 'before:deleteMany';
62
+ readonly AFTER_DELETE_MANY: 'after:deleteMany';
63
+ readonly ERROR_DELETE_MANY: 'error:deleteMany';
64
+ readonly BEFORE_FIND_ONE_AND_UPDATE: 'before:findOneAndUpdate';
65
+ readonly AFTER_FIND_ONE_AND_UPDATE: 'after:findOneAndUpdate';
66
+ readonly ERROR_FIND_ONE_AND_UPDATE: 'error:findOneAndUpdate';
67
+ readonly BEFORE_RESTORE: 'before:restore';
68
+ readonly AFTER_RESTORE: 'after:restore';
69
+ readonly ERROR_RESTORE: 'error:restore';
70
+ readonly BEFORE_GET_BY_QUERY: 'before:getByQuery';
71
+ readonly AFTER_GET_BY_QUERY: 'after:getByQuery';
72
+ readonly ERROR_GET_BY_QUERY: 'error:getByQuery';
73
+ readonly BEFORE_GET_ONE: 'before:getOne';
74
+ readonly AFTER_GET_ONE: 'after:getOne';
75
+ readonly ERROR_GET_ONE: 'error:getOne';
76
+ readonly BEFORE_FIND_ALL: 'before:findAll';
77
+ readonly AFTER_FIND_ALL: 'after:findAll';
78
+ readonly ERROR_FIND_ALL: 'error:findAll';
79
+ readonly BEFORE_GET_OR_CREATE: 'before:getOrCreate';
80
+ readonly AFTER_GET_OR_CREATE: 'after:getOrCreate';
81
+ readonly ERROR_GET_OR_CREATE: 'error:getOrCreate';
82
+ readonly BEFORE_COUNT: 'before:count';
83
+ readonly AFTER_COUNT: 'after:count';
84
+ readonly ERROR_COUNT: 'error:count';
85
+ readonly BEFORE_EXISTS: 'before:exists';
86
+ readonly AFTER_EXISTS: 'after:exists';
87
+ readonly ERROR_EXISTS: 'error:exists';
88
+ readonly BEFORE_DISTINCT: 'before:distinct';
89
+ readonly AFTER_DISTINCT: 'after:distinct';
90
+ readonly ERROR_DISTINCT: 'error:distinct';
91
91
  };
92
92
  /** String-literal union of every canonical hook event name. */
93
93
  type HookEventName = (typeof HOOK_EVENTS)[keyof typeof HOOK_EVENTS];
@@ -12,15 +12,19 @@
12
12
  * compose identically.
13
13
  */
14
14
  declare const HOOK_PRIORITY: {
15
- /** Policy enforcement — tenant isolation, soft-delete filtering. */readonly POLICY: 100;
15
+ /** Policy enforcement — tenant isolation, soft-delete filtering. */
16
+ readonly POLICY: 100;
16
17
  /**
17
18
  * Input validation — Standard Schema / custom validators. Runs after
18
19
  * policy (so tenant-stamped fields are present on the payload) and
19
20
  * before cache (an invalid request must never claim a cache slot).
20
21
  */
21
- readonly VALIDATION: 150; /** Cache lookup / store — must run after policy so filters are in the key. */
22
- readonly CACHE: 200; /** Observabilityaudit logging, metrics, telemetry. Must not mutate context. */
23
- readonly OBSERVABILITY: 300; /** Default priority for user-registered hooks with no explicit priority. */
22
+ readonly VALIDATION: 150;
23
+ /** Cache lookup / storemust run after policy so filters are in the key. */
24
+ readonly CACHE: 200;
25
+ /** Observability — audit logging, metrics, telemetry. Must not mutate context. */
26
+ readonly OBSERVABILITY: 300;
27
+ /** Default priority for user-registered hooks with no explicit priority. */
24
28
  readonly DEFAULT: 500;
25
29
  };
26
30
  /** The numeric type of any `HOOK_PRIORITY` value. */
@@ -12,10 +12,19 @@
12
12
  * compose identically.
13
13
  */
14
14
  const HOOK_PRIORITY = {
15
+ /** Policy enforcement — tenant isolation, soft-delete filtering. */
15
16
  POLICY: 100,
17
+ /**
18
+ * Input validation — Standard Schema / custom validators. Runs after
19
+ * policy (so tenant-stamped fields are present on the payload) and
20
+ * before cache (an invalid request must never claim a cache slot).
21
+ */
16
22
  VALIDATION: 150,
23
+ /** Cache lookup / store — must run after policy so filters are in the key. */
17
24
  CACHE: 200,
25
+ /** Observability — audit logging, metrics, telemetry. Must not mutate context. */
18
26
  OBSERVABILITY: 300,
27
+ /** Default priority for user-registered hooks with no explicit priority. */
19
28
  DEFAULT: 500
20
29
  };
21
30
  //#endregion
@@ -1,2 +1,2 @@
1
1
  import { LookupPopulateOptions, LookupPopulateResult, LookupRow, LookupSpec } from "./types.mjs";
2
- export { type LookupPopulateOptions, type LookupPopulateResult, type LookupRow, type LookupSpec };
2
+ export type { LookupPopulateOptions, LookupPopulateResult, LookupRow, LookupSpec };
@@ -1,6 +1,5 @@
1
1
  import { Filter } from "../filter/types.mjs";
2
2
  import { KeysetPaginationResultCore, OffsetPaginationResultCore } from "../pagination/types.mjs";
3
-
4
3
  //#region src/lookup/types.d.ts
5
4
  /**
6
5
  * Single lookup join. Reads like a `LEFT JOIN from ON from.foreignField
@@ -1,5 +1,4 @@
1
1
  import { CoreRepositoryOperation, OperationDescriptor, OperationRegistry, PolicyKey, RepositoryOperation } from "./types.mjs";
2
-
3
2
  //#region src/operations/registry.d.ts
4
3
  /**
5
4
  * The core registry. Driver kits merge their own descriptors on top of this
@@ -1,5 +1,4 @@
1
1
  import { AnyPaginationResult, BareListResult, PaginatedResult } from "./types.mjs";
2
-
3
2
  //#region src/pagination/canonical.d.ts
4
3
  /**
5
4
  * Type guard: is this value a paginated result envelope (vs a bare array
@@ -1,5 +1,4 @@
1
1
  import { DecodedCursor, SortSpec } from "./types.mjs";
2
-
3
2
  //#region src/pagination/cursor.d.ts
4
3
  /**
5
4
  * Encode a document's sort values and id into a base64url cursor token.
@@ -1,5 +1,4 @@
1
1
  import { SortSpec } from "./types.mjs";
2
-
3
2
  //#region src/pagination/keyset.d.ts
4
3
  /**
5
4
  * Normalize a sort object so non-`_id` fields come first, `_id` last.
@@ -1,5 +1,4 @@
1
1
  import { PaginationConfig } from "./types.mjs";
2
-
3
2
  //#region src/pagination/offset.d.ts
4
3
  /**
5
4
  * Parse, clamp, and sanitize a `limit` value. Accepts string or number
@@ -1,5 +1,4 @@
1
1
  import { PolicyKey } from "../operations/types.mjs";
2
-
3
2
  //#region src/plugins/tenant-helpers.d.ts
4
3
  /**
5
4
  * Minimal context shape this module reads. Kits' richer
@@ -1,12 +1,11 @@
1
1
  import { QueryParserOptions } from "./types.mjs";
2
-
3
2
  //#region src/query-parser/coerce.d.ts
4
3
  /**
5
4
  * Coerce a single URL value to its field-declared type, or to a best-guess
6
5
  * scalar when no hint exists. Always returns `string`, `number`, `boolean`,
7
6
  * `Date`, or `null` — never `undefined`.
8
7
  */
9
- declare function coerceValue(rawValue: string, fieldType: QueryParserOptions['fieldTypes'] extends infer T ? T extends Record<string, infer V> ? V | undefined : undefined : undefined): unknown;
8
+ declare function coerceValue(rawValue: string, fieldType: QueryParserOptions['fieldTypes'] extends (infer T) ? T extends Record<string, infer V> ? V | undefined : undefined : undefined): unknown;
10
9
  /**
11
10
  * Split a comma-separated URL value into an array of coerced scalars.
12
11
  * Used by `in`/`nin`/`between` which accept `field[in]=a,b,c`.
@@ -1,11 +1,11 @@
1
1
  //#region src/query-parser/coerce.ts
2
- const BOOLEAN_STRINGS = new Set([
2
+ const BOOLEAN_STRINGS = /* @__PURE__ */ new Set([
3
3
  "true",
4
4
  "1",
5
5
  "yes",
6
6
  "on"
7
7
  ]);
8
- const FALSEY_STRINGS = new Set([
8
+ const FALSEY_STRINGS = /* @__PURE__ */ new Set([
9
9
  "false",
10
10
  "0",
11
11
  "no",
@@ -1,5 +1,4 @@
1
1
  import { ParsedQuery, QueryParserInput, QueryParserOptions } from "./types.mjs";
2
-
3
2
  //#region src/query-parser/parse-url.d.ts
4
3
  /** Parse URL search params into a driver-agnostic ParsedQuery. */
5
4
  declare function parseUrl(input: QueryParserInput, options?: QueryParserOptions): ParsedQuery;
@@ -1,4 +1,4 @@
1
- import { TRUE, and, between, contains, endsWith, eq, gt, gte, iEq, in_, isNotNull, isNull, like, lt, lte, ne, nin, regex, startsWith } from "../filter/builders.mjs";
1
+ import { TRUE, and, anyOf as in_, between, contains, endsWith, eq, gt, gte, iEq, isNotNull, isNull, like, lt, lte, ne, nin, regex, startsWith } from "../filter/builders.mjs";
2
2
  import { coerceList, coerceValue } from "./coerce.mjs";
3
3
  import { isControlParam } from "./reserved.mjs";
4
4
  //#region src/query-parser/parse-url.ts
@@ -16,7 +16,7 @@ const DEFAULT_MAX_SEARCH = 200;
16
16
  * approach this bound.
17
17
  */
18
18
  const MAX_PARAM_KEY_LENGTH = 256;
19
- const ALL_OPERATORS = new Set([
19
+ const ALL_OPERATORS = /* @__PURE__ */ new Set([
20
20
  "eq",
21
21
  "ne",
22
22
  "gt",
@@ -23,7 +23,7 @@
23
23
  * Pagination + sort + select + populate + search + resource-dispatch
24
24
  * verbs (`_count`, `_distinct`, `_exists`).
25
25
  */
26
- const STANDARD_RESERVED_PARAMS = new Set([
26
+ const STANDARD_RESERVED_PARAMS = /* @__PURE__ */ new Set([
27
27
  "page",
28
28
  "limit",
29
29
  "after",
@@ -1,5 +1,4 @@
1
1
  import { Filter } from "../filter/types.mjs";
2
-
3
2
  //#region src/query-parser/types.d.ts
4
3
  /** Sort direction on a single field. */
5
4
  type ParsedSortDirection = 1 | -1;
@@ -1,5 +1,4 @@
1
1
  import { RetryPolicy } from "./resilience.mjs";
2
-
3
2
  //#region src/repository/archive.d.ts
4
3
  /**
5
4
  * Destination for archived documents — implemented by the HOST, not the
@@ -4,7 +4,6 @@ import { HookListener, HookMode } from "../hooks/types.mjs";
4
4
  import { HookEngine } from "../hooks/engine.mjs";
5
5
  import { StandardSchemaV1 } from "../schema/standard-schema.mjs";
6
6
  import { PluginType } from "./plugin-types.mjs";
7
-
8
7
  //#region src/repository/base.d.ts
9
8
  /** Construction options common to every kit. */
10
9
  interface RepositoryBaseOptions {
@@ -1,5 +1,4 @@
1
1
  import { FilterInput } from "./types.mjs";
2
-
3
2
  //#region src/repository/distribution.d.ts
4
3
  /** Per-repo declaration of how the underlying table/collection is distributed. */
5
4
  interface DistributionConfig {
@@ -50,7 +50,7 @@
50
50
  * options bag, and let kit plugins read what they implement. Unknown
51
51
  * ctx keys do NOT forward — the bag stays narrow.
52
52
  */
53
- declare const STANDARD_REPO_OPTION_KEYS: readonly ["organizationId", "userId", "user", "session", "requestId", "traceId"];
53
+ declare const STANDARD_REPO_OPTION_KEYS: readonly ['organizationId', 'userId', 'user', 'session', 'requestId', 'traceId'];
54
54
  /**
55
55
  * Type-level union of canonical option keys. Use to constrain
56
56
  * framework helpers that thread request context into repo options:
@@ -1,5 +1,4 @@
1
1
  import { RepositoryBase } from "./base.mjs";
2
-
3
2
  //#region src/repository/plugin-types.d.ts
4
3
  /** Object-style plugin — preferred form. Carries a stable `name` for ordering checks. */
5
4
  interface Plugin<TRepo extends RepositoryBase = RepositoryBase> {
@@ -1,5 +1,4 @@
1
1
  import { TenantPurgeOptions, TenantPurgeResult, TenantPurgeStrategy } from "./types.mjs";
2
-
3
2
  //#region src/repository/purge.d.ts
4
3
  /**
5
4
  * Strategies that perform a write. `skip` is handled by the orchestrator
@@ -6,7 +6,6 @@ import { RetryPolicy } from "./resilience.mjs";
6
6
  import { ArchiveOptions, ArchiveResult, ArchiveSink } from "./archive.mjs";
7
7
  import { RepoCapabilities } from "./capabilities.mjs";
8
8
  import { CacheOptions } from "../cache/options.mjs";
9
-
10
9
  //#region src/repository/types.d.ts
11
10
  /**
12
11
  * Accepted filter input across every repository method. A repository
@@ -146,31 +145,33 @@ type TenantPurgeStrategy =
146
145
  */
147
146
  {
148
147
  type: 'hard';
149
- }
148
+ } |
150
149
  /**
151
150
  * Mark every matching row as deleted via the soft-delete convention.
152
151
  * Pair with `softDeletePlugin` or a TTL index for eventual cleanup.
153
152
  */
154
- | {
155
- type: 'soft'; /** Boolean flag field set to `true`. Default `'deleted'`. */
156
- deletedField?: string; /** Timestamp field set to purge-time. Default `'deletedAt'`. */
153
+ {
154
+ type: 'soft';
155
+ /** Boolean flag field set to `true`. Default `'deleted'`. */
156
+ deletedField?: string;
157
+ /** Timestamp field set to purge-time. Default `'deletedAt'`. */
157
158
  deletedAtField?: string;
158
- }
159
+ } |
159
160
  /**
160
161
  * Retain every matching row but overwrite the declared fields. Field
161
162
  * values can be static (preferred — statically inspectable for audit)
162
163
  * or per-row functions when deterministic transforms are needed
163
164
  * (hashing, derived identifiers).
164
165
  */
165
- | {
166
+ {
166
167
  type: 'anonymize';
167
168
  fields: Record<string, unknown | ((doc: Record<string, unknown>) => unknown)>;
168
- }
169
+ } |
169
170
  /**
170
171
  * Take no action. `reason` is required — an undocumented skip is a
171
172
  * silent compliance leak. Surfaces in audit reports + introspection.
172
173
  */
173
- | {
174
+ {
174
175
  type: 'skip';
175
176
  reason: string;
176
177
  };
@@ -827,7 +828,9 @@ type AggDateBucketUnit = 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter
827
828
  * order without parsing the string back into a date.
828
829
  */
829
830
  type AggDateBucketInterval = AggDateBucketUnit | {
830
- /** Number of `unit`-sized chunks per bin. Must be a positive integer. */every: number; /** Base unit. `quarter` and `year` aren't supported in custom-bin form. */
831
+ /** Number of `unit`-sized chunks per bin. Must be a positive integer. */
832
+ every: number;
833
+ /** Base unit. `quarter` and `year` aren't supported in custom-bin form. */
831
834
  unit: Exclude<AggDateBucketUnit, 'quarter' | 'year'>;
832
835
  };
833
836
  /**
@@ -1421,6 +1424,31 @@ interface StandardRepo<TDoc> extends MinimalRepo<TDoc> {
1421
1424
  getOne?(filter: FilterInput, options?: QueryOptions): Promise<TDoc | null>;
1422
1425
  /** Alias many kits expose alongside `getOne`. Arc checks both names. */
1423
1426
  getByQuery?(filter: FilterInput, options?: QueryOptions): Promise<TDoc | null>;
1427
+ /**
1428
+ * Batch point-read — the `$in` / `IN (...)` counterpart to `getById`.
1429
+ * Fetches every doc whose primary key is in `ids` in ONE query and
1430
+ * returns a Map keyed by the STRINGIFIED id (`String(doc.id)`) for O(1)
1431
+ * lookup. The N+1 killer for callers resolving many ids in one tick
1432
+ * (order-line snapshotting, dashboard row enrichment, reference
1433
+ * expansion).
1434
+ *
1435
+ * **Contract:**
1436
+ * - Input ids are de-duplicated by the kit.
1437
+ * - Ids with no matching doc are simply ABSENT from the Map — never
1438
+ * throw on a miss (mirrors `getById`'s null-on-miss form).
1439
+ * - Structurally invalid ids (impossible for the backend's key type)
1440
+ * are treated as misses and dropped — one malformed id must never
1441
+ * fail the batch.
1442
+ * - Same policy surface as a single read: tenant scoping, soft-delete,
1443
+ * and hooks apply to the batch exactly as they would to `getById`.
1444
+ * - Empty input → empty Map, no driver round-trip.
1445
+ *
1446
+ * Optional: kits adopt incrementally. Callers feature-detect
1447
+ * (`repo.getByIds ?? per-id fallback`) until their kit floor ships it.
1448
+ * Sizing is backend-bound (e.g. Mongo's 16 MB query document); keep
1449
+ * batches ≤ ~10k ids and chunk beyond.
1450
+ */
1451
+ getByIds?(ids: readonly string[], options?: FindAllOptions): Promise<Map<string, TDoc>>;
1424
1452
  count?(filter?: FilterInput, options?: QueryOptions): Promise<number>;
1425
1453
  exists?(filter: FilterInput, options?: QueryOptions): Promise<boolean | {
1426
1454
  _id: unknown;
@@ -1,5 +1,4 @@
1
1
  import { JsonSchema, SchemaBuilderOptions, ValidationResult } from "./types.mjs";
2
-
3
2
  //#region src/schema/field-rules.d.ts
4
3
  /**
5
4
  * Collect the set of fields that must NOT appear in a generated schema.
@@ -33,7 +33,7 @@ function collectFieldsToOmit(options, purpose) {
33
33
  if (explicit) for (const f of explicit) result.add(f);
34
34
  return result;
35
35
  }
36
- const result = new Set([
36
+ const result = /* @__PURE__ */ new Set([
37
37
  "createdAt",
38
38
  "updatedAt",
39
39
  "__v",
@@ -1,5 +1,4 @@
1
1
  import { CrudSchemas, SchemaBuilderOptions } from "./types.mjs";
2
-
3
2
  //#region src/schema/generator.d.ts
4
3
  /**
5
4
  * Resource-level context threaded into the generator at boot. Lets the
@@ -149,9 +149,13 @@ interface SchemaBuilderOptions {
149
149
  dateAs?: 'date' | 'datetime';
150
150
  /** Create-schema overrides. */
151
151
  create?: {
152
- /** Fields to omit from the create body. */omitFields?: string[]; /** Force field to required (merged with auto-detected required). */
153
- requiredOverrides?: Record<string, boolean>; /** Force field to optional (even if DB-level required). */
154
- optionalOverrides?: Record<string, boolean>; /** Replace the generated schema for a specific field. */
152
+ /** Fields to omit from the create body. */
153
+ omitFields?: string[];
154
+ /** Force field to required (merged with auto-detected required). */
155
+ requiredOverrides?: Record<string, boolean>;
156
+ /** Force field to optional (even if DB-level required). */
157
+ optionalOverrides?: Record<string, boolean>;
158
+ /** Replace the generated schema for a specific field. */
155
159
  schemaOverrides?: Record<string, unknown>;
156
160
  };
157
161
  /**
@@ -165,12 +169,15 @@ interface SchemaBuilderOptions {
165
169
  softRequiredFields?: string[];
166
170
  /** Update-schema overrides. */
167
171
  update?: {
168
- /** Fields to omit from the update body. */omitFields?: string[]; /** When `true`, reject empty update bodies (`minProperties: 1`). */
172
+ /** Fields to omit from the update body. */
173
+ omitFields?: string[];
174
+ /** When `true`, reject empty update bodies (`minProperties: 1`). */
169
175
  requireAtLeastOne?: boolean;
170
176
  };
171
177
  /** List-query schema overrides. */
172
178
  query?: {
173
- /** Extra filterable fields exposed on the list-query schema. */filterableFields?: Record<string, {
179
+ /** Extra filterable fields exposed on the list-query schema. */
180
+ filterableFields?: Record<string, {
174
181
  type: string;
175
182
  } | unknown>;
176
183
  };
@@ -185,7 +192,8 @@ interface SchemaBuilderOptions {
185
192
  * scoring you want kept in update bodies but stripped from list reads).
186
193
  */
187
194
  response?: {
188
- /** Extra fields to omit from the response shape. */omitFields?: string[];
195
+ /** Extra fields to omit from the response shape. */
196
+ omitFields?: string[];
189
197
  };
190
198
  /**
191
199
  * Emit OpenAPI vendor extensions (`x-*` keywords like `x-ref` for populated
@@ -68,7 +68,7 @@ interface ChangesSinceOptions {
68
68
  readonly tenantId?: string;
69
69
  }
70
70
  interface ChangesPage<TDoc = unknown> {
71
- readonly changes: ReadonlyArray<ChangeEntry<TDoc>>;
71
+ readonly changes: readonly ChangeEntry<TDoc>[];
72
72
  /** Checkpoint AFTER applying this page — echo into the next `since`. */
73
73
  readonly cursor: string;
74
74
  /** True → more entries exist; pull again immediately. */
@@ -1,6 +1,8 @@
1
1
  //#region src/sync/index.ts
2
2
  /** Client checkpoint older than the store's compaction horizon → full resync. */
3
3
  var CursorExpiredError = class extends Error {
4
+ cursor;
5
+ horizon;
4
6
  constructor(cursor, horizon) {
5
7
  super(`[repo-core:sync] cursor "${cursor}" predates the compaction horizon — full resync required.`);
6
8
  this.cursor = cursor;
@@ -1,5 +1,4 @@
1
1
  import { ResolvedTenantConfig, TenantConfig } from "./types.mjs";
2
-
3
2
  //#region src/tenant/resolve.d.ts
4
3
  /**
5
4
  * Sensible defaults for a freshly-built package (field strategy).
@@ -10,7 +9,7 @@ import { ResolvedTenantConfig, TenantConfig } from "./types.mjs";
10
9
  * runtime default — `Pick<TenantConfig, 'fieldType'>` extension preserves
11
10
  * type-level alignment without forcing a runtime default change.
12
11
  */
13
- type TenantDefaults = { [K in 'strategy' | 'enabled' | 'tenantField' | 'fieldType' | 'ref' | 'contextKey' | 'required']-?: Exclude<TenantConfig[K], undefined> };
12
+ type TenantDefaults = { [K in 'strategy' | 'enabled' | 'tenantField' | 'fieldType' | 'ref' | 'contextKey' | 'required']-?: Exclude<TenantConfig[K], undefined>; };
14
13
  declare const DEFAULT_TENANT_CONFIG: TenantDefaults;
15
14
  declare function resolveTenantConfig(config?: TenantConfig | boolean): ResolvedTenantConfig;
16
15
  //#endregion
@@ -1,5 +1,4 @@
1
1
  import { ConformanceDoc, ConformanceHarness } from "./types.mjs";
2
-
3
2
  //#region src/testing/conformance.d.ts
4
3
  declare function runStandardRepoConformance<TDoc extends ConformanceDoc = ConformanceDoc>(harness: ConformanceHarness<TDoc>): void;
5
4
  //#endregion
@@ -1,4 +1,4 @@
1
- import { and, eq, gt, in_, isNull, like, ne, or } from "../filter/builders.mjs";
1
+ import { and, anyOf as in_, eq, gt, isNull, like, ne, or } from "../filter/builders.mjs";
2
2
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
3
  //#region src/testing/conformance.ts
4
4
  /**
@@ -1,5 +1,4 @@
1
1
  import { LockAdapter } from "../lock/index.mjs";
2
-
3
2
  //#region src/testing/lock-conformance.d.ts
4
3
  interface LockConformanceHarness {
5
4
  /**
@@ -1,6 +1,5 @@
1
1
  import { AggregateOpsSupport, RepoCapabilities } from "../repository/capabilities.mjs";
2
2
  import { MinimalRepo, StandardRepo } from "../repository/types.mjs";
3
-
4
3
  //#region src/testing/types.d.ts
5
4
  /**
6
5
  * The conformance scenarios operate on a minimal doc shape common to
@@ -114,4 +113,4 @@ interface ConformanceHarness<TDoc extends ConformanceDoc = ConformanceDoc> {
114
113
  makeDoc(overrides?: Partial<ConformanceDoc>): Partial<TDoc>;
115
114
  }
116
115
  //#endregion
117
- export { ConformanceContext, ConformanceDoc, ConformanceFeatures, ConformanceHarness };
116
+ export { type AggregateOpsSupport, ConformanceContext, ConformanceDoc, ConformanceFeatures, ConformanceHarness };
@@ -1,5 +1,4 @@
1
1
  import { UpdateSpec } from "./types.mjs";
2
-
3
2
  //#region src/update/builders.d.ts
4
3
  /**
5
4
  * Compose an `UpdateSpec` from the four primitive mutations.
@@ -1,5 +1,4 @@
1
1
  import { UpdateSpec } from "./types.mjs";
2
-
3
2
  //#region src/update/compile.d.ts
4
3
  /**
5
4
  * Compile an `UpdateSpec` to a Mongo operator record.
@@ -1,5 +1,4 @@
1
1
  import { UpdateSpec } from "./types.mjs";
2
-
3
2
  //#region src/update/guard.d.ts
4
3
  /**
5
4
  * True when `value` is an `UpdateSpec` — i.e. the portable, compile-to-native
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/repo-core",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Driver-agnostic repository primitives: hooks, Filter IR, operations, pagination, cache contract. Foundation for mongokit, sqlitekit, pgkit, and prismakit. Lean by design — no plugins ship here; each kit owns its own.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -157,8 +157,8 @@
157
157
  "fast-check": "^4.7.0",
158
158
  "knip": "^6.3.0",
159
159
  "publint": "^0.3.18",
160
- "tsdown": "^0.21.8",
161
- "typescript": "^6.0.3",
160
+ "tsdown": "^0.22.5",
161
+ "typescript": "^7.0.2",
162
162
  "vitest": "^4.1.4"
163
163
  },
164
164
  "peerDependencies": {