gdc-sdk-node-ts 2.0.9 → 2.0.11
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/README.md +30 -32
- package/dist/UserProfileIndexStore.d.ts +47 -0
- package/dist/UserProfileIndexStore.js +66 -0
- package/dist/UserProfileIndexStore.types.d.ts +16 -0
- package/dist/UserProfileIndexStore.types.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/individual-onboarding.js +7 -0
- package/dist/individual-start.js +9 -0
- package/dist/node-crypto-helper.d.ts +19 -0
- package/dist/node-crypto-helper.js +27 -0
- package/dist/node-managed-wallet.d.ts +154 -0
- package/dist/node-managed-wallet.js +604 -0
- package/dist/node-runtime-client.d.ts +25 -0
- package/dist/node-runtime-client.js +25 -0
- package/dist/orchestration/host-onboarding-sdk.d.ts +6 -0
- package/dist/orchestration/host-onboarding-sdk.js +6 -0
- package/dist/organization-controller-recovery.js +6 -0
- package/dist/resource-operations.d.ts +13 -3
- package/dist/resource-operations.js +10 -0
- package/dist/wallet-backed-job-manager.d.ts +19 -0
- package/dist/wallet-backed-job-manager.js +301 -0
- package/dist/wallet-backed-job-manager.types.d.ts +57 -0
- package/dist/wallet-backed-job-manager.types.js +2 -0
- package/package.json +7 -4
|
@@ -113,6 +113,12 @@ export class HttpRuntimeClient {
|
|
|
113
113
|
* - `_transaction` is the new host onboarding step
|
|
114
114
|
* - this runtime does not chain `_activate` after `_transaction`
|
|
115
115
|
* - `_activate` remains available only for the older ICA `_verify` based flow
|
|
116
|
+
*
|
|
117
|
+
* Commercial contract:
|
|
118
|
+
* - the final poll response is expected to mint
|
|
119
|
+
* `meta.claims['org.schema.Offer.identifier']`
|
|
120
|
+
* - callers should then pass that exact value to
|
|
121
|
+
* `confirmLegalOrganizationOrder(...)`
|
|
116
122
|
*/
|
|
117
123
|
async submitLegalOrganizationVerificationTransaction(hostCtx, input, pollOptions) {
|
|
118
124
|
const thid = `organization-verification-transaction-${runtimeUuid()}`;
|
|
@@ -134,6 +140,7 @@ export class HttpRuntimeClient {
|
|
|
134
140
|
* - reuse the same signed evidence/controller binding contract as `_transaction`
|
|
135
141
|
* - do not create a new Offer
|
|
136
142
|
* - expect GW CORE to reissue one controller activation code in the response
|
|
143
|
+
* - do not call `confirmLegalOrganizationOrder(...)` after this flow
|
|
137
144
|
*/
|
|
138
145
|
async submitLegalOrganizationIssue(hostCtx, input, pollOptions) {
|
|
139
146
|
const thid = `organization-issue-${runtimeUuid()}`;
|
|
@@ -182,6 +189,12 @@ export class HttpRuntimeClient {
|
|
|
182
189
|
* headers instead of plaintext `meta`
|
|
183
190
|
* - this mirrored metadata is transport fallback only; the canonical
|
|
184
191
|
* activation contract remains `body.vp_token` plus `body.controller.*`
|
|
192
|
+
*
|
|
193
|
+
* Commercial contract:
|
|
194
|
+
* - legacy `_activate` is still expected to mint
|
|
195
|
+
* `meta.claims['org.schema.Offer.identifier']`
|
|
196
|
+
* - callers should then pass that exact value to
|
|
197
|
+
* `confirmLegalOrganizationOrder(...)`
|
|
185
198
|
*/
|
|
186
199
|
async activateOrganizationInGatewayFromIcaProof(hostCtx, input, pollOptions) {
|
|
187
200
|
const thid = `activate-org-${runtimeUuid()}`;
|
|
@@ -230,6 +243,9 @@ export class HttpRuntimeClient {
|
|
|
230
243
|
}
|
|
231
244
|
/**
|
|
232
245
|
* Confirms a host-side legal organization order after the initial activation.
|
|
246
|
+
*
|
|
247
|
+
* Use this only when the previous flow actually returned one canonical Offer
|
|
248
|
+
* identifier. `_transaction` and legacy `_activate` do; `_issue` does not.
|
|
233
249
|
*/
|
|
234
250
|
async confirmLegalOrganizationOrder(hostCtx, input, pollOptions) {
|
|
235
251
|
return confirmLegalOrganizationOrderWithDeps({
|
|
@@ -440,6 +456,15 @@ export class HttpRuntimeClient {
|
|
|
440
456
|
}
|
|
441
457
|
/**
|
|
442
458
|
* Starts the onboarding flow for an individual-oriented tenant or index.
|
|
459
|
+
*
|
|
460
|
+
* Commercial contract:
|
|
461
|
+
* - this SDK method targets the family/individual commercial bootstrap flow
|
|
462
|
+
* - the registration poll response is expected to return one Offer id
|
|
463
|
+
* - callers should then confirm it through
|
|
464
|
+
* `confirmIndividualOrganizationOrder(...)`
|
|
465
|
+
*
|
|
466
|
+
* This is distinct from embedded legacy individual registration helpers in
|
|
467
|
+
* GW CORE that may persist an individual record without minting an Offer.
|
|
443
468
|
*/
|
|
444
469
|
async startIndividualOrganization(input) {
|
|
445
470
|
const routeCtx = this.routeCtxFromInput(input);
|
|
@@ -8,6 +8,12 @@ export declare class HostOnboardingSdk implements HostingControllerFacade {
|
|
|
8
8
|
/**
|
|
9
9
|
* Submits the legal organization activation proof and required declared
|
|
10
10
|
* service capabilities to GW CORE.
|
|
11
|
+
*
|
|
12
|
+
* Commercial contract:
|
|
13
|
+
* - this legacy `_activate` facade must return one canonical Offer in
|
|
14
|
+
* `meta.claims['org.schema.Offer.identifier']`
|
|
15
|
+
* - callers are expected to pass that Offer to
|
|
16
|
+
* `confirmLegalOrganizationOrder(...)`
|
|
11
17
|
*/
|
|
12
18
|
activateOrganizationInGatewayFromIcaProof(hostCtx: HostRouteContext, input: NodeOrganizationActivationInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
|
|
13
19
|
confirmLegalOrganizationOrder(hostCtx: HostRouteContext, input: LegalOrganizationOrderInput, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
|
|
@@ -11,6 +11,12 @@ export class HostOnboardingSdk {
|
|
|
11
11
|
/**
|
|
12
12
|
* Submits the legal organization activation proof and required declared
|
|
13
13
|
* service capabilities to GW CORE.
|
|
14
|
+
*
|
|
15
|
+
* Commercial contract:
|
|
16
|
+
* - this legacy `_activate` facade must return one canonical Offer in
|
|
17
|
+
* `meta.claims['org.schema.Offer.identifier']`
|
|
18
|
+
* - callers are expected to pass that Offer to
|
|
19
|
+
* `confirmLegalOrganizationOrder(...)`
|
|
14
20
|
*/
|
|
15
21
|
activateOrganizationInGatewayFromIcaProof(hostCtx, input, pollOptions) {
|
|
16
22
|
assertFacadeCapability(this.capabilities, ActorCapabilities.HostingActivateOrganization, ActorKinds.HostOnboarding, 'activateOrganizationInGatewayFromIcaProof');
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
2
|
import { activateEmployeeDeviceWithActivationCodeWithDeps } from './device-activation.js';
|
|
3
3
|
export async function recoverOrganizationControllerWithIssueWithDeps(deps) {
|
|
4
|
+
/**
|
|
5
|
+
* Existing-tenant controller recovery contract:
|
|
6
|
+
* - `Organization/_issue` is expected to reissue controller activation
|
|
7
|
+
* material only
|
|
8
|
+
* - this flow must not depend on a new commercial Offer or Order step
|
|
9
|
+
*/
|
|
4
10
|
const issue = await deps.submitLegalOrganizationIssue(deps.hostCtx, deps.input.issueInput, deps.input.issuePollOptions);
|
|
5
11
|
const activationCode = readActivationCodeFromIssueResult(issue);
|
|
6
12
|
if (!activationCode) {
|
|
@@ -37,13 +37,23 @@ export type OrganizationEmployeeCreationInput = {
|
|
|
37
37
|
*/
|
|
38
38
|
export type OrganizationEmployeeLifecycleInput = {
|
|
39
39
|
/**
|
|
40
|
-
* Canonical employee/person claims
|
|
40
|
+
* Canonical employee/person claims carried as the exportable employee identity.
|
|
41
|
+
*
|
|
42
|
+
* These claims should still include the business/external identifier
|
|
43
|
+
* (`org.schema.Person.identifier`) when available, but runtime lifecycle
|
|
44
|
+
* operations must prefer `resourceId` as the concrete GW profile locator.
|
|
45
|
+
* Treat `resource.id` as the current technical record anchor and
|
|
46
|
+
* `identifier` as the interoperable/exported identity value.
|
|
41
47
|
*/
|
|
42
48
|
employeeClaims: Record<string, unknown>;
|
|
43
49
|
/**
|
|
44
|
-
*
|
|
50
|
+
* Preferred current GW employee profile id returned by create/search.
|
|
51
|
+
*
|
|
52
|
+
* Pass this for disable/purge whenever the caller already knows the active
|
|
53
|
+
* profile row. The SDK forwards it as `Bundle.entry.resource.id`, which GW
|
|
54
|
+
* now treats as the primary operational locator for lifecycle actions.
|
|
45
55
|
*/
|
|
46
|
-
resourceId
|
|
56
|
+
resourceId: string;
|
|
47
57
|
dataType?: string;
|
|
48
58
|
};
|
|
49
59
|
export type OrganizationEmployeeSearchInput = {
|
|
@@ -19,6 +19,7 @@ export async function disableOrganizationEmployeeWithDeps(routeCtx, input, optio
|
|
|
19
19
|
// TODO(gw-core-lifecycle-target-patch-employee-disable): switch this
|
|
20
20
|
// legacy DELETE-in-_batch flow to `_batch + PATCH` when GW CORE deploys it.
|
|
21
21
|
void GwCoreLifecycleTodo.EmployeeDisablePatchMigration;
|
|
22
|
+
assertEmployeeLifecycleResourceId(input.resourceId, 'disableEmployee');
|
|
22
23
|
const payload = buildEmployeeLifecyclePayload({
|
|
23
24
|
routeCtx,
|
|
24
25
|
requestType: input.dataType || GwCoreLifecycleRequestType.EmployeeDisable,
|
|
@@ -30,6 +31,7 @@ export async function disableOrganizationEmployeeWithDeps(routeCtx, input, optio
|
|
|
30
31
|
return deps.submitAndPoll(deps.employeeBatchPath(routeCtx), deps.employeePollPath(routeCtx), payload, options);
|
|
31
32
|
}
|
|
32
33
|
export async function purgeOrganizationEmployeeWithDeps(routeCtx, input, options, deps) {
|
|
34
|
+
assertEmployeeLifecycleResourceId(input.resourceId, 'purgeEmployee');
|
|
33
35
|
const payload = buildEmployeeLifecyclePayload({
|
|
34
36
|
routeCtx,
|
|
35
37
|
requestType: input.dataType || GwCoreLifecycleRequestType.EmployeePurge,
|
|
@@ -40,6 +42,12 @@ export async function purgeOrganizationEmployeeWithDeps(routeCtx, input, options
|
|
|
40
42
|
});
|
|
41
43
|
return deps.submitAndPoll(deps.employeePurgePath(routeCtx), deps.employeePurgePollPath(routeCtx), payload, options);
|
|
42
44
|
}
|
|
45
|
+
function assertEmployeeLifecycleResourceId(resourceId, operation) {
|
|
46
|
+
const normalized = String(resourceId || '').trim();
|
|
47
|
+
if (!normalized) {
|
|
48
|
+
throw new Error(`${operation}: resourceId is required and must be the current GW technical employee id (resource.id).`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
43
51
|
export async function searchOrganizationEmployeesWithDeps(routeCtx, input, deps) {
|
|
44
52
|
return deps.submitAndPoll(deps.employeeSearchPath(routeCtx), deps.employeeSearchPollPath(routeCtx), {
|
|
45
53
|
thid: input.requestThid || `employee-search-${createRuntimeUuid()}`,
|
|
@@ -422,6 +430,8 @@ function buildEmployeeLifecyclePayload(input) {
|
|
|
422
430
|
type: input.requestType,
|
|
423
431
|
method: input.requestMethod,
|
|
424
432
|
claims,
|
|
433
|
+
// Keep the transport-local GW profile anchor separate from the
|
|
434
|
+
// exportable employee identifier carried in claims.
|
|
425
435
|
resourceId: input.resourceId,
|
|
426
436
|
})],
|
|
427
437
|
},
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IJobManager } from 'gdc-sdk-core-ts';
|
|
2
|
+
import type { WalletBackedJobManagerOptions } from './wallet-backed-job-manager.types.js';
|
|
3
|
+
export type { WalletBackedJobManagerOptions, WalletBackedJobPollResult, WalletBackedJobSubmitResult, WalletBackedJobTransport, WalletExecutionContextLike, } from './wallet-backed-job-manager.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates one backend/session job manager that stores protected job payloads in
|
|
6
|
+
* one local vault while reusing one shared wallet for protect/unprotect and
|
|
7
|
+
* transport packing.
|
|
8
|
+
*
|
|
9
|
+
* Intended for:
|
|
10
|
+
* - portal/BFF runtimes
|
|
11
|
+
* - short-lived service sessions
|
|
12
|
+
* - send-first or cache-first orchestration over one local in-memory vault
|
|
13
|
+
*/
|
|
14
|
+
export declare function createWalletBackedJobManager(options: WalletBackedJobManagerOptions): IJobManager;
|
|
15
|
+
/**
|
|
16
|
+
* Convenience helper for the default transient backend/session mode where one
|
|
17
|
+
* in-memory vault is enough and durable cloud sync is optional.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createWalletBackedJobManagerInMemory(options: WalletBackedJobManagerOptions): IJobManager;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { JobStatus } from 'gdc-common-utils-ts/models/confidential-job';
|
|
3
|
+
import { VaultMemRepository } from 'gdc-common-utils-ts/storage';
|
|
4
|
+
const JOB_COLLECTION = 'wallet-backed-jobs';
|
|
5
|
+
const RESPONSE_COLLECTION = 'wallet-backed-job-responses';
|
|
6
|
+
function clone(value) {
|
|
7
|
+
return JSON.parse(JSON.stringify(value));
|
|
8
|
+
}
|
|
9
|
+
function createRuntimeUuid() {
|
|
10
|
+
const cryptoLike = globalThis;
|
|
11
|
+
if (typeof cryptoLike.crypto?.randomUUID === 'function') {
|
|
12
|
+
return cryptoLike.crypto.randomUUID();
|
|
13
|
+
}
|
|
14
|
+
return `job-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
15
|
+
}
|
|
16
|
+
function inferFormType(content) {
|
|
17
|
+
const first = Array.isArray(content?.body?.data) ? content.body.data[0] : undefined;
|
|
18
|
+
return String(first?.type || content?.type || '').trim();
|
|
19
|
+
}
|
|
20
|
+
function updateJobShape(job, patch) {
|
|
21
|
+
return {
|
|
22
|
+
...job,
|
|
23
|
+
...patch,
|
|
24
|
+
sequence: (job.sequence || 0) + 1,
|
|
25
|
+
previousSequence: job.sequence,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates one backend/session job manager that stores protected job payloads in
|
|
30
|
+
* one local vault while reusing one shared wallet for protect/unprotect and
|
|
31
|
+
* transport packing.
|
|
32
|
+
*
|
|
33
|
+
* Intended for:
|
|
34
|
+
* - portal/BFF runtimes
|
|
35
|
+
* - short-lived service sessions
|
|
36
|
+
* - send-first or cache-first orchestration over one local in-memory vault
|
|
37
|
+
*/
|
|
38
|
+
export function createWalletBackedJobManager(options) {
|
|
39
|
+
const wallet = options.wallet;
|
|
40
|
+
const vaultRepository = options.vaultRepository ?? new VaultMemRepository();
|
|
41
|
+
const context = options.walletContext;
|
|
42
|
+
const localEntityId = context.profile?.profileId ?? context.runtime?.runtimeId ?? options.descriptor.profileId;
|
|
43
|
+
let isInitialized = false;
|
|
44
|
+
let listener;
|
|
45
|
+
let lastAccessToken = '';
|
|
46
|
+
async function notify() {
|
|
47
|
+
listener?.();
|
|
48
|
+
}
|
|
49
|
+
async function protectJob(job) {
|
|
50
|
+
if (wallet.protectManagedConfidentialData) {
|
|
51
|
+
return wallet.protectManagedConfidentialData(job, context);
|
|
52
|
+
}
|
|
53
|
+
return wallet.protectConfidentialData(job, localEntityId);
|
|
54
|
+
}
|
|
55
|
+
async function unprotectJob(job) {
|
|
56
|
+
if (!job?.jwe)
|
|
57
|
+
return clone(job);
|
|
58
|
+
if (wallet.unprotectManagedConfidentialData) {
|
|
59
|
+
return wallet.unprotectManagedConfidentialData(job, context);
|
|
60
|
+
}
|
|
61
|
+
return wallet.unprotectConfidentialData(job, localEntityId);
|
|
62
|
+
}
|
|
63
|
+
async function putProtectedJob(job) {
|
|
64
|
+
const protectedJob = await protectJob(job);
|
|
65
|
+
await vaultRepository.put(JOB_COLLECTION, protectedJob);
|
|
66
|
+
await notify();
|
|
67
|
+
return clone(job);
|
|
68
|
+
}
|
|
69
|
+
async function readProtectedJob(jobId) {
|
|
70
|
+
return vaultRepository.get(JOB_COLLECTION, jobId);
|
|
71
|
+
}
|
|
72
|
+
async function readUnprotectedJob(jobId) {
|
|
73
|
+
const stored = await readProtectedJob(jobId);
|
|
74
|
+
if (!stored)
|
|
75
|
+
return undefined;
|
|
76
|
+
return unprotectJob(stored);
|
|
77
|
+
}
|
|
78
|
+
async function listUnprotectedJobs(query) {
|
|
79
|
+
const stored = await vaultRepository.query(JOB_COLLECTION, query);
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const job of stored) {
|
|
82
|
+
out.push(await unprotectJob(job));
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
async function buildEnvelope(job) {
|
|
87
|
+
if (!job.content) {
|
|
88
|
+
throw new Error(`WalletBackedJobManager cannot build an envelope for job '${job.id}' without plaintext content.`);
|
|
89
|
+
}
|
|
90
|
+
if (options.recipientDidOrJwk) {
|
|
91
|
+
if (wallet.packForRecipientWithContext) {
|
|
92
|
+
return wallet.packForRecipientWithContext(job.content, options.recipientDidOrJwk, { context });
|
|
93
|
+
}
|
|
94
|
+
if (typeof options.recipientDidOrJwk === 'string' && wallet.packForRecipient) {
|
|
95
|
+
return wallet.packForRecipient(job.content, options.recipientDidOrJwk);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return JSON.stringify(job.content);
|
|
99
|
+
}
|
|
100
|
+
async function putResponse(thid, responseBody) {
|
|
101
|
+
const responseId = createRuntimeUuid();
|
|
102
|
+
const response = {
|
|
103
|
+
id: responseId,
|
|
104
|
+
thid,
|
|
105
|
+
content: clone(responseBody),
|
|
106
|
+
createdAtTimestamp: Date.now(),
|
|
107
|
+
};
|
|
108
|
+
await vaultRepository.put(RESPONSE_COLLECTION, response);
|
|
109
|
+
return responseId;
|
|
110
|
+
}
|
|
111
|
+
async function markCompleted(job, responseBody) {
|
|
112
|
+
const responseMessageId = await putResponse(String(job.thid || job.id), responseBody);
|
|
113
|
+
const updated = updateJobShape(job, {
|
|
114
|
+
status: JobStatus.COMPLETED,
|
|
115
|
+
responseMessageId,
|
|
116
|
+
errorMessage: undefined,
|
|
117
|
+
});
|
|
118
|
+
await putProtectedJob(updated);
|
|
119
|
+
return updated;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
descriptor: { ...options.descriptor },
|
|
123
|
+
get isInitialized() {
|
|
124
|
+
return isInitialized;
|
|
125
|
+
},
|
|
126
|
+
async initialize() {
|
|
127
|
+
isInitialized = true;
|
|
128
|
+
},
|
|
129
|
+
shutdown() {
|
|
130
|
+
isInitialized = false;
|
|
131
|
+
listener = undefined;
|
|
132
|
+
},
|
|
133
|
+
setListener(nextListener) {
|
|
134
|
+
listener = nextListener;
|
|
135
|
+
},
|
|
136
|
+
async createJob(content, selector) {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const draft = {
|
|
139
|
+
id: createRuntimeUuid(),
|
|
140
|
+
thid: String(content?.thid || '').trim() || undefined,
|
|
141
|
+
status: JobStatus.DRAFT,
|
|
142
|
+
sequence: 1,
|
|
143
|
+
createdAtTimestamp: now,
|
|
144
|
+
content: clone(content),
|
|
145
|
+
...selector,
|
|
146
|
+
};
|
|
147
|
+
await putProtectedJob(draft);
|
|
148
|
+
return clone(draft);
|
|
149
|
+
},
|
|
150
|
+
async findDraftJobByFormType(formType) {
|
|
151
|
+
const normalizedFormType = String(formType || '').trim();
|
|
152
|
+
const drafts = await listUnprotectedJobs({
|
|
153
|
+
where: [{ attribute: 'status', equals: JobStatus.DRAFT }],
|
|
154
|
+
});
|
|
155
|
+
return drafts.find((job) => inferFormType(job.content) === normalizedFormType) ?? null;
|
|
156
|
+
},
|
|
157
|
+
async createOrUpdateDraftJob(content, selector) {
|
|
158
|
+
const formType = inferFormType(content);
|
|
159
|
+
const existing = formType ? await this.findDraftJobByFormType(formType) : null;
|
|
160
|
+
if (!existing) {
|
|
161
|
+
return this.createJob(content, selector);
|
|
162
|
+
}
|
|
163
|
+
const updated = updateJobShape(existing, {
|
|
164
|
+
content: clone(content),
|
|
165
|
+
thid: String(content?.thid || '').trim() || existing.thid,
|
|
166
|
+
...selector,
|
|
167
|
+
});
|
|
168
|
+
await putProtectedJob(updated);
|
|
169
|
+
return clone(updated);
|
|
170
|
+
},
|
|
171
|
+
async sync(accessToken) {
|
|
172
|
+
lastAccessToken = accessToken;
|
|
173
|
+
const pending = await listUnprotectedJobs({
|
|
174
|
+
where: [{
|
|
175
|
+
attribute: 'status',
|
|
176
|
+
in: [JobStatus.DRAFT, JobStatus.ERROR_RETRYABLE, JobStatus.SENT],
|
|
177
|
+
}],
|
|
178
|
+
});
|
|
179
|
+
for (const job of pending) {
|
|
180
|
+
if (job.status === JobStatus.SENT && options.transport?.poll) {
|
|
181
|
+
const poll = await options.transport.poll({
|
|
182
|
+
job,
|
|
183
|
+
accessToken,
|
|
184
|
+
context,
|
|
185
|
+
});
|
|
186
|
+
if (poll.pending)
|
|
187
|
+
continue;
|
|
188
|
+
if (poll.completed && poll.responseBody !== undefined) {
|
|
189
|
+
await markCompleted(job, poll.responseBody);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const failed = updateJobShape(job, {
|
|
193
|
+
status: poll.retryable ? JobStatus.ERROR_RETRYABLE : JobStatus.FAILED,
|
|
194
|
+
errorMessage: poll.errorMessage,
|
|
195
|
+
});
|
|
196
|
+
await putProtectedJob(failed);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
await this.submitJob(job);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
async queryJobs(query) {
|
|
203
|
+
return listUnprotectedJobs(query);
|
|
204
|
+
},
|
|
205
|
+
async submitJob(job) {
|
|
206
|
+
if (!options.transport) {
|
|
207
|
+
const sent = updateJobShape(job, {
|
|
208
|
+
status: JobStatus.SENT,
|
|
209
|
+
});
|
|
210
|
+
await putProtectedJob(sent);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!lastAccessToken) {
|
|
214
|
+
throw new Error('WalletBackedJobManager.submitJob requires sync(accessToken) before transport submission.');
|
|
215
|
+
}
|
|
216
|
+
const submitting = updateJobShape(job, {
|
|
217
|
+
status: JobStatus.SUBMITTING,
|
|
218
|
+
});
|
|
219
|
+
await putProtectedJob(submitting);
|
|
220
|
+
const envelope = await buildEnvelope(submitting);
|
|
221
|
+
const submitResult = await options.transport.submit({
|
|
222
|
+
job: submitting,
|
|
223
|
+
envelope,
|
|
224
|
+
accessToken: lastAccessToken,
|
|
225
|
+
context,
|
|
226
|
+
});
|
|
227
|
+
if (submitResult.completed && submitResult.responseBody !== undefined) {
|
|
228
|
+
await markCompleted(submitting, submitResult.responseBody);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const nextStatus = submitResult.retryable
|
|
232
|
+
? JobStatus.ERROR_RETRYABLE
|
|
233
|
+
: submitResult.accepted === false
|
|
234
|
+
? JobStatus.FAILED
|
|
235
|
+
: JobStatus.SENT;
|
|
236
|
+
const updated = updateJobShape(submitting, {
|
|
237
|
+
status: nextStatus,
|
|
238
|
+
locationUrl: submitResult.locationUrl,
|
|
239
|
+
errorMessage: submitResult.errorMessage,
|
|
240
|
+
});
|
|
241
|
+
await putProtectedJob(updated);
|
|
242
|
+
if (updated.status === JobStatus.SENT && options.transport.poll) {
|
|
243
|
+
const pollResult = await options.transport.poll({
|
|
244
|
+
job: updated,
|
|
245
|
+
accessToken: lastAccessToken,
|
|
246
|
+
context,
|
|
247
|
+
});
|
|
248
|
+
if (pollResult.completed && pollResult.responseBody !== undefined) {
|
|
249
|
+
await markCompleted(updated, pollResult.responseBody);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!pollResult.pending) {
|
|
253
|
+
const failedAfterPoll = updateJobShape(updated, {
|
|
254
|
+
status: pollResult.retryable ? JobStatus.ERROR_RETRYABLE : JobStatus.FAILED,
|
|
255
|
+
errorMessage: pollResult.errorMessage,
|
|
256
|
+
});
|
|
257
|
+
await putProtectedJob(failedAfterPoll);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
async sealJobWithToken(job, accessToken) {
|
|
262
|
+
if (!job.content)
|
|
263
|
+
return job;
|
|
264
|
+
const sealed = updateJobShape(job, {
|
|
265
|
+
content: {
|
|
266
|
+
...clone(job.content),
|
|
267
|
+
meta: {
|
|
268
|
+
...(job.content.meta || {}),
|
|
269
|
+
bearer: {
|
|
270
|
+
compact: accessToken,
|
|
271
|
+
jwt: {},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
await putProtectedJob(sealed);
|
|
277
|
+
return sealed;
|
|
278
|
+
},
|
|
279
|
+
async getJobResponseByThid(thid) {
|
|
280
|
+
const responses = await vaultRepository.query(RESPONSE_COLLECTION, {
|
|
281
|
+
where: [{ attribute: 'thid', equals: thid }],
|
|
282
|
+
orderBy: { attribute: 'createdAtTimestamp', direction: 'desc' },
|
|
283
|
+
limit: 1,
|
|
284
|
+
});
|
|
285
|
+
return responses[0]?.content ?? null;
|
|
286
|
+
},
|
|
287
|
+
generateId() {
|
|
288
|
+
return createRuntimeUuid();
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Convenience helper for the default transient backend/session mode where one
|
|
294
|
+
* in-memory vault is enough and durable cloud sync is optional.
|
|
295
|
+
*/
|
|
296
|
+
export function createWalletBackedJobManagerInMemory(options) {
|
|
297
|
+
return createWalletBackedJobManager({
|
|
298
|
+
...options,
|
|
299
|
+
vaultRepository: options.vaultRepository ?? new VaultMemRepository(),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { JobRequest } from 'gdc-common-utils-ts/models/confidential-job';
|
|
2
|
+
import type { JWK } from 'gdc-common-utils-ts/models/jwk';
|
|
3
|
+
import type { IVaultRepository } from 'gdc-common-utils-ts/storage';
|
|
4
|
+
import type { ActorProfileDescriptor, IWallet } from 'gdc-sdk-core-ts';
|
|
5
|
+
export type WalletExecutionContextLike = Readonly<{
|
|
6
|
+
profile?: Readonly<{
|
|
7
|
+
profileId: string;
|
|
8
|
+
actorType?: string;
|
|
9
|
+
actorId?: string;
|
|
10
|
+
}>;
|
|
11
|
+
runtime?: Readonly<{
|
|
12
|
+
runtimeId: string;
|
|
13
|
+
runtimeType?: string;
|
|
14
|
+
}>;
|
|
15
|
+
route?: Readonly<{
|
|
16
|
+
tenantId?: string;
|
|
17
|
+
jurisdiction?: string;
|
|
18
|
+
sector?: string;
|
|
19
|
+
}>;
|
|
20
|
+
walletId?: string;
|
|
21
|
+
}>;
|
|
22
|
+
export type WalletBackedJobSubmitResult = Readonly<{
|
|
23
|
+
accepted?: boolean;
|
|
24
|
+
completed?: boolean;
|
|
25
|
+
locationUrl?: string;
|
|
26
|
+
responseBody?: unknown;
|
|
27
|
+
errorMessage?: string;
|
|
28
|
+
retryable?: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
export type WalletBackedJobPollResult = Readonly<{
|
|
31
|
+
pending?: boolean;
|
|
32
|
+
completed?: boolean;
|
|
33
|
+
responseBody?: unknown;
|
|
34
|
+
errorMessage?: string;
|
|
35
|
+
retryable?: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
export type WalletBackedJobTransport = Readonly<{
|
|
38
|
+
submit: (input: Readonly<{
|
|
39
|
+
job: JobRequest;
|
|
40
|
+
envelope: string;
|
|
41
|
+
accessToken: string;
|
|
42
|
+
context: WalletExecutionContextLike;
|
|
43
|
+
}>) => Promise<WalletBackedJobSubmitResult>;
|
|
44
|
+
poll?: (input: Readonly<{
|
|
45
|
+
job: JobRequest;
|
|
46
|
+
accessToken: string;
|
|
47
|
+
context: WalletExecutionContextLike;
|
|
48
|
+
}>) => Promise<WalletBackedJobPollResult>;
|
|
49
|
+
}>;
|
|
50
|
+
export type WalletBackedJobManagerOptions = Readonly<{
|
|
51
|
+
descriptor: ActorProfileDescriptor;
|
|
52
|
+
wallet: IWallet;
|
|
53
|
+
walletContext: WalletExecutionContextLike;
|
|
54
|
+
vaultRepository?: IVaultRepository;
|
|
55
|
+
transport?: WalletBackedJobTransport;
|
|
56
|
+
recipientDidOrJwk?: string | JWK;
|
|
57
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-sdk-node-ts",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"description": "Next-generation Node runtime package for the GDC SDK family",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Antifraud Services Inc.",
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
"local:close": "PORTS=3000 bash ./scripts/local-close.sh",
|
|
17
17
|
"docker:close": "PORTS=8000 bash ./scripts/local-close.sh",
|
|
18
18
|
"test": "npm run build && node --test tests/*.test.mjs",
|
|
19
|
-
"test:e2e:
|
|
19
|
+
"test:e2e:live-controller-lifecycle": "bash ./scripts/run-live-controller-lifecycle.sh",
|
|
20
|
+
"test:e2e:live-controller-lifecycle:direct": "npm run build && RUN_LIVE_101_ORGANIZATION_CONTROLLER_LIFECYCLE_E2E=1 node --test tests/101-organization-controller-lifecycle.live.test.mjs",
|
|
21
|
+
"test:e2e:live-full-cycle": "bash ./scripts/run-live-101-full-cycle-clean.sh",
|
|
22
|
+
"test:e2e:live-full-cycle:direct": "npm run build && RUN_LIVE_101_FULL_CYCLE_E2E=1 node --test tests/101-live-full-cycle-bff-runtime.e2e.test.mjs",
|
|
20
23
|
"test:e2e:live-gw": "npm run build && RUN_LIVE_GW_E2E=1 node --test tests/live-gw-node-runtime.e2e.test.mjs",
|
|
21
24
|
"test:e2e:live-gw:professional": "npm run build && RUN_LIVE_GW_E2E=1 LIVE_GW_E2E_SUITE=professional node --test tests/live-gw-node-runtime.e2e.test.mjs",
|
|
22
25
|
"test:e2e:live-gw:individual": "npm run build && RUN_LIVE_GW_E2E=1 LIVE_GW_E2E_SUITE=individual node --test tests/live-gw-node-runtime.e2e.test.mjs",
|
|
@@ -32,8 +35,8 @@
|
|
|
32
35
|
"test:e2e:live-gw:clean": "bash ./scripts/run-live-gw-clean.sh"
|
|
33
36
|
},
|
|
34
37
|
"dependencies": {
|
|
35
|
-
"gdc-common-utils-ts": "^2.0.
|
|
36
|
-
"gdc-sdk-core-ts": "^2.0.
|
|
38
|
+
"gdc-common-utils-ts": "^2.0.17",
|
|
39
|
+
"gdc-sdk-core-ts": "^2.0.10"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@types/node": "^20.14.10",
|