@tanstack/ai 0.46.0 → 0.47.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/index.d.ts +36 -11
- package/dist/esm/activities/chat/index.js +440 -79
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +1 -0
- package/dist/esm/activities/chat/messages.js +12 -7
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.d.ts +7 -2
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -3
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.d.ts +6 -3
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/generic-interrupts.d.ts +13 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js +8 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +4 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +54 -3
- package/dist/esm/activities/chat/middleware/types.js +16 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -5
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +6 -0
- package/dist/esm/adapter-internals.js +4 -1
- package/dist/esm/client.d.ts +4 -0
- package/dist/esm/client.js +3 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/generic-interrupt-continuation.d.ts +45 -0
- package/dist/esm/generic-interrupt-continuation.js +80 -0
- package/dist/esm/generic-interrupt-continuation.js.map +1 -0
- package/dist/esm/index.d.ts +6 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/interrupt-definition.d.ts +113 -0
- package/dist/esm/interrupt-definition.js +169 -0
- package/dist/esm/interrupt-definition.js.map +1 -0
- package/dist/esm/interrupt-resume.d.ts +3 -0
- package/dist/esm/interrupt-resume.js +77 -16
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/interrupts.d.ts +12 -3
- package/dist/esm/interrupts.js.map +1 -1
- package/dist/esm/types.d.ts +11 -3
- package/dist/esm/utilities/chat-params.js +10 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/media-generation/SKILL.md +4 -1
- package/skills/ai-core/middleware/SKILL.md +53 -44
- package/skills/ai-core/structured-outputs/SKILL.md +59 -55
- package/skills/ai-core/tool-calling/SKILL.md +54 -1
- package/src/activities/chat/index.ts +1030 -211
- package/src/activities/chat/messages.ts +11 -3
- package/src/activities/chat/middleware/builder.ts +29 -4
- package/src/activities/chat/middleware/compose.ts +95 -5
- package/src/activities/chat/middleware/define.ts +13 -3
- package/src/activities/chat/middleware/generic-interrupts.ts +26 -0
- package/src/activities/chat/middleware/index.ts +15 -0
- package/src/activities/chat/middleware/types.ts +127 -2
- package/src/activities/chat/stream/processor.ts +21 -0
- package/src/adapter-internals.ts +20 -0
- package/src/client.ts +20 -0
- package/src/generic-interrupt-continuation.ts +162 -0
- package/src/index.ts +34 -0
- package/src/interrupt-definition.ts +581 -0
- package/src/interrupt-resume.ts +156 -25
- package/src/interrupts.ts +13 -3
- package/src/types.ts +11 -3
- package/src/utilities/chat-params.ts +16 -3
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
StandardJSONSchemaV1,
|
|
3
|
+
StandardSchemaV1,
|
|
4
|
+
} from '@standard-schema/spec'
|
|
5
|
+
import {
|
|
6
|
+
canonicalInterruptJson,
|
|
7
|
+
cloneAndDeepFreezeJson,
|
|
8
|
+
digestInterruptJson,
|
|
9
|
+
} from './interrupt-serialization'
|
|
10
|
+
import {
|
|
11
|
+
isStandardSchema,
|
|
12
|
+
isStandardJSONSchema,
|
|
13
|
+
} from './activities/chat/tools/schema-converter'
|
|
14
|
+
import { INTERRUPT_BINDING_VERSION } from './interrupts'
|
|
15
|
+
|
|
16
|
+
export const INTERRUPT_PAYLOAD_METADATA_KEY =
|
|
17
|
+
'tanstack:interruptPayload' as const
|
|
18
|
+
export const INTERRUPT_BINDING_KIND = 'generic' as const
|
|
19
|
+
|
|
20
|
+
type PortableSchema =
|
|
21
|
+
| StandardJSONSchemaV1<any, any>
|
|
22
|
+
| StandardSchemaV1<any, any>
|
|
23
|
+
|
|
24
|
+
type InferSchemaOutput<TSchema> =
|
|
25
|
+
TSchema extends StandardSchemaV1<any, infer TOutput>
|
|
26
|
+
? TOutput
|
|
27
|
+
: TSchema extends StandardJSONSchemaV1<any, infer TOutput>
|
|
28
|
+
? TOutput
|
|
29
|
+
: never
|
|
30
|
+
type InferSchemaInput<TSchema> =
|
|
31
|
+
TSchema extends StandardSchemaV1<infer TInput, any>
|
|
32
|
+
? TInput
|
|
33
|
+
: TSchema extends StandardJSONSchemaV1<infer TInput, any>
|
|
34
|
+
? TInput
|
|
35
|
+
: never
|
|
36
|
+
type DefinitionSchemaState = {
|
|
37
|
+
responseSchemaCanonicalJson?: string
|
|
38
|
+
responseSchemaHash?: string
|
|
39
|
+
payloadSchemaCanonicalJson?: string
|
|
40
|
+
payloadSchemaHash?: string
|
|
41
|
+
}
|
|
42
|
+
const definitionSchemaState = new WeakMap<object, DefinitionSchemaState>()
|
|
43
|
+
|
|
44
|
+
export interface InterruptDefinitionOptions<
|
|
45
|
+
TId extends string,
|
|
46
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
47
|
+
TResponseSchema extends PortableSchema | undefined,
|
|
48
|
+
> {
|
|
49
|
+
id: TId
|
|
50
|
+
payloadSchema?: TPayloadSchema
|
|
51
|
+
responseSchema?: TResponseSchema
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface InterruptBindingDescriptor {
|
|
55
|
+
v: typeof INTERRUPT_BINDING_VERSION
|
|
56
|
+
kind: typeof INTERRUPT_BINDING_KIND
|
|
57
|
+
definitionId: string
|
|
58
|
+
key: string
|
|
59
|
+
threadId?: string
|
|
60
|
+
interruptedRunId?: string
|
|
61
|
+
generation?: number
|
|
62
|
+
batchIndex?: number
|
|
63
|
+
responseSchemaCanonicalJson?: string
|
|
64
|
+
payloadSchemaCanonicalJson?: string
|
|
65
|
+
payloadSchemaHash?: string
|
|
66
|
+
responseSchemaHash?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface InterruptPreEmissionData {
|
|
70
|
+
descriptor: InterruptBindingDescriptor
|
|
71
|
+
payload?: unknown
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type InterruptInput<
|
|
75
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
76
|
+
TPayload = unknown,
|
|
77
|
+
> = {
|
|
78
|
+
key: string
|
|
79
|
+
reason: string
|
|
80
|
+
message: string
|
|
81
|
+
expiresAt?: string
|
|
82
|
+
} & ([TPayloadSchema] extends [undefined] ? {} : { payload?: TPayload })
|
|
83
|
+
|
|
84
|
+
type GenericInterruptRequestBase<
|
|
85
|
+
TDefinition extends InterruptDefinition<any, any, any, any>,
|
|
86
|
+
> = {
|
|
87
|
+
readonly definition: TDefinition
|
|
88
|
+
readonly key: string
|
|
89
|
+
readonly reason: string
|
|
90
|
+
readonly message: string
|
|
91
|
+
readonly expiresAt?: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type GenericInterruptRequestFor<
|
|
95
|
+
TDefinition extends InterruptDefinition<any, any, any, any>,
|
|
96
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
97
|
+
TPayload,
|
|
98
|
+
> = GenericInterruptRequestBase<TDefinition> &
|
|
99
|
+
([TPayloadSchema] extends [undefined]
|
|
100
|
+
? {}
|
|
101
|
+
: { readonly payload: TPayload | undefined })
|
|
102
|
+
|
|
103
|
+
export type GenericInterruptRequest<
|
|
104
|
+
TDefinition extends InterruptDefinition<any, any, any, any>,
|
|
105
|
+
> = [TDefinition] extends [never]
|
|
106
|
+
? never
|
|
107
|
+
: TDefinition extends InterruptDefinition<
|
|
108
|
+
any,
|
|
109
|
+
infer TPayloadSchema,
|
|
110
|
+
any,
|
|
111
|
+
infer TPayload
|
|
112
|
+
>
|
|
113
|
+
? GenericInterruptRequestFor<TDefinition, TPayloadSchema, TPayload>
|
|
114
|
+
: GenericInterruptRequestBase<TDefinition>
|
|
115
|
+
|
|
116
|
+
type InterruptInputKey = 'key' | 'reason' | 'message' | 'expiresAt' | 'payload'
|
|
117
|
+
type RejectUnexpectedInputKeys<TInput> =
|
|
118
|
+
Exclude<keyof TInput, InterruptInputKey> extends never
|
|
119
|
+
? unknown
|
|
120
|
+
: { [K in Exclude<keyof TInput, InterruptInputKey>]: never }
|
|
121
|
+
type RejectUnexpectedPayload<TInput> = 'payload' extends keyof TInput
|
|
122
|
+
? { payload: never }
|
|
123
|
+
: unknown
|
|
124
|
+
type ValidInterruptInput<
|
|
125
|
+
TInput,
|
|
126
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
127
|
+
TPayload = unknown,
|
|
128
|
+
> =
|
|
129
|
+
TInput extends InterruptInput<TPayloadSchema, TPayload>
|
|
130
|
+
? RejectUnexpectedInputKeys<TInput> &
|
|
131
|
+
([TPayloadSchema] extends [undefined]
|
|
132
|
+
? RejectUnexpectedPayload<TInput>
|
|
133
|
+
: unknown)
|
|
134
|
+
: never
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extracting a class method preserves the intentional bivariant assignment
|
|
138
|
+
* behavior of the public `interrupt` callback without exposing a method
|
|
139
|
+
* signature in an interface.
|
|
140
|
+
*/
|
|
141
|
+
declare abstract class InterruptRequestMethodSignature<
|
|
142
|
+
TId extends string,
|
|
143
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
144
|
+
TResponseSchema extends PortableSchema | undefined,
|
|
145
|
+
TPayload,
|
|
146
|
+
TPayloadInput,
|
|
147
|
+
> {
|
|
148
|
+
abstract call<TInput>(
|
|
149
|
+
input: TInput & ValidInterruptInput<TInput, TPayloadSchema, TPayloadInput>,
|
|
150
|
+
): GenericInterruptRequestFor<
|
|
151
|
+
InterruptDefinition<
|
|
152
|
+
TId,
|
|
153
|
+
TPayloadSchema,
|
|
154
|
+
TResponseSchema,
|
|
155
|
+
TPayload,
|
|
156
|
+
TPayloadInput
|
|
157
|
+
>,
|
|
158
|
+
TPayloadSchema,
|
|
159
|
+
TPayload
|
|
160
|
+
>
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type InterruptRequestMethod<
|
|
164
|
+
TId extends string,
|
|
165
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
166
|
+
TResponseSchema extends PortableSchema | undefined,
|
|
167
|
+
TPayload,
|
|
168
|
+
TPayloadInput,
|
|
169
|
+
> = InterruptRequestMethodSignature<
|
|
170
|
+
TId,
|
|
171
|
+
TPayloadSchema,
|
|
172
|
+
TResponseSchema,
|
|
173
|
+
TPayload,
|
|
174
|
+
TPayloadInput
|
|
175
|
+
>['call']
|
|
176
|
+
|
|
177
|
+
type DefinedInterruptDefinition<
|
|
178
|
+
TId extends string,
|
|
179
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
180
|
+
TResponseSchema extends PortableSchema | undefined,
|
|
181
|
+
TPayload = unknown,
|
|
182
|
+
TPayloadInput = TPayload,
|
|
183
|
+
> = Omit<
|
|
184
|
+
InterruptDefinition<
|
|
185
|
+
TId,
|
|
186
|
+
TPayloadSchema,
|
|
187
|
+
TResponseSchema,
|
|
188
|
+
TPayload,
|
|
189
|
+
TPayloadInput
|
|
190
|
+
>,
|
|
191
|
+
'interrupt'
|
|
192
|
+
> & {
|
|
193
|
+
interrupt: InterruptRequestMethod<
|
|
194
|
+
TId,
|
|
195
|
+
TPayloadSchema,
|
|
196
|
+
TResponseSchema,
|
|
197
|
+
TPayload,
|
|
198
|
+
TPayloadInput
|
|
199
|
+
>
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface InterruptDefinition<
|
|
203
|
+
TId extends string,
|
|
204
|
+
TPayloadSchema extends PortableSchema | undefined,
|
|
205
|
+
TResponseSchema extends PortableSchema | undefined,
|
|
206
|
+
TPayload = unknown,
|
|
207
|
+
TPayloadInput = TPayload,
|
|
208
|
+
> {
|
|
209
|
+
readonly id: TId
|
|
210
|
+
readonly payloadSchema: TPayloadSchema
|
|
211
|
+
readonly responseSchema: TResponseSchema
|
|
212
|
+
interrupt: InterruptRequestMethod<
|
|
213
|
+
TId,
|
|
214
|
+
TPayloadSchema,
|
|
215
|
+
TResponseSchema,
|
|
216
|
+
TPayload,
|
|
217
|
+
TPayloadInput
|
|
218
|
+
>
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function createInterruptBinding(
|
|
222
|
+
request: GenericInterruptRequest<InterruptDefinition<any, any, any, any>>,
|
|
223
|
+
fields: Pick<
|
|
224
|
+
InterruptBindingDescriptor,
|
|
225
|
+
'threadId' | 'interruptedRunId' | 'generation' | 'batchIndex'
|
|
226
|
+
> = {},
|
|
227
|
+
): InterruptPreEmissionData {
|
|
228
|
+
const schemaState = definitionSchemaState.get(request.definition)
|
|
229
|
+
if (!schemaState) {
|
|
230
|
+
throw new TypeError('Interrupt definition schema state is unavailable.')
|
|
231
|
+
}
|
|
232
|
+
const { threadId, interruptedRunId, generation, batchIndex } = fields
|
|
233
|
+
return {
|
|
234
|
+
descriptor: {
|
|
235
|
+
v: INTERRUPT_BINDING_VERSION,
|
|
236
|
+
kind: INTERRUPT_BINDING_KIND,
|
|
237
|
+
definitionId: request.definition.id,
|
|
238
|
+
key: request.key,
|
|
239
|
+
...(threadId !== undefined ? { threadId } : {}),
|
|
240
|
+
...(interruptedRunId !== undefined ? { interruptedRunId } : {}),
|
|
241
|
+
...(generation !== undefined ? { generation } : {}),
|
|
242
|
+
...(batchIndex !== undefined ? { batchIndex } : {}),
|
|
243
|
+
...(schemaState.responseSchemaCanonicalJson
|
|
244
|
+
? {
|
|
245
|
+
responseSchemaCanonicalJson:
|
|
246
|
+
schemaState.responseSchemaCanonicalJson,
|
|
247
|
+
}
|
|
248
|
+
: {}),
|
|
249
|
+
...(schemaState.payloadSchemaCanonicalJson
|
|
250
|
+
? { payloadSchemaCanonicalJson: schemaState.payloadSchemaCanonicalJson }
|
|
251
|
+
: {}),
|
|
252
|
+
...(schemaState.payloadSchemaHash
|
|
253
|
+
? { payloadSchemaHash: schemaState.payloadSchemaHash }
|
|
254
|
+
: {}),
|
|
255
|
+
...(schemaState.responseSchemaHash
|
|
256
|
+
? { responseSchemaHash: schemaState.responseSchemaHash }
|
|
257
|
+
: {}),
|
|
258
|
+
},
|
|
259
|
+
...('payload' in request && request.payload !== undefined
|
|
260
|
+
? { payload: request.payload }
|
|
261
|
+
: {}),
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
type ParsedInterruptInput = {
|
|
266
|
+
key: string
|
|
267
|
+
reason: string
|
|
268
|
+
message: string
|
|
269
|
+
expiresAt?: string
|
|
270
|
+
payload?: unknown
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
type InterruptRequestFactory = (
|
|
274
|
+
input: ParsedInterruptInput,
|
|
275
|
+
payloadIsParsed: boolean,
|
|
276
|
+
) => GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
277
|
+
|
|
278
|
+
const interruptRequestFactories = new WeakMap<object, InterruptRequestFactory>()
|
|
279
|
+
const interruptRequestInputs = new WeakMap<
|
|
280
|
+
object,
|
|
281
|
+
Readonly<ParsedInterruptInput>
|
|
282
|
+
>()
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Returns the schema input captured for a newly emitted request. This is
|
|
286
|
+
* internal because continuation state can cross a client boundary and must be
|
|
287
|
+
* parsed again when it returns to the server.
|
|
288
|
+
*/
|
|
289
|
+
export function getInterruptRequestInput(
|
|
290
|
+
request: GenericInterruptRequest<InterruptDefinition<any, any, any, any>>,
|
|
291
|
+
): Readonly<ParsedInterruptInput> {
|
|
292
|
+
const input = interruptRequestInputs.get(request)
|
|
293
|
+
if (!input) {
|
|
294
|
+
throw new TypeError('Interrupt request input is unavailable.')
|
|
295
|
+
}
|
|
296
|
+
return input
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Rebuild a request from a persisted display payload that has already passed
|
|
301
|
+
* the definition's payload schema. This is internal because callers must not
|
|
302
|
+
* bypass public input validation for new requests.
|
|
303
|
+
*/
|
|
304
|
+
export function rehydrateInterruptRequest(
|
|
305
|
+
definition: InterruptDefinition<any, any, any, any>,
|
|
306
|
+
input: ParsedInterruptInput,
|
|
307
|
+
): GenericInterruptRequest<InterruptDefinition<any, any, any, any>> {
|
|
308
|
+
const factory = interruptRequestFactories.get(definition)
|
|
309
|
+
if (!factory) {
|
|
310
|
+
throw new TypeError('Interrupt definition request factory is unavailable.')
|
|
311
|
+
}
|
|
312
|
+
return factory(input, true)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
interface CanonicalSchemaJson {
|
|
316
|
+
json: Record<string, unknown>
|
|
317
|
+
canonicalJson: string
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function schemaJson(schema: unknown, name: string): CanonicalSchemaJson {
|
|
321
|
+
if (!isStandardJSONSchema(schema)) {
|
|
322
|
+
throw new TypeError(
|
|
323
|
+
`${name} must be a Standard Schema with a JSON Schema converter.`,
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
const exported = schema['~standard'].jsonSchema.input({
|
|
328
|
+
target: 'draft-07',
|
|
329
|
+
})
|
|
330
|
+
if (exported === undefined) {
|
|
331
|
+
throw new TypeError('The exported schema is undefined.')
|
|
332
|
+
}
|
|
333
|
+
if (typeof exported === 'function') {
|
|
334
|
+
throw new TypeError('The exported schema must not be a function.')
|
|
335
|
+
}
|
|
336
|
+
if (Array.isArray(exported)) {
|
|
337
|
+
throw new TypeError('The exported schema must be a plain JSON object.')
|
|
338
|
+
}
|
|
339
|
+
if (
|
|
340
|
+
!exported ||
|
|
341
|
+
typeof exported !== 'object' ||
|
|
342
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(exported))
|
|
343
|
+
) {
|
|
344
|
+
throw new TypeError('The exported schema must be a plain JSON object.')
|
|
345
|
+
}
|
|
346
|
+
const converted: Record<string, unknown> = {}
|
|
347
|
+
for (const [key, value] of Object.entries(exported)) {
|
|
348
|
+
if (key !== '$schema') converted[key] = value
|
|
349
|
+
}
|
|
350
|
+
const canonicalJson = canonicalInterruptJson(converted)
|
|
351
|
+
return { json: converted, canonicalJson }
|
|
352
|
+
} catch (error) {
|
|
353
|
+
throw new TypeError(
|
|
354
|
+
`${name} could not export compatible JSON Schema: ${error instanceof Error ? error.message : String(error)}`,
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Same hash the producer stamps on a first-party generic binding. */
|
|
360
|
+
export function hashInterruptDefinitionSchema(schema: unknown): string {
|
|
361
|
+
return digestInterruptJson(
|
|
362
|
+
schemaJson(schema, 'Interrupt schema').canonicalJson,
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function validateJson(value: unknown, label: string): void {
|
|
367
|
+
try {
|
|
368
|
+
canonicalInterruptJson(value)
|
|
369
|
+
} catch (error) {
|
|
370
|
+
throw new TypeError(
|
|
371
|
+
`${label} must be JSON-compatible: ${error instanceof Error ? error.message : String(error)}`,
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function validateNonEmptyString(value: unknown, label: string): string {
|
|
377
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
378
|
+
throw new TypeError(`${label} must be a non-empty string.`)
|
|
379
|
+
}
|
|
380
|
+
return value
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function validateExpiresAt(value: unknown): string {
|
|
384
|
+
if (typeof value !== 'string' || !Number.isFinite(Date.parse(value))) {
|
|
385
|
+
throw new TypeError('Interrupt expiresAt must be a valid date string.')
|
|
386
|
+
}
|
|
387
|
+
return value
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
391
|
+
return (
|
|
392
|
+
value !== null &&
|
|
393
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
394
|
+
'then' in value &&
|
|
395
|
+
typeof value.then === 'function'
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function parseInterruptPayload(
|
|
400
|
+
schema: PortableSchema,
|
|
401
|
+
value: unknown,
|
|
402
|
+
): unknown {
|
|
403
|
+
if (!isStandardSchema(schema)) return value
|
|
404
|
+
const result = schema['~standard'].validate(value)
|
|
405
|
+
if (isPromiseLike(result)) {
|
|
406
|
+
throw new TypeError(
|
|
407
|
+
'Interrupt payloadSchema validation must be synchronous.',
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
if (result.issues !== undefined) {
|
|
411
|
+
throw new TypeError(
|
|
412
|
+
`Interrupt payload is invalid: ${result.issues.map((issue) => issue.message).join(' ')}`,
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
return result.value
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function defineInterrupt<
|
|
419
|
+
const TId extends string,
|
|
420
|
+
const TPayloadSchema extends PortableSchema,
|
|
421
|
+
const TResponseSchema extends PortableSchema,
|
|
422
|
+
>(options: {
|
|
423
|
+
id: TId
|
|
424
|
+
payloadSchema: TPayloadSchema
|
|
425
|
+
responseSchema: TResponseSchema
|
|
426
|
+
}): DefinedInterruptDefinition<
|
|
427
|
+
TId,
|
|
428
|
+
TPayloadSchema,
|
|
429
|
+
TResponseSchema,
|
|
430
|
+
InferSchemaOutput<TPayloadSchema>,
|
|
431
|
+
InferSchemaInput<TPayloadSchema>
|
|
432
|
+
>
|
|
433
|
+
export function defineInterrupt<
|
|
434
|
+
const TId extends string,
|
|
435
|
+
const TPayloadSchema extends PortableSchema,
|
|
436
|
+
>(options: {
|
|
437
|
+
id: TId
|
|
438
|
+
payloadSchema: TPayloadSchema
|
|
439
|
+
responseSchema?: never
|
|
440
|
+
}): DefinedInterruptDefinition<
|
|
441
|
+
TId,
|
|
442
|
+
TPayloadSchema,
|
|
443
|
+
undefined,
|
|
444
|
+
InferSchemaOutput<TPayloadSchema>,
|
|
445
|
+
InferSchemaInput<TPayloadSchema>
|
|
446
|
+
>
|
|
447
|
+
export function defineInterrupt<
|
|
448
|
+
const TId extends string,
|
|
449
|
+
const TResponseSchema extends PortableSchema,
|
|
450
|
+
>(options: {
|
|
451
|
+
id: TId
|
|
452
|
+
responseSchema: TResponseSchema
|
|
453
|
+
payloadSchema?: never
|
|
454
|
+
}): DefinedInterruptDefinition<
|
|
455
|
+
TId,
|
|
456
|
+
undefined,
|
|
457
|
+
TResponseSchema,
|
|
458
|
+
undefined,
|
|
459
|
+
undefined
|
|
460
|
+
>
|
|
461
|
+
export function defineInterrupt<
|
|
462
|
+
const TId extends string,
|
|
463
|
+
const TPayloadSchema extends PortableSchema | undefined,
|
|
464
|
+
const TResponseSchema extends PortableSchema | undefined,
|
|
465
|
+
>(
|
|
466
|
+
options: InterruptDefinitionOptions<TId, TPayloadSchema, TResponseSchema>,
|
|
467
|
+
): InterruptDefinition<
|
|
468
|
+
TId,
|
|
469
|
+
TPayloadSchema,
|
|
470
|
+
TResponseSchema,
|
|
471
|
+
InferSchemaOutput<TPayloadSchema>,
|
|
472
|
+
InferSchemaInput<TPayloadSchema>
|
|
473
|
+
> {
|
|
474
|
+
validateNonEmptyString(options.id, 'Interrupt definition id')
|
|
475
|
+
const hasResponseSchema = options.responseSchema !== undefined
|
|
476
|
+
const responseJson = hasResponseSchema
|
|
477
|
+
? schemaJson(options.responseSchema, 'responseSchema')
|
|
478
|
+
: undefined
|
|
479
|
+
const hasPayloadSchema = Object.prototype.hasOwnProperty.call(
|
|
480
|
+
options,
|
|
481
|
+
'payloadSchema',
|
|
482
|
+
)
|
|
483
|
+
const payloadJson = hasPayloadSchema
|
|
484
|
+
? schemaJson(options.payloadSchema, 'payloadSchema')
|
|
485
|
+
: undefined
|
|
486
|
+
const schemaState: DefinitionSchemaState = {
|
|
487
|
+
...(responseJson
|
|
488
|
+
? {
|
|
489
|
+
responseSchemaCanonicalJson: responseJson.canonicalJson,
|
|
490
|
+
responseSchemaHash: digestInterruptJson(responseJson.canonicalJson),
|
|
491
|
+
}
|
|
492
|
+
: {}),
|
|
493
|
+
...(payloadJson
|
|
494
|
+
? {
|
|
495
|
+
payloadSchemaCanonicalJson: payloadJson.canonicalJson,
|
|
496
|
+
payloadSchemaHash: digestInterruptJson(payloadJson.canonicalJson),
|
|
497
|
+
}
|
|
498
|
+
: {}),
|
|
499
|
+
}
|
|
500
|
+
const definition = {
|
|
501
|
+
id: options.id,
|
|
502
|
+
payloadSchema: options.payloadSchema,
|
|
503
|
+
responseSchema: options.responseSchema,
|
|
504
|
+
interrupt(input: InterruptInput<TPayloadSchema>) {
|
|
505
|
+
return createRequest(input, false)
|
|
506
|
+
},
|
|
507
|
+
} as InterruptDefinition<
|
|
508
|
+
TId,
|
|
509
|
+
TPayloadSchema,
|
|
510
|
+
TResponseSchema,
|
|
511
|
+
InferSchemaOutput<TPayloadSchema>,
|
|
512
|
+
InferSchemaInput<TPayloadSchema>
|
|
513
|
+
>
|
|
514
|
+
const parsePayload = (payload: unknown): unknown => {
|
|
515
|
+
const payloadSchema = options.payloadSchema
|
|
516
|
+
if (payloadSchema === undefined) {
|
|
517
|
+
throw new TypeError(
|
|
518
|
+
'This interrupt definition does not accept a payload.',
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
return parseInterruptPayload(payloadSchema, payload)
|
|
522
|
+
}
|
|
523
|
+
const createRequest: InterruptRequestFactory = (input, payloadIsParsed) => {
|
|
524
|
+
for (const key of Object.keys(input)) {
|
|
525
|
+
if (!['key', 'payload', 'reason', 'message', 'expiresAt'].includes(key)) {
|
|
526
|
+
throw new TypeError(`Interrupt input field ${key} is not allowed.`)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const key = validateNonEmptyString(input.key, 'Interrupt key')
|
|
530
|
+
const reason = validateNonEmptyString(input.reason, 'Interrupt reason')
|
|
531
|
+
const message = validateNonEmptyString(input.message, 'Interrupt message')
|
|
532
|
+
if ('payload' in input) {
|
|
533
|
+
if (!hasPayloadSchema) {
|
|
534
|
+
throw new TypeError(
|
|
535
|
+
'This interrupt definition does not accept a payload.',
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
if (input.payload !== undefined) {
|
|
539
|
+
validateJson(input.payload, 'Interrupt payload')
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const parsedPayload =
|
|
543
|
+
'payload' in input
|
|
544
|
+
? payloadIsParsed
|
|
545
|
+
? input.payload
|
|
546
|
+
: parsePayload(input.payload)
|
|
547
|
+
: undefined
|
|
548
|
+
const payload =
|
|
549
|
+
parsedPayload === undefined
|
|
550
|
+
? undefined
|
|
551
|
+
: cloneAndDeepFreezeJson(parsedPayload)
|
|
552
|
+
const expiresAt =
|
|
553
|
+
input.expiresAt === undefined
|
|
554
|
+
? undefined
|
|
555
|
+
: validateExpiresAt(input.expiresAt)
|
|
556
|
+
const request = Object.freeze({
|
|
557
|
+
definition,
|
|
558
|
+
key,
|
|
559
|
+
...(hasPayloadSchema && payload !== undefined ? { payload } : {}),
|
|
560
|
+
reason,
|
|
561
|
+
message,
|
|
562
|
+
...(expiresAt !== undefined ? { expiresAt } : {}),
|
|
563
|
+
})
|
|
564
|
+
if (!payloadIsParsed) {
|
|
565
|
+
interruptRequestInputs.set(
|
|
566
|
+
request,
|
|
567
|
+
cloneAndDeepFreezeJson({
|
|
568
|
+
key,
|
|
569
|
+
reason,
|
|
570
|
+
message,
|
|
571
|
+
...(expiresAt !== undefined ? { expiresAt } : {}),
|
|
572
|
+
...(parsedPayload !== undefined ? { payload: parsedPayload } : {}),
|
|
573
|
+
}),
|
|
574
|
+
)
|
|
575
|
+
}
|
|
576
|
+
return request
|
|
577
|
+
}
|
|
578
|
+
definitionSchemaState.set(definition, schemaState)
|
|
579
|
+
interruptRequestFactories.set(definition, createRequest)
|
|
580
|
+
return Object.freeze(definition)
|
|
581
|
+
}
|