@classytic/repo-core 0.6.0 → 0.6.1
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 +18 -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 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ 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.6.1] - 2026-07-04
|
|
8
|
+
|
|
9
|
+
### Changed — `TenantConfig` optionals widened to `T | undefined` (P10)
|
|
10
|
+
|
|
11
|
+
Every optional prop on `TenantConfig` (`strategy`, `enabled`, `tenantField`,
|
|
12
|
+
`fieldType`, `ref`, `contextKey`, `required`, `resolve`) is now typed
|
|
13
|
+
`T | undefined`, so downstream packages compiling with
|
|
14
|
+
`exactOptionalPropertyTypes: true` can extend it without redeclaring props
|
|
15
|
+
(unblocks `@classytic/ledger`'s `MultiTenantConfig.required` exception).
|
|
16
|
+
|
|
17
|
+
`resolveTenantConfig` strips explicit-`undefined` keys before spreading
|
|
18
|
+
over `DEFAULT_TENANT_CONFIG`, so `{ required: maybeUndefined }` can never
|
|
19
|
+
clobber a default with `undefined` — previously the un-widened type made
|
|
20
|
+
that unrepresentable; now it's handled at runtime and pinned by tests.
|
|
21
|
+
`DEFAULT_TENANT_CONFIG`'s annotation moved from `Required<Pick<...>>` to an
|
|
22
|
+
`Exclude<..., undefined>` mapping (`-?` does not strip an explicit
|
|
23
|
+
undefined union member). No runtime behavior change for existing callers.
|
|
24
|
+
|
|
7
25
|
## [0.6.0] - 2026-06-11
|
|
8
26
|
|
|
9
27
|
Standardization release. Coordinated with mongokit 3.16 + sqlitekit 0.6.
|
|
@@ -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.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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,
|