@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/dist/cjs/index.cjs +683 -0
- package/dist/cjs/index.d.cts +567 -0
- package/dist/cjs/index.d.cts.map +1 -0
- package/dist/es/index.d.ts +567 -0
- package/dist/es/index.d.ts.map +1 -0
- package/dist/es/index.js +683 -0
- package/package.json +4 -1
- package/src/apps.ts +0 -19
- package/src/client.ts +0 -75
- package/src/index.ts +0 -82
- package/src/resources/clients.ts +0 -65
- package/src/resources/currencies.ts +0 -41
- package/src/resources/departments.ts +0 -43
- package/src/resources/employees.ts +0 -65
- package/src/resources/files.ts +0 -105
- package/src/resources/generic-rates.ts +0 -43
- package/src/resources/partners.ts +0 -61
- package/src/resources/project-types.ts +0 -41
- package/src/resources/projects.ts +0 -65
- package/src/transform.ts +0 -266
- package/src/transport.ts +0 -145
- package/src/types.ts +0 -447
- package/tsconfig.json +0 -12
- package/tsconfig.verify.json +0 -11
- package/typeb-digital-nucleus-sdk-0.0.1.tgz +0 -0
- package/verify.ts +0 -182
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
var ofetch = require('ofetch');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BASE_URL = 'https://nucleus.typeb.digital';
|
|
6
|
+
function statusToCode(status, apiCode) {
|
|
7
|
+
if (apiCode === 'INVALID_SCOPE') return 'INVALID_SCOPE';
|
|
8
|
+
if (status === 404) return 'NOT_FOUND';
|
|
9
|
+
if (status === 429) return 'RATE_LIMITED';
|
|
10
|
+
return 'FORBIDDEN';
|
|
11
|
+
}
|
|
12
|
+
function makeErrorResult(code, message) {
|
|
13
|
+
return {
|
|
14
|
+
error: {
|
|
15
|
+
code,
|
|
16
|
+
message
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
class NucleusTransport {
|
|
21
|
+
constructor(token, baseUrl){
|
|
22
|
+
this.headers = {
|
|
23
|
+
Authorization: `Bearer ${token}`,
|
|
24
|
+
'Content-Type': 'application/json'
|
|
25
|
+
};
|
|
26
|
+
this.baseUrl = (baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
27
|
+
}
|
|
28
|
+
async get(path, query) {
|
|
29
|
+
try {
|
|
30
|
+
const params = query ? Object.fromEntries(Object.entries(query).filter(([, v])=>v !== undefined)) : undefined;
|
|
31
|
+
const envelope = await ofetch.$fetch(`${this.baseUrl}${path}`, {
|
|
32
|
+
method: 'GET',
|
|
33
|
+
headers: this.headers,
|
|
34
|
+
params
|
|
35
|
+
});
|
|
36
|
+
if (!envelope.success) {
|
|
37
|
+
return makeErrorResult('FORBIDDEN', envelope.error);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
data: envelope.data
|
|
41
|
+
};
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof ofetch.FetchError) {
|
|
44
|
+
const body = err.data;
|
|
45
|
+
const code = statusToCode(err.status ?? 0, body?.error);
|
|
46
|
+
return makeErrorResult(code, err.message);
|
|
47
|
+
}
|
|
48
|
+
return makeErrorResult('NETWORK_ERROR', String(err));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async post(path, body) {
|
|
52
|
+
try {
|
|
53
|
+
const envelope = await ofetch.$fetch(`${this.baseUrl}${path}`, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: this.headers,
|
|
56
|
+
body: JSON.stringify(body)
|
|
57
|
+
});
|
|
58
|
+
if (!envelope.success) return makeErrorResult('FORBIDDEN', envelope.error);
|
|
59
|
+
return {
|
|
60
|
+
data: envelope.data
|
|
61
|
+
};
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (err instanceof ofetch.FetchError) {
|
|
64
|
+
const errBody = err.data;
|
|
65
|
+
return makeErrorResult(statusToCode(err.status ?? 0, errBody?.error), err.message);
|
|
66
|
+
}
|
|
67
|
+
return makeErrorResult('NETWORK_ERROR', String(err));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async del(path, query) {
|
|
71
|
+
try {
|
|
72
|
+
const params = query ? Object.fromEntries(Object.entries(query).filter(([, v])=>v !== undefined)) : undefined;
|
|
73
|
+
const envelope = await ofetch.$fetch(`${this.baseUrl}${path}`, {
|
|
74
|
+
method: 'DELETE',
|
|
75
|
+
headers: this.headers,
|
|
76
|
+
params
|
|
77
|
+
});
|
|
78
|
+
if (!envelope.success) return makeErrorResult('FORBIDDEN', envelope.error);
|
|
79
|
+
return {
|
|
80
|
+
data: envelope.data
|
|
81
|
+
};
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (err instanceof ofetch.FetchError) {
|
|
84
|
+
const errBody = err.data;
|
|
85
|
+
return makeErrorResult(statusToCode(err.status ?? 0, errBody?.error), err.message);
|
|
86
|
+
}
|
|
87
|
+
return makeErrorResult('NETWORK_ERROR', String(err));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async getList(path, query) {
|
|
91
|
+
try {
|
|
92
|
+
const params = query ? Object.fromEntries(Object.entries(query).filter(([, v])=>v !== undefined)) : undefined;
|
|
93
|
+
const envelope = await ofetch.$fetch(`${this.baseUrl}${path}`, {
|
|
94
|
+
method: 'GET',
|
|
95
|
+
headers: this.headers,
|
|
96
|
+
params
|
|
97
|
+
});
|
|
98
|
+
if (!envelope.success) {
|
|
99
|
+
return makeErrorResult('FORBIDDEN', envelope.error);
|
|
100
|
+
}
|
|
101
|
+
const { data, meta } = envelope;
|
|
102
|
+
const listMeta = {
|
|
103
|
+
total: meta.total,
|
|
104
|
+
page: meta.page,
|
|
105
|
+
pageSize: meta.pageSize,
|
|
106
|
+
hasMore: meta.page * meta.pageSize < meta.total
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
data,
|
|
110
|
+
meta: listMeta
|
|
111
|
+
};
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof ofetch.FetchError) {
|
|
114
|
+
const body = err.data;
|
|
115
|
+
const code = statusToCode(err.status ?? 0, body?.error);
|
|
116
|
+
return makeErrorResult(code, err.message);
|
|
117
|
+
}
|
|
118
|
+
return makeErrorResult('NETWORK_ERROR', String(err));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Transform utilities — map snake_case Prisma API responses to camelCase SDK types.
|
|
125
|
+
* Each function accepts `unknown` so it works regardless of which buckets were active.
|
|
126
|
+
*/ function str(v) {
|
|
127
|
+
return typeof v === 'string' ? v : null;
|
|
128
|
+
}
|
|
129
|
+
function num(v) {
|
|
130
|
+
return typeof v === 'number' ? v : null;
|
|
131
|
+
}
|
|
132
|
+
function bool(v) {
|
|
133
|
+
return v === true;
|
|
134
|
+
}
|
|
135
|
+
function transformProfile(profile) {
|
|
136
|
+
if (!profile || typeof profile !== 'object') return {};
|
|
137
|
+
const p = profile;
|
|
138
|
+
const out = {};
|
|
139
|
+
if ('phone' in p) out['phone'] = p['phone'];
|
|
140
|
+
if ('city' in p) out['city'] = p['city'];
|
|
141
|
+
if ('country' in p) out['country'] = p['country'];
|
|
142
|
+
if ('biography' in p) out['biography'] = p['biography'];
|
|
143
|
+
if ('emergency_contact_name' in p || 'emergency_contact_phone' in p) {
|
|
144
|
+
out['emergencyContact'] = {
|
|
145
|
+
name: str(p['emergency_contact_name']),
|
|
146
|
+
phone: str(p['emergency_contact_phone'])
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function transformCompensation(raw) {
|
|
152
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
153
|
+
const r = raw;
|
|
154
|
+
return {
|
|
155
|
+
hourlyCostRate: num(r['hourly_cost_rate']),
|
|
156
|
+
hourlyBillableRate: num(r['hourly_billable_rate']),
|
|
157
|
+
monthlyCostRate: num(r['monthly_cost_rate']),
|
|
158
|
+
monthlyBillableRate: num(r['monthly_billable_rate']),
|
|
159
|
+
currencyCode: str(r['currency_code']) ?? 'USD',
|
|
160
|
+
effectiveFrom: str(r['effective_from']) ?? ''
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function transformCompensationHistoryEntry(raw) {
|
|
164
|
+
const r = raw ?? {};
|
|
165
|
+
return {
|
|
166
|
+
hourlyCostRate: num(r['hourly_cost_rate']),
|
|
167
|
+
hourlyBillableRate: num(r['hourly_billable_rate']),
|
|
168
|
+
monthlyCostRate: num(r['monthly_cost_rate']),
|
|
169
|
+
monthlyBillableRate: num(r['monthly_billable_rate']),
|
|
170
|
+
currencyCode: str(r['currency_code']) ?? 'USD',
|
|
171
|
+
effectiveFrom: str(r['effective_from']) ?? '',
|
|
172
|
+
effectiveTo: str(r['effective_to'])
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function transformEmployee(raw) {
|
|
176
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
177
|
+
const r = raw;
|
|
178
|
+
const out = {};
|
|
179
|
+
// identity
|
|
180
|
+
if ('id' in r) out['id'] = r['id'];
|
|
181
|
+
if ('display_name' in r) out['displayName'] = str(r['display_name']);
|
|
182
|
+
if ('first_name' in r) out['firstName'] = str(r['first_name']);
|
|
183
|
+
if ('last_name' in r) out['lastName'] = str(r['last_name']);
|
|
184
|
+
if ('email' in r) out['email'] = str(r['email']);
|
|
185
|
+
if ('picture_url' in r) out['pictureUrl'] = str(r['picture_url']);
|
|
186
|
+
if ('job_title' in r) out['jobTitle'] = str(r['job_title']);
|
|
187
|
+
if ('department' in r) out['department'] = str(r['department']);
|
|
188
|
+
if ('employment_status' in r) out['employmentStatus'] = str(r['employment_status']);
|
|
189
|
+
if ('is_external' in r) out['isExternal'] = bool(r['is_external']);
|
|
190
|
+
// employment
|
|
191
|
+
if ('start_date' in r) out['startDate'] = str(r['start_date']);
|
|
192
|
+
if ('weekly_capacity' in r) out['weeklyCapacity'] = num(r['weekly_capacity']);
|
|
193
|
+
if ('manager_id' in r) out['managerId'] = str(r['manager_id']);
|
|
194
|
+
if ('partner_id' in r) out['partnerId'] = str(r['partner_id']);
|
|
195
|
+
if ('timezone' in r) out['timezone'] = str(r['timezone']);
|
|
196
|
+
// sensitive
|
|
197
|
+
if ('birthday' in r) out['birthday'] = str(r['birthday']);
|
|
198
|
+
if ('custom_fields' in r) out['customFields'] = r['custom_fields'];
|
|
199
|
+
// profile JSON (contact + employment.biography + sensitive.emergencyContact)
|
|
200
|
+
if ('profile' in r) {
|
|
201
|
+
out['profile'] = transformProfile(r['profile']);
|
|
202
|
+
}
|
|
203
|
+
// synthesised compensation (injected by Layer 3 interceptor)
|
|
204
|
+
if ('current_compensation' in r) {
|
|
205
|
+
out['currentCompensation'] = transformCompensation(r['current_compensation']);
|
|
206
|
+
}
|
|
207
|
+
if ('compensation_history' in r && Array.isArray(r['compensation_history'])) {
|
|
208
|
+
out['compensationHistory'] = r['compensation_history'].map(transformCompensationHistoryEntry);
|
|
209
|
+
}
|
|
210
|
+
// Expand: manager / partner (recursively transformed)
|
|
211
|
+
if ('manager' in r) {
|
|
212
|
+
out['manager'] = r['manager'] ? transformEmployee(r['manager']) : null;
|
|
213
|
+
}
|
|
214
|
+
if ('partner' in r) {
|
|
215
|
+
out['partner'] = r['partner'] ? transformPartner(r['partner']) : null;
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
function transformProject(raw) {
|
|
220
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
221
|
+
const r = raw;
|
|
222
|
+
const out = {};
|
|
223
|
+
// core
|
|
224
|
+
if ('id' in r) out['id'] = r['id'];
|
|
225
|
+
if ('name' in r) out['name'] = str(r['name']);
|
|
226
|
+
if ('status' in r) out['status'] = str(r['status']);
|
|
227
|
+
if ('color_tag' in r) out['colorTag'] = str(r['color_tag']);
|
|
228
|
+
if ('icon_name' in r) out['iconName'] = str(r['icon_name']);
|
|
229
|
+
if ('start_date' in r) out['startDate'] = str(r['start_date']);
|
|
230
|
+
if ('end_date' in r) out['endDate'] = str(r['end_date']);
|
|
231
|
+
if ('project_type_id' in r) out['projectTypeId'] = str(r['project_type_id']);
|
|
232
|
+
if ('client_id' in r) out['clientId'] = str(r['client_id']);
|
|
233
|
+
// team
|
|
234
|
+
if ('project_manager_id' in r) out['projectManagerId'] = str(r['project_manager_id']);
|
|
235
|
+
if ('engagement_lead_id' in r) out['engagementLeadId'] = str(r['engagement_lead_id']);
|
|
236
|
+
if ('memberships' in r && Array.isArray(r['memberships'])) {
|
|
237
|
+
out['memberships'] = r['memberships'].map((m)=>{
|
|
238
|
+
const mem = m ?? {};
|
|
239
|
+
const entry = {
|
|
240
|
+
employeeId: str(mem['employee_id']),
|
|
241
|
+
role: str(mem['role'])
|
|
242
|
+
};
|
|
243
|
+
if ('employee' in mem) {
|
|
244
|
+
entry['employee'] = mem['employee'] ? transformEmployee(mem['employee']) : null;
|
|
245
|
+
}
|
|
246
|
+
return entry;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// integrations
|
|
250
|
+
if ('source' in r) out['source'] = str(r['source']);
|
|
251
|
+
if ('clockify_project_id' in r) out['clockifyProjectId'] = str(r['clockify_project_id']);
|
|
252
|
+
if ('clockify_workspace_id' in r) out['clockifyWorkspaceId'] = str(r['clockify_workspace_id']);
|
|
253
|
+
if ('apollo_deal_id' in r) out['apolloDealId'] = str(r['apollo_deal_id']);
|
|
254
|
+
// expand: client / projectType / projectManager / engagementLead
|
|
255
|
+
if ('client' in r) out['client'] = r['client'] ? transformClient(r['client']) : null;
|
|
256
|
+
if ('project_type' in r || 'projectType' in r) {
|
|
257
|
+
const pt = r['project_type'] ?? r['projectType'];
|
|
258
|
+
out['projectType'] = pt ? transformProjectType(pt) : null;
|
|
259
|
+
}
|
|
260
|
+
if ('project_manager' in r || 'projectManager' in r) {
|
|
261
|
+
const pm = r['project_manager'] ?? r['projectManager'];
|
|
262
|
+
out['projectManager'] = pm ? transformEmployee(pm) : null;
|
|
263
|
+
}
|
|
264
|
+
if ('engagement_lead' in r || 'engagementLead' in r) {
|
|
265
|
+
const el = r['engagement_lead'] ?? r['engagementLead'];
|
|
266
|
+
out['engagementLead'] = el ? transformEmployee(el) : null;
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
function transformClient(raw) {
|
|
271
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
272
|
+
const r = raw;
|
|
273
|
+
const out = {};
|
|
274
|
+
if ('id' in r) out['id'] = r['id'];
|
|
275
|
+
if ('name' in r) out['name'] = str(r['name']);
|
|
276
|
+
if ('code' in r) out['code'] = str(r['code']);
|
|
277
|
+
if ('industry' in r) out['industry'] = str(r['industry']);
|
|
278
|
+
if ('website' in r) out['website'] = str(r['website']);
|
|
279
|
+
if ('country' in r) out['country'] = str(r['country']);
|
|
280
|
+
if ('primary_contact_name' in r) out['primaryContactName'] = str(r['primary_contact_name']);
|
|
281
|
+
if ('primary_contact_email' in r) out['primaryContactEmail'] = str(r['primary_contact_email']);
|
|
282
|
+
if ('contact_phone' in r) out['contactPhone'] = str(r['contact_phone']);
|
|
283
|
+
if ('default_currency' in r) out['defaultCurrency'] = str(r['default_currency']);
|
|
284
|
+
if ('notes' in r) out['notes'] = str(r['notes']);
|
|
285
|
+
if ('projects' in r && Array.isArray(r['projects'])) {
|
|
286
|
+
out['projects'] = r['projects'].map(transformProject);
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
function transformPartner(raw) {
|
|
291
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
292
|
+
const r = raw;
|
|
293
|
+
const out = {};
|
|
294
|
+
if ('id' in r) out['id'] = r['id'];
|
|
295
|
+
if ('name' in r) out['name'] = str(r['name']);
|
|
296
|
+
if ('contact_name' in r) out['contactName'] = str(r['contact_name']);
|
|
297
|
+
if ('contact_email' in r) out['contactEmail'] = str(r['contact_email']);
|
|
298
|
+
if ('contact_phone' in r) out['contactPhone'] = str(r['contact_phone']);
|
|
299
|
+
if ('notes' in r) out['notes'] = str(r['notes']);
|
|
300
|
+
if ('employees' in r && Array.isArray(r['employees'])) {
|
|
301
|
+
out['employees'] = r['employees'].map(transformEmployee);
|
|
302
|
+
}
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
function transformDepartment(raw) {
|
|
306
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
307
|
+
const r = raw;
|
|
308
|
+
const out = {};
|
|
309
|
+
if ('id' in r) out['id'] = r['id'];
|
|
310
|
+
if ('name' in r) out['name'] = str(r['name']);
|
|
311
|
+
if ('titles' in r) out['titles'] = Array.isArray(r['titles']) ? r['titles'] : [];
|
|
312
|
+
if ('sort_order' in r) out['sortOrder'] = num(r['sort_order']) ?? 0;
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
function transformProjectType(raw) {
|
|
316
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
317
|
+
const r = raw;
|
|
318
|
+
const out = {};
|
|
319
|
+
if ('id' in r) out['id'] = r['id'];
|
|
320
|
+
if ('name' in r) out['name'] = str(r['name']);
|
|
321
|
+
if ('description' in r) out['description'] = str(r['description']);
|
|
322
|
+
if ('sort_order' in r) out['sortOrder'] = num(r['sort_order']) ?? 0;
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
function transformCurrency(raw) {
|
|
326
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
327
|
+
const r = raw;
|
|
328
|
+
const out = {};
|
|
329
|
+
if ('code' in r) out['code'] = r['code'];
|
|
330
|
+
if ('name' in r) out['name'] = str(r['name']);
|
|
331
|
+
if ('symbol' in r) out['symbol'] = str(r['symbol']);
|
|
332
|
+
if ('is_default' in r) out['isDefault'] = bool(r['is_default']);
|
|
333
|
+
if ('sort_order' in r) out['sortOrder'] = num(r['sort_order']) ?? 0;
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
function transformGenericRate(raw) {
|
|
337
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
338
|
+
const r = raw;
|
|
339
|
+
const out = {};
|
|
340
|
+
if ('id' in r) out['id'] = r['id'];
|
|
341
|
+
if ('title' in r) out['title'] = str(r['title']);
|
|
342
|
+
if ('department' in r) out['department'] = str(r['department']);
|
|
343
|
+
if ('currency_code' in r) out['currencyCode'] = str(r['currency_code']);
|
|
344
|
+
if ('cost_rate' in r) out['costRate'] = num(r['cost_rate']);
|
|
345
|
+
if ('billable_rate' in r) out['billableRate'] = num(r['billable_rate']);
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
class EmployeesAccessor {
|
|
350
|
+
constructor(transport){
|
|
351
|
+
this.transport = transport;
|
|
352
|
+
}
|
|
353
|
+
async list(params) {
|
|
354
|
+
const query = {};
|
|
355
|
+
if (params?.search) query['search'] = params.search;
|
|
356
|
+
if (params?.department) query['department'] = params.department;
|
|
357
|
+
if (params?.employmentStatus) query['employmentStatus'] = params.employmentStatus;
|
|
358
|
+
if (params?.page) query['page'] = params.page;
|
|
359
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
360
|
+
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
361
|
+
const result = await this.transport.getList('/api/v1/data/employees', query);
|
|
362
|
+
if ('error' in result) return result;
|
|
363
|
+
return {
|
|
364
|
+
data: result.data.map((r)=>transformEmployee(r)),
|
|
365
|
+
meta: result.meta
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async getById(id, options) {
|
|
369
|
+
const query = {};
|
|
370
|
+
if (options?.expand?.length) query['expand'] = options.expand.join(',');
|
|
371
|
+
const result = await this.transport.get(`/api/v1/data/employees/${id}`, query);
|
|
372
|
+
if ('error' in result) return result;
|
|
373
|
+
return {
|
|
374
|
+
data: transformEmployee(result.data)
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
class ProjectsAccessor {
|
|
380
|
+
constructor(transport){
|
|
381
|
+
this.transport = transport;
|
|
382
|
+
}
|
|
383
|
+
async list(params) {
|
|
384
|
+
const query = {};
|
|
385
|
+
if (params?.search) query['search'] = params.search;
|
|
386
|
+
if (params?.status) query['status'] = params.status;
|
|
387
|
+
if (params?.clientId) query['clientId'] = params.clientId;
|
|
388
|
+
if (params?.page) query['page'] = params.page;
|
|
389
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
390
|
+
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
391
|
+
const result = await this.transport.getList('/api/v1/data/projects', query);
|
|
392
|
+
if ('error' in result) return result;
|
|
393
|
+
return {
|
|
394
|
+
data: result.data.map((r)=>transformProject(r)),
|
|
395
|
+
meta: result.meta
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
async getById(id, options) {
|
|
399
|
+
const query = {};
|
|
400
|
+
if (options?.expand?.length) query['expand'] = options.expand.join(',');
|
|
401
|
+
const result = await this.transport.get(`/api/v1/data/projects/${id}`, query);
|
|
402
|
+
if ('error' in result) return result;
|
|
403
|
+
return {
|
|
404
|
+
data: transformProject(result.data)
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
class ClientsAccessor {
|
|
410
|
+
constructor(transport){
|
|
411
|
+
this.transport = transport;
|
|
412
|
+
}
|
|
413
|
+
async list(params) {
|
|
414
|
+
const query = {};
|
|
415
|
+
if (params?.search) query['search'] = params.search;
|
|
416
|
+
if (params?.industry) query['industry'] = params.industry;
|
|
417
|
+
if (params?.country) query['country'] = params.country;
|
|
418
|
+
if (params?.page) query['page'] = params.page;
|
|
419
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
420
|
+
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
421
|
+
const result = await this.transport.getList('/api/v1/data/clients', query);
|
|
422
|
+
if ('error' in result) return result;
|
|
423
|
+
return {
|
|
424
|
+
data: result.data.map((r)=>transformClient(r)),
|
|
425
|
+
meta: result.meta
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async getById(id, options) {
|
|
429
|
+
const query = {};
|
|
430
|
+
if (options?.expand?.length) query['expand'] = options.expand.join(',');
|
|
431
|
+
const result = await this.transport.get(`/api/v1/data/clients/${id}`, query);
|
|
432
|
+
if ('error' in result) return result;
|
|
433
|
+
return {
|
|
434
|
+
data: transformClient(result.data)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
class PartnersAccessor {
|
|
440
|
+
constructor(transport){
|
|
441
|
+
this.transport = transport;
|
|
442
|
+
}
|
|
443
|
+
async list(params) {
|
|
444
|
+
const query = {};
|
|
445
|
+
if (params?.search) query['search'] = params.search;
|
|
446
|
+
if (params?.page) query['page'] = params.page;
|
|
447
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
448
|
+
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
449
|
+
const result = await this.transport.getList('/api/v1/data/partners', query);
|
|
450
|
+
if ('error' in result) return result;
|
|
451
|
+
return {
|
|
452
|
+
data: result.data.map((r)=>transformPartner(r)),
|
|
453
|
+
meta: result.meta
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
async getById(id, options) {
|
|
457
|
+
const query = {};
|
|
458
|
+
if (options?.expand?.length) query['expand'] = options.expand.join(',');
|
|
459
|
+
const result = await this.transport.get(`/api/v1/data/partners/${id}`, query);
|
|
460
|
+
if ('error' in result) return result;
|
|
461
|
+
return {
|
|
462
|
+
data: transformPartner(result.data)
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
class DepartmentsAccessor {
|
|
468
|
+
constructor(transport){
|
|
469
|
+
this.transport = transport;
|
|
470
|
+
}
|
|
471
|
+
async list(params) {
|
|
472
|
+
const query = {};
|
|
473
|
+
if (params?.search) query['search'] = params.search;
|
|
474
|
+
if (params?.page) query['page'] = params.page;
|
|
475
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
476
|
+
const result = await this.transport.getList('/api/v1/data/departments', query);
|
|
477
|
+
if ('error' in result) return result;
|
|
478
|
+
return {
|
|
479
|
+
data: result.data.map((r)=>transformDepartment(r)),
|
|
480
|
+
meta: result.meta
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
async getById(id) {
|
|
484
|
+
const result = await this.transport.get(`/api/v1/data/departments/${id}`);
|
|
485
|
+
if ('error' in result) return result;
|
|
486
|
+
return {
|
|
487
|
+
data: transformDepartment(result.data)
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
class ProjectTypesAccessor {
|
|
493
|
+
constructor(transport){
|
|
494
|
+
this.transport = transport;
|
|
495
|
+
}
|
|
496
|
+
async list(params) {
|
|
497
|
+
const query = {};
|
|
498
|
+
if (params?.page) query['page'] = params.page;
|
|
499
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
500
|
+
const result = await this.transport.getList('/api/v1/data/project-types', query);
|
|
501
|
+
if ('error' in result) return result;
|
|
502
|
+
return {
|
|
503
|
+
data: result.data.map((r)=>transformProjectType(r)),
|
|
504
|
+
meta: result.meta
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
async getById(id) {
|
|
508
|
+
const result = await this.transport.get(`/api/v1/data/project-types/${id}`);
|
|
509
|
+
if ('error' in result) return result;
|
|
510
|
+
return {
|
|
511
|
+
data: transformProjectType(result.data)
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
class CurrenciesAccessor {
|
|
517
|
+
constructor(transport){
|
|
518
|
+
this.transport = transport;
|
|
519
|
+
}
|
|
520
|
+
async list(params) {
|
|
521
|
+
const query = {};
|
|
522
|
+
if (params?.page) query['page'] = params.page;
|
|
523
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
524
|
+
const result = await this.transport.getList('/api/v1/data/currencies', query);
|
|
525
|
+
if ('error' in result) return result;
|
|
526
|
+
return {
|
|
527
|
+
data: result.data.map((r)=>transformCurrency(r)),
|
|
528
|
+
meta: result.meta
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
async getByCode(code) {
|
|
532
|
+
const result = await this.transport.get(`/api/v1/data/currencies/${code}`);
|
|
533
|
+
if ('error' in result) return result;
|
|
534
|
+
return {
|
|
535
|
+
data: transformCurrency(result.data)
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
class GenericRatesAccessor {
|
|
541
|
+
constructor(transport){
|
|
542
|
+
this.transport = transport;
|
|
543
|
+
}
|
|
544
|
+
async list(params) {
|
|
545
|
+
const query = {};
|
|
546
|
+
if (params?.department) query['department'] = params.department;
|
|
547
|
+
if (params?.page) query['page'] = params.page;
|
|
548
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
549
|
+
const result = await this.transport.getList('/api/v1/data/generic-rates', query);
|
|
550
|
+
if ('error' in result) return result;
|
|
551
|
+
return {
|
|
552
|
+
data: result.data.map((r)=>transformGenericRate(r)),
|
|
553
|
+
meta: result.meta
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
async getById(id) {
|
|
557
|
+
const result = await this.transport.get(`/api/v1/data/generic-rates/${id}`);
|
|
558
|
+
if ('error' in result) return result;
|
|
559
|
+
return {
|
|
560
|
+
data: transformGenericRate(result.data)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* AppsAccessor — wraps the self-service app info endpoint (§3.8, §6).
|
|
567
|
+
* Calls GET /api/v1/platform/apps/me with the app token.
|
|
568
|
+
* This is the only platform endpoint accessible via app token.
|
|
569
|
+
*/ class AppsAccessor {
|
|
570
|
+
constructor(transport){
|
|
571
|
+
this.transport = transport;
|
|
572
|
+
}
|
|
573
|
+
async me() {
|
|
574
|
+
const result = await this.transport.get('/api/v1/platform/apps/me');
|
|
575
|
+
if ('error' in result) return result;
|
|
576
|
+
return {
|
|
577
|
+
data: result.data
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
class FilesAccessor {
|
|
583
|
+
constructor(transport){
|
|
584
|
+
this.transport = transport;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Upload a file. Upsert semantics: re-uploading the same key overwrites the
|
|
588
|
+
* existing R2 object and updates the File record — no duplicate, no error.
|
|
589
|
+
*/ async upload(params) {
|
|
590
|
+
return this.transport.post('/api/v1/platform/files', {
|
|
591
|
+
key: params.key,
|
|
592
|
+
file: params.file.toString('base64'),
|
|
593
|
+
mimeType: params.mimeType,
|
|
594
|
+
visibility: params.visibility,
|
|
595
|
+
...params.allowedUsers !== undefined ? {
|
|
596
|
+
allowedUsers: params.allowedUsers
|
|
597
|
+
} : {},
|
|
598
|
+
...params.metadata !== undefined ? {
|
|
599
|
+
metadata: params.metadata
|
|
600
|
+
} : {},
|
|
601
|
+
...params.filename !== undefined ? {
|
|
602
|
+
filename: params.filename
|
|
603
|
+
} : {}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Resolve a usable URL for a file.
|
|
608
|
+
* - Public files → deterministic CDN URL
|
|
609
|
+
* - Private app-wide files → fresh pre-signed URL (expires in R2_SIGNED_URL_TTL seconds)
|
|
610
|
+
* - Private user-scoped files → supply asUser; 403 if user is not in allowedUsers
|
|
611
|
+
*/ async getUrl(key, options) {
|
|
612
|
+
return this.transport.post('/api/v1/platform/files/url', {
|
|
613
|
+
key,
|
|
614
|
+
...options?.asUser ? {
|
|
615
|
+
asUser: options.asUser
|
|
616
|
+
} : {}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
/** Soft-delete. Resolution of the key returns 404 thereafter. */ async delete(key) {
|
|
620
|
+
return this.transport.del('/api/v1/platform/files', {
|
|
621
|
+
key
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
/** List this app's files. Optionally filter by path prefix. */ async list(params) {
|
|
625
|
+
const query = {};
|
|
626
|
+
if (params?.prefix) query['prefix'] = params.prefix;
|
|
627
|
+
if (params?.page) query['page'] = params.page;
|
|
628
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
629
|
+
return this.transport.getList('/api/v1/platform/files', query);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* NucleusClient — entry point for the Nucleus server SDK.
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* ```typescript
|
|
638
|
+
* import { NucleusClient } from '@typeb-digital/nucleus-sdk';
|
|
639
|
+
*
|
|
640
|
+
* export const nucleus = new NucleusClient({
|
|
641
|
+
* token: process.env.NUCLEUS_TOKEN!,
|
|
642
|
+
* scopes: {
|
|
643
|
+
* employees: ['identity', 'employment'],
|
|
644
|
+
* projects: ['core'],
|
|
645
|
+
* clients: ['identity'],
|
|
646
|
+
* },
|
|
647
|
+
* });
|
|
648
|
+
* ```
|
|
649
|
+
*
|
|
650
|
+
* The `scopes` object is the heart of the type system — it is declared once at
|
|
651
|
+
* instantiation and drives the return type of every method on the client.
|
|
652
|
+
* TypeScript infers it automatically; no explicit type parameters needed.
|
|
653
|
+
*/ class NucleusClient {
|
|
654
|
+
constructor(config){
|
|
655
|
+
const transport = new NucleusTransport(config.token, config.baseUrl);
|
|
656
|
+
this.employees = new EmployeesAccessor(transport);
|
|
657
|
+
this.projects = new ProjectsAccessor(transport);
|
|
658
|
+
this.clients = new ClientsAccessor(transport);
|
|
659
|
+
this.partners = new PartnersAccessor(transport);
|
|
660
|
+
this.departments = new DepartmentsAccessor(transport);
|
|
661
|
+
this.projectTypes = new ProjectTypesAccessor(transport);
|
|
662
|
+
this.currencies = new CurrenciesAccessor(transport);
|
|
663
|
+
this.genericRates = new GenericRatesAccessor(transport);
|
|
664
|
+
this.apps = new AppsAccessor(transport);
|
|
665
|
+
this.files = new FilesAccessor(transport);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Nucleus SDK — Type System
|
|
671
|
+
*
|
|
672
|
+
* Bucket types are transcribed from docs/NUCLEUS_PIVOT.md §7 (Bucket Definitions).
|
|
673
|
+
* Each bucket type contains only that bucket's fields.
|
|
674
|
+
* The scope-to-type inference intersects selected bucket types at compile time.
|
|
675
|
+
*/ // ─── Utility types ────────────────────────────────────────────────────────────
|
|
676
|
+
// Converts a union to an intersection: A | B → A & B
|
|
677
|
+
function isError(result) {
|
|
678
|
+
return 'error' in result;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
exports.FilesAccessor = FilesAccessor;
|
|
682
|
+
exports.NucleusClient = NucleusClient;
|
|
683
|
+
exports.isError = isError;
|