@typeb-digital/nucleus-sdk 0.0.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/package.json +36 -0
- package/src/apps.ts +19 -0
- package/src/client.ts +75 -0
- package/src/index.ts +82 -0
- package/src/resources/clients.ts +65 -0
- package/src/resources/currencies.ts +41 -0
- package/src/resources/departments.ts +43 -0
- package/src/resources/employees.ts +65 -0
- package/src/resources/files.ts +105 -0
- package/src/resources/generic-rates.ts +43 -0
- package/src/resources/partners.ts +61 -0
- package/src/resources/project-types.ts +41 -0
- package/src/resources/projects.ts +65 -0
- package/src/transform.ts +266 -0
- package/src/transport.ts +145 -0
- package/src/types.ts +447 -0
- package/tsconfig.json +12 -0
- package/tsconfig.verify.json +11 -0
- package/typeb-digital-nucleus-sdk-0.0.1.tgz +0 -0
- package/verify.ts +182 -0
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@typeb-digital/nucleus-sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Server-side TypeScript SDK for the Nucleus data platform",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=18"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/cjs/index.cjs",
|
|
9
|
+
"module": "./dist/es/index.js",
|
|
10
|
+
"types": "./dist/es/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./dist/es/index.d.ts",
|
|
15
|
+
"default": "./dist/es/index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./dist/cjs/index.d.cts",
|
|
19
|
+
"default": "./dist/cjs/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "bunchee",
|
|
25
|
+
"dev": "bunchee --watch",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"ofetch": "1.5.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "25.9.1",
|
|
33
|
+
"bunchee": "6.10.0",
|
|
34
|
+
"typescript": "6.0.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/apps.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppsAccessor — wraps the self-service app info endpoint (§3.8, §6).
|
|
3
|
+
* Calls GET /api/v1/platform/apps/me with the app token.
|
|
4
|
+
* This is the only platform endpoint accessible via app token.
|
|
5
|
+
*/
|
|
6
|
+
import type { AppInfo, SingleResult, ErrorResult } from './types';
|
|
7
|
+
import type { NucleusTransport } from './transport';
|
|
8
|
+
|
|
9
|
+
export class AppsAccessor {
|
|
10
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
11
|
+
|
|
12
|
+
async me(): Promise<SingleResult<AppInfo>> {
|
|
13
|
+
const result = await this.transport.get<AppInfo>('/api/v1/platform/apps/me');
|
|
14
|
+
|
|
15
|
+
if ('error' in result) return result as ErrorResult;
|
|
16
|
+
|
|
17
|
+
return { data: result.data };
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ScopeDeclaration } from './types';
|
|
2
|
+
import { NucleusTransport } from './transport';
|
|
3
|
+
import { EmployeesAccessor } from './resources/employees';
|
|
4
|
+
import { ProjectsAccessor } from './resources/projects';
|
|
5
|
+
import { ClientsAccessor } from './resources/clients';
|
|
6
|
+
import { PartnersAccessor } from './resources/partners';
|
|
7
|
+
import { DepartmentsAccessor } from './resources/departments';
|
|
8
|
+
import { ProjectTypesAccessor } from './resources/project-types';
|
|
9
|
+
import { CurrenciesAccessor } from './resources/currencies';
|
|
10
|
+
import { GenericRatesAccessor } from './resources/generic-rates';
|
|
11
|
+
import { AppsAccessor } from './apps';
|
|
12
|
+
import { FilesAccessor } from './resources/files';
|
|
13
|
+
|
|
14
|
+
export type NucleusClientConfig<S extends ScopeDeclaration> = {
|
|
15
|
+
/** App token — `el_live_...` or `el_test_...`. Store in an environment variable, never commit. */
|
|
16
|
+
token: string;
|
|
17
|
+
/** Base URL of the Nucleus API. Defaults to the production platform URL. */
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Declared scopes — drives the return types of every resource accessor.
|
|
21
|
+
* Declare exactly the buckets your app token is approved for.
|
|
22
|
+
* Declaring a bucket the token lacks causes the API to return filtered data;
|
|
23
|
+
* it does not cause a runtime error during client construction.
|
|
24
|
+
*/
|
|
25
|
+
scopes: S;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* NucleusClient — entry point for the Nucleus server SDK.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { NucleusClient } from '@typeb-digital/nucleus-sdk';
|
|
34
|
+
*
|
|
35
|
+
* export const nucleus = new NucleusClient({
|
|
36
|
+
* token: process.env.NUCLEUS_TOKEN!,
|
|
37
|
+
* scopes: {
|
|
38
|
+
* employees: ['identity', 'employment'],
|
|
39
|
+
* projects: ['core'],
|
|
40
|
+
* clients: ['identity'],
|
|
41
|
+
* },
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* The `scopes` object is the heart of the type system — it is declared once at
|
|
46
|
+
* instantiation and drives the return type of every method on the client.
|
|
47
|
+
* TypeScript infers it automatically; no explicit type parameters needed.
|
|
48
|
+
*/
|
|
49
|
+
export class NucleusClient<const S extends ScopeDeclaration> {
|
|
50
|
+
readonly employees: EmployeesAccessor<S>;
|
|
51
|
+
readonly projects: ProjectsAccessor<S>;
|
|
52
|
+
readonly clients: ClientsAccessor<S>;
|
|
53
|
+
readonly partners: PartnersAccessor<S>;
|
|
54
|
+
readonly departments: DepartmentsAccessor<S>;
|
|
55
|
+
readonly projectTypes: ProjectTypesAccessor<S>;
|
|
56
|
+
readonly currencies: CurrenciesAccessor<S>;
|
|
57
|
+
readonly genericRates: GenericRatesAccessor<S>;
|
|
58
|
+
readonly apps: AppsAccessor;
|
|
59
|
+
readonly files: FilesAccessor;
|
|
60
|
+
|
|
61
|
+
constructor(config: NucleusClientConfig<S>) {
|
|
62
|
+
const transport = new NucleusTransport(config.token, config.baseUrl);
|
|
63
|
+
|
|
64
|
+
this.employees = new EmployeesAccessor<S>(transport);
|
|
65
|
+
this.projects = new ProjectsAccessor<S>(transport);
|
|
66
|
+
this.clients = new ClientsAccessor<S>(transport);
|
|
67
|
+
this.partners = new PartnersAccessor<S>(transport);
|
|
68
|
+
this.departments = new DepartmentsAccessor<S>(transport);
|
|
69
|
+
this.projectTypes = new ProjectTypesAccessor<S>(transport);
|
|
70
|
+
this.currencies = new CurrenciesAccessor<S>(transport);
|
|
71
|
+
this.genericRates = new GenericRatesAccessor<S>(transport);
|
|
72
|
+
this.apps = new AppsAccessor(transport);
|
|
73
|
+
this.files = new FilesAccessor(transport);
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export { NucleusClient } from './client';
|
|
2
|
+
export type { NucleusClientConfig } from './client';
|
|
3
|
+
|
|
4
|
+
// Result types
|
|
5
|
+
export type {
|
|
6
|
+
ErrorCode,
|
|
7
|
+
ErrorResult,
|
|
8
|
+
SuccessResult,
|
|
9
|
+
ListMeta,
|
|
10
|
+
ListResult,
|
|
11
|
+
SingleResult,
|
|
12
|
+
PaginatedResult,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
// Narrowing helper
|
|
16
|
+
export { isError } from './types';
|
|
17
|
+
|
|
18
|
+
// Bucket types — re-exported so app code can annotate variables
|
|
19
|
+
export type {
|
|
20
|
+
EmployeeIdentity,
|
|
21
|
+
EmployeeContact,
|
|
22
|
+
EmployeeEmployment,
|
|
23
|
+
EmployeeSensitive,
|
|
24
|
+
EmployeeCompensation,
|
|
25
|
+
EmployeeCompensationHistory,
|
|
26
|
+
ProjectCore,
|
|
27
|
+
ProjectTeam,
|
|
28
|
+
ProjectFinancials,
|
|
29
|
+
ProjectIntegrations,
|
|
30
|
+
ProjectMembership,
|
|
31
|
+
ClientIdentity,
|
|
32
|
+
ClientContact,
|
|
33
|
+
ClientFinancials,
|
|
34
|
+
ClientNotes,
|
|
35
|
+
PartnerIdentity,
|
|
36
|
+
PartnerContact,
|
|
37
|
+
PartnerNotes,
|
|
38
|
+
DepartmentCore,
|
|
39
|
+
ProjectTypeCore,
|
|
40
|
+
CurrencyCore,
|
|
41
|
+
GenericRateCore,
|
|
42
|
+
GenericRateFinancials,
|
|
43
|
+
} from './types';
|
|
44
|
+
|
|
45
|
+
// Scope resolution utilities
|
|
46
|
+
export type {
|
|
47
|
+
ScopeDeclaration,
|
|
48
|
+
ResolveEmployee,
|
|
49
|
+
ResolveProject,
|
|
50
|
+
ResolveClient,
|
|
51
|
+
ResolvePartner,
|
|
52
|
+
ResolveDepartment,
|
|
53
|
+
ResolveProjectType,
|
|
54
|
+
ResolveCurrency,
|
|
55
|
+
ResolveGenericRate,
|
|
56
|
+
} from './types';
|
|
57
|
+
|
|
58
|
+
// Expand path types
|
|
59
|
+
export type {
|
|
60
|
+
EmployeeExpandPath,
|
|
61
|
+
ProjectExpandPath,
|
|
62
|
+
ClientExpandPath,
|
|
63
|
+
PartnerExpandPath,
|
|
64
|
+
WithEmployeeExpand,
|
|
65
|
+
WithProjectExpand,
|
|
66
|
+
WithClientExpand,
|
|
67
|
+
WithPartnerExpand,
|
|
68
|
+
} from './types';
|
|
69
|
+
|
|
70
|
+
// App info
|
|
71
|
+
export type { AppInfo, AppScopeInfo } from './types';
|
|
72
|
+
|
|
73
|
+
// File types
|
|
74
|
+
export type {
|
|
75
|
+
FileVisibility,
|
|
76
|
+
FileRecord,
|
|
77
|
+
FileUrlResult,
|
|
78
|
+
UploadParams,
|
|
79
|
+
GetUrlOptions,
|
|
80
|
+
ListParams as FileListParams,
|
|
81
|
+
} from './resources/files';
|
|
82
|
+
export { FilesAccessor } from './resources/files';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScopeDeclaration,
|
|
3
|
+
ResolveClient,
|
|
4
|
+
ClientExpandPath,
|
|
5
|
+
WithClientExpand,
|
|
6
|
+
SingleResult,
|
|
7
|
+
PaginatedResult,
|
|
8
|
+
ErrorResult,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import type { NucleusTransport } from '../transport';
|
|
11
|
+
import { transformClient } from '../transform';
|
|
12
|
+
|
|
13
|
+
export type ClientListParams = {
|
|
14
|
+
search?: string;
|
|
15
|
+
industry?: string;
|
|
16
|
+
country?: string;
|
|
17
|
+
page?: number;
|
|
18
|
+
pageSize?: number;
|
|
19
|
+
expand?: readonly ClientExpandPath[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class ClientsAccessor<S extends ScopeDeclaration> {
|
|
23
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
24
|
+
|
|
25
|
+
async list<E extends readonly ClientExpandPath[] = never[]>(
|
|
26
|
+
params?: ClientListParams & { expand?: E },
|
|
27
|
+
): Promise<PaginatedResult<WithClientExpand<ResolveClient<S>, E, S>>> {
|
|
28
|
+
const query: Record<string, string | number | undefined> = {};
|
|
29
|
+
if (params?.search) query['search'] = params.search;
|
|
30
|
+
if (params?.industry) query['industry'] = params.industry;
|
|
31
|
+
if (params?.country) query['country'] = params.country;
|
|
32
|
+
if (params?.page) query['page'] = params.page;
|
|
33
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
34
|
+
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
35
|
+
|
|
36
|
+
const result = await this.transport.getList<unknown>('/api/v1/data/clients', query);
|
|
37
|
+
|
|
38
|
+
if ('error' in result) return result as ErrorResult;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
data: result.data.map((r) => transformClient(r)) as WithClientExpand<
|
|
42
|
+
ResolveClient<S>,
|
|
43
|
+
E,
|
|
44
|
+
S
|
|
45
|
+
>[],
|
|
46
|
+
meta: result.meta,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getById<E extends readonly ClientExpandPath[] = never[]>(
|
|
51
|
+
id: string,
|
|
52
|
+
options?: { expand?: E },
|
|
53
|
+
): Promise<SingleResult<WithClientExpand<ResolveClient<S>, E, S>>> {
|
|
54
|
+
const query: Record<string, string | undefined> = {};
|
|
55
|
+
if (options?.expand?.length) query['expand'] = options.expand.join(',');
|
|
56
|
+
|
|
57
|
+
const result = await this.transport.get<unknown>(`/api/v1/data/clients/${id}`, query);
|
|
58
|
+
|
|
59
|
+
if ('error' in result) return result as ErrorResult;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
data: transformClient(result.data) as WithClientExpand<ResolveClient<S>, E, S>,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScopeDeclaration,
|
|
3
|
+
ResolveCurrency,
|
|
4
|
+
SingleResult,
|
|
5
|
+
PaginatedResult,
|
|
6
|
+
ErrorResult,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import type { NucleusTransport } from '../transport';
|
|
9
|
+
import { transformCurrency } from '../transform';
|
|
10
|
+
|
|
11
|
+
export type CurrencyListParams = {
|
|
12
|
+
page?: number;
|
|
13
|
+
pageSize?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class CurrenciesAccessor<S extends ScopeDeclaration> {
|
|
17
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
18
|
+
|
|
19
|
+
async list(params?: CurrencyListParams): Promise<PaginatedResult<ResolveCurrency<S>>> {
|
|
20
|
+
const query: Record<string, string | number | undefined> = {};
|
|
21
|
+
if (params?.page) query['page'] = params.page;
|
|
22
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
23
|
+
|
|
24
|
+
const result = await this.transport.getList<unknown>('/api/v1/data/currencies', query);
|
|
25
|
+
|
|
26
|
+
if ('error' in result) return result as ErrorResult;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
data: result.data.map((r) => transformCurrency(r)) as ResolveCurrency<S>[],
|
|
30
|
+
meta: result.meta,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getByCode(code: string): Promise<SingleResult<ResolveCurrency<S>>> {
|
|
35
|
+
const result = await this.transport.get<unknown>(`/api/v1/data/currencies/${code}`);
|
|
36
|
+
|
|
37
|
+
if ('error' in result) return result as ErrorResult;
|
|
38
|
+
|
|
39
|
+
return { data: transformCurrency(result.data) as ResolveCurrency<S> };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScopeDeclaration,
|
|
3
|
+
ResolveDepartment,
|
|
4
|
+
SingleResult,
|
|
5
|
+
PaginatedResult,
|
|
6
|
+
ErrorResult,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import type { NucleusTransport } from '../transport';
|
|
9
|
+
import { transformDepartment } from '../transform';
|
|
10
|
+
|
|
11
|
+
export type DepartmentListParams = {
|
|
12
|
+
search?: string;
|
|
13
|
+
page?: number;
|
|
14
|
+
pageSize?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class DepartmentsAccessor<S extends ScopeDeclaration> {
|
|
18
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
19
|
+
|
|
20
|
+
async list(params?: DepartmentListParams): Promise<PaginatedResult<ResolveDepartment<S>>> {
|
|
21
|
+
const query: Record<string, string | number | undefined> = {};
|
|
22
|
+
if (params?.search) query['search'] = params.search;
|
|
23
|
+
if (params?.page) query['page'] = params.page;
|
|
24
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
25
|
+
|
|
26
|
+
const result = await this.transport.getList<unknown>('/api/v1/data/departments', query);
|
|
27
|
+
|
|
28
|
+
if ('error' in result) return result as ErrorResult;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
data: result.data.map((r) => transformDepartment(r)) as ResolveDepartment<S>[],
|
|
32
|
+
meta: result.meta,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getById(id: string): Promise<SingleResult<ResolveDepartment<S>>> {
|
|
37
|
+
const result = await this.transport.get<unknown>(`/api/v1/data/departments/${id}`);
|
|
38
|
+
|
|
39
|
+
if ('error' in result) return result as ErrorResult;
|
|
40
|
+
|
|
41
|
+
return { data: transformDepartment(result.data) as ResolveDepartment<S> };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScopeDeclaration,
|
|
3
|
+
ResolveEmployee,
|
|
4
|
+
EmployeeExpandPath,
|
|
5
|
+
WithEmployeeExpand,
|
|
6
|
+
SingleResult,
|
|
7
|
+
PaginatedResult,
|
|
8
|
+
ErrorResult,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import type { NucleusTransport } from '../transport';
|
|
11
|
+
import { transformEmployee } from '../transform';
|
|
12
|
+
|
|
13
|
+
export type EmployeeListParams = {
|
|
14
|
+
search?: string;
|
|
15
|
+
department?: string;
|
|
16
|
+
employmentStatus?: string;
|
|
17
|
+
page?: number;
|
|
18
|
+
pageSize?: number;
|
|
19
|
+
expand?: readonly EmployeeExpandPath[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class EmployeesAccessor<S extends ScopeDeclaration> {
|
|
23
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
24
|
+
|
|
25
|
+
async list<E extends readonly EmployeeExpandPath[] = never[]>(
|
|
26
|
+
params?: EmployeeListParams & { expand?: E },
|
|
27
|
+
): Promise<PaginatedResult<WithEmployeeExpand<ResolveEmployee<S>, E, S>>> {
|
|
28
|
+
const query: Record<string, string | number | undefined> = {};
|
|
29
|
+
if (params?.search) query['search'] = params.search;
|
|
30
|
+
if (params?.department) query['department'] = params.department;
|
|
31
|
+
if (params?.employmentStatus) query['employmentStatus'] = params.employmentStatus;
|
|
32
|
+
if (params?.page) query['page'] = params.page;
|
|
33
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
34
|
+
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
35
|
+
|
|
36
|
+
const result = await this.transport.getList<unknown>('/api/v1/data/employees', query);
|
|
37
|
+
|
|
38
|
+
if ('error' in result) return result as ErrorResult;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
data: result.data.map((r) => transformEmployee(r)) as WithEmployeeExpand<
|
|
42
|
+
ResolveEmployee<S>,
|
|
43
|
+
E,
|
|
44
|
+
S
|
|
45
|
+
>[],
|
|
46
|
+
meta: result.meta,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getById<E extends readonly EmployeeExpandPath[] = never[]>(
|
|
51
|
+
id: string,
|
|
52
|
+
options?: { expand?: E },
|
|
53
|
+
): Promise<SingleResult<WithEmployeeExpand<ResolveEmployee<S>, E, S>>> {
|
|
54
|
+
const query: Record<string, string | undefined> = {};
|
|
55
|
+
if (options?.expand?.length) query['expand'] = options.expand.join(',');
|
|
56
|
+
|
|
57
|
+
const result = await this.transport.get<unknown>(`/api/v1/data/employees/${id}`, query);
|
|
58
|
+
|
|
59
|
+
if ('error' in result) return result as ErrorResult;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
data: transformEmployee(result.data) as WithEmployeeExpand<ResolveEmployee<S>, E, S>,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { NucleusTransport } from '../transport';
|
|
2
|
+
import type { SingleResult, PaginatedResult, ErrorResult } from '../types';
|
|
3
|
+
|
|
4
|
+
export type FileVisibility = 'public' | 'private';
|
|
5
|
+
|
|
6
|
+
export type FileRecord = {
|
|
7
|
+
id: string;
|
|
8
|
+
key: string;
|
|
9
|
+
bucket: string;
|
|
10
|
+
filename: string | null;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
sizeBytes: number;
|
|
13
|
+
allowedUsers: string[];
|
|
14
|
+
metadata: Record<string, unknown>;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type FileUrlResult = {
|
|
20
|
+
url: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type UploadParams = {
|
|
24
|
+
/** App-provided path-style key, e.g. 'users/123/avatar' or 'contracts/2026/q1.pdf'. */
|
|
25
|
+
key: string;
|
|
26
|
+
/** File bytes. */
|
|
27
|
+
file: Buffer;
|
|
28
|
+
mimeType: string;
|
|
29
|
+
visibility: FileVisibility;
|
|
30
|
+
/** Empty or omit → app-wide access. Non-empty restricts to these employee IDs. */
|
|
31
|
+
allowedUsers?: string[];
|
|
32
|
+
/** Arbitrary JSON stored alongside the file record. */
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
/** Optional content-disposition filename. Derived from key's last segment if omitted. */
|
|
35
|
+
filename?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type GetUrlOptions = {
|
|
39
|
+
/**
|
|
40
|
+
* Nucleus user access token (from the client SDK's getSession().accessToken).
|
|
41
|
+
* Required when the file has allowedUsers — verifies the requesting user is permitted.
|
|
42
|
+
* Omit for app-wide files.
|
|
43
|
+
*/
|
|
44
|
+
asUser?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ListParams = {
|
|
48
|
+
/** Filter to files under this path prefix, e.g. 'users/123/'. */
|
|
49
|
+
prefix?: string;
|
|
50
|
+
page?: number;
|
|
51
|
+
pageSize?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export class FilesAccessor {
|
|
55
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Upload a file. Upsert semantics: re-uploading the same key overwrites the
|
|
59
|
+
* existing R2 object and updates the File record — no duplicate, no error.
|
|
60
|
+
*/
|
|
61
|
+
async upload(params: UploadParams): Promise<SingleResult<FileRecord> | ErrorResult> {
|
|
62
|
+
return this.transport.post<FileRecord>('/api/v1/platform/files', {
|
|
63
|
+
key: params.key,
|
|
64
|
+
file: params.file.toString('base64'),
|
|
65
|
+
mimeType: params.mimeType,
|
|
66
|
+
visibility: params.visibility,
|
|
67
|
+
...(params.allowedUsers !== undefined ? { allowedUsers: params.allowedUsers } : {}),
|
|
68
|
+
...(params.metadata !== undefined ? { metadata: params.metadata } : {}),
|
|
69
|
+
...(params.filename !== undefined ? { filename: params.filename } : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a usable URL for a file.
|
|
75
|
+
* - Public files → deterministic CDN URL
|
|
76
|
+
* - Private app-wide files → fresh pre-signed URL (expires in R2_SIGNED_URL_TTL seconds)
|
|
77
|
+
* - Private user-scoped files → supply asUser; 403 if user is not in allowedUsers
|
|
78
|
+
*/
|
|
79
|
+
async getUrl(
|
|
80
|
+
key: string,
|
|
81
|
+
options?: GetUrlOptions,
|
|
82
|
+
): Promise<SingleResult<FileUrlResult> | ErrorResult> {
|
|
83
|
+
return this.transport.post<FileUrlResult>('/api/v1/platform/files/url', {
|
|
84
|
+
key,
|
|
85
|
+
...(options?.asUser ? { asUser: options.asUser } : {}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Soft-delete. Resolution of the key returns 404 thereafter. */
|
|
90
|
+
async delete(key: string): Promise<SingleResult<null> | ErrorResult> {
|
|
91
|
+
return this.transport.del<null>('/api/v1/platform/files', { key });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** List this app's files. Optionally filter by path prefix. */
|
|
95
|
+
async list(params?: ListParams): Promise<PaginatedResult<FileRecord>> {
|
|
96
|
+
const query: Record<string, string | number | undefined> = {};
|
|
97
|
+
if (params?.prefix) query['prefix'] = params.prefix;
|
|
98
|
+
if (params?.page) query['page'] = params.page;
|
|
99
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
100
|
+
|
|
101
|
+
return this.transport.getList<FileRecord>('/api/v1/platform/files', query) as Promise<
|
|
102
|
+
PaginatedResult<FileRecord>
|
|
103
|
+
>;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScopeDeclaration,
|
|
3
|
+
ResolveGenericRate,
|
|
4
|
+
SingleResult,
|
|
5
|
+
PaginatedResult,
|
|
6
|
+
ErrorResult,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import type { NucleusTransport } from '../transport';
|
|
9
|
+
import { transformGenericRate } from '../transform';
|
|
10
|
+
|
|
11
|
+
export type GenericRateListParams = {
|
|
12
|
+
department?: string;
|
|
13
|
+
page?: number;
|
|
14
|
+
pageSize?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class GenericRatesAccessor<S extends ScopeDeclaration> {
|
|
18
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
19
|
+
|
|
20
|
+
async list(params?: GenericRateListParams): Promise<PaginatedResult<ResolveGenericRate<S>>> {
|
|
21
|
+
const query: Record<string, string | number | undefined> = {};
|
|
22
|
+
if (params?.department) query['department'] = params.department;
|
|
23
|
+
if (params?.page) query['page'] = params.page;
|
|
24
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
25
|
+
|
|
26
|
+
const result = await this.transport.getList<unknown>('/api/v1/data/generic-rates', query);
|
|
27
|
+
|
|
28
|
+
if ('error' in result) return result as ErrorResult;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
data: result.data.map((r) => transformGenericRate(r)) as ResolveGenericRate<S>[],
|
|
32
|
+
meta: result.meta,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getById(id: string): Promise<SingleResult<ResolveGenericRate<S>>> {
|
|
37
|
+
const result = await this.transport.get<unknown>(`/api/v1/data/generic-rates/${id}`);
|
|
38
|
+
|
|
39
|
+
if ('error' in result) return result as ErrorResult;
|
|
40
|
+
|
|
41
|
+
return { data: transformGenericRate(result.data) as ResolveGenericRate<S> };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScopeDeclaration,
|
|
3
|
+
ResolvePartner,
|
|
4
|
+
PartnerExpandPath,
|
|
5
|
+
WithPartnerExpand,
|
|
6
|
+
SingleResult,
|
|
7
|
+
PaginatedResult,
|
|
8
|
+
ErrorResult,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import type { NucleusTransport } from '../transport';
|
|
11
|
+
import { transformPartner } from '../transform';
|
|
12
|
+
|
|
13
|
+
export type PartnerListParams = {
|
|
14
|
+
search?: string;
|
|
15
|
+
page?: number;
|
|
16
|
+
pageSize?: number;
|
|
17
|
+
expand?: readonly PartnerExpandPath[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class PartnersAccessor<S extends ScopeDeclaration> {
|
|
21
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
22
|
+
|
|
23
|
+
async list<E extends readonly PartnerExpandPath[] = never[]>(
|
|
24
|
+
params?: PartnerListParams & { expand?: E },
|
|
25
|
+
): Promise<PaginatedResult<WithPartnerExpand<ResolvePartner<S>, E, S>>> {
|
|
26
|
+
const query: Record<string, string | number | undefined> = {};
|
|
27
|
+
if (params?.search) query['search'] = params.search;
|
|
28
|
+
if (params?.page) query['page'] = params.page;
|
|
29
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
30
|
+
if (params?.expand?.length) query['expand'] = params.expand.join(',');
|
|
31
|
+
|
|
32
|
+
const result = await this.transport.getList<unknown>('/api/v1/data/partners', query);
|
|
33
|
+
|
|
34
|
+
if ('error' in result) return result as ErrorResult;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
data: result.data.map((r) => transformPartner(r)) as WithPartnerExpand<
|
|
38
|
+
ResolvePartner<S>,
|
|
39
|
+
E,
|
|
40
|
+
S
|
|
41
|
+
>[],
|
|
42
|
+
meta: result.meta,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getById<E extends readonly PartnerExpandPath[] = never[]>(
|
|
47
|
+
id: string,
|
|
48
|
+
options?: { expand?: E },
|
|
49
|
+
): Promise<SingleResult<WithPartnerExpand<ResolvePartner<S>, E, S>>> {
|
|
50
|
+
const query: Record<string, string | undefined> = {};
|
|
51
|
+
if (options?.expand?.length) query['expand'] = options.expand.join(',');
|
|
52
|
+
|
|
53
|
+
const result = await this.transport.get<unknown>(`/api/v1/data/partners/${id}`, query);
|
|
54
|
+
|
|
55
|
+
if ('error' in result) return result as ErrorResult;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
data: transformPartner(result.data) as WithPartnerExpand<ResolvePartner<S>, E, S>,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ScopeDeclaration,
|
|
3
|
+
ResolveProjectType,
|
|
4
|
+
SingleResult,
|
|
5
|
+
PaginatedResult,
|
|
6
|
+
ErrorResult,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import type { NucleusTransport } from '../transport';
|
|
9
|
+
import { transformProjectType } from '../transform';
|
|
10
|
+
|
|
11
|
+
export type ProjectTypeListParams = {
|
|
12
|
+
page?: number;
|
|
13
|
+
pageSize?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class ProjectTypesAccessor<S extends ScopeDeclaration> {
|
|
17
|
+
constructor(private readonly transport: NucleusTransport) {}
|
|
18
|
+
|
|
19
|
+
async list(params?: ProjectTypeListParams): Promise<PaginatedResult<ResolveProjectType<S>>> {
|
|
20
|
+
const query: Record<string, string | number | undefined> = {};
|
|
21
|
+
if (params?.page) query['page'] = params.page;
|
|
22
|
+
if (params?.pageSize) query['pageSize'] = params.pageSize;
|
|
23
|
+
|
|
24
|
+
const result = await this.transport.getList<unknown>('/api/v1/data/project-types', query);
|
|
25
|
+
|
|
26
|
+
if ('error' in result) return result as ErrorResult;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
data: result.data.map((r) => transformProjectType(r)) as ResolveProjectType<S>[],
|
|
30
|
+
meta: result.meta,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getById(id: string): Promise<SingleResult<ResolveProjectType<S>>> {
|
|
35
|
+
const result = await this.transport.get<unknown>(`/api/v1/data/project-types/${id}`);
|
|
36
|
+
|
|
37
|
+
if ('error' in result) return result as ErrorResult;
|
|
38
|
+
|
|
39
|
+
return { data: transformProjectType(result.data) as ResolveProjectType<S> };
|
|
40
|
+
}
|
|
41
|
+
}
|