@registrystack/client 0.39.0 → 0.41.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/README.md +31 -7
- package/breg/client.d.ts +100 -10
- package/breg/client.js +41 -34
- package/breg/index.d.ts +56 -2
- package/casework/client.d.ts +90 -30
- package/casework/client.js +22 -17
- package/casework/index.d.ts +7 -0
- package/discovery/client.d.ts +7 -7
- package/discovery/client.js +1 -1
- package/evidence/client.d.ts +1 -1
- package/evidence/index.d.ts +3 -6
- package/index.d.ts +2 -1
- package/index.js +2 -1
- package/messaging/client.d.ts +26 -4
- package/messaging/client.js +33 -21
- package/messaging/index.d.ts +2 -10
- package/native.js +2 -2
- package/package.json +10 -8
- package/scheduling/client.d.ts +229 -0
- package/scheduling/client.js +142 -0
- package/scheduling/index.d.ts +28 -0
- package/scheduling/index.js +3 -0
package/messaging/client.js
CHANGED
|
@@ -12,12 +12,19 @@ class MessagingClientError extends Error {
|
|
|
12
12
|
super(envelope.message);
|
|
13
13
|
this.name = 'MessagingClientError';
|
|
14
14
|
this.kind = envelope.kind;
|
|
15
|
+
this.outcomeUnknown = envelope.outcomeUnknown === true;
|
|
15
16
|
for (const field of ['code', 'title', 'detail', 'status', 'traceId', 'retryAfterSeconds', 'transportKind', 'protocolFailure']) {
|
|
16
17
|
if (envelope[field] !== undefined && envelope[field] !== null) this[field] = envelope[field];
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
const FALLBACK_MESSAGES = {
|
|
23
|
+
configuration: 'Messaging client configuration is invalid',
|
|
24
|
+
'invalid-request': 'Messaging client arguments are invalid',
|
|
25
|
+
protocol: 'Registry Messaging client failed',
|
|
26
|
+
};
|
|
27
|
+
|
|
21
28
|
function normalized(error, fallbackKind) {
|
|
22
29
|
if (error instanceof MessagingClientError) return error;
|
|
23
30
|
if (error instanceof Error && typeof error.message === 'string') {
|
|
@@ -31,49 +38,51 @@ function normalized(error, fallbackKind) {
|
|
|
31
38
|
if (fallbackKind) {
|
|
32
39
|
return new MessagingClientError({
|
|
33
40
|
kind: fallbackKind,
|
|
34
|
-
message: fallbackKind
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
message: FALLBACK_MESSAGES[fallbackKind],
|
|
42
|
+
// A rejection after the native client took the call may follow an
|
|
43
|
+
// exchange, so it leaves the outcome unknown as a native protocol
|
|
44
|
+
// failure does.
|
|
45
|
+
outcomeUnknown: fallbackKind === 'protocol',
|
|
37
46
|
});
|
|
38
47
|
}
|
|
39
48
|
return error;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
function cloneJson(value, budget, depth) {
|
|
43
|
-
if (depth > MAX_JSON_DEPTH || ++budget.nodes > MAX_JSON_NODES) throw normalized({},
|
|
51
|
+
function cloneJson(value, budget, depth, kind) {
|
|
52
|
+
if (depth > MAX_JSON_DEPTH || ++budget.nodes > MAX_JSON_NODES) throw normalized({}, kind);
|
|
44
53
|
if (value === null || typeof value === 'boolean') return value;
|
|
45
54
|
if (typeof value === 'string') {
|
|
46
55
|
budget.bytes += Buffer.byteLength(value, 'utf8');
|
|
47
|
-
if (budget.bytes > MAX_JSON_STRING_BYTES) throw normalized({},
|
|
56
|
+
if (budget.bytes > MAX_JSON_STRING_BYTES) throw normalized({}, kind);
|
|
48
57
|
return value;
|
|
49
58
|
}
|
|
50
59
|
if (typeof value === 'number') {
|
|
51
60
|
if (!Number.isFinite(value) || (Number.isInteger(value) && !Number.isSafeInteger(value))) {
|
|
52
|
-
throw normalized({},
|
|
61
|
+
throw normalized({}, kind);
|
|
53
62
|
}
|
|
54
63
|
return value;
|
|
55
64
|
}
|
|
56
65
|
if (value === undefined || typeof value !== 'object' || isProxy(value)) {
|
|
57
|
-
throw normalized({},
|
|
66
|
+
throw normalized({}, kind);
|
|
58
67
|
}
|
|
59
68
|
const array = Array.isArray(value);
|
|
60
69
|
const prototype = Object.getPrototypeOf(value);
|
|
61
70
|
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) {
|
|
62
|
-
throw normalized({},
|
|
71
|
+
throw normalized({}, kind);
|
|
63
72
|
}
|
|
64
|
-
if (budget.active.has(value)) throw normalized({},
|
|
73
|
+
if (budget.active.has(value)) throw normalized({}, kind);
|
|
65
74
|
budget.active.add(value);
|
|
66
75
|
try {
|
|
67
|
-
if (array) return value.map((member) => cloneJson(member, budget, depth + 1));
|
|
76
|
+
if (array) return value.map((member) => cloneJson(member, budget, depth + 1, kind));
|
|
68
77
|
const result = Object.create(null);
|
|
69
78
|
for (const key of Reflect.ownKeys(value)) {
|
|
70
|
-
if (typeof key !== 'string') throw normalized({},
|
|
79
|
+
if (typeof key !== 'string') throw normalized({}, kind);
|
|
71
80
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
72
|
-
if (!descriptor || !Object.hasOwn(descriptor, 'value')) throw normalized({},
|
|
81
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value')) throw normalized({}, kind);
|
|
73
82
|
if (descriptor.enumerable) {
|
|
74
83
|
budget.bytes += Buffer.byteLength(key, 'utf8');
|
|
75
|
-
if (budget.bytes > MAX_JSON_STRING_BYTES) throw normalized({},
|
|
76
|
-
result[key] = cloneJson(descriptor.value, budget, depth + 1);
|
|
84
|
+
if (budget.bytes > MAX_JSON_STRING_BYTES) throw normalized({}, kind);
|
|
85
|
+
result[key] = cloneJson(descriptor.value, budget, depth + 1, kind);
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
return result;
|
|
@@ -82,8 +91,10 @@ function cloneJson(value, budget, depth) {
|
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
// A refusal is reported as `kind`: `configuration` for the constructor's
|
|
95
|
+
// settings, `invalid-request` for a method's arguments.
|
|
96
|
+
function sanitize(value, kind) {
|
|
97
|
+
return cloneJson(value, { nodes: 0, bytes: 0, active: new WeakSet() }, 0, kind);
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
const NativeMessagingClient = native.MessagingClient;
|
|
@@ -91,7 +102,7 @@ const NativeMessagingClient = native.MessagingClient;
|
|
|
91
102
|
class MessagingClient {
|
|
92
103
|
constructor(config) {
|
|
93
104
|
try {
|
|
94
|
-
this.native = new NativeMessagingClient(sanitize(config));
|
|
105
|
+
this.native = new NativeMessagingClient(sanitize(config, 'configuration'));
|
|
95
106
|
} catch (error) {
|
|
96
107
|
throw normalized(error, 'configuration');
|
|
97
108
|
}
|
|
@@ -102,6 +113,7 @@ for (const [method, jsonIndexes] of [
|
|
|
102
113
|
['health', []],
|
|
103
114
|
['ready', []],
|
|
104
115
|
['submit', [2]],
|
|
116
|
+
['messageReceipt', [2]],
|
|
105
117
|
['message', []],
|
|
106
118
|
['cancel', []],
|
|
107
119
|
['preview', [3]],
|
|
@@ -109,11 +121,11 @@ for (const [method, jsonIndexes] of [
|
|
|
109
121
|
MessagingClient.prototype[method] = function (...args) {
|
|
110
122
|
try {
|
|
111
123
|
for (const index of jsonIndexes) {
|
|
112
|
-
if (args[index] !== undefined && args[index] !== null) args[index] = sanitize(args[index]);
|
|
124
|
+
if (args[index] !== undefined && args[index] !== null) args[index] = sanitize(args[index], 'invalid-request');
|
|
113
125
|
}
|
|
114
|
-
return this.native[method](...args).catch((error) => { throw normalized(error); });
|
|
126
|
+
return this.native[method](...args).catch((error) => { throw normalized(error, 'protocol'); });
|
|
115
127
|
} catch (error) {
|
|
116
|
-
throw normalized(error, '
|
|
128
|
+
throw normalized(error, 'invalid-request');
|
|
117
129
|
}
|
|
118
130
|
};
|
|
119
131
|
}
|
package/messaging/index.d.ts
CHANGED
|
@@ -2,24 +2,16 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
|
|
4
4
|
export declare class MessagingClient {
|
|
5
|
-
constructor(config:
|
|
5
|
+
constructor(config: any)
|
|
6
6
|
health(): Promise<MessagingOutcome>
|
|
7
7
|
ready(): Promise<MessagingOutcome>
|
|
8
8
|
submit(token: string, idempotencyKey: string, request: any): Promise<MessagingOutcome>
|
|
9
|
+
messageReceipt(token: string, idempotencyKey: string, request: any): Promise<MessagingOutcome>
|
|
9
10
|
message(token: string, messageId: string): Promise<MessagingOutcome>
|
|
10
11
|
cancel(token: string, messageId: string): Promise<MessagingOutcome>
|
|
11
12
|
preview(token: string, templateId: string, version: string, request: any): Promise<MessagingOutcome>
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export interface MessagingClientConfig {
|
|
15
|
-
baseUrl: string
|
|
16
|
-
requestTimeoutMilliseconds?: number
|
|
17
|
-
connectTimeoutMilliseconds?: number
|
|
18
|
-
maxResponseBytes?: number
|
|
19
|
-
userAgent?: string
|
|
20
|
-
trustedRootCertificates?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
export interface MessagingOutcome {
|
|
24
16
|
kind: string
|
|
25
17
|
value: any
|
package/native.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const PACKAGE_VERSION = '0.
|
|
4
|
-
const PRODUCTS = new Set(['discovery', 'evidence', 'breg', 'casework', 'messaging']);
|
|
3
|
+
const PACKAGE_VERSION = '0.41.0';
|
|
4
|
+
const PRODUCTS = new Set(['discovery', 'evidence', 'breg', 'casework', 'messaging', 'scheduling']);
|
|
5
5
|
|
|
6
6
|
function target() {
|
|
7
7
|
if (process.platform === 'darwin' && process.arch === 'arm64') return 'darwin-arm64';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@registrystack/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "Unified Node.js client for Registry Stack products.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://docs.registrystack.org/reference/client-api/",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"evidence",
|
|
15
15
|
"breg",
|
|
16
16
|
"casework",
|
|
17
|
-
"messaging"
|
|
17
|
+
"messaging",
|
|
18
|
+
"scheduling"
|
|
18
19
|
],
|
|
19
20
|
"publishConfig": {
|
|
20
21
|
"access": "public",
|
|
@@ -41,10 +42,11 @@
|
|
|
41
42
|
"evidence",
|
|
42
43
|
"breg",
|
|
43
44
|
"casework",
|
|
44
|
-
"messaging"
|
|
45
|
+
"messaging",
|
|
46
|
+
"scheduling"
|
|
45
47
|
],
|
|
46
48
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
49
|
+
"node": ">=24.11.0"
|
|
48
50
|
},
|
|
49
51
|
"scripts": {
|
|
50
52
|
"sync": "python3 ../../release/scripts/sync-registry-client-node.py",
|
|
@@ -53,12 +55,12 @@
|
|
|
53
55
|
"check:types": "tsc --noEmit --strict --skipLibCheck false --types node --moduleResolution node16 --module node16 --target es2022 index.d.ts __test__/facade.test-d.ts"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
|
-
"@types/node": "
|
|
58
|
+
"@types/node": "24.19.1",
|
|
57
59
|
"typescript": "6.0.3"
|
|
58
60
|
},
|
|
59
61
|
"optionalDependencies": {
|
|
60
|
-
"@registrystack/client-darwin-arm64": "0.
|
|
61
|
-
"@registrystack/client-linux-arm64-gnu": "0.
|
|
62
|
-
"@registrystack/client-linux-x64-gnu": "0.
|
|
62
|
+
"@registrystack/client-darwin-arm64": "0.41.0",
|
|
63
|
+
"@registrystack/client-linux-arm64-gnu": "0.41.0",
|
|
64
|
+
"@registrystack/client-linux-x64-gnu": "0.41.0"
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/** An integer for which `Number.isSafeInteger` is true. */
|
|
2
|
+
export type SafeInteger = number
|
|
3
|
+
/**
|
|
4
|
+
* An RFC 3339 instant. Instants the binding sends are parsed and normalized
|
|
5
|
+
* to UTC before any request; instants it answers are UTC.
|
|
6
|
+
*/
|
|
7
|
+
export type Instant = string
|
|
8
|
+
export type JsonScalar = string | number | boolean | null
|
|
9
|
+
export type JsonValue = JsonScalar | ReadonlyArray<JsonValue> | { readonly [key: string]: JsonValue }
|
|
10
|
+
|
|
11
|
+
export interface SchedulingClientConfig {
|
|
12
|
+
baseUrl: string
|
|
13
|
+
requestTimeoutMilliseconds?: SafeInteger | null
|
|
14
|
+
connectTimeoutMilliseconds?: SafeInteger | null
|
|
15
|
+
maxResponseBytes?: SafeInteger | null
|
|
16
|
+
userAgent?: string | null
|
|
17
|
+
trustedRootCertificates?: string | null
|
|
18
|
+
/**
|
|
19
|
+
* How many times a keyed command (hold, appointment create, reschedule,
|
|
20
|
+
* cancel) whose outcome is unknown is resent, identically and under the
|
|
21
|
+
* same idempotency key, before its error is returned: a whole number from 0
|
|
22
|
+
* to 2, default 2; 0 sends it once. Any other value is a `configuration`
|
|
23
|
+
* error.
|
|
24
|
+
*/
|
|
25
|
+
maxMutationRetries?: SafeInteger | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** An opaque record link Scheduling retains with a hold or appointment. */
|
|
29
|
+
export interface ExternalReference {
|
|
30
|
+
product: string
|
|
31
|
+
recordType: string
|
|
32
|
+
identifier: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PartyCounts { recipients: SafeInteger; attendees: SafeInteger }
|
|
36
|
+
|
|
37
|
+
/** One admission: the offering, start, and party a hold or booking asks capacity for. */
|
|
38
|
+
export interface AdmissionRequest {
|
|
39
|
+
offering: string
|
|
40
|
+
start: Instant
|
|
41
|
+
party: PartyCounts
|
|
42
|
+
channel?: string | null
|
|
43
|
+
duplicateKey?: string | null
|
|
44
|
+
policyRevision: SafeInteger
|
|
45
|
+
windowRevision?: SafeInteger | null
|
|
46
|
+
capabilities: ReadonlyArray<string>
|
|
47
|
+
prerequisites: ReadonlyArray<string>
|
|
48
|
+
externalReferences?: ReadonlyArray<ExternalReference>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Exactly one of `hold` (confirm a held claim) or `admission` (book directly). */
|
|
52
|
+
export type CreateAppointmentRequest =
|
|
53
|
+
| { hold: string; admission?: null }
|
|
54
|
+
| { admission: AdmissionRequest; hold?: null }
|
|
55
|
+
|
|
56
|
+
export interface RescheduleAppointmentRequest { observedRevision: SafeInteger; admission: AdmissionRequest }
|
|
57
|
+
export interface CancelAppointmentRequest { observedRevision: SafeInteger; reason?: string | null }
|
|
58
|
+
|
|
59
|
+
export interface AvailabilityQuery {
|
|
60
|
+
start?: Instant | null
|
|
61
|
+
end?: Instant | null
|
|
62
|
+
cursor?: string | null
|
|
63
|
+
limit?: SafeInteger | null
|
|
64
|
+
}
|
|
65
|
+
export interface PageQuery { cursor?: string | null; limit?: SafeInteger | null }
|
|
66
|
+
|
|
67
|
+
export interface SchedulingServiceDocument { schedulingId: string; policyRevision: SafeInteger; policyDigest: string }
|
|
68
|
+
export interface ServiceDocument { id: string; label: string }
|
|
69
|
+
export type SchedulingMode = 'exact-time' | 'arrival-window'
|
|
70
|
+
export interface ReminderDocument { minutesBefore: SafeInteger }
|
|
71
|
+
export interface WindowDocument { id: string; revision: SafeInteger; start: Instant; end: Instant; units: SafeInteger }
|
|
72
|
+
export interface OfferingDocument {
|
|
73
|
+
id: string
|
|
74
|
+
service: string
|
|
75
|
+
label: string
|
|
76
|
+
mode: SchedulingMode
|
|
77
|
+
location: string
|
|
78
|
+
leadTimeMinutes: SafeInteger
|
|
79
|
+
horizonDays: SafeInteger
|
|
80
|
+
cancellationCutoffMinutes: SafeInteger
|
|
81
|
+
durationMinutes: SafeInteger | null
|
|
82
|
+
bufferBeforeMinutes: SafeInteger | null
|
|
83
|
+
bufferAfterMinutes: SafeInteger | null
|
|
84
|
+
startIncrementMinutes: SafeInteger | null
|
|
85
|
+
maxRecipients: SafeInteger | null
|
|
86
|
+
window: WindowDocument | null
|
|
87
|
+
reminders: ReadonlyArray<ReminderDocument>
|
|
88
|
+
requiresCapabilities: ReadonlyArray<string>
|
|
89
|
+
prerequisites: ReadonlyArray<string>
|
|
90
|
+
}
|
|
91
|
+
export interface ResourceDocument { resourceId: string; pool: string; capabilities: ReadonlyArray<string>; available: boolean }
|
|
92
|
+
export interface LocationDocument { locationId: string; timezone: string }
|
|
93
|
+
|
|
94
|
+
/** Exact-time offerings answer grid slots; arrival-window offerings answer windows. */
|
|
95
|
+
export type AvailabilityEntry =
|
|
96
|
+
| { kind: 'slot'; start: Instant; end: Instant; free: SafeInteger }
|
|
97
|
+
| { kind: 'window'; window: string; start: Instant; end: Instant; remaining: SafeInteger; channelRemaining: SafeInteger | null }
|
|
98
|
+
|
|
99
|
+
export interface PageDocument<T> { items: ReadonlyArray<T>; nextCursor: string | null }
|
|
100
|
+
|
|
101
|
+
export interface HoldDocument {
|
|
102
|
+
holdId: string
|
|
103
|
+
offering: string
|
|
104
|
+
start: Instant
|
|
105
|
+
end: Instant
|
|
106
|
+
resource: string | null
|
|
107
|
+
units: SafeInteger
|
|
108
|
+
expiresAt: Instant
|
|
109
|
+
policyRevision: SafeInteger
|
|
110
|
+
externalReferences: ReadonlyArray<ExternalReference>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type AppointmentState = 'confirmed' | 'cancelled'
|
|
114
|
+
export interface AppointmentDocument {
|
|
115
|
+
appointmentId: string
|
|
116
|
+
offering: string
|
|
117
|
+
start: Instant
|
|
118
|
+
end: Instant
|
|
119
|
+
resource: string | null
|
|
120
|
+
units: SafeInteger
|
|
121
|
+
channel: string | null
|
|
122
|
+
revision: SafeInteger
|
|
123
|
+
state: AppointmentState
|
|
124
|
+
policyRevision: SafeInteger
|
|
125
|
+
createdAt: Instant
|
|
126
|
+
cancelledAt: Instant | null
|
|
127
|
+
externalReferences: ReadonlyArray<ExternalReference>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface AppointmentHistoryEntryDocument {
|
|
131
|
+
eventId: string
|
|
132
|
+
kind: string
|
|
133
|
+
revision: SafeInteger
|
|
134
|
+
occurredAt: Instant
|
|
135
|
+
actor: string | null
|
|
136
|
+
detail: JsonValue
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ExplainDocument {
|
|
140
|
+
offering: string
|
|
141
|
+
start: Instant
|
|
142
|
+
publicCode?: string
|
|
143
|
+
detailedCode?: string
|
|
144
|
+
explanation?: string
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface SchedulingOutcome<T> { kind: 'complete'; value: T; traceId: string }
|
|
148
|
+
|
|
149
|
+
/** The closed catalogue: the client answers any other code as a protocol failure. */
|
|
150
|
+
export type SchedulingProblemCode =
|
|
151
|
+
| 'authentication.refused'
|
|
152
|
+
| 'booking.duplicate-active'
|
|
153
|
+
| 'cancellation.cutoff-passed'
|
|
154
|
+
| 'capability.unmatched'
|
|
155
|
+
| 'capacity.exhausted'
|
|
156
|
+
| 'cursor.expired'
|
|
157
|
+
| 'cursor.invalid'
|
|
158
|
+
| 'eligibility.unavailable'
|
|
159
|
+
| 'hold.expired'
|
|
160
|
+
| 'hold.released'
|
|
161
|
+
| 'hook.unavailable'
|
|
162
|
+
| 'horizon.outside'
|
|
163
|
+
| 'idempotency.expired'
|
|
164
|
+
| 'idempotency.key-reused'
|
|
165
|
+
| 'location.closed'
|
|
166
|
+
| 'operation.not-authorized'
|
|
167
|
+
| 'party.capacity-inadequate'
|
|
168
|
+
| 'policy.changed'
|
|
169
|
+
| 'precondition.failed'
|
|
170
|
+
| 'precondition.required'
|
|
171
|
+
| 'prerequisite.missing'
|
|
172
|
+
| 'profile.not-authorized'
|
|
173
|
+
| 'receipt.unresolved'
|
|
174
|
+
| 'request.body-too-large'
|
|
175
|
+
| 'request.invalid'
|
|
176
|
+
| 'request.method-not-allowed'
|
|
177
|
+
| 'request.not-found'
|
|
178
|
+
| 'request.unprocessable'
|
|
179
|
+
| 'request.unsupported-media-type'
|
|
180
|
+
| 'resource.unavailable'
|
|
181
|
+
| 'revision.mismatch'
|
|
182
|
+
| 'schedule.unpublished'
|
|
183
|
+
| 'service.unavailable'
|
|
184
|
+
export type SchedulingProtocolFailure = 'header-bounds' | 'trace-context' | 'media-type' | 'body' | 'problem' | 'status' | 'protocol'
|
|
185
|
+
|
|
186
|
+
export class SchedulingClientError extends Error {
|
|
187
|
+
readonly kind: 'configuration' | 'invalid-request' | 'transport' | 'problem' | 'protocol'
|
|
188
|
+
/**
|
|
189
|
+
* Whether the request may have taken effect although this error was
|
|
190
|
+
* raised: a timeout or broken exchange after sending, an unusable answer,
|
|
191
|
+
* or a 5xx. When true, recover a keyed request by sending it again under
|
|
192
|
+
* the same idempotency key; a new key could apply it twice. False for a
|
|
193
|
+
* configuration or request defect, a connection never established, and
|
|
194
|
+
* every 4xx refusal. It is false for `410 idempotency.expired` too, but
|
|
195
|
+
* there an earlier attempt under the key was answered and may have
|
|
196
|
+
* committed: read an appointment by its external reference or identifier
|
|
197
|
+
* before choosing a new key. A hold cannot be read and expires on its own,
|
|
198
|
+
* so start a new request.
|
|
199
|
+
*/
|
|
200
|
+
readonly outcomeUnknown: boolean
|
|
201
|
+
readonly code?: SchedulingProblemCode
|
|
202
|
+
readonly title?: string
|
|
203
|
+
readonly detail?: string
|
|
204
|
+
readonly status?: SafeInteger
|
|
205
|
+
readonly traceId?: string
|
|
206
|
+
readonly transportKind?: string
|
|
207
|
+
readonly protocolFailure?: SchedulingProtocolFailure
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class SchedulingClient {
|
|
211
|
+
constructor(config: SchedulingClientConfig)
|
|
212
|
+
getScheduling(token: string): Promise<SchedulingOutcome<SchedulingServiceDocument>>
|
|
213
|
+
listServices(token: string, cursor?: string | null): Promise<SchedulingOutcome<PageDocument<ServiceDocument>>>
|
|
214
|
+
listOfferings(token: string, cursor?: string | null): Promise<SchedulingOutcome<PageDocument<OfferingDocument>>>
|
|
215
|
+
availability(token: string, offering: string, query?: AvailabilityQuery | null): Promise<SchedulingOutcome<PageDocument<AvailabilityEntry>>>
|
|
216
|
+
explain(token: string, offering: string, start: Instant): Promise<SchedulingOutcome<ExplainDocument>>
|
|
217
|
+
createHold(token: string, idempotencyKey: string, request: AdmissionRequest): Promise<SchedulingOutcome<HoldDocument>>
|
|
218
|
+
releaseHold(token: string, holdId: string): Promise<SchedulingOutcome<null>>
|
|
219
|
+
createAppointment(token: string, idempotencyKey: string, request: CreateAppointmentRequest): Promise<SchedulingOutcome<AppointmentDocument>>
|
|
220
|
+
/** Observe the original success using the same body/key and current token; never resend or mutate. */
|
|
221
|
+
appointmentReceipt(token: string, idempotencyKey: string, request: CreateAppointmentRequest): Promise<SchedulingOutcome<AppointmentDocument>>
|
|
222
|
+
getAppointment(token: string, appointmentId: string): Promise<SchedulingOutcome<AppointmentDocument>>
|
|
223
|
+
listAppointments(token: string, reference: ExternalReference, page?: PageQuery | null): Promise<SchedulingOutcome<PageDocument<AppointmentDocument>>>
|
|
224
|
+
rescheduleAppointment(token: string, appointmentId: string, idempotencyKey: string, request: RescheduleAppointmentRequest): Promise<SchedulingOutcome<AppointmentDocument>>
|
|
225
|
+
cancelAppointment(token: string, appointmentId: string, idempotencyKey: string, request: CancelAppointmentRequest): Promise<SchedulingOutcome<AppointmentDocument>>
|
|
226
|
+
appointmentHistory(token: string, appointmentId: string, cursor?: string | null): Promise<SchedulingOutcome<PageDocument<AppointmentHistoryEntryDocument>>>
|
|
227
|
+
listResources(token: string, cursor?: string | null): Promise<SchedulingOutcome<PageDocument<ResourceDocument>>>
|
|
228
|
+
listLocations(token: string, cursor?: string | null): Promise<SchedulingOutcome<PageDocument<LocationDocument>>>
|
|
229
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { types: { isProxy } } = require('node:util');
|
|
4
|
+
const native = require('./index');
|
|
5
|
+
|
|
6
|
+
const MAX_JSON_DEPTH = 64;
|
|
7
|
+
const MAX_JSON_NODES = 20_000;
|
|
8
|
+
const MAX_JSON_STRING_BYTES = 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
class SchedulingClientError extends Error {
|
|
11
|
+
constructor(envelope) {
|
|
12
|
+
super(envelope.message);
|
|
13
|
+
this.name = 'SchedulingClientError';
|
|
14
|
+
this.kind = envelope.kind;
|
|
15
|
+
this.outcomeUnknown = envelope.outcomeUnknown === true;
|
|
16
|
+
for (const field of ['code', 'title', 'detail', 'status', 'traceId', 'transportKind', 'protocolFailure']) {
|
|
17
|
+
if (envelope[field] !== undefined && envelope[field] !== null) this[field] = envelope[field];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const FALLBACK_MESSAGES = {
|
|
23
|
+
configuration: 'Scheduling client configuration is invalid',
|
|
24
|
+
'invalid-request': 'Scheduling client arguments are invalid',
|
|
25
|
+
protocol: 'Registry Scheduling client failed',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function normalized(error, fallbackKind) {
|
|
29
|
+
if (error instanceof SchedulingClientError) return error;
|
|
30
|
+
if (error instanceof Error && typeof error.message === 'string') {
|
|
31
|
+
try {
|
|
32
|
+
const envelope = JSON.parse(error.message);
|
|
33
|
+
if (envelope && typeof envelope.kind === 'string') return new SchedulingClientError(envelope);
|
|
34
|
+
} catch {
|
|
35
|
+
// napi argument conversion errors have no mapped envelope.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (fallbackKind) {
|
|
39
|
+
return new SchedulingClientError({
|
|
40
|
+
kind: fallbackKind,
|
|
41
|
+
message: FALLBACK_MESSAGES[fallbackKind],
|
|
42
|
+
// A rejection after the native client took the call may follow an
|
|
43
|
+
// exchange, so it leaves the outcome unknown as a native protocol
|
|
44
|
+
// failure does.
|
|
45
|
+
outcomeUnknown: fallbackKind === 'protocol',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cloneJson(value, budget, depth, kind) {
|
|
52
|
+
if (depth > MAX_JSON_DEPTH || ++budget.nodes > MAX_JSON_NODES) throw normalized({}, kind);
|
|
53
|
+
if (value === null || typeof value === 'boolean') return value;
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
budget.bytes += Buffer.byteLength(value, 'utf8');
|
|
56
|
+
if (budget.bytes > MAX_JSON_STRING_BYTES) throw normalized({}, kind);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
if (typeof value === 'number') {
|
|
60
|
+
if (!Number.isFinite(value) || (Number.isInteger(value) && !Number.isSafeInteger(value))) {
|
|
61
|
+
throw normalized({}, kind);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
if (value === undefined || typeof value !== 'object' || isProxy(value)) {
|
|
66
|
+
throw normalized({}, kind);
|
|
67
|
+
}
|
|
68
|
+
const array = Array.isArray(value);
|
|
69
|
+
const prototype = Object.getPrototypeOf(value);
|
|
70
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) {
|
|
71
|
+
throw normalized({}, kind);
|
|
72
|
+
}
|
|
73
|
+
if (budget.active.has(value)) throw normalized({}, kind);
|
|
74
|
+
budget.active.add(value);
|
|
75
|
+
try {
|
|
76
|
+
if (array) return value.map((member) => cloneJson(member, budget, depth + 1, kind));
|
|
77
|
+
const result = Object.create(null);
|
|
78
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
79
|
+
if (typeof key !== 'string') throw normalized({}, kind);
|
|
80
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
81
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value')) throw normalized({}, kind);
|
|
82
|
+
if (descriptor.enumerable) {
|
|
83
|
+
budget.bytes += Buffer.byteLength(key, 'utf8');
|
|
84
|
+
if (budget.bytes > MAX_JSON_STRING_BYTES) throw normalized({}, kind);
|
|
85
|
+
result[key] = cloneJson(descriptor.value, budget, depth + 1, kind);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
} finally {
|
|
90
|
+
budget.active.delete(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// A refusal is reported as `kind`: `configuration` for the constructor's
|
|
95
|
+
// settings, `invalid-request` for a method's arguments.
|
|
96
|
+
function sanitize(value, kind) {
|
|
97
|
+
return cloneJson(value, { nodes: 0, bytes: 0, active: new WeakSet() }, 0, kind);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const NativeSchedulingClient = native.SchedulingClient;
|
|
101
|
+
|
|
102
|
+
class SchedulingClient {
|
|
103
|
+
constructor(config) {
|
|
104
|
+
try {
|
|
105
|
+
this.native = new NativeSchedulingClient(sanitize(config, 'configuration'));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw normalized(error, 'configuration');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const [method, jsonIndexes] of [
|
|
113
|
+
['getScheduling', []],
|
|
114
|
+
['listServices', []],
|
|
115
|
+
['listOfferings', []],
|
|
116
|
+
['availability', [2]],
|
|
117
|
+
['explain', []],
|
|
118
|
+
['createHold', [2]],
|
|
119
|
+
['releaseHold', []],
|
|
120
|
+
['createAppointment', [2]],
|
|
121
|
+
['appointmentReceipt', [2]],
|
|
122
|
+
['getAppointment', []],
|
|
123
|
+
['listAppointments', [1, 2]],
|
|
124
|
+
['rescheduleAppointment', [3]],
|
|
125
|
+
['cancelAppointment', [3]],
|
|
126
|
+
['appointmentHistory', []],
|
|
127
|
+
['listResources', []],
|
|
128
|
+
['listLocations', []],
|
|
129
|
+
]) {
|
|
130
|
+
SchedulingClient.prototype[method] = function (...args) {
|
|
131
|
+
try {
|
|
132
|
+
for (const index of jsonIndexes) {
|
|
133
|
+
if (args[index] !== undefined && args[index] !== null) args[index] = sanitize(args[index], 'invalid-request');
|
|
134
|
+
}
|
|
135
|
+
return this.native[method](...args).catch((error) => { throw normalized(error, 'protocol'); });
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw normalized(error, 'invalid-request');
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { SchedulingClient, SchedulingClientError };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* auto-generated by NAPI-RS */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
export declare class SchedulingClient {
|
|
5
|
+
constructor(config: any)
|
|
6
|
+
getScheduling(token: string): Promise<SchedulingOutcome>
|
|
7
|
+
listServices(token: string, cursor?: string | undefined | null): Promise<SchedulingOutcome>
|
|
8
|
+
listOfferings(token: string, cursor?: string | undefined | null): Promise<SchedulingOutcome>
|
|
9
|
+
availability(token: string, offering: string, query?: any | undefined | null): Promise<SchedulingOutcome>
|
|
10
|
+
explain(token: string, offering: string, start: string): Promise<SchedulingOutcome>
|
|
11
|
+
createHold(token: string, idempotencyKey: string, request: any): Promise<SchedulingOutcome>
|
|
12
|
+
releaseHold(token: string, holdId: string): Promise<SchedulingOutcome>
|
|
13
|
+
createAppointment(token: string, idempotencyKey: string, request: any): Promise<SchedulingOutcome>
|
|
14
|
+
appointmentReceipt(token: string, idempotencyKey: string, request: any): Promise<SchedulingOutcome>
|
|
15
|
+
getAppointment(token: string, appointmentId: string): Promise<SchedulingOutcome>
|
|
16
|
+
listAppointments(token: string, reference: any, page?: any | undefined | null): Promise<SchedulingOutcome>
|
|
17
|
+
rescheduleAppointment(token: string, appointmentId: string, idempotencyKey: string, request: any): Promise<SchedulingOutcome>
|
|
18
|
+
cancelAppointment(token: string, appointmentId: string, idempotencyKey: string, request: any): Promise<SchedulingOutcome>
|
|
19
|
+
appointmentHistory(token: string, appointmentId: string, cursor?: string | undefined | null): Promise<SchedulingOutcome>
|
|
20
|
+
listResources(token: string, cursor?: string | undefined | null): Promise<SchedulingOutcome>
|
|
21
|
+
listLocations(token: string, cursor?: string | undefined | null): Promise<SchedulingOutcome>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SchedulingOutcome {
|
|
25
|
+
kind: string
|
|
26
|
+
value: any
|
|
27
|
+
traceId: string
|
|
28
|
+
}
|