ai-database 2.0.2 → 2.1.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/CHANGELOG.md +36 -0
- package/dist/actions.d.ts +247 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +260 -0
- package/dist/actions.js.map +1 -0
- package/dist/ai-promise-db.d.ts +34 -2
- package/dist/ai-promise-db.d.ts.map +1 -1
- package/dist/ai-promise-db.js +511 -66
- package/dist/ai-promise-db.js.map +1 -1
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -0
- package/dist/events.d.ts +153 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +154 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/memory-provider.d.ts +144 -2
- package/dist/memory-provider.d.ts.map +1 -1
- package/dist/memory-provider.js +569 -13
- package/dist/memory-provider.js.map +1 -1
- package/dist/schema/cascade.d.ts +96 -0
- package/dist/schema/cascade.d.ts.map +1 -0
- package/dist/schema/cascade.js +528 -0
- package/dist/schema/cascade.js.map +1 -0
- package/dist/schema/index.d.ts +197 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +1211 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/parse.d.ts +225 -0
- package/dist/schema/parse.d.ts.map +1 -0
- package/dist/schema/parse.js +732 -0
- package/dist/schema/parse.js.map +1 -0
- package/dist/schema/provider.d.ts +176 -0
- package/dist/schema/provider.d.ts.map +1 -0
- package/dist/schema/provider.js +258 -0
- package/dist/schema/provider.js.map +1 -0
- package/dist/schema/resolve.d.ts +87 -0
- package/dist/schema/resolve.d.ts.map +1 -0
- package/dist/schema/resolve.js +474 -0
- package/dist/schema/resolve.js.map +1 -0
- package/dist/schema/semantic.d.ts +53 -0
- package/dist/schema/semantic.d.ts.map +1 -0
- package/dist/schema/semantic.js +247 -0
- package/dist/schema/semantic.js.map +1 -0
- package/dist/schema/types.d.ts +528 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +9 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema.d.ts +24 -867
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +41 -1124
- package/dist/schema.js.map +1 -1
- package/dist/semantic.d.ts +175 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +338 -0
- package/dist/semantic.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +13 -4
- package/.turbo/turbo-build.log +0 -5
- package/TESTING.md +0 -410
- package/TEST_SUMMARY.md +0 -250
- package/TODO.md +0 -128
- package/src/ai-promise-db.ts +0 -1243
- package/src/authorization.ts +0 -1102
- package/src/durable-clickhouse.ts +0 -596
- package/src/durable-promise.ts +0 -582
- package/src/execution-queue.ts +0 -608
- package/src/index.test.ts +0 -868
- package/src/index.ts +0 -337
- package/src/linguistic.ts +0 -404
- package/src/memory-provider.test.ts +0 -1036
- package/src/memory-provider.ts +0 -1119
- package/src/schema.test.ts +0 -1254
- package/src/schema.ts +0 -2296
- package/src/tests.ts +0 -725
- package/src/types.ts +0 -1177
- package/test/README.md +0 -153
- package/test/edge-cases.test.ts +0 -646
- package/test/provider-resolution.test.ts +0 -402
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
package/src/durable-promise.ts
DELETED
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Durable Promise - Promise-like wrapper around database Actions
|
|
3
|
-
*
|
|
4
|
-
* Time is an implementation detail. Whether an operation takes 10ms or 10 hours,
|
|
5
|
-
* the same code works. The DurablePromise persists its state as an Action,
|
|
6
|
-
* allowing crash recovery, observability, and time-agnostic execution.
|
|
7
|
-
*
|
|
8
|
-
* @packageDocumentation
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Action, MemoryProvider } from './memory-provider.js'
|
|
12
|
-
|
|
13
|
-
// =============================================================================
|
|
14
|
-
// Types
|
|
15
|
-
// =============================================================================
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Execution priority tiers
|
|
19
|
-
*
|
|
20
|
-
* - priority: Pay more for immediate execution (fastest, highest cost)
|
|
21
|
-
* - standard: Normal price/latency tradeoff (default)
|
|
22
|
-
* - flex: Discount for variable latency (cost savings)
|
|
23
|
-
* - batch: Maximum discount, 24h SLA (50% savings)
|
|
24
|
-
*/
|
|
25
|
-
export type ExecutionPriority = 'priority' | 'standard' | 'flex' | 'batch'
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Options for creating a DurablePromise
|
|
29
|
-
*/
|
|
30
|
-
export interface DurablePromiseOptions<T = unknown> {
|
|
31
|
-
/** Method identifier (e.g., 'ai.generate', 'db.get', 'api.fetch') */
|
|
32
|
-
method: string
|
|
33
|
-
|
|
34
|
-
/** Arguments passed to the method */
|
|
35
|
-
args?: unknown[]
|
|
36
|
-
|
|
37
|
-
/** The executor function that performs the actual work */
|
|
38
|
-
executor: () => Promise<T>
|
|
39
|
-
|
|
40
|
-
/** Execution priority tier (defaults to context or 'standard') */
|
|
41
|
-
priority?: ExecutionPriority
|
|
42
|
-
|
|
43
|
-
/** Concurrency key for queue grouping */
|
|
44
|
-
concurrencyKey?: string
|
|
45
|
-
|
|
46
|
-
/** Defer execution until this time (for batch windows) */
|
|
47
|
-
deferUntil?: Date
|
|
48
|
-
|
|
49
|
-
/** Action IDs this promise depends on (must complete first) */
|
|
50
|
-
dependsOn?: string[]
|
|
51
|
-
|
|
52
|
-
/** Actor identifier (who initiated this operation) */
|
|
53
|
-
actor?: string
|
|
54
|
-
|
|
55
|
-
/** Additional metadata */
|
|
56
|
-
meta?: Record<string, unknown>
|
|
57
|
-
|
|
58
|
-
/** Provider instance (if not using context) */
|
|
59
|
-
provider?: MemoryProvider
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Result of DurablePromise resolution
|
|
64
|
-
*/
|
|
65
|
-
export interface DurablePromiseResult<T> {
|
|
66
|
-
/** The resolved value */
|
|
67
|
-
value: T
|
|
68
|
-
/** The underlying Action record */
|
|
69
|
-
action: Action
|
|
70
|
-
/** Total execution time in ms */
|
|
71
|
-
duration: number
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Internal state for promise resolution
|
|
76
|
-
*/
|
|
77
|
-
interface PromiseState<T> {
|
|
78
|
-
status: 'pending' | 'resolved' | 'rejected'
|
|
79
|
-
value?: T
|
|
80
|
-
error?: Error
|
|
81
|
-
resolvers: Array<{
|
|
82
|
-
resolve: (value: T | PromiseLike<T>) => void
|
|
83
|
-
reject: (reason?: unknown) => void
|
|
84
|
-
}>
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// =============================================================================
|
|
88
|
-
// Context Stack (for inheriting priority from workflow)
|
|
89
|
-
// =============================================================================
|
|
90
|
-
|
|
91
|
-
interface ExecutionContext {
|
|
92
|
-
priority: ExecutionPriority
|
|
93
|
-
provider?: MemoryProvider
|
|
94
|
-
concurrencyKey?: string
|
|
95
|
-
actor?: string
|
|
96
|
-
batchWindow?: number
|
|
97
|
-
onFlush?: () => Promise<void>
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const contextStack: ExecutionContext[] = []
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get the current execution context
|
|
104
|
-
*/
|
|
105
|
-
export function getCurrentContext(): ExecutionContext | undefined {
|
|
106
|
-
return contextStack[contextStack.length - 1]
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Run code within an execution context
|
|
111
|
-
*/
|
|
112
|
-
export async function withContext<T>(
|
|
113
|
-
context: Partial<ExecutionContext>,
|
|
114
|
-
fn: () => Promise<T>
|
|
115
|
-
): Promise<T> {
|
|
116
|
-
const parent = getCurrentContext()
|
|
117
|
-
const merged: ExecutionContext = {
|
|
118
|
-
priority: context.priority ?? parent?.priority ?? 'standard',
|
|
119
|
-
provider: context.provider ?? parent?.provider,
|
|
120
|
-
concurrencyKey: context.concurrencyKey ?? parent?.concurrencyKey,
|
|
121
|
-
actor: context.actor ?? parent?.actor,
|
|
122
|
-
batchWindow: context.batchWindow ?? parent?.batchWindow,
|
|
123
|
-
onFlush: context.onFlush ?? parent?.onFlush,
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
contextStack.push(merged)
|
|
127
|
-
try {
|
|
128
|
-
return await fn()
|
|
129
|
-
} finally {
|
|
130
|
-
contextStack.pop()
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Set global default context
|
|
136
|
-
*/
|
|
137
|
-
export function setDefaultContext(context: Partial<ExecutionContext>): void {
|
|
138
|
-
if (contextStack.length === 0) {
|
|
139
|
-
contextStack.push({
|
|
140
|
-
priority: context.priority ?? 'standard',
|
|
141
|
-
provider: context.provider,
|
|
142
|
-
concurrencyKey: context.concurrencyKey,
|
|
143
|
-
actor: context.actor,
|
|
144
|
-
batchWindow: context.batchWindow,
|
|
145
|
-
onFlush: context.onFlush,
|
|
146
|
-
})
|
|
147
|
-
} else {
|
|
148
|
-
Object.assign(contextStack[0]!, context)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// =============================================================================
|
|
153
|
-
// DurablePromise Class
|
|
154
|
-
// =============================================================================
|
|
155
|
-
|
|
156
|
-
/** Symbol to identify DurablePromise instances */
|
|
157
|
-
export const DURABLE_PROMISE_SYMBOL = Symbol.for('ai-database.DurablePromise')
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* A Promise-like class that persists its state as an Action
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* ```ts
|
|
164
|
-
* const promise = new DurablePromise({
|
|
165
|
-
* method: 'ai.generate',
|
|
166
|
-
* args: [{ prompt: 'Hello' }],
|
|
167
|
-
* executor: async () => await ai.generate({ prompt: 'Hello' }),
|
|
168
|
-
* priority: 'batch',
|
|
169
|
-
* })
|
|
170
|
-
*
|
|
171
|
-
* // Access the underlying action
|
|
172
|
-
* console.log(promise.actionId)
|
|
173
|
-
*
|
|
174
|
-
* // Await like a normal promise
|
|
175
|
-
* const result = await promise
|
|
176
|
-
* ```
|
|
177
|
-
*/
|
|
178
|
-
export class DurablePromise<T> implements PromiseLike<T> {
|
|
179
|
-
readonly [DURABLE_PROMISE_SYMBOL] = true
|
|
180
|
-
|
|
181
|
-
/** The Action ID backing this promise */
|
|
182
|
-
readonly actionId: string
|
|
183
|
-
|
|
184
|
-
/** The method being executed */
|
|
185
|
-
readonly method: string
|
|
186
|
-
|
|
187
|
-
/** The execution priority */
|
|
188
|
-
readonly priority: ExecutionPriority
|
|
189
|
-
|
|
190
|
-
/** Dependencies that must complete first */
|
|
191
|
-
readonly dependsOn: string[]
|
|
192
|
-
|
|
193
|
-
private readonly state: PromiseState<T> = {
|
|
194
|
-
status: 'pending',
|
|
195
|
-
resolvers: [],
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private action: Action | null = null
|
|
199
|
-
private provider: MemoryProvider | null = null
|
|
200
|
-
private executor: (() => Promise<T>) | null = null
|
|
201
|
-
private startTime: number = Date.now()
|
|
202
|
-
|
|
203
|
-
constructor(options: DurablePromiseOptions<T>) {
|
|
204
|
-
const context = getCurrentContext()
|
|
205
|
-
|
|
206
|
-
this.method = options.method
|
|
207
|
-
this.priority = options.priority ?? context?.priority ?? 'standard'
|
|
208
|
-
this.dependsOn = options.dependsOn ?? []
|
|
209
|
-
this.provider = options.provider ?? context?.provider ?? null
|
|
210
|
-
this.executor = options.executor
|
|
211
|
-
this.actionId = crypto.randomUUID()
|
|
212
|
-
|
|
213
|
-
// Create the action immediately if we have a provider
|
|
214
|
-
if (this.provider) {
|
|
215
|
-
this.initializeAction(options)
|
|
216
|
-
} else {
|
|
217
|
-
// Defer action creation but start execution
|
|
218
|
-
this.executeDirectly()
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private async initializeAction(options: DurablePromiseOptions<T>): Promise<void> {
|
|
223
|
-
if (!this.provider) return
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
// Create the action record
|
|
227
|
-
this.action = await this.provider.createAction({
|
|
228
|
-
actor: options.actor ?? getCurrentContext()?.actor ?? 'system',
|
|
229
|
-
action: this.parseActionVerb(this.method),
|
|
230
|
-
object: this.method,
|
|
231
|
-
objectData: {
|
|
232
|
-
method: this.method,
|
|
233
|
-
args: options.args,
|
|
234
|
-
priority: this.priority,
|
|
235
|
-
concurrencyKey: options.concurrencyKey,
|
|
236
|
-
deferUntil: options.deferUntil?.toISOString(),
|
|
237
|
-
dependsOn: this.dependsOn,
|
|
238
|
-
},
|
|
239
|
-
meta: options.meta,
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
// Override the generated ID with our pre-generated one
|
|
243
|
-
// (This allows us to return actionId synchronously)
|
|
244
|
-
;(this.action as { id: string }).id = this.actionId
|
|
245
|
-
|
|
246
|
-
// Check if we should defer execution
|
|
247
|
-
if (this.priority === 'batch') {
|
|
248
|
-
// Register with the batch scheduler instead of executing immediately
|
|
249
|
-
const scheduler = getBatchScheduler()
|
|
250
|
-
if (scheduler) {
|
|
251
|
-
scheduler.enqueue(this as DurablePromise<unknown>)
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Check dependencies
|
|
257
|
-
if (this.dependsOn.length > 0) {
|
|
258
|
-
await this.waitForDependencies()
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Execute
|
|
262
|
-
await this.execute()
|
|
263
|
-
} catch (error) {
|
|
264
|
-
this.reject(error as Error)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
private parseActionVerb(method: string): string {
|
|
269
|
-
// Extract verb from method like 'ai.generate' -> 'generate'
|
|
270
|
-
const parts = method.split('.')
|
|
271
|
-
return parts[parts.length - 1] || 'process'
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private async waitForDependencies(): Promise<void> {
|
|
275
|
-
if (!this.provider || this.dependsOn.length === 0) return
|
|
276
|
-
|
|
277
|
-
// Poll for dependency completion
|
|
278
|
-
const checkInterval = 100 // ms
|
|
279
|
-
const maxWait = 24 * 60 * 60 * 1000 // 24 hours
|
|
280
|
-
|
|
281
|
-
const startWait = Date.now()
|
|
282
|
-
|
|
283
|
-
while (Date.now() - startWait < maxWait) {
|
|
284
|
-
const pending = await this.provider.listActions({
|
|
285
|
-
status: 'pending',
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
const active = await this.provider.listActions({
|
|
289
|
-
status: 'active',
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
const blockedBy = [...pending, ...active]
|
|
293
|
-
.filter((a) => this.dependsOn.includes(a.id))
|
|
294
|
-
.map((a) => a.id)
|
|
295
|
-
|
|
296
|
-
if (blockedBy.length === 0) {
|
|
297
|
-
// All dependencies resolved
|
|
298
|
-
return
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Wait before checking again
|
|
302
|
-
await new Promise((resolve) => setTimeout(resolve, checkInterval))
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
throw new Error(`Timeout waiting for dependencies: ${this.dependsOn.join(', ')}`)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private async execute(): Promise<void> {
|
|
309
|
-
if (!this.executor) {
|
|
310
|
-
throw new Error('No executor provided')
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
// Mark as active
|
|
315
|
-
if (this.provider && this.action) {
|
|
316
|
-
await this.provider.updateAction(this.actionId, { status: 'active' })
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Execute the actual work
|
|
320
|
-
const result = await this.executor()
|
|
321
|
-
|
|
322
|
-
// Mark as completed
|
|
323
|
-
if (this.provider && this.action) {
|
|
324
|
-
await this.provider.updateAction(this.actionId, {
|
|
325
|
-
status: 'completed',
|
|
326
|
-
result: { value: result as unknown as Record<string, unknown> },
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
this.resolve(result)
|
|
331
|
-
} catch (error) {
|
|
332
|
-
// Mark as failed
|
|
333
|
-
if (this.provider && this.action) {
|
|
334
|
-
await this.provider.updateAction(this.actionId, {
|
|
335
|
-
status: 'failed',
|
|
336
|
-
error: (error as Error).message,
|
|
337
|
-
})
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
this.reject(error as Error)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
private async executeDirectly(): Promise<void> {
|
|
345
|
-
if (!this.executor) {
|
|
346
|
-
throw new Error('No executor provided')
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const result = await this.executor()
|
|
351
|
-
this.resolve(result)
|
|
352
|
-
} catch (error) {
|
|
353
|
-
this.reject(error as Error)
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private resolve(value: T): void {
|
|
358
|
-
if (this.state.status !== 'pending') return
|
|
359
|
-
|
|
360
|
-
this.state.status = 'resolved'
|
|
361
|
-
this.state.value = value
|
|
362
|
-
|
|
363
|
-
for (const { resolve } of this.state.resolvers) {
|
|
364
|
-
resolve(value)
|
|
365
|
-
}
|
|
366
|
-
this.state.resolvers = []
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
private reject(error: Error): void {
|
|
370
|
-
if (this.state.status !== 'pending') return
|
|
371
|
-
|
|
372
|
-
this.state.status = 'rejected'
|
|
373
|
-
this.state.error = error
|
|
374
|
-
|
|
375
|
-
for (const { reject } of this.state.resolvers) {
|
|
376
|
-
reject(error)
|
|
377
|
-
}
|
|
378
|
-
this.state.resolvers = []
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Implement PromiseLike interface
|
|
383
|
-
*/
|
|
384
|
-
then<TResult1 = T, TResult2 = never>(
|
|
385
|
-
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
386
|
-
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
|
|
387
|
-
): Promise<TResult1 | TResult2> {
|
|
388
|
-
return new Promise<TResult1 | TResult2>((resolve, reject) => {
|
|
389
|
-
const handleResolve = (value: T) => {
|
|
390
|
-
if (onfulfilled) {
|
|
391
|
-
try {
|
|
392
|
-
resolve(onfulfilled(value))
|
|
393
|
-
} catch (e) {
|
|
394
|
-
reject(e)
|
|
395
|
-
}
|
|
396
|
-
} else {
|
|
397
|
-
resolve(value as unknown as TResult1)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const handleReject = (error: unknown) => {
|
|
402
|
-
if (onrejected) {
|
|
403
|
-
try {
|
|
404
|
-
resolve(onrejected(error))
|
|
405
|
-
} catch (e) {
|
|
406
|
-
reject(e)
|
|
407
|
-
}
|
|
408
|
-
} else {
|
|
409
|
-
reject(error)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (this.state.status === 'resolved') {
|
|
414
|
-
handleResolve(this.state.value!)
|
|
415
|
-
} else if (this.state.status === 'rejected') {
|
|
416
|
-
handleReject(this.state.error)
|
|
417
|
-
} else {
|
|
418
|
-
this.state.resolvers.push({
|
|
419
|
-
resolve: handleResolve as (value: T | PromiseLike<T>) => void,
|
|
420
|
-
reject: handleReject,
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
})
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Catch handler
|
|
428
|
-
*/
|
|
429
|
-
catch<TResult = never>(
|
|
430
|
-
onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
|
|
431
|
-
): Promise<T | TResult> {
|
|
432
|
-
return this.then(null, onrejected)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Finally handler
|
|
437
|
-
*/
|
|
438
|
-
finally(onfinally?: (() => void) | null): Promise<T> {
|
|
439
|
-
return this.then(
|
|
440
|
-
(value) => {
|
|
441
|
-
onfinally?.()
|
|
442
|
-
return value
|
|
443
|
-
},
|
|
444
|
-
(reason) => {
|
|
445
|
-
onfinally?.()
|
|
446
|
-
throw reason
|
|
447
|
-
}
|
|
448
|
-
)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Get the current status
|
|
453
|
-
*/
|
|
454
|
-
get status(): Action['status'] {
|
|
455
|
-
if (this.action) return this.action.status
|
|
456
|
-
if (this.state.status === 'resolved') return 'completed'
|
|
457
|
-
if (this.state.status === 'rejected') return 'failed'
|
|
458
|
-
return 'pending'
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Get the underlying Action (if available)
|
|
463
|
-
*/
|
|
464
|
-
async getAction(): Promise<Action | null> {
|
|
465
|
-
if (this.action) return this.action
|
|
466
|
-
if (!this.provider) return null
|
|
467
|
-
return this.provider.getAction(this.actionId)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Get the result with full metadata
|
|
472
|
-
*/
|
|
473
|
-
async getResult(): Promise<DurablePromiseResult<T>> {
|
|
474
|
-
const value = await this
|
|
475
|
-
const action = await this.getAction()
|
|
476
|
-
|
|
477
|
-
return {
|
|
478
|
-
value,
|
|
479
|
-
action: action!,
|
|
480
|
-
duration: Date.now() - this.startTime,
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Cancel the promise if still pending
|
|
486
|
-
*/
|
|
487
|
-
async cancel(): Promise<void> {
|
|
488
|
-
if (this.state.status !== 'pending') {
|
|
489
|
-
throw new Error('Cannot cancel a resolved or rejected promise')
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
if (this.provider) {
|
|
493
|
-
await this.provider.cancelAction(this.actionId)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
this.reject(new Error('Promise cancelled'))
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Retry a failed promise
|
|
501
|
-
*/
|
|
502
|
-
async retry(): Promise<this> {
|
|
503
|
-
if (this.state.status !== 'rejected') {
|
|
504
|
-
throw new Error('Can only retry failed promises')
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (this.provider) {
|
|
508
|
-
await this.provider.retryAction(this.actionId)
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Re-execute
|
|
512
|
-
this.state.status = 'pending'
|
|
513
|
-
await this.execute()
|
|
514
|
-
return this
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Check if a value is a DurablePromise
|
|
520
|
-
*/
|
|
521
|
-
export function isDurablePromise(value: unknown): value is DurablePromise<unknown> {
|
|
522
|
-
return (
|
|
523
|
-
typeof value === 'object' &&
|
|
524
|
-
value !== null &&
|
|
525
|
-
DURABLE_PROMISE_SYMBOL in value &&
|
|
526
|
-
(value as Record<symbol, unknown>)[DURABLE_PROMISE_SYMBOL] === true
|
|
527
|
-
)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Create a durable promise from an executor function
|
|
532
|
-
*/
|
|
533
|
-
export function durable<T>(
|
|
534
|
-
method: string,
|
|
535
|
-
executor: () => Promise<T>,
|
|
536
|
-
options?: Omit<DurablePromiseOptions<T>, 'method' | 'executor'>
|
|
537
|
-
): DurablePromise<T> {
|
|
538
|
-
return new DurablePromise({
|
|
539
|
-
method,
|
|
540
|
-
executor,
|
|
541
|
-
...options,
|
|
542
|
-
})
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// =============================================================================
|
|
546
|
-
// Batch Scheduler Singleton
|
|
547
|
-
// =============================================================================
|
|
548
|
-
|
|
549
|
-
let batchScheduler: BatchScheduler | null = null
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Get the global batch scheduler
|
|
553
|
-
*/
|
|
554
|
-
export function getBatchScheduler(): BatchScheduler | null {
|
|
555
|
-
return batchScheduler
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Set the global batch scheduler
|
|
560
|
-
*/
|
|
561
|
-
export function setBatchScheduler(scheduler: BatchScheduler | null): void {
|
|
562
|
-
batchScheduler = scheduler
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// =============================================================================
|
|
566
|
-
// BatchScheduler Interface (implemented in execution-queue.ts)
|
|
567
|
-
// =============================================================================
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Interface for batch scheduling
|
|
571
|
-
* Full implementation is in execution-queue.ts
|
|
572
|
-
*/
|
|
573
|
-
export interface BatchScheduler {
|
|
574
|
-
/** Add a promise to the batch queue */
|
|
575
|
-
enqueue(promise: DurablePromise<unknown>): void
|
|
576
|
-
|
|
577
|
-
/** Flush all pending batches */
|
|
578
|
-
flush(): Promise<void>
|
|
579
|
-
|
|
580
|
-
/** Get count of pending promises */
|
|
581
|
-
readonly pending: number
|
|
582
|
-
}
|