@typeb-digital/nucleus-sdk 0.1.0 → 0.2.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 +14 -0
- package/dist/cjs/index.cjs +291 -16
- package/dist/cjs/index.d.cts +290 -11
- package/dist/cjs/index.d.cts.map +1 -1
- package/dist/es/index.d.ts +290 -11
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +291 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While the
|
|
7
7
|
package is pre-`1.0.0`, **breaking changes may land in a minor version** (e.g. `0.0.x` → `0.1.0`).
|
|
8
8
|
|
|
9
|
+
## [0.1.1] - 2026-06-22
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Employees are read-only.** Nucleus owns employee writes (sourced via Google/HRIS
|
|
14
|
+
sync), so the scope system no longer accepts `create` / `update` / `delete` grants
|
|
15
|
+
for `employees`:
|
|
16
|
+
- `ScopeDeclaration` types `employees` as read-only — declaring a write action on it
|
|
17
|
+
is now a compile-time error.
|
|
18
|
+
- Removed `employees.update()` / `employees.delete()` from the SDK.
|
|
19
|
+
|
|
20
|
+
(The platform also rejects employee write grants at app-registration / group-permission
|
|
21
|
+
time, and the dashboard scope matrix shows `employees` as read-only.)
|
|
22
|
+
|
|
9
23
|
## [0.1.0] - 2026-06-22
|
|
10
24
|
|
|
11
25
|
Action-typed scopes and write support. **This is a breaking release** — every consumer's
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -415,6 +415,30 @@ function transformJurisdiction(raw) {
|
|
|
415
415
|
if ('country' in r) out['country'] = str(r['country']);
|
|
416
416
|
return out;
|
|
417
417
|
}
|
|
418
|
+
function transformJobLevel(raw) {
|
|
419
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
420
|
+
const r = raw;
|
|
421
|
+
const out = {};
|
|
422
|
+
if ('id' in r) out['id'] = r['id'];
|
|
423
|
+
if ('name' in r) out['name'] = str(r['name']);
|
|
424
|
+
if ('code' in r) out['code'] = str(r['code']);
|
|
425
|
+
if ('department_id' in r) out['departmentId'] = str(r['department_id']);
|
|
426
|
+
if ('sort_order' in r) out['sortOrder'] = num(r['sort_order']) ?? 0;
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
function transformSalaryBand(raw) {
|
|
430
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
431
|
+
const r = raw;
|
|
432
|
+
const out = {};
|
|
433
|
+
if ('id' in r) out['id'] = r['id'];
|
|
434
|
+
if ('job_level_id' in r) out['jobLevelId'] = str(r['job_level_id']);
|
|
435
|
+
if ('jurisdiction_id' in r) out['jurisdictionId'] = str(r['jurisdiction_id']);
|
|
436
|
+
if ('currency_code' in r) out['currencyCode'] = str(r['currency_code']);
|
|
437
|
+
if ('min_salary' in r) out['minSalary'] = num(r['min_salary']);
|
|
438
|
+
if ('max_salary' in r) out['maxSalary'] = num(r['max_salary']);
|
|
439
|
+
if ('target_salary' in r) out['targetSalary'] = num(r['target_salary']);
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
418
442
|
function transformLeaveType(raw) {
|
|
419
443
|
if (!raw || typeof raw !== 'object') return {};
|
|
420
444
|
const r = raw;
|
|
@@ -566,6 +590,7 @@ class EmployeesAccessor {
|
|
|
566
590
|
if (params?.employmentStatus) query['employmentStatus'] = params.employmentStatus;
|
|
567
591
|
if (params?.page) query['page'] = params.page;
|
|
568
592
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
593
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
569
594
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
570
595
|
const result = await this.transport.getList('/api/v1/data/employees', query);
|
|
571
596
|
if ('error' in result) return result;
|
|
@@ -583,23 +608,16 @@ class EmployeesAccessor {
|
|
|
583
608
|
data: transformEmployee(result.data)
|
|
584
609
|
};
|
|
585
610
|
}
|
|
586
|
-
// No create: the platform exposes no POST route for employees.
|
|
587
|
-
/** Update an employee (managerId / timezone overrides). Requires `employees.update` scope. */ async update(id, data) {
|
|
588
|
-
const result = await this.transport.patch(`/api/v1/data/employees/${id}`, toSnakeBody(data));
|
|
589
|
-
if ('error' in result) return result;
|
|
590
|
-
return {
|
|
591
|
-
data: transformEmployee(result.data)
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
/** Soft-delete an employee. Requires `employees.delete` scope. */ async delete(id) {
|
|
595
|
-
const result = await this.transport.del(`/api/v1/data/employees/${id}`);
|
|
596
|
-
if ('error' in result) return result;
|
|
597
|
-
return {
|
|
598
|
-
data: null
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
611
|
}
|
|
602
612
|
|
|
613
|
+
function transformMember(raw) {
|
|
614
|
+
return {
|
|
615
|
+
id: raw['id'],
|
|
616
|
+
projectId: raw['project_id'],
|
|
617
|
+
employeeId: raw['employee_id'],
|
|
618
|
+
role: raw['role'] ?? null
|
|
619
|
+
};
|
|
620
|
+
}
|
|
603
621
|
class ProjectsAccessor {
|
|
604
622
|
constructor(transport){
|
|
605
623
|
this.transport = transport;
|
|
@@ -608,9 +626,11 @@ class ProjectsAccessor {
|
|
|
608
626
|
const query = {};
|
|
609
627
|
if (params?.search) query['search'] = params.search;
|
|
610
628
|
if (params?.status) query['status'] = params.status;
|
|
629
|
+
if (params?.stage) query['stage'] = params.stage;
|
|
611
630
|
if (params?.clientId) query['clientId'] = params.clientId;
|
|
612
631
|
if (params?.page) query['page'] = params.page;
|
|
613
632
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
633
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
614
634
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
615
635
|
const result = await this.transport.getList('/api/v1/data/projects', query);
|
|
616
636
|
if ('error' in result) return result;
|
|
@@ -649,6 +669,33 @@ class ProjectsAccessor {
|
|
|
649
669
|
data: null
|
|
650
670
|
};
|
|
651
671
|
}
|
|
672
|
+
// ─── Team membership (the projects `team` bucket) ──────────────────────────
|
|
673
|
+
// Membership is the project↔employee join. Reads need `projects` read on `team`;
|
|
674
|
+
// add/remove need `projects` update on `team` (re-adding restores a soft-deleted row).
|
|
675
|
+
membersBase(projectId) {
|
|
676
|
+
return `/api/v1/data/project-memberships/projects/${projectId}/members`;
|
|
677
|
+
}
|
|
678
|
+
/** List a project's team members. Requires `projects` read on the `team` bucket. */ async listMembers(projectId) {
|
|
679
|
+
const result = await this.transport.get(this.membersBase(projectId));
|
|
680
|
+
if ('error' in result) return result;
|
|
681
|
+
return {
|
|
682
|
+
data: (result.data ?? []).map((r)=>transformMember(r))
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
/** Add a team member. Requires `projects` update on the `team` bucket. */ async addMember(projectId, data) {
|
|
686
|
+
const result = await this.transport.post(this.membersBase(projectId), toSnakeBody(data));
|
|
687
|
+
if ('error' in result) return result;
|
|
688
|
+
return {
|
|
689
|
+
data: transformMember(result.data)
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
/** Remove a team member. Requires `projects` update on the `team` bucket. */ async removeMember(projectId, employeeId) {
|
|
693
|
+
const result = await this.transport.del(`${this.membersBase(projectId)}/${employeeId}`);
|
|
694
|
+
if ('error' in result) return result;
|
|
695
|
+
return {
|
|
696
|
+
data: null
|
|
697
|
+
};
|
|
698
|
+
}
|
|
652
699
|
}
|
|
653
700
|
|
|
654
701
|
class ClientsAccessor {
|
|
@@ -662,6 +709,7 @@ class ClientsAccessor {
|
|
|
662
709
|
if (params?.country) query['country'] = params.country;
|
|
663
710
|
if (params?.page) query['page'] = params.page;
|
|
664
711
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
712
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
665
713
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
666
714
|
const result = await this.transport.getList('/api/v1/data/clients', query);
|
|
667
715
|
if ('error' in result) return result;
|
|
@@ -711,6 +759,7 @@ class PartnersAccessor {
|
|
|
711
759
|
if (params?.search) query['search'] = params.search;
|
|
712
760
|
if (params?.page) query['page'] = params.page;
|
|
713
761
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
762
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
714
763
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
715
764
|
const result = await this.transport.getList('/api/v1/data/partners', query);
|
|
716
765
|
if ('error' in result) return result;
|
|
@@ -760,6 +809,7 @@ class DepartmentsAccessor {
|
|
|
760
809
|
if (params?.search) query['search'] = params.search;
|
|
761
810
|
if (params?.page) query['page'] = params.page;
|
|
762
811
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
812
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
763
813
|
const result = await this.transport.getList('/api/v1/data/departments', query);
|
|
764
814
|
if ('error' in result) return result;
|
|
765
815
|
return {
|
|
@@ -805,6 +855,7 @@ class ProjectTypesAccessor {
|
|
|
805
855
|
const query = {};
|
|
806
856
|
if (params?.page) query['page'] = params.page;
|
|
807
857
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
858
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
808
859
|
const result = await this.transport.getList('/api/v1/data/project-types', query);
|
|
809
860
|
if ('error' in result) return result;
|
|
810
861
|
return {
|
|
@@ -850,6 +901,7 @@ class CurrenciesAccessor {
|
|
|
850
901
|
const query = {};
|
|
851
902
|
if (params?.page) query['page'] = params.page;
|
|
852
903
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
904
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
853
905
|
const result = await this.transport.getList('/api/v1/data/currencies', query);
|
|
854
906
|
if ('error' in result) return result;
|
|
855
907
|
return {
|
|
@@ -899,6 +951,7 @@ class GenericRatesAccessor {
|
|
|
899
951
|
if (params?.department) query['department'] = params.department;
|
|
900
952
|
if (params?.page) query['page'] = params.page;
|
|
901
953
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
954
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
902
955
|
const result = await this.transport.getList('/api/v1/data/generic-rates', query);
|
|
903
956
|
if ('error' in result) return result;
|
|
904
957
|
return {
|
|
@@ -945,6 +998,7 @@ class JurisdictionsAccessor {
|
|
|
945
998
|
if (params?.search) query['search'] = params.search;
|
|
946
999
|
if (params?.page) query['page'] = params.page;
|
|
947
1000
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1001
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
948
1002
|
const result = await this.transport.getList('/api/v1/data/jurisdictions', query);
|
|
949
1003
|
if ('error' in result) return result;
|
|
950
1004
|
return {
|
|
@@ -982,6 +1036,105 @@ class JurisdictionsAccessor {
|
|
|
982
1036
|
}
|
|
983
1037
|
}
|
|
984
1038
|
|
|
1039
|
+
/** JobLevelsAccessor — the company's career-ladder rungs (WS7). */ class JobLevelsAccessor {
|
|
1040
|
+
constructor(transport){
|
|
1041
|
+
this.transport = transport;
|
|
1042
|
+
}
|
|
1043
|
+
async list(params) {
|
|
1044
|
+
const query = {};
|
|
1045
|
+
if (params?.search) query['search'] = params.search;
|
|
1046
|
+
if (params?.departmentId) query['departmentId'] = params.departmentId;
|
|
1047
|
+
if (params?.page) query['page'] = params.page;
|
|
1048
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1049
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1050
|
+
const result = await this.transport.getList('/api/v1/data/job-levels', query);
|
|
1051
|
+
if ('error' in result) return result;
|
|
1052
|
+
return {
|
|
1053
|
+
data: result.data.map((r)=>transformJobLevel(r)),
|
|
1054
|
+
meta: result.meta
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
async getById(id) {
|
|
1058
|
+
const result = await this.transport.get(`/api/v1/data/job-levels/${id}`);
|
|
1059
|
+
if ('error' in result) return result;
|
|
1060
|
+
return {
|
|
1061
|
+
data: transformJobLevel(result.data)
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/** Create a job level. Requires `job-levels.create` scope. */ async create(data) {
|
|
1065
|
+
const result = await this.transport.post('/api/v1/data/job-levels', toSnakeBody(data));
|
|
1066
|
+
if ('error' in result) return result;
|
|
1067
|
+
return {
|
|
1068
|
+
data: transformJobLevel(result.data)
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
/** Update a job level. Requires `job-levels.update` scope. */ async update(id, data) {
|
|
1072
|
+
const result = await this.transport.put(`/api/v1/data/job-levels/${id}`, toSnakeBody(data));
|
|
1073
|
+
if ('error' in result) return result;
|
|
1074
|
+
return {
|
|
1075
|
+
data: transformJobLevel(result.data)
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
/** Soft-delete a job level. Requires `job-levels.delete` scope. */ async delete(id) {
|
|
1079
|
+
const result = await this.transport.del(`/api/v1/data/job-levels/${id}`);
|
|
1080
|
+
if ('error' in result) return result;
|
|
1081
|
+
return {
|
|
1082
|
+
data: null
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* SalaryBandsAccessor — per-jurisdiction pay ranges for a job level (WS7).
|
|
1089
|
+
* The `financials` bucket (min/max/target) is flagged sensitive server-side.
|
|
1090
|
+
*/ class SalaryBandsAccessor {
|
|
1091
|
+
constructor(transport){
|
|
1092
|
+
this.transport = transport;
|
|
1093
|
+
}
|
|
1094
|
+
async list(params) {
|
|
1095
|
+
const query = {};
|
|
1096
|
+
if (params?.jobLevelId) query['jobLevelId'] = params.jobLevelId;
|
|
1097
|
+
if (params?.jurisdictionId) query['jurisdictionId'] = params.jurisdictionId;
|
|
1098
|
+
if (params?.page) query['page'] = params.page;
|
|
1099
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1100
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1101
|
+
const result = await this.transport.getList('/api/v1/data/salary-bands', query);
|
|
1102
|
+
if ('error' in result) return result;
|
|
1103
|
+
return {
|
|
1104
|
+
data: result.data.map((r)=>transformSalaryBand(r)),
|
|
1105
|
+
meta: result.meta
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
async getById(id) {
|
|
1109
|
+
const result = await this.transport.get(`/api/v1/data/salary-bands/${id}`);
|
|
1110
|
+
if ('error' in result) return result;
|
|
1111
|
+
return {
|
|
1112
|
+
data: transformSalaryBand(result.data)
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
/** Create a salary band. Requires `salary-bands.create` scope. */ async create(data) {
|
|
1116
|
+
const result = await this.transport.post('/api/v1/data/salary-bands', toSnakeBody(data));
|
|
1117
|
+
if ('error' in result) return result;
|
|
1118
|
+
return {
|
|
1119
|
+
data: transformSalaryBand(result.data)
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
/** Update a salary band. Requires `salary-bands.update` scope. */ async update(id, data) {
|
|
1123
|
+
const result = await this.transport.put(`/api/v1/data/salary-bands/${id}`, toSnakeBody(data));
|
|
1124
|
+
if ('error' in result) return result;
|
|
1125
|
+
return {
|
|
1126
|
+
data: transformSalaryBand(result.data)
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
/** Soft-delete a salary band. Requires `salary-bands.delete` scope. */ async delete(id) {
|
|
1130
|
+
const result = await this.transport.del(`/api/v1/data/salary-bands/${id}`);
|
|
1131
|
+
if ('error' in result) return result;
|
|
1132
|
+
return {
|
|
1133
|
+
data: null
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
985
1138
|
class LeaveTypesAccessor {
|
|
986
1139
|
constructor(transport){
|
|
987
1140
|
this.transport = transport;
|
|
@@ -991,6 +1144,7 @@ class LeaveTypesAccessor {
|
|
|
991
1144
|
if (params?.search) query['search'] = params.search;
|
|
992
1145
|
if (params?.page) query['page'] = params.page;
|
|
993
1146
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1147
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
994
1148
|
const result = await this.transport.getList('/api/v1/data/leave-types', query);
|
|
995
1149
|
if ('error' in result) return result;
|
|
996
1150
|
return {
|
|
@@ -1039,6 +1193,7 @@ class LeavesAccessor {
|
|
|
1039
1193
|
if (params?.status) query['status'] = params.status;
|
|
1040
1194
|
if (params?.page) query['page'] = params.page;
|
|
1041
1195
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1196
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1042
1197
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
1043
1198
|
const result = await this.transport.getList('/api/v1/data/leaves', query);
|
|
1044
1199
|
if ('error' in result) return result;
|
|
@@ -1090,6 +1245,7 @@ class LeaveBalancesAccessor {
|
|
|
1090
1245
|
if (params?.year) query['year'] = params.year;
|
|
1091
1246
|
if (params?.page) query['page'] = params.page;
|
|
1092
1247
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1248
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1093
1249
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
1094
1250
|
const result = await this.transport.getList('/api/v1/data/leave-balances', query);
|
|
1095
1251
|
if ('error' in result) return result;
|
|
@@ -1140,6 +1296,7 @@ class PoliciesAccessor {
|
|
|
1140
1296
|
if (params?.category) query['category'] = params.category;
|
|
1141
1297
|
if (params?.page) query['page'] = params.page;
|
|
1142
1298
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1299
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1143
1300
|
const result = await this.transport.getList('/api/v1/data/policies', query);
|
|
1144
1301
|
if ('error' in result) return result;
|
|
1145
1302
|
return {
|
|
@@ -1187,6 +1344,7 @@ class PolicyAcknowledgementsAccessor {
|
|
|
1187
1344
|
if (params?.employeeId) query['employeeId'] = params.employeeId;
|
|
1188
1345
|
if (params?.page) query['page'] = params.page;
|
|
1189
1346
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1347
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1190
1348
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
1191
1349
|
const result = await this.transport.getList('/api/v1/data/policy-acknowledgements', query);
|
|
1192
1350
|
if ('error' in result) return result;
|
|
@@ -1235,8 +1393,10 @@ class CalendarEventsAccessor {
|
|
|
1235
1393
|
const query = {};
|
|
1236
1394
|
if (params?.search) query['search'] = params.search;
|
|
1237
1395
|
if (params?.type) query['type'] = params.type;
|
|
1396
|
+
if (params?.jurisdictionId) query['jurisdictionId'] = params.jurisdictionId;
|
|
1238
1397
|
if (params?.page) query['page'] = params.page;
|
|
1239
1398
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1399
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1240
1400
|
const result = await this.transport.getList('/api/v1/data/calendar-events', query);
|
|
1241
1401
|
if ('error' in result) return result;
|
|
1242
1402
|
return {
|
|
@@ -1284,6 +1444,7 @@ class TimesheetsAccessor {
|
|
|
1284
1444
|
if (params?.projectId) query['projectId'] = params.projectId;
|
|
1285
1445
|
if (params?.page) query['page'] = params.page;
|
|
1286
1446
|
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
1447
|
+
if (params?.updatedSince) query['updatedSince'] = params.updatedSince;
|
|
1287
1448
|
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
1288
1449
|
const result = await this.transport.getList('/api/v1/data/timesheets', query);
|
|
1289
1450
|
if ('error' in result) return result;
|
|
@@ -1308,6 +1469,23 @@ class TimesheetsAccessor {
|
|
|
1308
1469
|
data: transformTimesheet(result.data)
|
|
1309
1470
|
};
|
|
1310
1471
|
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Bulk-create timesheet entries (partial accept). Requires `timesheets.create` scope.
|
|
1474
|
+
* Returns `{ created, rejected }` — every valid item is inserted; invalid items come back
|
|
1475
|
+
* with their input index + reason.
|
|
1476
|
+
*/ async bulkCreate(items) {
|
|
1477
|
+
const result = await this.transport.post('/api/v1/data/timesheets/bulk', {
|
|
1478
|
+
items: items.map((i)=>toSnakeBody(i))
|
|
1479
|
+
});
|
|
1480
|
+
if ('error' in result) return result;
|
|
1481
|
+
const data = result.data;
|
|
1482
|
+
return {
|
|
1483
|
+
data: {
|
|
1484
|
+
created: data.created.map((r)=>transformTimesheet(r)),
|
|
1485
|
+
rejected: data.rejected
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1311
1489
|
/** Update a timesheet entry. Requires `timesheets.update` scope. */ async update(id, data) {
|
|
1312
1490
|
const result = await this.transport.put(`/api/v1/data/timesheets/${id}`, toSnakeBody(data));
|
|
1313
1491
|
if ('error' in result) return result;
|
|
@@ -1324,6 +1502,98 @@ class TimesheetsAccessor {
|
|
|
1324
1502
|
}
|
|
1325
1503
|
}
|
|
1326
1504
|
|
|
1505
|
+
/**
|
|
1506
|
+
* CalendarAccessor — read workspace users' Google Calendar events (WS3).
|
|
1507
|
+
* Requires the app's `calendar:read` scope. Not part of the bucket/CRUD scope system.
|
|
1508
|
+
*/ class CalendarAccessor {
|
|
1509
|
+
constructor(transport){
|
|
1510
|
+
this.transport = transport;
|
|
1511
|
+
}
|
|
1512
|
+
async listEvents(params) {
|
|
1513
|
+
const result = await this.transport.get('/api/v1/data/calendar/events', {
|
|
1514
|
+
employeeId: params.employeeId,
|
|
1515
|
+
timeMin: params.timeMin,
|
|
1516
|
+
timeMax: params.timeMax,
|
|
1517
|
+
q: params.q
|
|
1518
|
+
});
|
|
1519
|
+
if ('error' in result) return result;
|
|
1520
|
+
return {
|
|
1521
|
+
data: result.data
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/**
|
|
1527
|
+
* EmailAccessor — send Nucleus-mediated email (WS5). Requires the app's `email:send` scope.
|
|
1528
|
+
* In test mode the message is marked `[SANDBOX]` and external recipients are suppressed.
|
|
1529
|
+
*/ class EmailAccessor {
|
|
1530
|
+
constructor(transport){
|
|
1531
|
+
this.transport = transport;
|
|
1532
|
+
}
|
|
1533
|
+
async send(input) {
|
|
1534
|
+
const result = await this.transport.post('/api/v1/data/comms/send', input);
|
|
1535
|
+
if ('error' in result) return result;
|
|
1536
|
+
return {
|
|
1537
|
+
data: result.data
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function transform(raw) {
|
|
1543
|
+
return {
|
|
1544
|
+
id: raw['id'],
|
|
1545
|
+
employeeId: raw['employee_id'],
|
|
1546
|
+
hourlyCostRate: raw['hourly_cost_rate'] ?? null,
|
|
1547
|
+
monthlyCostRate: raw['monthly_cost_rate'] ?? null,
|
|
1548
|
+
hourlyBillableRate: raw['hourly_billable_rate'] ?? null,
|
|
1549
|
+
monthlyBillableRate: raw['monthly_billable_rate'] ?? null,
|
|
1550
|
+
salary: raw['salary'] ?? null,
|
|
1551
|
+
currencyCode: raw['currency_code'],
|
|
1552
|
+
effectiveFrom: raw['effective_from'],
|
|
1553
|
+
effectiveTo: raw['effective_to'] ?? null
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* EmployeeCompensationAccessor — read/write an employee's effective-dated compensation (WS6).
|
|
1558
|
+
* Gated server-side by the `employee-compensation` scope (buckets: `core`, `rates`, `salary`).
|
|
1559
|
+
* `create` closes the prior open record and opens a new one (Nucleus = single comp authority).
|
|
1560
|
+
*/ class EmployeeCompensationAccessor {
|
|
1561
|
+
constructor(transport){
|
|
1562
|
+
this.transport = transport;
|
|
1563
|
+
}
|
|
1564
|
+
base(employeeId) {
|
|
1565
|
+
return `/api/v1/data/employee-compensation/employees/${employeeId}/compensation`;
|
|
1566
|
+
}
|
|
1567
|
+
/** Full history, newest first. */ async list(employeeId) {
|
|
1568
|
+
const result = await this.transport.get(this.base(employeeId));
|
|
1569
|
+
if ('error' in result) return result;
|
|
1570
|
+
return {
|
|
1571
|
+
data: (result.data ?? []).map((r)=>transform(r))
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
/** The current (open) record, or null. */ async current(employeeId) {
|
|
1575
|
+
const result = await this.transport.get(`${this.base(employeeId)}/current`);
|
|
1576
|
+
if ('error' in result) return result;
|
|
1577
|
+
return {
|
|
1578
|
+
data: result.data ? transform(result.data) : null
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
/** Add a new effective-dated record (closes the prior open one). */ async create(employeeId, data) {
|
|
1582
|
+
const result = await this.transport.post(this.base(employeeId), toSnakeBody(data));
|
|
1583
|
+
if ('error' in result) return result;
|
|
1584
|
+
return {
|
|
1585
|
+
data: transform(result.data)
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
/** Soft-delete a compensation record. */ async delete(employeeId, id) {
|
|
1589
|
+
const result = await this.transport.del(`${this.base(employeeId)}/${id}`);
|
|
1590
|
+
if ('error' in result) return result;
|
|
1591
|
+
return {
|
|
1592
|
+
data: null
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1327
1597
|
/**
|
|
1328
1598
|
* AppsAccessor — wraps the self-service app info endpoint (§3.8, §6).
|
|
1329
1599
|
* Calls GET /api/v1/platform/apps/me with the app token.
|
|
@@ -1478,6 +1748,8 @@ class AuthAccessor {
|
|
|
1478
1748
|
this.currencies = new CurrenciesAccessor(transport);
|
|
1479
1749
|
this.genericRates = new GenericRatesAccessor(transport);
|
|
1480
1750
|
this.jurisdictions = new JurisdictionsAccessor(transport);
|
|
1751
|
+
this.jobLevels = new JobLevelsAccessor(transport);
|
|
1752
|
+
this.salaryBands = new SalaryBandsAccessor(transport);
|
|
1481
1753
|
this.leaveTypes = new LeaveTypesAccessor(transport);
|
|
1482
1754
|
this.leaves = new LeavesAccessor(transport);
|
|
1483
1755
|
this.leaveBalances = new LeaveBalancesAccessor(transport);
|
|
@@ -1485,6 +1757,9 @@ class AuthAccessor {
|
|
|
1485
1757
|
this.policyAcknowledgements = new PolicyAcknowledgementsAccessor(transport);
|
|
1486
1758
|
this.calendarEvents = new CalendarEventsAccessor(transport);
|
|
1487
1759
|
this.timesheets = new TimesheetsAccessor(transport);
|
|
1760
|
+
this.calendar = new CalendarAccessor(transport);
|
|
1761
|
+
this.email = new EmailAccessor(transport);
|
|
1762
|
+
this.employeeCompensation = new EmployeeCompensationAccessor(transport);
|
|
1488
1763
|
this.apps = new AppsAccessor(transport);
|
|
1489
1764
|
this.files = new FilesAccessor(transport);
|
|
1490
1765
|
this.auth = new AuthAccessor(config.baseUrl);
|
|
@@ -1494,7 +1769,7 @@ class AuthAccessor {
|
|
|
1494
1769
|
/**
|
|
1495
1770
|
* Nucleus SDK — Type System
|
|
1496
1771
|
*
|
|
1497
|
-
* Bucket types are transcribed from docs/
|
|
1772
|
+
* Bucket types are transcribed from docs/ARCHITECTURE.md §7 (Bucket Definitions).
|
|
1498
1773
|
* Each bucket type contains only that bucket's fields.
|
|
1499
1774
|
* The scope-to-type inference intersects selected bucket types at compile time.
|
|
1500
1775
|
*/ // ─── Utility types ────────────────────────────────────────────────────────────
|