@classytic/repo-core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/sync/index.d.mts +131 -0
- package/dist/sync/index.mjs +41 -0
- package/dist/tenant/resolve.d.mts +2 -12
- package/dist/tenant/resolve.mjs +15 -29
- package/dist/tenant/types.d.mts +9 -9
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ 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.7.0] - 2026-07-04
|
|
8
|
+
|
|
9
|
+
### Added — `./sync` change-log / cursor contract
|
|
10
|
+
|
|
11
|
+
Storage-agnostic data-sync spine for offline-first + incremental replication:
|
|
12
|
+
`ChangeLogStore`, `ChangeEntry` (tombstone deletes), pull `ChangesPage`
|
|
13
|
+
(opaque exclusive cursors, `hasMore` paging), Replicache-style `PushMutation`
|
|
14
|
+
/ `PushVerdict` (idempotent client mutation ids, server-authoritative
|
|
15
|
+
conflicts), `CursorExpiredError` (compaction horizon → full resync), and the
|
|
16
|
+
`MemoryChangeLogStore` reference impl pinning the contract semantics.
|
|
17
|
+
|
|
18
|
+
Synthesized from CouchDB `_changes` (tombstones/checkpoints), Drive/Graph
|
|
19
|
+
delta APIs (opaque server-issued cursors), Mongo change-stream resume tokens,
|
|
20
|
+
and Replicache push/pull. Contract only — kits (mongokit/prismakit/sqlitekit)
|
|
21
|
+
implement capture + durable stores via repository plugins; arc re-exports the
|
|
22
|
+
surface at `@classytic/arc/sync`; sync HTTP endpoints are an arc module.
|
|
23
|
+
|
|
24
|
+
## [0.6.1] - 2026-07-04
|
|
25
|
+
|
|
26
|
+
### Changed — `TenantConfig` optionals widened to `T | undefined` (P10)
|
|
27
|
+
|
|
28
|
+
Every optional prop on `TenantConfig` (`strategy`, `enabled`, `tenantField`,
|
|
29
|
+
`fieldType`, `ref`, `contextKey`, `required`, `resolve`) is now typed
|
|
30
|
+
`T | undefined`, so downstream packages compiling with
|
|
31
|
+
`exactOptionalPropertyTypes: true` can extend it without redeclaring props
|
|
32
|
+
(unblocks `@classytic/ledger`'s `MultiTenantConfig.required` exception).
|
|
33
|
+
|
|
34
|
+
`resolveTenantConfig` strips explicit-`undefined` keys before spreading
|
|
35
|
+
over `DEFAULT_TENANT_CONFIG`, so `{ required: maybeUndefined }` can never
|
|
36
|
+
clobber a default with `undefined` — previously the un-widened type made
|
|
37
|
+
that unrepresentable; now it's handled at runtime and pinned by tests.
|
|
38
|
+
`DEFAULT_TENANT_CONFIG`'s annotation moved from `Required<Pick<...>>` to an
|
|
39
|
+
`Exclude<..., undefined>` mapping (`-?` does not strip an explicit
|
|
40
|
+
undefined union member). No runtime behavior change for existing callers.
|
|
41
|
+
|
|
7
42
|
## [0.6.0] - 2026-06-11
|
|
8
43
|
|
|
9
44
|
Standardization release. Coordinated with mongokit 3.16 + sqlitekit 0.6.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//#region src/sync/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Change-log / cursor contract — the data-sync spine for offline-first and
|
|
4
|
+
* incremental replication (`arc-sync`-style capability packages, POS offline,
|
|
5
|
+
* bulk delta export, cross-service read models).
|
|
6
|
+
*
|
|
7
|
+
* Synthesized from the proven protocols, taking the best of each:
|
|
8
|
+
*
|
|
9
|
+
* - CouchDB `_changes` → append-only feed, TOMBSTONES for deletes,
|
|
10
|
+
* client checkpoints
|
|
11
|
+
* - Drive/Graph delta APIs → OPAQUE server-issued cursors (`deltaLink` /
|
|
12
|
+
* `startPageToken`) — clients never parse them
|
|
13
|
+
* - Mongo change streams → resumable, ordered, per-scope feeds
|
|
14
|
+
* - Replicache push/pull → client mutation ids for idempotent PUSH,
|
|
15
|
+
* server-authoritative conflict verdicts
|
|
16
|
+
*
|
|
17
|
+
* CONTRACT ONLY — storage-agnostic (repo-core is the cross-kit contract home,
|
|
18
|
+
* like DataAdapter/EventTransport/IdempotencyStore). `MemoryChangeLogStore` is
|
|
19
|
+
* the reference impl. Durable stores + capture plugins live in the KITS
|
|
20
|
+
* (mongokit/prismakit/sqlitekit repository plugins); the HTTP surface
|
|
21
|
+
* (`/sync/pull`, `/sync/push`) is an arc module; arc re-exports this contract
|
|
22
|
+
* at `@classytic/arc/sync`. repo-core is source of truth.
|
|
23
|
+
*
|
|
24
|
+
* ## Required semantics (stores MUST honor)
|
|
25
|
+
*
|
|
26
|
+
* 1. **Cursors are opaque, totally ordered per store.** Clients echo them
|
|
27
|
+
* verbatim. A cursor from one store/scope-set is meaningless elsewhere.
|
|
28
|
+
* 2. **`append` is ordered and atomic with the business write** when a
|
|
29
|
+
* `session` is provided (same transaction — the outbox discipline).
|
|
30
|
+
* 3. **Deletes are tombstones**, never gaps. A client at cursor C must be able
|
|
31
|
+
* to converge by applying every entry after C — including deletions.
|
|
32
|
+
* 4. **`since` is exclusive** of the given cursor and returns entries in
|
|
33
|
+
* cursor order. `hasMore: true` means call again with the new cursor.
|
|
34
|
+
* 5. **Compaction (`prune`) may drop superseded intermediate versions but
|
|
35
|
+
* NEVER the latest state or an un-consumed tombstone horizon** — a store
|
|
36
|
+
* advertises its horizon so clients older than it do a full resync.
|
|
37
|
+
*/
|
|
38
|
+
/** What happened to a document. Field-level patches are deliberately out of
|
|
39
|
+
* scope — version-checked upserts + server authority beat CRDT complexity
|
|
40
|
+
* for ERP data (the Sheets/Replicache position, not the Figma one). */
|
|
41
|
+
type ChangeOp = "upsert" | "delete";
|
|
42
|
+
interface ChangeEntry<TDoc = unknown> {
|
|
43
|
+
/** Which logical collection/resource this change belongs to (e.g. `pos-order`). */
|
|
44
|
+
readonly scope: string;
|
|
45
|
+
/** Document identity within the scope. */
|
|
46
|
+
readonly docId: string;
|
|
47
|
+
readonly op: ChangeOp;
|
|
48
|
+
/**
|
|
49
|
+
* Monotonic per-document version — the optimistic-concurrency token.
|
|
50
|
+
* Conflict rule: a push carrying `baseVersion < current` is a conflict.
|
|
51
|
+
*/
|
|
52
|
+
readonly version: number;
|
|
53
|
+
/** Full document snapshot for `upsert`; absent for `delete` (tombstone). */
|
|
54
|
+
readonly doc?: TDoc;
|
|
55
|
+
/** Tenant partition (organizationId) — sync feeds are tenant-scoped. */
|
|
56
|
+
readonly tenantId?: string;
|
|
57
|
+
/** Server clock at capture — informational; ORDERING comes from the cursor. */
|
|
58
|
+
readonly at: Date;
|
|
59
|
+
/** Opaque position of THIS entry in the feed (assigned by the store). */
|
|
60
|
+
readonly cursor: string;
|
|
61
|
+
}
|
|
62
|
+
interface ChangesSinceOptions {
|
|
63
|
+
/** Max entries to return. Stores should default sensibly (e.g. 500). */
|
|
64
|
+
readonly limit?: number;
|
|
65
|
+
/** Restrict to these scopes (a client syncs the resources it opted into). */
|
|
66
|
+
readonly scopes?: readonly string[];
|
|
67
|
+
/** Tenant partition — REQUIRED by multi-tenant stores. */
|
|
68
|
+
readonly tenantId?: string;
|
|
69
|
+
}
|
|
70
|
+
interface ChangesPage<TDoc = unknown> {
|
|
71
|
+
readonly changes: ReadonlyArray<ChangeEntry<TDoc>>;
|
|
72
|
+
/** Checkpoint AFTER applying this page — echo into the next `since`. */
|
|
73
|
+
readonly cursor: string;
|
|
74
|
+
/** True → more entries exist; pull again immediately. */
|
|
75
|
+
readonly hasMore: boolean;
|
|
76
|
+
}
|
|
77
|
+
interface PushMutation<TDoc = unknown> {
|
|
78
|
+
readonly scope: string;
|
|
79
|
+
readonly docId: string;
|
|
80
|
+
readonly op: ChangeOp;
|
|
81
|
+
/** Version the client last saw — the optimistic-concurrency precondition. */
|
|
82
|
+
readonly baseVersion?: number;
|
|
83
|
+
readonly doc?: TDoc;
|
|
84
|
+
/**
|
|
85
|
+
* Client-unique id (`<clientId>:<seq>`) — replays of the same id MUST be
|
|
86
|
+
* acknowledged as already-applied, never re-executed (at-least-once safe).
|
|
87
|
+
*/
|
|
88
|
+
readonly mutationId: string;
|
|
89
|
+
}
|
|
90
|
+
type PushVerdictStatus = "applied" | "already_applied" | "conflict" | "rejected";
|
|
91
|
+
interface PushVerdict<TDoc = unknown> {
|
|
92
|
+
readonly mutationId: string;
|
|
93
|
+
readonly status: PushVerdictStatus;
|
|
94
|
+
/** Authoritative post-push version (also on conflict: the WINNING version). */
|
|
95
|
+
readonly version?: number;
|
|
96
|
+
/** Authoritative doc on conflict so the client can rebase (server wins). */
|
|
97
|
+
readonly current?: TDoc;
|
|
98
|
+
readonly reason?: string;
|
|
99
|
+
}
|
|
100
|
+
interface ChangeLogAppendOptions {
|
|
101
|
+
/** DB session/transaction handle — append atomically with the business write. */
|
|
102
|
+
readonly session?: unknown;
|
|
103
|
+
}
|
|
104
|
+
interface ChangeLogStore<TDoc = unknown> {
|
|
105
|
+
/** Record a change. `cursor`/`at` are ASSIGNED by the store; callers pass the rest. */
|
|
106
|
+
append(entry: Omit<ChangeEntry<TDoc>, "cursor" | "at">, options?: ChangeLogAppendOptions): Promise<ChangeEntry<TDoc>>;
|
|
107
|
+
/** Entries strictly AFTER `cursor` (empty string = from the beginning). */
|
|
108
|
+
since(cursor: string, options?: ChangesSinceOptions): Promise<ChangesPage<TDoc>>;
|
|
109
|
+
/** The current head checkpoint — what a fresh client stores after a full load. */
|
|
110
|
+
latestCursor(options?: Pick<ChangesSinceOptions, "tenantId" | "scopes">): Promise<string>;
|
|
111
|
+
/**
|
|
112
|
+
* Compact entries older than `before`, keeping per-doc latest state.
|
|
113
|
+
* Returns the new HORIZON cursor: clients checkpointed before it must full-resync.
|
|
114
|
+
*/
|
|
115
|
+
prune?(before: Date): Promise<string>;
|
|
116
|
+
}
|
|
117
|
+
/** Client checkpoint older than the store's compaction horizon → full resync. */
|
|
118
|
+
declare class CursorExpiredError extends Error {
|
|
119
|
+
readonly cursor: string;
|
|
120
|
+
readonly horizon: string;
|
|
121
|
+
constructor(cursor: string, horizon: string);
|
|
122
|
+
}
|
|
123
|
+
declare class MemoryChangeLogStore<TDoc = unknown> implements ChangeLogStore<TDoc> {
|
|
124
|
+
private entries;
|
|
125
|
+
private seq;
|
|
126
|
+
append(entry: Omit<ChangeEntry<TDoc>, "cursor" | "at">, _options?: ChangeLogAppendOptions): Promise<ChangeEntry<TDoc>>;
|
|
127
|
+
since(cursor: string, options?: ChangesSinceOptions): Promise<ChangesPage<TDoc>>;
|
|
128
|
+
latestCursor(): Promise<string>;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { ChangeEntry, ChangeLogAppendOptions, ChangeLogStore, ChangeOp, ChangesPage, ChangesSinceOptions, CursorExpiredError, MemoryChangeLogStore, PushMutation, PushVerdict, PushVerdictStatus };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/sync/index.ts
|
|
2
|
+
/** Client checkpoint older than the store's compaction horizon → full resync. */
|
|
3
|
+
var CursorExpiredError = class extends Error {
|
|
4
|
+
constructor(cursor, horizon) {
|
|
5
|
+
super(`[repo-core:sync] cursor "${cursor}" predates the compaction horizon — full resync required.`);
|
|
6
|
+
this.cursor = cursor;
|
|
7
|
+
this.horizon = horizon;
|
|
8
|
+
this.name = "CursorExpiredError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var MemoryChangeLogStore = class {
|
|
12
|
+
entries = [];
|
|
13
|
+
seq = 0;
|
|
14
|
+
async append(entry, _options) {
|
|
15
|
+
const cursor = String(++this.seq).padStart(16, "0");
|
|
16
|
+
const full = {
|
|
17
|
+
...entry,
|
|
18
|
+
cursor,
|
|
19
|
+
at: /* @__PURE__ */ new Date()
|
|
20
|
+
};
|
|
21
|
+
this.entries.push(full);
|
|
22
|
+
return full;
|
|
23
|
+
}
|
|
24
|
+
async since(cursor, options = {}) {
|
|
25
|
+
const { limit = 500, scopes, tenantId } = options;
|
|
26
|
+
const filtered = this.entries.filter((e) => e.cursor > cursor && (!scopes || scopes.includes(e.scope)) && (tenantId === void 0 || e.tenantId === tenantId));
|
|
27
|
+
const page = filtered.slice(0, limit);
|
|
28
|
+
const last = page[page.length - 1];
|
|
29
|
+
return {
|
|
30
|
+
changes: page,
|
|
31
|
+
cursor: last ? last.cursor : cursor,
|
|
32
|
+
hasMore: filtered.length > page.length
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async latestCursor() {
|
|
36
|
+
const last = this.entries[this.entries.length - 1];
|
|
37
|
+
return last ? last.cursor : "";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { CursorExpiredError, MemoryChangeLogStore };
|
|
@@ -10,18 +10,8 @@ import { ResolvedTenantConfig, TenantConfig } from "./types.mjs";
|
|
|
10
10
|
* runtime default — `Pick<TenantConfig, 'fieldType'>` extension preserves
|
|
11
11
|
* type-level alignment without forcing a runtime default change.
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* Resolve a possibly-partial {@link TenantConfig} against the defaults.
|
|
16
|
-
*
|
|
17
|
-
* - `false` → `enabled: false`, `strategy: 'none'`, `required: false`.
|
|
18
|
-
* - `true` / `undefined` → default field strategy.
|
|
19
|
-
* - Object with `strategy: 'custom'` → `resolve` is required; throws
|
|
20
|
-
* otherwise so the misconfiguration surfaces at boot, not runtime.
|
|
21
|
-
* - Object with `strategy: 'none'` → `enabled: false` (preserves
|
|
22
|
-
* user-supplied `tenantField` / `fieldType` / `ref` so the doc field
|
|
23
|
-
* stays correctly typed even with scoping off).
|
|
24
|
-
*/
|
|
13
|
+
type TenantDefaults = { [K in 'strategy' | 'enabled' | 'tenantField' | 'fieldType' | 'ref' | 'contextKey' | 'required']-?: Exclude<TenantConfig[K], undefined> };
|
|
14
|
+
declare const DEFAULT_TENANT_CONFIG: TenantDefaults;
|
|
25
15
|
declare function resolveTenantConfig(config?: TenantConfig | boolean): ResolvedTenantConfig;
|
|
26
16
|
//#endregion
|
|
27
17
|
export { DEFAULT_TENANT_CONFIG, resolveTenantConfig };
|
package/dist/tenant/resolve.mjs
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
//#region src/tenant/resolve.ts
|
|
2
|
-
/**
|
|
3
|
-
* Sensible defaults for a freshly-built package (field strategy).
|
|
4
|
-
*
|
|
5
|
-
* `fieldType: 'objectId'` is the recommended default for new Mongo-shaped
|
|
6
|
-
* kits because it enables `$lookup` / `.populate()`. Existing kits that
|
|
7
|
-
* historically defaulted to `'string'` (mongokit pre-3.x) keep their own
|
|
8
|
-
* runtime default — `Pick<TenantConfig, 'fieldType'>` extension preserves
|
|
9
|
-
* type-level alignment without forcing a runtime default change.
|
|
10
|
-
*/
|
|
11
2
|
const DEFAULT_TENANT_CONFIG = {
|
|
12
3
|
strategy: "field",
|
|
13
4
|
enabled: true,
|
|
@@ -17,17 +8,11 @@ const DEFAULT_TENANT_CONFIG = {
|
|
|
17
8
|
contextKey: "organizationId",
|
|
18
9
|
required: true
|
|
19
10
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* - Object with `strategy: 'custom'` → `resolve` is required; throws
|
|
26
|
-
* otherwise so the misconfiguration surfaces at boot, not runtime.
|
|
27
|
-
* - Object with `strategy: 'none'` → `enabled: false` (preserves
|
|
28
|
-
* user-supplied `tenantField` / `fieldType` / `ref` so the doc field
|
|
29
|
-
* stays correctly typed even with scoping off).
|
|
30
|
-
*/
|
|
11
|
+
function stripUndefined(obj) {
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const [k, v] of Object.entries(obj)) if (v !== void 0) out[k] = v;
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
31
16
|
function resolveTenantConfig(config) {
|
|
32
17
|
if (config === false) return {
|
|
33
18
|
...DEFAULT_TENANT_CONFIG,
|
|
@@ -36,33 +21,34 @@ function resolveTenantConfig(config) {
|
|
|
36
21
|
required: false
|
|
37
22
|
};
|
|
38
23
|
if (config === true || config === void 0) return { ...DEFAULT_TENANT_CONFIG };
|
|
39
|
-
const
|
|
40
|
-
const
|
|
24
|
+
const cleaned = stripUndefined(config);
|
|
25
|
+
const strategy = cleaned.strategy ?? (cleaned.enabled === false ? "none" : "field");
|
|
26
|
+
const contextKey = cleaned.contextKey ?? cleaned.tenantField ?? DEFAULT_TENANT_CONFIG.contextKey;
|
|
41
27
|
if (strategy === "none") return {
|
|
42
28
|
...DEFAULT_TENANT_CONFIG,
|
|
43
|
-
...
|
|
29
|
+
...cleaned,
|
|
44
30
|
contextKey,
|
|
45
31
|
strategy: "none",
|
|
46
32
|
enabled: false,
|
|
47
33
|
required: false
|
|
48
34
|
};
|
|
49
35
|
if (strategy === "custom") {
|
|
50
|
-
if (typeof
|
|
36
|
+
if (typeof cleaned.resolve !== "function") throw new Error("[repo-core] TenantConfig.strategy 'custom' requires a 'resolve' function");
|
|
51
37
|
return {
|
|
52
38
|
...DEFAULT_TENANT_CONFIG,
|
|
53
|
-
...
|
|
39
|
+
...cleaned,
|
|
54
40
|
contextKey,
|
|
55
41
|
strategy: "custom",
|
|
56
|
-
enabled:
|
|
57
|
-
resolve:
|
|
42
|
+
enabled: cleaned.enabled ?? true,
|
|
43
|
+
resolve: cleaned.resolve
|
|
58
44
|
};
|
|
59
45
|
}
|
|
60
46
|
return {
|
|
61
47
|
...DEFAULT_TENANT_CONFIG,
|
|
62
|
-
...
|
|
48
|
+
...cleaned,
|
|
63
49
|
contextKey,
|
|
64
50
|
strategy: "field",
|
|
65
|
-
enabled:
|
|
51
|
+
enabled: cleaned.enabled ?? true
|
|
66
52
|
};
|
|
67
53
|
}
|
|
68
54
|
//#endregion
|
package/dist/tenant/types.d.mts
CHANGED
|
@@ -50,7 +50,7 @@ interface TenantConfig {
|
|
|
50
50
|
*
|
|
51
51
|
* @default 'field'
|
|
52
52
|
*/
|
|
53
|
-
strategy?: TenantStrategy;
|
|
53
|
+
strategy?: TenantStrategy | undefined;
|
|
54
54
|
/**
|
|
55
55
|
* Whether tenant scoping is active. When `false`, the package runs in
|
|
56
56
|
* single-tenant mode — no filter injection, no tenant field on documents.
|
|
@@ -58,27 +58,27 @@ interface TenantConfig {
|
|
|
58
58
|
*
|
|
59
59
|
* @default true
|
|
60
60
|
*/
|
|
61
|
-
enabled?: boolean;
|
|
61
|
+
enabled?: boolean | undefined;
|
|
62
62
|
/**
|
|
63
63
|
* Document / column field name that stores the tenant id. Used when
|
|
64
64
|
* `strategy === 'field'`.
|
|
65
65
|
*
|
|
66
66
|
* @default 'organizationId'
|
|
67
67
|
*/
|
|
68
|
-
tenantField?: string;
|
|
68
|
+
tenantField?: string | undefined;
|
|
69
69
|
/**
|
|
70
70
|
* How to store / cast the tenant id.
|
|
71
71
|
*
|
|
72
72
|
* @default 'objectId'
|
|
73
73
|
*/
|
|
74
|
-
fieldType?: TenantFieldType;
|
|
74
|
+
fieldType?: TenantFieldType | undefined;
|
|
75
75
|
/**
|
|
76
76
|
* Mongoose ref for `'objectId'` types. Ignored by SQL kits and when
|
|
77
77
|
* `fieldType === 'string'`.
|
|
78
78
|
*
|
|
79
79
|
* @default 'organization'
|
|
80
80
|
*/
|
|
81
|
-
ref?: string;
|
|
81
|
+
ref?: string | undefined;
|
|
82
82
|
/**
|
|
83
83
|
* Which key on the repository context to read the tenant id from.
|
|
84
84
|
*
|
|
@@ -92,14 +92,14 @@ interface TenantConfig {
|
|
|
92
92
|
*
|
|
93
93
|
* @default tenantField ?? 'organizationId'
|
|
94
94
|
*/
|
|
95
|
-
contextKey?: string;
|
|
95
|
+
contextKey?: string | undefined;
|
|
96
96
|
/**
|
|
97
97
|
* Whether the field is required. When `false`, the package permits
|
|
98
98
|
* unscoped / cross-tenant reads (typically only for admin paths).
|
|
99
99
|
*
|
|
100
100
|
* @default true
|
|
101
101
|
*/
|
|
102
|
-
required?: boolean;
|
|
102
|
+
required?: boolean | undefined;
|
|
103
103
|
/**
|
|
104
104
|
* Custom resolver — called when `strategy === 'custom'` to produce the
|
|
105
105
|
* filter object injected into queries. Packages pass the request /
|
|
@@ -121,7 +121,7 @@ interface TenantConfig {
|
|
|
121
121
|
* }
|
|
122
122
|
* ```
|
|
123
123
|
*/
|
|
124
|
-
resolve?: (ctx: Record<string, unknown>) => Record<string, unknown
|
|
124
|
+
resolve?: (ctx: Record<string, unknown>) => Record<string, unknown> | undefined;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
127
|
* Resolved shape returned by `resolveTenantConfig`. Always includes the
|
|
@@ -136,7 +136,7 @@ type ResolvedTenantConfig = {
|
|
|
136
136
|
ref: string;
|
|
137
137
|
contextKey: string;
|
|
138
138
|
required: boolean;
|
|
139
|
-
resolve?: TenantConfig['resolve'];
|
|
139
|
+
resolve?: TenantConfig['resolve'] | undefined;
|
|
140
140
|
};
|
|
141
141
|
//#endregion
|
|
142
142
|
export { ResolvedTenantConfig, TenantConfig, TenantFieldType, TenantStrategy };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/repo-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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,
|
|
@@ -94,7 +94,11 @@
|
|
|
94
94
|
"types": "./dist/lock/index.d.mts",
|
|
95
95
|
"default": "./dist/lock/index.mjs"
|
|
96
96
|
},
|
|
97
|
-
"./package.json": "./package.json"
|
|
97
|
+
"./package.json": "./package.json",
|
|
98
|
+
"./sync": {
|
|
99
|
+
"types": "./dist/sync/index.d.mts",
|
|
100
|
+
"default": "./dist/sync/index.mjs"
|
|
101
|
+
}
|
|
98
102
|
},
|
|
99
103
|
"keywords": [
|
|
100
104
|
"repository",
|