@company-semantics/contracts 5.0.0 → 5.1.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/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
toQueryKey,
|
|
4
|
+
fromQueryKey,
|
|
5
|
+
matchesResourceKey,
|
|
6
|
+
type ResourceKey,
|
|
7
|
+
} from '../resource-keys.js';
|
|
8
|
+
|
|
9
|
+
const ORG_ID = '11111111-1111-4111-8111-111111111111';
|
|
10
|
+
|
|
11
|
+
describe('resource-keys: orgUnitOwners (org-scoped)', () => {
|
|
12
|
+
const key: ResourceKey = { type: 'orgUnitOwners', orgId: ORG_ID };
|
|
13
|
+
|
|
14
|
+
it('toQueryKey produces [type, orgId] with no unitId', () => {
|
|
15
|
+
const qk = toQueryKey(key);
|
|
16
|
+
expect(qk).toEqual(['orgUnitOwners', ORG_ID]);
|
|
17
|
+
// org-scoped: exactly two segments, no per-unit identity
|
|
18
|
+
expect(qk).toHaveLength(2);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('roundtrips through fromQueryKey', () => {
|
|
22
|
+
const parsed = fromQueryKey(toQueryKey(key));
|
|
23
|
+
expect(parsed).toEqual(key);
|
|
24
|
+
expect('unitId' in parsed).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('matchesResourceKey is org-scoped: rejects a different orgId', () => {
|
|
28
|
+
const other = '22222222-2222-4222-8222-222222222222';
|
|
29
|
+
expect(matchesResourceKey(toQueryKey(key), key)).toBe(true);
|
|
30
|
+
expect(matchesResourceKey(['orgUnitOwners', other], key)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('resource-keys: system-scoped internalAdmin* types', () => {
|
|
35
|
+
const systemTypes = [
|
|
36
|
+
'internalAdminAiProviders',
|
|
37
|
+
'internalAdminPrompts',
|
|
38
|
+
'internalAdminAiRuntimeDefaults',
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
for (const type of systemTypes) {
|
|
42
|
+
const key = { type, scope: 'system' } as ResourceKey;
|
|
43
|
+
|
|
44
|
+
it(`${type}: toQueryKey produces [type, 'system']`, () => {
|
|
45
|
+
expect(toQueryKey(key)).toEqual([type, 'system']);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it(`${type}: roundtrips through fromQueryKey with scope: 'system'`, () => {
|
|
49
|
+
const parsed = fromQueryKey(toQueryKey(key));
|
|
50
|
+
expect(parsed).toEqual(key);
|
|
51
|
+
expect('orgId' in parsed).toBe(false);
|
|
52
|
+
expect('userId' in parsed).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it(`${type}: matchesResourceKey accepts a matching system key`, () => {
|
|
56
|
+
expect(matchesResourceKey(toQueryKey(key), key)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
it('matchesResourceKey rejects a mismatched system type', () => {
|
|
61
|
+
const target: ResourceKey = { type: 'internalAdminPrompts', scope: 'system' };
|
|
62
|
+
expect(matchesResourceKey(['internalAdminAiProviders', 'system'], target)).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
});
|
package/src/resource-keys.ts
CHANGED
|
@@ -36,8 +36,16 @@ export type ResourceKey =
|
|
|
36
36
|
| { type: 'orgUnitAncestors'; orgId: string; unitId: string }
|
|
37
37
|
| { type: 'orgUnitMemberships'; orgId: string; unitId: string }
|
|
38
38
|
| { type: 'orgUnitPermissions'; orgId: string; unitId: string }
|
|
39
|
+
// Org-unit owners list (ADR-CONTRACTS-052) — owners are an org-wide
|
|
40
|
+
// projection, not a per-unit collection, so unitId is intentionally excluded.
|
|
41
|
+
| { type: 'orgUnitOwners'; orgId: string }
|
|
39
42
|
// People reporting (ADR-BE-166) — drives the settings Org chart drill-down
|
|
40
43
|
| { type: 'peopleOrgChart'; orgId: string }
|
|
44
|
+
// System-scoped (ADR-CONTRACTS-052) — tenant-less super-admin resources.
|
|
45
|
+
// No orgId/userId: these live above any single org. scope is the literal 'system'.
|
|
46
|
+
| { type: 'internalAdminAiProviders'; scope: 'system' }
|
|
47
|
+
| { type: 'internalAdminPrompts'; scope: 'system' }
|
|
48
|
+
| { type: 'internalAdminAiRuntimeDefaults'; scope: 'system' }
|
|
41
49
|
// User-scoped
|
|
42
50
|
| { type: 'dismissedBanners'; userId: string }
|
|
43
51
|
| { type: 'userOrgs'; userId: string }
|
|
@@ -64,11 +72,20 @@ const ORG_SCOPED_TYPES = [
|
|
|
64
72
|
'members', 'departments', 'chats', 'teams', 'integrations', 'invites',
|
|
65
73
|
'auditEvents', 'timeline', 'workspace', 'workspaceDomains', 'authSettings',
|
|
66
74
|
'billing', 'aiUsage', 'deletionEligibility', 'transferOwnership', 'companyMdDocs',
|
|
67
|
-
'orgTree', 'orgLevelConfig', 'peopleOrgChart',
|
|
75
|
+
'orgTree', 'orgLevelConfig', 'peopleOrgChart', 'orgUnitOwners',
|
|
68
76
|
] as const;
|
|
69
77
|
|
|
70
78
|
const USER_SCOPED_TYPES = ['dismissedBanners', 'userOrgs', 'sessions', 'viewer'] as const;
|
|
71
79
|
|
|
80
|
+
/**
|
|
81
|
+
* System-scoped types (ADR-CONTRACTS-052) — tenant-less super-admin resources.
|
|
82
|
+
* Their query key is [type, 'system']; they carry no orgId or userId.
|
|
83
|
+
* Intentionally NOT exported — internal to the parser like the other scope arrays.
|
|
84
|
+
*/
|
|
85
|
+
const SYSTEM_SCOPED_TYPES = [
|
|
86
|
+
'internalAdminAiProviders', 'internalAdminPrompts', 'internalAdminAiRuntimeDefaults',
|
|
87
|
+
] as const;
|
|
88
|
+
|
|
72
89
|
/**
|
|
73
90
|
* Canonical ResourceKey → query key conversion.
|
|
74
91
|
* This is the ONLY function that constructs query keys — no ad-hoc key
|
|
@@ -128,8 +145,15 @@ export function toQueryKey(key: ResourceKey): readonly string[] {
|
|
|
128
145
|
case 'orgTree':
|
|
129
146
|
case 'orgLevelConfig':
|
|
130
147
|
case 'peopleOrgChart':
|
|
148
|
+
case 'orgUnitOwners':
|
|
131
149
|
return [key.type, key.orgId] as const;
|
|
132
150
|
|
|
151
|
+
// System-scoped (ADR-CONTRACTS-052) — tenant-less super-admin resources
|
|
152
|
+
case 'internalAdminAiProviders':
|
|
153
|
+
case 'internalAdminPrompts':
|
|
154
|
+
case 'internalAdminAiRuntimeDefaults':
|
|
155
|
+
return [key.type, key.scope] as const;
|
|
156
|
+
|
|
133
157
|
default: {
|
|
134
158
|
const _exhaustive: never = key;
|
|
135
159
|
throw new Error(`Unknown resource type: ${JSON.stringify(_exhaustive)}`);
|
|
@@ -170,6 +194,11 @@ export function fromQueryKey(queryKey: readonly string[]): ResourceKey {
|
|
|
170
194
|
return { type, orgId: rest[0], [identityFields[type]]: rest[1] } as ResourceKey;
|
|
171
195
|
}
|
|
172
196
|
|
|
197
|
+
// System-scoped types (ADR-CONTRACTS-052) — query key is [type, 'system']
|
|
198
|
+
if ((SYSTEM_SCOPED_TYPES as readonly string[]).includes(type)) {
|
|
199
|
+
return { type, scope: 'system' } as ResourceKey;
|
|
200
|
+
}
|
|
201
|
+
|
|
173
202
|
// User-scoped types
|
|
174
203
|
if ((USER_SCOPED_TYPES as readonly string[]).includes(type)) {
|
|
175
204
|
return { type, userId: rest[0] } as ResourceKey;
|
|
@@ -238,6 +267,7 @@ export function matchesResourceKey(queryKey: readonly unknown[], targetKey: Reso
|
|
|
238
267
|
if ('chatId' in parsed && 'chatId' in targetKey && parsed.chatId !== targetKey.chatId) return false;
|
|
239
268
|
if ('slug' in parsed && 'slug' in targetKey && parsed.slug !== targetKey.slug) return false;
|
|
240
269
|
if ('unitId' in parsed && 'unitId' in targetKey && parsed.unitId !== targetKey.unitId) return false;
|
|
270
|
+
if ('scope' in parsed && 'scope' in targetKey && parsed.scope !== targetKey.scope) return false;
|
|
241
271
|
|
|
242
272
|
return true;
|
|
243
273
|
}
|