@typeb-digital/nucleus-sdk 0.0.1 → 0.0.2

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/src/types.ts DELETED
@@ -1,447 +0,0 @@
1
- /**
2
- * Nucleus SDK — Type System
3
- *
4
- * Bucket types are transcribed from docs/NUCLEUS_PIVOT.md §7 (Bucket Definitions).
5
- * Each bucket type contains only that bucket's fields.
6
- * The scope-to-type inference intersects selected bucket types at compile time.
7
- */
8
-
9
- // ─── Utility types ────────────────────────────────────────────────────────────
10
-
11
- // Converts a union to an intersection: A | B → A & B
12
- export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (
13
- k: infer I,
14
- ) => void
15
- ? I
16
- : never;
17
-
18
- // Returned when a resource has no declared scopes — accessing any field is a compile error
19
- export type NoScope = Record<never, never>;
20
-
21
- // ─── Result types (§3.5) ─────────────────────────────────────────────────────
22
-
23
- export type ErrorCode =
24
- | 'FORBIDDEN'
25
- | 'NOT_FOUND'
26
- | 'INVALID_SCOPE'
27
- | 'RATE_LIMITED'
28
- | 'NETWORK_ERROR';
29
-
30
- export type ErrorResult = {
31
- error: { code: ErrorCode; message: string };
32
- };
33
-
34
- export type SuccessResult<T> = { data: T };
35
-
36
- export type ListMeta = {
37
- total: number;
38
- page: number;
39
- pageSize: number;
40
- hasMore: boolean;
41
- };
42
-
43
- export type ListResult<T> = { data: T[]; meta: ListMeta };
44
-
45
- export type SingleResult<T> = SuccessResult<T> | ErrorResult;
46
- export type PaginatedResult<T> = ListResult<T> | ErrorResult;
47
-
48
- export function isError<T>(
49
- result: SuccessResult<T> | ListResult<T> | ErrorResult,
50
- ): result is ErrorResult {
51
- return 'error' in result;
52
- }
53
-
54
- // ─── §7.1 Employees — bucket types ──────────────────────────────────────────
55
-
56
- export type EmployeeIdentity = {
57
- id: string;
58
- displayName: string;
59
- firstName: string;
60
- lastName: string;
61
- email: string;
62
- pictureUrl: string | null;
63
- jobTitle: string | null;
64
- department: string | null;
65
- employmentStatus: string;
66
- isExternal: boolean;
67
- };
68
-
69
- export type EmployeeContact = {
70
- profile: {
71
- phone?: string | null;
72
- city?: string | null;
73
- country?: string | null;
74
- };
75
- };
76
-
77
- export type EmployeeEmployment = {
78
- startDate: string | null;
79
- weeklyCapacity: number | null;
80
- managerId: string | null;
81
- partnerId: string | null;
82
- /** IANA timezone string, e.g. "Asia/Colombo". Null when not yet set. */
83
- timezone: string | null;
84
- profile: {
85
- biography?: string | null;
86
- };
87
- };
88
-
89
- export type EmployeeSensitive = {
90
- birthday: string | null;
91
- profile: {
92
- emergencyContact?: {
93
- name: string | null;
94
- phone: string | null;
95
- } | null;
96
- };
97
- customFields: Record<string, unknown> | null;
98
- };
99
-
100
- export type EmployeeCompensation = {
101
- currentCompensation: {
102
- hourlyCostRate: number | null;
103
- hourlyBillableRate: number | null;
104
- monthlyCostRate: number | null;
105
- monthlyBillableRate: number | null;
106
- currencyCode: string;
107
- effectiveFrom: string;
108
- } | null;
109
- };
110
-
111
- export type EmployeeCompensationHistory = {
112
- compensationHistory: Array<{
113
- hourlyCostRate: number | null;
114
- hourlyBillableRate: number | null;
115
- monthlyCostRate: number | null;
116
- monthlyBillableRate: number | null;
117
- currencyCode: string;
118
- effectiveFrom: string;
119
- effectiveTo: string | null;
120
- }>;
121
- };
122
-
123
- export type EmployeeBucketMap = {
124
- identity: EmployeeIdentity;
125
- contact: EmployeeContact;
126
- employment: EmployeeEmployment;
127
- sensitive: EmployeeSensitive;
128
- compensation: EmployeeCompensation;
129
- compensation_history: EmployeeCompensationHistory;
130
- };
131
-
132
- // ─── §7.2 Projects — bucket types ───────────────────────────────────────────
133
-
134
- export type ProjectMembership = {
135
- employeeId: string;
136
- role: string | null;
137
- };
138
-
139
- export type ProjectCore = {
140
- id: string;
141
- name: string;
142
- status: string;
143
- colorTag: string | null;
144
- iconName: string | null;
145
- startDate: string | null;
146
- endDate: string | null;
147
- projectTypeId: string | null;
148
- clientId: string | null;
149
- };
150
-
151
- export type ProjectTeam = {
152
- projectManagerId: string | null;
153
- engagementLeadId: string | null;
154
- memberships: ProjectMembership[];
155
- };
156
-
157
- export type ProjectFinancials = Record<never, never>; // reserved — no fields yet
158
-
159
- export type ProjectIntegrations = {
160
- source: string | null;
161
- clockifyProjectId: string | null;
162
- clockifyWorkspaceId: string | null;
163
- apolloDealId: string | null;
164
- };
165
-
166
- export type ProjectBucketMap = {
167
- core: ProjectCore;
168
- team: ProjectTeam;
169
- financials: ProjectFinancials;
170
- integrations: ProjectIntegrations;
171
- };
172
-
173
- // ─── §7.3 Clients — bucket types ────────────────────────────────────────────
174
-
175
- export type ClientIdentity = {
176
- id: string;
177
- name: string;
178
- code: string | null;
179
- industry: string | null;
180
- website: string | null;
181
- country: string | null;
182
- };
183
-
184
- export type ClientContact = {
185
- primaryContactName: string | null;
186
- primaryContactEmail: string | null;
187
- contactPhone: string | null;
188
- };
189
-
190
- export type ClientFinancials = {
191
- defaultCurrency: string | null;
192
- };
193
-
194
- export type ClientNotes = {
195
- notes: string | null;
196
- };
197
-
198
- export type ClientBucketMap = {
199
- identity: ClientIdentity;
200
- contact: ClientContact;
201
- financials: ClientFinancials;
202
- notes: ClientNotes;
203
- };
204
-
205
- // ─── §7.4 Partners — bucket types ───────────────────────────────────────────
206
-
207
- export type PartnerIdentity = {
208
- id: string;
209
- name: string;
210
- };
211
-
212
- export type PartnerContact = {
213
- contactName: string | null;
214
- contactEmail: string | null;
215
- contactPhone: string | null;
216
- };
217
-
218
- export type PartnerNotes = {
219
- notes: string | null;
220
- };
221
-
222
- export type PartnerBucketMap = {
223
- identity: PartnerIdentity;
224
- contact: PartnerContact;
225
- notes: PartnerNotes;
226
- };
227
-
228
- // ─── §7.5 Departments ───────────────────────────────────────────────────────
229
-
230
- export type DepartmentCore = {
231
- id: string;
232
- name: string;
233
- titles: string[];
234
- sortOrder: number;
235
- };
236
-
237
- export type DepartmentBucketMap = {
238
- core: DepartmentCore;
239
- };
240
-
241
- // ─── §7.6 Project Types ─────────────────────────────────────────────────────
242
-
243
- export type ProjectTypeCore = {
244
- id: string;
245
- name: string;
246
- description: string | null;
247
- sortOrder: number;
248
- };
249
-
250
- export type ProjectTypeBucketMap = {
251
- core: ProjectTypeCore;
252
- };
253
-
254
- // ─── §7.7 Currencies ────────────────────────────────────────────────────────
255
-
256
- export type CurrencyCore = {
257
- code: string;
258
- name: string;
259
- symbol: string | null;
260
- isDefault: boolean;
261
- sortOrder: number;
262
- };
263
-
264
- export type CurrencyBucketMap = {
265
- core: CurrencyCore;
266
- };
267
-
268
- // ─── §7.8 Generic Rates ─────────────────────────────────────────────────────
269
-
270
- export type GenericRateCore = {
271
- id: string;
272
- title: string;
273
- department: string;
274
- currencyCode: string;
275
- };
276
-
277
- export type GenericRateFinancials = {
278
- costRate: number;
279
- billableRate: number;
280
- };
281
-
282
- export type GenericRateBucketMap = {
283
- core: GenericRateCore;
284
- financials: GenericRateFinancials;
285
- };
286
-
287
- // ─── Scope declaration ────────────────────────────────────────────────────────
288
-
289
- export type ScopeDeclaration = {
290
- employees?: readonly (keyof EmployeeBucketMap)[];
291
- projects?: readonly (keyof ProjectBucketMap)[];
292
- clients?: readonly (keyof ClientBucketMap)[];
293
- partners?: readonly (keyof PartnerBucketMap)[];
294
- departments?: readonly (keyof DepartmentBucketMap)[];
295
- projectTypes?: readonly (keyof ProjectTypeBucketMap)[];
296
- currencies?: readonly (keyof CurrencyBucketMap)[];
297
- genericRates?: readonly (keyof GenericRateBucketMap)[];
298
- };
299
-
300
- // ─── Scope resolution ────────────────────────────────────────────────────────
301
- //
302
- // ResolveBuckets<BucketMap, Buckets> intersects the bucket types selected by
303
- // the developer. When no scopes are declared, returns NoScope so that accessing
304
- // any field is a compile-time error.
305
-
306
- type ResolveBuckets<M, B> = B extends readonly (keyof M)[]
307
- ? [B[number]] extends [never]
308
- ? NoScope
309
- : UnionToIntersection<M[B[number]]>
310
- : NoScope;
311
-
312
- export type ResolveEmployee<S extends ScopeDeclaration> = ResolveBuckets<
313
- EmployeeBucketMap,
314
- S['employees']
315
- >;
316
-
317
- export type ResolveProject<S extends ScopeDeclaration> = ResolveBuckets<
318
- ProjectBucketMap,
319
- S['projects']
320
- >;
321
-
322
- export type ResolveClient<S extends ScopeDeclaration> = ResolveBuckets<
323
- ClientBucketMap,
324
- S['clients']
325
- >;
326
-
327
- export type ResolvePartner<S extends ScopeDeclaration> = ResolveBuckets<
328
- PartnerBucketMap,
329
- S['partners']
330
- >;
331
-
332
- export type ResolveDepartment<S extends ScopeDeclaration> = ResolveBuckets<
333
- DepartmentBucketMap,
334
- S['departments']
335
- >;
336
-
337
- export type ResolveProjectType<S extends ScopeDeclaration> = ResolveBuckets<
338
- ProjectTypeBucketMap,
339
- S['projectTypes']
340
- >;
341
-
342
- export type ResolveCurrency<S extends ScopeDeclaration> = ResolveBuckets<
343
- CurrencyBucketMap,
344
- S['currencies']
345
- >;
346
-
347
- export type ResolveGenericRate<S extends ScopeDeclaration> = ResolveBuckets<
348
- GenericRateBucketMap,
349
- S['genericRates']
350
- >;
351
-
352
- // ─── Expand paths (§3.7, §7) — max two levels, no three-segment paths ────────
353
-
354
- export type EmployeeExpandPath = 'manager' | 'partner';
355
-
356
- // Expand contributions added to the base employee type
357
- export type WithEmployeeExpand<
358
- T,
359
- E extends readonly EmployeeExpandPath[],
360
- S extends ScopeDeclaration,
361
- > = T &
362
- ('manager' extends E[number] ? { manager: ResolveEmployee<S> | null } : unknown) &
363
- ('partner' extends E[number] ? { partner: PartnerIdentity | null } : unknown);
364
-
365
- export type ProjectExpandPath =
366
- | 'projectType'
367
- | 'client'
368
- | 'projectManager'
369
- | 'projectManager.manager'
370
- | 'engagementLead'
371
- | 'engagementLead.manager'
372
- | 'memberships.employee';
373
-
374
- // Expand contributions for projects. Handles depth-2 paths by nesting manager.
375
- // memberships.employee overrides the memberships[] type to include employee.
376
- type ProjectMembershipExpanded<S extends ScopeDeclaration> = {
377
- employeeId: string;
378
- role: string | null;
379
- employee: ResolveEmployee<S>;
380
- };
381
-
382
- export type WithProjectExpand<
383
- T,
384
- E extends readonly ProjectExpandPath[],
385
- S extends ScopeDeclaration,
386
- > = ('memberships.employee' extends E[number]
387
- ? Omit<T, 'memberships'> & { memberships: Array<ProjectMembershipExpanded<S>> }
388
- : T) &
389
- ('client' extends E[number] ? { client: ResolveClient<S> | null } : unknown) &
390
- ('projectType' extends E[number] ? { projectType: ProjectTypeCore | null } : unknown) &
391
- ('projectManager.manager' extends E[number]
392
- ? { projectManager: (ResolveEmployee<S> & { manager: ResolveEmployee<S> | null }) | null }
393
- : 'projectManager' extends E[number]
394
- ? { projectManager: ResolveEmployee<S> | null }
395
- : unknown) &
396
- ('engagementLead.manager' extends E[number]
397
- ? { engagementLead: (ResolveEmployee<S> & { manager: ResolveEmployee<S> | null }) | null }
398
- : 'engagementLead' extends E[number]
399
- ? { engagementLead: ResolveEmployee<S> | null }
400
- : unknown);
401
-
402
- export type ClientExpandPath = 'projects';
403
-
404
- export type WithClientExpand<
405
- T,
406
- E extends readonly ClientExpandPath[],
407
- S extends ScopeDeclaration,
408
- > = T & ('projects' extends E[number] ? { projects: Array<ResolveProject<S>> } : unknown);
409
-
410
- export type PartnerExpandPath = 'employees';
411
-
412
- export type WithPartnerExpand<
413
- T,
414
- E extends readonly PartnerExpandPath[],
415
- S extends ScopeDeclaration,
416
- > = T & ('employees' extends E[number] ? { employees: Array<ResolveEmployee<S>> } : unknown);
417
-
418
- // ─── apps.me() response ──────────────────────────────────────────────────────
419
-
420
- export type AppScopeInfo = {
421
- id: string;
422
- resource: string;
423
- buckets: string[];
424
- status: 'active' | 'pending' | 'rejected';
425
- reviewNote: string | null;
426
- };
427
-
428
- export type AppInfo = {
429
- id: string;
430
- appId: string;
431
- name: string;
432
- description: string | null;
433
- icon: string | null;
434
- environment: string;
435
- status: string;
436
- scopes: AppScopeInfo[];
437
- tokens: {
438
- id: string;
439
- tokenPrefix: string;
440
- environment: string;
441
- status: string;
442
- lastUsedAt: string | null;
443
- expiresAt: string | null;
444
- createdAt: string;
445
- }[];
446
- createdAt: string;
447
- };
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "module": "CommonJS",
5
- "moduleResolution": "node",
6
- "outDir": "dist",
7
- "rootDir": "src",
8
- "types": ["node"],
9
- "noUncheckedIndexedAccess": false
10
- },
11
- "include": ["src"]
12
- }
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "module": "CommonJS",
5
- "moduleResolution": "node",
6
- "types": ["node"],
7
- "noUncheckedIndexedAccess": false,
8
- "noEmit": true
9
- },
10
- "include": ["src", "verify.ts"]
11
- }
Binary file
package/verify.ts DELETED
@@ -1,182 +0,0 @@
1
- /**
2
- * Compile-time type verification for the Nucleus SDK.
3
- *
4
- * This file is never executed — it only needs to pass `tsc --noEmit`.
5
- * Each block demonstrates an expected compile-time behaviour.
6
- * Lines marked @ts-expect-error MUST produce a type error; if they don't,
7
- * the check fails.
8
- */
9
-
10
- import { NucleusClient, isError, type ResolveEmployee, type ScopeDeclaration } from './src/index';
11
-
12
- // ─── 1. identity-only scope ──────────────────────────────────────────────────
13
-
14
- declare const nucleus1: NucleusClient<{
15
- employees: ['identity'];
16
- }>;
17
-
18
- async function check1() {
19
- const result = await nucleus1.employees.getById('emp_123');
20
- if (isError(result)) return;
21
-
22
- const emp = result.data;
23
-
24
- // identity fields — must be accessible
25
- const _email: string = emp.email;
26
- const _displayName: string = emp.displayName;
27
- const _id: string = emp.id;
28
-
29
- // @ts-expect-error — 'phone' is in the contact bucket, not declared
30
- const _phone = emp.phone;
31
-
32
- // @ts-expect-error — 'managerId' is in the employment bucket, not declared
33
- const _mgr = emp.managerId;
34
-
35
- // @ts-expect-error — 'birthday' is in the sensitive bucket, not declared
36
- const _bday = emp.birthday;
37
- }
38
-
39
- // ─── 2. identity + employment scope ─────────────────────────────────────────
40
-
41
- declare const nucleus2: NucleusClient<{
42
- employees: ['identity', 'employment'];
43
- }>;
44
-
45
- async function check2() {
46
- const result = await nucleus2.employees.getById('emp_123');
47
- if (isError(result)) return;
48
-
49
- const emp = result.data;
50
-
51
- // both buckets accessible
52
- const _email: string = emp.email;
53
- const _managerId: string | null = emp.managerId;
54
- const _weeklyCapacity: number | null = emp.weeklyCapacity;
55
-
56
- // @ts-expect-error — still no contact scope
57
- const _phone = emp.phone;
58
- }
59
-
60
- // ─── 3. expand: ['manager'] changes type ─────────────────────────────────────
61
-
62
- declare const nucleus3: NucleusClient<{
63
- employees: ['identity', 'employment'];
64
- }>;
65
-
66
- async function check3() {
67
- const result = await nucleus3.employees.getById('emp_123', {
68
- expand: ['manager'],
69
- });
70
- if (isError(result)) return;
71
-
72
- const emp = result.data;
73
-
74
- // manager is now an object (or null) — not just a managerId string
75
- if (emp.manager) {
76
- const _mgrEmail: string = emp.manager.email;
77
- const _mgrName: string = emp.manager.displayName;
78
- }
79
- }
80
-
81
- // ─── 4. three-segment expand path is a compile error ────────────────────────
82
-
83
- declare const nucleus4: NucleusClient<{
84
- projects: ['core', 'team'];
85
- employees: ['identity'];
86
- clients: ['identity'];
87
- }>;
88
-
89
- async function check4() {
90
- await nucleus4.projects.getById('proj_123', {
91
- // @ts-expect-error — 'client.projects.something' has 3 segments → not a valid ProjectExpandPath
92
- expand: ['client.projects.something'],
93
- });
94
-
95
- // Two-segment paths are valid
96
- const result = await nucleus4.projects.getById('proj_123', {
97
- expand: ['projectManager', 'projectManager.manager'],
98
- });
99
- if (isError(result)) return;
100
-
101
- const proj = result.data;
102
- if (proj.projectManager) {
103
- const _name: string = proj.projectManager.displayName;
104
- if (proj.projectManager.manager) {
105
- const _mgrName: string = proj.projectManager.manager.displayName;
106
- }
107
- }
108
- }
109
-
110
- // ─── 5. list() returns meta with hasMore ─────────────────────────────────────
111
-
112
- declare const nucleus5: NucleusClient<{ employees: ['identity'] }>;
113
-
114
- async function check5() {
115
- const result = await nucleus5.employees.list({ page: 1, pageSize: 20 });
116
- if (isError(result)) return;
117
-
118
- const _total: number = result.meta.total;
119
- const _hasMore: boolean = result.meta.hasMore;
120
- const _page: number = result.meta.page;
121
- }
122
-
123
- // ─── 6. isError() narrow helper ──────────────────────────────────────────────
124
-
125
- declare const nucleus6: NucleusClient<{ employees: ['identity'] }>;
126
-
127
- async function check6() {
128
- const result = await nucleus6.employees.getById('emp_x');
129
-
130
- if (isError(result)) {
131
- // code is typed, not just string
132
- const _code: 'FORBIDDEN' | 'NOT_FOUND' | 'INVALID_SCOPE' | 'RATE_LIMITED' | 'NETWORK_ERROR' =
133
- result.error.code;
134
- return;
135
- }
136
-
137
- // TypeScript knows result.data is available here
138
- const _id: string = result.data.id;
139
- }
140
-
141
- // ─── 7. apps.me() return type ────────────────────────────────────────────────
142
-
143
- declare const nucleus7: NucleusClient<{}>;
144
-
145
- async function check7() {
146
- const result = await nucleus7.apps.me();
147
- if (isError(result)) return;
148
-
149
- const _name: string = result.data.name;
150
- const _scopes = result.data.scopes; // AppScopeInfo[]
151
- const _status = _scopes[0]?.status; // 'active' | 'pending' | 'rejected' | undefined
152
- }
153
-
154
- // ─── 8. no scope declared → NoScope type ────────────────────────────────────
155
-
156
- declare const nucleus8: NucleusClient<{}>;
157
-
158
- async function check8() {
159
- const result = await nucleus8.employees.getById('emp_x');
160
- if (isError(result)) return;
161
-
162
- // @ts-expect-error — no employees scope declared at all
163
- const _email = result.data.email;
164
- }
165
-
166
- // ─── 9. type helper: ResolveEmployee ─────────────────────────────────────────
167
-
168
- type S = { employees: ['identity', 'compensation'] };
169
- type Emp = ResolveEmployee<S>;
170
- // Should have identity fields
171
- type _hasId = Emp extends { id: string } ? true : false; // true
172
- type _hasComp = Emp extends { currentCompensation: unknown } ? true : false; // true
173
-
174
- // ─── Suppress unused-variable warnings ───────────────────────────────────────
175
- void check1;
176
- void check2;
177
- void check3;
178
- void check4;
179
- void check5;
180
- void check6;
181
- void check7;
182
- void check8;