@slflows/sdk 0.0.2 → 0.0.4
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/v1/index.d.ts +683 -4
- package/package.json +3 -3
package/dist/v1/index.d.ts
CHANGED
|
@@ -1,15 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main schema interface for defining a Flows app.
|
|
3
|
+
*
|
|
4
|
+
* An app consists of blocks (entities) that can process events, manage state,
|
|
5
|
+
* expose HTTP endpoints, and handle scheduled tasks. Apps are the fundamental
|
|
6
|
+
* unit of deployment in Flows.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* export default defineApp({
|
|
11
|
+
* name: "Data Processor",
|
|
12
|
+
* blocks: {
|
|
13
|
+
* processor: {
|
|
14
|
+
* name: "Process Data",
|
|
15
|
+
* description: "Transforms incoming data",
|
|
16
|
+
* inputs: {
|
|
17
|
+
* data: {
|
|
18
|
+
* name: "Input Data",
|
|
19
|
+
* onEvent: async (input) => {
|
|
20
|
+
* // Process the event
|
|
21
|
+
* await events.emit(processedData);
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* },
|
|
25
|
+
* outputs: {
|
|
26
|
+
* processed: {
|
|
27
|
+
* name: "Processed Data",
|
|
28
|
+
* default: true
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
1
36
|
interface AppSchema {
|
|
37
|
+
/** Display name for the app */
|
|
2
38
|
name?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Instructions shown to users when installing the app.
|
|
41
|
+
* Supports Markdown formatting.
|
|
42
|
+
*/
|
|
3
43
|
installationInstructions?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Configuration fields for the app. These are set during installation
|
|
46
|
+
* and available to all blocks within the app.
|
|
47
|
+
*/
|
|
4
48
|
config?: Record<string, AppConfigField>;
|
|
49
|
+
/**
|
|
50
|
+
* Lifecycle hook called when the app needs to synchronize its state.
|
|
51
|
+
* Use this to provision resources, validate configuration, or perform setup.
|
|
52
|
+
*/
|
|
5
53
|
onSync?: (input: AppInput) => Promise<AppLifecycleCallbackOutput>;
|
|
54
|
+
/**
|
|
55
|
+
* Lifecycle hook called when the app is being drained (shut down).
|
|
56
|
+
* Use this to clean up resources, save state, or perform teardown.
|
|
57
|
+
*/
|
|
6
58
|
onDrain?: (input: AppInput) => Promise<AppLifecycleCallbackOutput>;
|
|
59
|
+
/** Custom status description shown when the app is in draft mode */
|
|
7
60
|
draftCustomStatusDescription?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Handler for internal messages sent to the app from blocks.
|
|
63
|
+
* Used for app-level coordination and communication.
|
|
64
|
+
*/
|
|
8
65
|
onInternalMessage?: (input: AppOnInternalMessageInput) => Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Map of block definitions. Each block becomes an entity that can be
|
|
68
|
+
* placed on the flow canvas. Key is the block type ID.
|
|
69
|
+
*/
|
|
9
70
|
blocks: Record<string, AppBlock>;
|
|
71
|
+
/** HTTP server configuration for app-level endpoints */
|
|
10
72
|
http?: AppHTTPComponent;
|
|
73
|
+
/** UI component configuration for custom app interfaces */
|
|
11
74
|
ui?: AppUIComponent;
|
|
75
|
+
/**
|
|
76
|
+
* Declarative schedules that trigger at app level.
|
|
77
|
+
* Use for periodic maintenance, data synchronization, etc.
|
|
78
|
+
*/
|
|
12
79
|
schedules?: Record<string, AppSchedule>;
|
|
80
|
+
/**
|
|
81
|
+
* Handler for imperative timers set by the app.
|
|
82
|
+
* Called when a timer created with timers.set() expires.
|
|
83
|
+
*/
|
|
13
84
|
onTimer?: (input: AppOnTimerInput) => Promise<void>;
|
|
14
85
|
}
|
|
15
86
|
interface AppUIComponent {
|
|
@@ -26,7 +97,9 @@ interface JsonSchema {
|
|
|
26
97
|
anyOf?: JsonSchema[];
|
|
27
98
|
oneOf?: JsonSchema[];
|
|
28
99
|
description?: string;
|
|
29
|
-
additionalProperties?: boolean
|
|
100
|
+
additionalProperties?: boolean | {
|
|
101
|
+
type: "string" | "number" | "boolean" | "object" | "array";
|
|
102
|
+
};
|
|
30
103
|
}
|
|
31
104
|
type Type = SimpleType | SimpleTypeArray | JsonSchema;
|
|
32
105
|
interface AppConfigField {
|
|
@@ -128,27 +201,61 @@ interface EntityView {
|
|
|
128
201
|
type: EntityViewType;
|
|
129
202
|
}
|
|
130
203
|
type EntityViewType = "default" | "fullScreen" | "regular";
|
|
204
|
+
/**
|
|
205
|
+
* Input context provided to app-level handlers.
|
|
206
|
+
* Contains app configuration and runtime information.
|
|
207
|
+
*/
|
|
131
208
|
interface AppInput {
|
|
209
|
+
/** App installation context and configuration */
|
|
132
210
|
app: AppContext;
|
|
133
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* App runtime context containing configuration and endpoints.
|
|
214
|
+
* Available in all app-level handlers (onSync, onDrain, onInternalMessage, etc.).
|
|
215
|
+
*/
|
|
134
216
|
interface AppContext {
|
|
217
|
+
/** App configuration values set during installation */
|
|
135
218
|
config: Record<string, any>;
|
|
136
|
-
|
|
219
|
+
/** Current status of the app installation */
|
|
220
|
+
status: "draft" | "in_progress" | "ready" | "failed" | "draining" | "draining_failed" | "drained";
|
|
221
|
+
/** HTTP endpoint information for this app */
|
|
137
222
|
http: AppHTTPEndpoint;
|
|
223
|
+
/** URL for managing this app installation */
|
|
138
224
|
installationUrl: string;
|
|
225
|
+
/** Signals exported by this app */
|
|
226
|
+
signals: Record<string, any>;
|
|
139
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* HTTP endpoint information for app-level HTTP handlers.
|
|
230
|
+
*/
|
|
140
231
|
interface AppHTTPEndpoint {
|
|
232
|
+
/** Base URL for this app's HTTP endpoints */
|
|
141
233
|
url: string;
|
|
142
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Input context provided to block-level handlers.
|
|
237
|
+
* Extends AppInput with block-specific context.
|
|
238
|
+
*/
|
|
143
239
|
interface EntityInput extends AppInput {
|
|
240
|
+
/** Block (entity) instance context and configuration */
|
|
144
241
|
block: EntityContext;
|
|
145
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Block (entity) runtime context containing configuration and state.
|
|
245
|
+
* Available in all block-level handlers (onEvent, onSync, onDrain, etc.).
|
|
246
|
+
*/
|
|
146
247
|
interface EntityContext {
|
|
248
|
+
/** Unique identifier for this block instance */
|
|
147
249
|
id: string;
|
|
250
|
+
/** Display name of this block instance */
|
|
148
251
|
name: string;
|
|
252
|
+
/** Description of this block instance */
|
|
149
253
|
description: string;
|
|
254
|
+
/** Block configuration values set in the flow */
|
|
150
255
|
config: Record<string, any>;
|
|
256
|
+
/** Lifecycle state information (null if block has no lifecycle) */
|
|
151
257
|
lifecycle: EntityLifecycleComponent | null;
|
|
258
|
+
/** HTTP endpoint information (null if block has no HTTP handler) */
|
|
152
259
|
http: EntityHTTPEndpoint | null;
|
|
153
260
|
}
|
|
154
261
|
interface EntityLifecycleComponent {
|
|
@@ -159,14 +266,31 @@ type EntityLifecycleStatus = "draft" | "in_progress" | "ready" | "drifted" | "fa
|
|
|
159
266
|
interface EntityHTTPEndpoint {
|
|
160
267
|
url: string;
|
|
161
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Input context provided to event handlers (onEvent).
|
|
271
|
+
* Extends EntityInput with event-specific information.
|
|
272
|
+
*/
|
|
162
273
|
interface EventInput extends EntityInput {
|
|
274
|
+
/** Event data and metadata */
|
|
163
275
|
event: EventContext;
|
|
164
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Event context containing the event data and metadata.
|
|
279
|
+
* Available in all onEvent handlers when processing incoming events.
|
|
280
|
+
*/
|
|
165
281
|
interface EventContext {
|
|
282
|
+
/** Unique identifier for this event */
|
|
166
283
|
id: string;
|
|
284
|
+
/** Configuration values for the specific input that received this event */
|
|
167
285
|
inputConfig: Record<string, any>;
|
|
286
|
+
/**
|
|
287
|
+
* Echo information for request-response patterns.
|
|
288
|
+
* Present when this event was emitted with echo: true.
|
|
289
|
+
*/
|
|
168
290
|
echo?: {
|
|
291
|
+
/** The event body that was echoed */
|
|
169
292
|
body: any;
|
|
293
|
+
/** The output key where the original event was emitted */
|
|
170
294
|
outputKey: string;
|
|
171
295
|
};
|
|
172
296
|
}
|
|
@@ -257,68 +381,209 @@ interface UIRequest {
|
|
|
257
381
|
payload?: any;
|
|
258
382
|
}
|
|
259
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Key-value pair for storage operations.
|
|
386
|
+
* Supports TTL (time-to-live) and optimistic locking for safe concurrent access.
|
|
387
|
+
*/
|
|
260
388
|
interface KVPair {
|
|
389
|
+
/** The storage key */
|
|
261
390
|
key: string;
|
|
391
|
+
/**
|
|
392
|
+
* The stored value. May be undefined (key doesn't exist),
|
|
393
|
+
* null (explicitly set to null), or any other value.
|
|
394
|
+
*/
|
|
262
395
|
value?: any;
|
|
396
|
+
/** Unix timestamp of when this key was last updated */
|
|
263
397
|
updatedAt?: number;
|
|
398
|
+
/** Time-to-live in seconds. If set, key will expire after this duration */
|
|
264
399
|
ttl?: number;
|
|
400
|
+
/** Optional lock information for atomic operations */
|
|
265
401
|
lock?: {
|
|
402
|
+
/** Unique lock identifier */
|
|
266
403
|
id: string;
|
|
404
|
+
/** Lock timeout in seconds */
|
|
267
405
|
timeout?: number;
|
|
268
406
|
};
|
|
269
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Input parameters for listing key-value pairs with optional pagination.
|
|
410
|
+
* Used with kv.app.list() and kv.block.list() to retrieve multiple keys.
|
|
411
|
+
*/
|
|
270
412
|
interface KVListInput {
|
|
413
|
+
/** Prefix to filter keys (e.g., "user:" to get all user-related keys) */
|
|
271
414
|
keyPrefix: string;
|
|
415
|
+
/** Starting key for pagination (from previous nextStartingKey) */
|
|
272
416
|
startingKey?: string;
|
|
273
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Output from key-value list operations with pagination support.
|
|
420
|
+
* Contains the retrieved key-value pairs and pagination information.
|
|
421
|
+
*/
|
|
274
422
|
interface KVListOutput {
|
|
423
|
+
/** Array of key-value pairs matching the prefix */
|
|
275
424
|
pairs: Array<KVPair>;
|
|
425
|
+
/** Next starting key for pagination (undefined if no more results) */
|
|
276
426
|
nextStartingKey?: string;
|
|
277
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Input parameters for listing blocks within the same app installation.
|
|
430
|
+
* Used to discover other blocks for coordination and messaging.
|
|
431
|
+
*/
|
|
278
432
|
interface ListBlocksInput {
|
|
433
|
+
/** Filter by specific block type IDs (omit to list all blocks) */
|
|
279
434
|
typeIds?: string[];
|
|
280
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Output from blocks.list() operation containing discovered blocks.
|
|
438
|
+
* Provides information about other blocks in the same app installation.
|
|
439
|
+
*/
|
|
281
440
|
interface ListBlocksOutput {
|
|
441
|
+
/** Array of blocks found in the app installation */
|
|
282
442
|
blocks: {
|
|
443
|
+
/** Unique identifier of the block instance */
|
|
283
444
|
id: string;
|
|
445
|
+
/** Type ID of the block (from the app's block definitions) */
|
|
284
446
|
typeId: string;
|
|
447
|
+
/** Current configuration values of the block */
|
|
285
448
|
config: Record<string, any>;
|
|
286
449
|
}[];
|
|
287
450
|
}
|
|
288
|
-
|
|
451
|
+
/**
|
|
452
|
+
* Options for emitting events to downstream blocks.
|
|
453
|
+
*/
|
|
289
454
|
interface EmitOptions {
|
|
455
|
+
/** The output key to emit on (defaults to default output) */
|
|
290
456
|
outputKey?: string;
|
|
457
|
+
/** Parent event ID for lineage tracking */
|
|
291
458
|
parentEventId?: string;
|
|
459
|
+
/** Additional parent event IDs for complex lineage */
|
|
292
460
|
secondaryParentEventIds?: string[];
|
|
461
|
+
/**
|
|
462
|
+
* Whether to echo this event back for request-response patterns.
|
|
463
|
+
*
|
|
464
|
+
* When enabled, if this block later receives an event that has one of its own
|
|
465
|
+
* events in its ancestry, the original event will be automatically made available
|
|
466
|
+
* in input.event.echo. This eliminates the need for manual tracking of
|
|
467
|
+
* request-response relationships.
|
|
468
|
+
*
|
|
469
|
+
* The echo contains:
|
|
470
|
+
* - body: The original event payload
|
|
471
|
+
* - outputKey: The output ID where the original event was emitted
|
|
472
|
+
*
|
|
473
|
+
* Particularly useful for:
|
|
474
|
+
* - HTTP request/response patterns
|
|
475
|
+
* - Multi-step workflows where responses need to be matched to original requests
|
|
476
|
+
* - Any scenario where maintaining context across multiple events is important
|
|
477
|
+
*/
|
|
293
478
|
echo?: boolean;
|
|
479
|
+
/** Pending event ID to complete when emitting */
|
|
294
480
|
complete?: string;
|
|
295
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Options for creating a pending event.
|
|
484
|
+
*/
|
|
296
485
|
interface CreatePendingEventOptions {
|
|
486
|
+
/** Optional predicted event payload */
|
|
297
487
|
event?: Record<string, any>;
|
|
488
|
+
/** Which output this will emit on (optional) */
|
|
298
489
|
outputId?: string;
|
|
490
|
+
/** Parent event (auto-populated in handlers) */
|
|
299
491
|
parentEventId?: string;
|
|
492
|
+
/** Additional parent event IDs for complex lineage */
|
|
300
493
|
secondaryParentEventIds?: string[];
|
|
494
|
+
/** User-readable status message */
|
|
301
495
|
statusDescription: string | null;
|
|
302
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Options for updating an existing pending event.
|
|
499
|
+
* Used with updatePendingEvent() to modify pending event data or status.
|
|
500
|
+
*/
|
|
303
501
|
interface UpdatePendingEventOptions {
|
|
502
|
+
/** Updated event data (replaces previous event data) */
|
|
304
503
|
event?: Record<string, any>;
|
|
504
|
+
/** Updated output ID to emit on when completed */
|
|
305
505
|
outputId?: string;
|
|
506
|
+
/** Updated parent event ID for lineage tracking */
|
|
306
507
|
parentEventId?: string;
|
|
508
|
+
/** Updated additional parent event IDs */
|
|
307
509
|
secondaryParentEventIds?: string[];
|
|
510
|
+
/** Updated status description shown while pending */
|
|
308
511
|
statusDescription?: string | null;
|
|
309
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Options for creating user prompts in approval workflows.
|
|
515
|
+
* Used with createPrompt() to request manual user input or approval.
|
|
516
|
+
*/
|
|
310
517
|
interface PromptOptions {
|
|
518
|
+
/** Redirect configuration for the approval interface */
|
|
311
519
|
redirect: {
|
|
520
|
+
/** URL where users will be redirected for approval */
|
|
312
521
|
url: string;
|
|
522
|
+
/** HTTP method for the approval request */
|
|
313
523
|
method: "GET" | "POST";
|
|
524
|
+
/** Form fields to include with POST requests */
|
|
314
525
|
formFields?: Record<string, string>;
|
|
315
526
|
};
|
|
316
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Metadata about the current function invocation.
|
|
530
|
+
* Provides context for logging, debugging, and correlation.
|
|
531
|
+
*/
|
|
317
532
|
interface InvocationMetadata {
|
|
533
|
+
/** Unique identifier for this invocation */
|
|
318
534
|
invocationId: string;
|
|
535
|
+
/** Unix timestamp when the invocation started */
|
|
319
536
|
timestamp: number;
|
|
320
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Gets metadata about the current function invocation.
|
|
540
|
+
*
|
|
541
|
+
* Provides information about when and how the current handler was invoked.
|
|
542
|
+
* Useful for logging, debugging, and correlation across distributed operations.
|
|
543
|
+
*
|
|
544
|
+
* @returns Promise resolving to invocation metadata
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* ```typescript
|
|
548
|
+
* const metadata = await getInvocationMetadata();
|
|
549
|
+
* console.log(`Invocation ${metadata.invocationId} started at ${new Date(metadata.timestamp)}`);
|
|
550
|
+
*
|
|
551
|
+
* // Use in logging for correlation
|
|
552
|
+
* console.log(`[${metadata.invocationId}] Processing event:`, input.event.body);
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
321
555
|
declare const getInvocationMetadata: () => Promise<InvocationMetadata>;
|
|
556
|
+
/**
|
|
557
|
+
* Events service for emitting and managing events within flows.
|
|
558
|
+
*
|
|
559
|
+
* Events are the primary way blocks communicate with each other.
|
|
560
|
+
* The events service provides both immediate emission and pending event management
|
|
561
|
+
* for handling long-running operations.
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* ```typescript
|
|
565
|
+
* // Simple event emission
|
|
566
|
+
* await events.emit({ message: "Hello World" });
|
|
567
|
+
*
|
|
568
|
+
* // Emit to specific output
|
|
569
|
+
* await events.emit(data, { outputId: "success" });
|
|
570
|
+
*
|
|
571
|
+
* // Create pending event for long operation
|
|
572
|
+
* const pendingId = await events.createPending({
|
|
573
|
+
* statusDescription: "Processing data...",
|
|
574
|
+
* event: { progress: 0 }
|
|
575
|
+
* });
|
|
576
|
+
*
|
|
577
|
+
* // Update progress
|
|
578
|
+
* await events.updatePending(pendingId, {
|
|
579
|
+
* event: { progress: 50 },
|
|
580
|
+
* statusDescription: "Half complete..."
|
|
581
|
+
* });
|
|
582
|
+
*
|
|
583
|
+
* // Complete the operation
|
|
584
|
+
* await events.completePending(pendingId);
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
322
587
|
declare const events: {
|
|
323
588
|
emit: (event: any, options?: EmitOptions) => Promise<void>;
|
|
324
589
|
createPending: (options: CreatePendingEventOptions) => Promise<string>;
|
|
@@ -326,7 +591,112 @@ declare const events: {
|
|
|
326
591
|
cancelPending: (pendingEventId: string, reason: string) => Promise<void>;
|
|
327
592
|
completePending: (pendingEventId: string) => Promise<void>;
|
|
328
593
|
};
|
|
594
|
+
/**
|
|
595
|
+
* Key-Value storage service for persisting data at block and app level.
|
|
596
|
+
*
|
|
597
|
+
* KV storage provides persistent, scoped storage with TTL support and optimistic locking.
|
|
598
|
+
* Block-level storage is isolated per block instance, while app-level storage is shared
|
|
599
|
+
* across all blocks in the app installation.
|
|
600
|
+
*
|
|
601
|
+
* ## Locking Mechanism
|
|
602
|
+
*
|
|
603
|
+
* The KV service includes a robust locking system for coordinating concurrent access:
|
|
604
|
+
*
|
|
605
|
+
* - **Lock Acquisition**: Use unique lock IDs to acquire exclusive access to keys
|
|
606
|
+
* - **Timeout Management**: Locks automatically expire after specified duration
|
|
607
|
+
* - **Lock Ownership**: Only the exact lock ID can modify or release the lock
|
|
608
|
+
* - **Graceful Failures**: Lock conflicts return `false` instead of throwing errors
|
|
609
|
+
* - **Automatic Cleanup**: Expired locks are automatically cleared
|
|
610
|
+
* - **Database-Level Safety**: Uses PostgreSQL row-level locks for atomicity
|
|
611
|
+
*
|
|
612
|
+
* ## Lock Behavior Details
|
|
613
|
+
*
|
|
614
|
+
* - **Conflict Resolution**: If a key is already locked by a different lock ID,
|
|
615
|
+
* operations return `success: false` rather than throwing errors
|
|
616
|
+
* - **Lock Expiration**: Locks with timeouts are automatically considered expired
|
|
617
|
+
* when `lock_timeout < NOW()`, allowing new acquisitions
|
|
618
|
+
* - **Lock Renewal**: Extend lock duration using `renewLock()` with the same lock ID
|
|
619
|
+
* - **Permanent Locks**: Omit timeout for locks that persist until explicitly released
|
|
620
|
+
*
|
|
621
|
+
* @example
|
|
622
|
+
* ```typescript
|
|
623
|
+
* // Block-level storage (scoped to this block)
|
|
624
|
+
* await kv.block.set({
|
|
625
|
+
* key: "counter",
|
|
626
|
+
* value: 42,
|
|
627
|
+
* ttl: 3600 // 1 hour TTL
|
|
628
|
+
* });
|
|
629
|
+
* const counter = await kv.block.get("counter");
|
|
630
|
+
*
|
|
631
|
+
* // App-level storage (shared across all blocks)
|
|
632
|
+
* await kv.app.set({
|
|
633
|
+
* key: "shared-config",
|
|
634
|
+
* value: { apiKey: "secret" }
|
|
635
|
+
* });
|
|
636
|
+
*
|
|
637
|
+
* // Safe concurrent access with locking
|
|
638
|
+
* import { randomUUID } from "crypto";
|
|
639
|
+
*
|
|
640
|
+
* const lockId = randomUUID();
|
|
641
|
+
* const acquired = await kv.app.set({
|
|
642
|
+
* key: "shared-counter",
|
|
643
|
+
* value: "processing",
|
|
644
|
+
* lock: { id: lockId, timeout: 30 } // 30 second timeout
|
|
645
|
+
* });
|
|
646
|
+
*
|
|
647
|
+
* if (acquired) {
|
|
648
|
+
* try {
|
|
649
|
+
* // Perform atomic read-modify-write operation
|
|
650
|
+
* const current = await kv.app.get("shared-counter");
|
|
651
|
+
*
|
|
652
|
+
* // Simulate some processing time
|
|
653
|
+
* await new Promise(resolve => setTimeout(resolve, 1000));
|
|
654
|
+
*
|
|
655
|
+
* // Update with same lock ID
|
|
656
|
+
* const updated = await kv.app.set({
|
|
657
|
+
* key: "shared-counter",
|
|
658
|
+
* value: current.value + 1,
|
|
659
|
+
* lock: { id: lockId } // Same lock ID required for updates
|
|
660
|
+
* });
|
|
661
|
+
*
|
|
662
|
+
* if (!updated) {
|
|
663
|
+
* throw new Error("Lock was lost during operation");
|
|
664
|
+
* }
|
|
665
|
+
* } finally {
|
|
666
|
+
* // Always release the lock to avoid blocking other operations
|
|
667
|
+
* await kv.app.releaseLock({ key: "shared-counter", lockId });
|
|
668
|
+
* }
|
|
669
|
+
* } else {
|
|
670
|
+
* // Lock acquisition failed - another process has the lock
|
|
671
|
+
* console.log("Resource is currently locked, will retry later");
|
|
672
|
+
*
|
|
673
|
+
* // Implement retry logic with exponential backoff
|
|
674
|
+
* setTimeout(() => { yourRetryLogic }, 1000);
|
|
675
|
+
* }
|
|
676
|
+
*
|
|
677
|
+
* // Renew lock for long-running operations
|
|
678
|
+
* const lockId2 = randomUUID();
|
|
679
|
+
* await kv.app.set({
|
|
680
|
+
* key: "long-process",
|
|
681
|
+
* value: "starting",
|
|
682
|
+
* lock: { id: lockId2, timeout: 30 }
|
|
683
|
+
* });
|
|
684
|
+
*
|
|
685
|
+
* // Extend lock before it expires
|
|
686
|
+
* const renewed = await kv.app.renewLock({
|
|
687
|
+
* key: "long-process",
|
|
688
|
+
* lock: { id: lockId2, timeout: 60 } // Extend for 60 more seconds
|
|
689
|
+
* });
|
|
690
|
+
*
|
|
691
|
+
* if (renewed) {
|
|
692
|
+
* console.log("Lock extended successfully");
|
|
693
|
+
* } else {
|
|
694
|
+
* console.log("Lock renewal failed - may have expired or been released");
|
|
695
|
+
* }
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
329
698
|
declare const kv: {
|
|
699
|
+
/** Block-scoped key-value storage (isolated per block instance) */
|
|
330
700
|
block: {
|
|
331
701
|
getMany: (keys: string[]) => Promise<KVPair[]>;
|
|
332
702
|
get: (key: string) => Promise<KVPair>;
|
|
@@ -346,6 +716,7 @@ declare const kv: {
|
|
|
346
716
|
};
|
|
347
717
|
}) => Promise<boolean>;
|
|
348
718
|
};
|
|
719
|
+
/** App-scoped key-value storage (shared across all blocks in the app) */
|
|
349
720
|
app: {
|
|
350
721
|
getMany: (keys: string[]) => Promise<KVPair[]>;
|
|
351
722
|
get: (key: string) => Promise<KVPair>;
|
|
@@ -366,6 +737,27 @@ declare const kv: {
|
|
|
366
737
|
}) => Promise<boolean>;
|
|
367
738
|
};
|
|
368
739
|
};
|
|
740
|
+
/**
|
|
741
|
+
* Messaging service for internal communication between app components.
|
|
742
|
+
*
|
|
743
|
+
* Enables blocks to send messages to other blocks or to the app level,
|
|
744
|
+
* bypassing the normal event flow. Useful for coordination, service discovery,
|
|
745
|
+
* and notifications.
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* ```typescript
|
|
749
|
+
* // Send message to specific blocks
|
|
750
|
+
* await messaging.sendToBlocks({
|
|
751
|
+
* body: { action: "refresh", data: newConfig },
|
|
752
|
+
* blockIds: ["block-1", "block-2"]
|
|
753
|
+
* });
|
|
754
|
+
*
|
|
755
|
+
* // Send message to app level
|
|
756
|
+
* await messaging.sendToApp({
|
|
757
|
+
* body: { type: "status-update", status: "ready" }
|
|
758
|
+
* });
|
|
759
|
+
* ```
|
|
760
|
+
*/
|
|
369
761
|
declare const messaging: {
|
|
370
762
|
sendToBlocks: (input: {
|
|
371
763
|
body: unknown;
|
|
@@ -375,9 +767,36 @@ declare const messaging: {
|
|
|
375
767
|
body: unknown;
|
|
376
768
|
}) => Promise<void>;
|
|
377
769
|
};
|
|
770
|
+
/**
|
|
771
|
+
* Blocks service for querying other blocks within the same app installation.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```typescript
|
|
775
|
+
* // List all blocks
|
|
776
|
+
* const allBlocks = await blocks.list();
|
|
777
|
+
*
|
|
778
|
+
* // List specific block types
|
|
779
|
+
* const processors = await blocks.list({
|
|
780
|
+
* typeIds: ["dataProcessor", "validator"]
|
|
781
|
+
* });
|
|
782
|
+
* ```
|
|
783
|
+
*/
|
|
378
784
|
declare const blocks: {
|
|
379
785
|
list: (input?: ListBlocksInput) => Promise<ListBlocksOutput>;
|
|
380
786
|
};
|
|
787
|
+
/**
|
|
788
|
+
* HTTP service for responding to HTTP requests.
|
|
789
|
+
*
|
|
790
|
+
* @example
|
|
791
|
+
* ```typescript
|
|
792
|
+
* // In an HTTP handler
|
|
793
|
+
* await http.respond(input.request.requestId, {
|
|
794
|
+
* statusCode: 200,
|
|
795
|
+
* headers: { "Content-Type": "application/json" },
|
|
796
|
+
* body: { success: true, data: result }
|
|
797
|
+
* });
|
|
798
|
+
* ```
|
|
799
|
+
*/
|
|
381
800
|
declare const http: {
|
|
382
801
|
respond: (requestId: string, response: {
|
|
383
802
|
statusCode?: number;
|
|
@@ -385,6 +804,190 @@ declare const http: {
|
|
|
385
804
|
body?: unknown;
|
|
386
805
|
}) => Promise<void>;
|
|
387
806
|
};
|
|
807
|
+
/**
|
|
808
|
+
* Lifecycle service for managing block state, resource provisioning, and user prompts.
|
|
809
|
+
*
|
|
810
|
+
* The lifecycle service enables blocks to manage their state in a controlled, observable way.
|
|
811
|
+
* It provides mechanisms for blocks to reconcile their desired state with actual state,
|
|
812
|
+
* report status, and share computed values with other blocks through signals.
|
|
813
|
+
*
|
|
814
|
+
* ## Core Concepts
|
|
815
|
+
*
|
|
816
|
+
* - **State Management**: Track and update block status through defined lifecycle phases
|
|
817
|
+
* - **Resource Provisioning**: Create, update, and cleanup external resources (cloud, APIs, etc.)
|
|
818
|
+
* - **Signal System**: Share computed values between blocks for reactive dependencies
|
|
819
|
+
* - **Status Reporting**: Provide meaningful status descriptions for monitoring and debugging
|
|
820
|
+
* - **Approval Workflows**: Implement manual approval steps using prompts and proceed operations
|
|
821
|
+
*
|
|
822
|
+
* ## Lifecycle States
|
|
823
|
+
*
|
|
824
|
+
* - `draft`: Initial state, block configured but hasn't processed state yet
|
|
825
|
+
* - `in_progress`: Block currently computing or updating its state
|
|
826
|
+
* - `ready`: All calculations/operations complete, block ready for use
|
|
827
|
+
* - `failed`: Block failed to compute, update, or maintain its state
|
|
828
|
+
* - `draining`: Block currently cleaning up resources or state
|
|
829
|
+
* - `draining_failed`: Cleanup operations failed
|
|
830
|
+
* - `drained`: All cleanup successfully completed
|
|
831
|
+
*
|
|
832
|
+
* ## Implementation Pattern
|
|
833
|
+
*
|
|
834
|
+
* Blocks with lifecycle implement `onSync` and `onDrain` handlers:
|
|
835
|
+
* - **onSync**: Reconcile desired state, provision resources, update signals
|
|
836
|
+
* - **onDrain**: Clean up resources when block is removed or app is deleted
|
|
837
|
+
* - **signals**: Export computed values for other blocks to reference
|
|
838
|
+
*
|
|
839
|
+
* ## Signal Dependencies
|
|
840
|
+
*
|
|
841
|
+
* Signals create reactive dependencies between blocks. When a block updates its signals,
|
|
842
|
+
* dependent blocks automatically trigger their sync operations to adapt to changes.
|
|
843
|
+
* This enables building complex reactive systems where changes cascade automatically.
|
|
844
|
+
*
|
|
845
|
+
* @example
|
|
846
|
+
* ```typescript
|
|
847
|
+
* // Complete lifecycle example with resource management
|
|
848
|
+
* export const cloudResourceBlock: AppBlock = {
|
|
849
|
+
* name: "Cloud Storage Bucket",
|
|
850
|
+
* description: "Manages a cloud storage bucket with lifecycle",
|
|
851
|
+
*
|
|
852
|
+
* config: {
|
|
853
|
+
* bucketName: {
|
|
854
|
+
* name: "Bucket Name",
|
|
855
|
+
* type: "string",
|
|
856
|
+
* required: true
|
|
857
|
+
* },
|
|
858
|
+
* region: {
|
|
859
|
+
* name: "Region",
|
|
860
|
+
* type: "string",
|
|
861
|
+
* required: true,
|
|
862
|
+
* default: "us-west-2"
|
|
863
|
+
* }
|
|
864
|
+
* },
|
|
865
|
+
*
|
|
866
|
+
* // Values shared with other blocks
|
|
867
|
+
* signals: {
|
|
868
|
+
* bucketId: {
|
|
869
|
+
* name: "Bucket ID",
|
|
870
|
+
* description: "The unique identifier of the created bucket"
|
|
871
|
+
* },
|
|
872
|
+
* bucketUrl: {
|
|
873
|
+
* name: "Bucket URL",
|
|
874
|
+
* description: "The access URL for the bucket"
|
|
875
|
+
* }
|
|
876
|
+
* },
|
|
877
|
+
*
|
|
878
|
+
* // Provision resources and maintain state
|
|
879
|
+
* onSync: async (input) => {
|
|
880
|
+
* const config = input.block.config;
|
|
881
|
+
*
|
|
882
|
+
* // Check if bucket already exists
|
|
883
|
+
* const existingBucket = await kv.block.get("bucketId");
|
|
884
|
+
*
|
|
885
|
+
* if (!existingBucket?.value) {
|
|
886
|
+
* // Create new bucket
|
|
887
|
+
* const bucketId = await cloudApi.createBucket({
|
|
888
|
+
* name: config.bucketName,
|
|
889
|
+
* region: config.region
|
|
890
|
+
* });
|
|
891
|
+
*
|
|
892
|
+
* await kv.block.set({ key: "bucketId", value: bucketId });
|
|
893
|
+
*
|
|
894
|
+
* return {
|
|
895
|
+
* newStatus: "in_progress",
|
|
896
|
+
* customStatusDescription: "Bucket creation in progress",
|
|
897
|
+
* nextScheduleDelay: 30 // Check again in 30 seconds
|
|
898
|
+
* };
|
|
899
|
+
* }
|
|
900
|
+
*
|
|
901
|
+
* // Check bucket status
|
|
902
|
+
* const bucketStatus = await cloudApi.getBucketStatus(existingBucket.value);
|
|
903
|
+
*
|
|
904
|
+
* if (bucketStatus === "ready") {
|
|
905
|
+
* const bucketDetails = await cloudApi.getBucketDetails(existingBucket.value);
|
|
906
|
+
*
|
|
907
|
+
* return {
|
|
908
|
+
* newStatus: "ready",
|
|
909
|
+
* signalUpdates: {
|
|
910
|
+
* bucketId: existingBucket.value,
|
|
911
|
+
* bucketUrl: bucketDetails.url
|
|
912
|
+
* }
|
|
913
|
+
* };
|
|
914
|
+
* }
|
|
915
|
+
*
|
|
916
|
+
* return {
|
|
917
|
+
* newStatus: "in_progress",
|
|
918
|
+
* customStatusDescription: "Waiting for bucket to be ready",
|
|
919
|
+
* nextScheduleDelay: 30
|
|
920
|
+
* };
|
|
921
|
+
* },
|
|
922
|
+
*
|
|
923
|
+
* // Clean up resources when block is removed
|
|
924
|
+
* onDrain: async (input) => {
|
|
925
|
+
* const bucketId = await kv.block.get("bucketId");
|
|
926
|
+
*
|
|
927
|
+
* if (bucketId?.value) {
|
|
928
|
+
* await cloudApi.deleteBucket(bucketId.value);
|
|
929
|
+
* await kv.block.delete(["bucketId"]);
|
|
930
|
+
* }
|
|
931
|
+
*
|
|
932
|
+
* return {
|
|
933
|
+
* newStatus: "drained",
|
|
934
|
+
* signalUpdates: {
|
|
935
|
+
* bucketId: null,
|
|
936
|
+
* bucketUrl: null
|
|
937
|
+
* }
|
|
938
|
+
* };
|
|
939
|
+
* },
|
|
940
|
+
*
|
|
941
|
+
* // Trigger sync when configuration changes
|
|
942
|
+
* inputs: {
|
|
943
|
+
* configChange: {
|
|
944
|
+
* name: "Configuration Change",
|
|
945
|
+
* onEvent: async (input) => {
|
|
946
|
+
* // Configuration updated, trigger sync
|
|
947
|
+
* await lifecycle.sync();
|
|
948
|
+
* }
|
|
949
|
+
* }
|
|
950
|
+
* }
|
|
951
|
+
* };
|
|
952
|
+
*
|
|
953
|
+
* // Approval workflow example
|
|
954
|
+
* export const approvalBlock: AppBlock = {
|
|
955
|
+
* name: "Production Deployment Approval",
|
|
956
|
+
*
|
|
957
|
+
* http: {
|
|
958
|
+
* onRequest: async (input) => {
|
|
959
|
+
* if (input.request.path === "/approve") {
|
|
960
|
+
* // Manual approval received
|
|
961
|
+
* await lifecycle.proceed();
|
|
962
|
+
*
|
|
963
|
+
* await http.respond(input.request.requestId, {
|
|
964
|
+
* statusCode: 200,
|
|
965
|
+
* body: { message: "Deployment approved" }
|
|
966
|
+
* });
|
|
967
|
+
* }
|
|
968
|
+
* }
|
|
969
|
+
* },
|
|
970
|
+
*
|
|
971
|
+
* onSync: async (input) => {
|
|
972
|
+
* // Create approval prompt if needed
|
|
973
|
+
* const promptId = await lifecycle.prompt.create(
|
|
974
|
+
* "Please approve production deployment",
|
|
975
|
+
* {
|
|
976
|
+
* redirect: {
|
|
977
|
+
* url: `${input.block.http.url}/approve`,
|
|
978
|
+
* method: "POST"
|
|
979
|
+
* }
|
|
980
|
+
* }
|
|
981
|
+
* );
|
|
982
|
+
*
|
|
983
|
+
* return {
|
|
984
|
+
* newStatus: "in_progress",
|
|
985
|
+
* customStatusDescription: "Waiting for manual approval"
|
|
986
|
+
* };
|
|
987
|
+
* }
|
|
988
|
+
* };
|
|
989
|
+
* ```
|
|
990
|
+
*/
|
|
388
991
|
declare const lifecycle: {
|
|
389
992
|
sync: () => Promise<void>;
|
|
390
993
|
proceed: () => Promise<void>;
|
|
@@ -393,6 +996,21 @@ declare const lifecycle: {
|
|
|
393
996
|
delete: (promptId: string) => Promise<void>;
|
|
394
997
|
};
|
|
395
998
|
};
|
|
999
|
+
/**
|
|
1000
|
+
* Timers service for scheduling delayed execution.
|
|
1001
|
+
*
|
|
1002
|
+
* @example
|
|
1003
|
+
* ```typescript
|
|
1004
|
+
* // Set a timer for 30 seconds
|
|
1005
|
+
* const timerId = await timers.set(30, {
|
|
1006
|
+
* inputPayload: { action: "retry" },
|
|
1007
|
+
* description: "Retry failed operation"
|
|
1008
|
+
* });
|
|
1009
|
+
*
|
|
1010
|
+
* // Cancel the timer
|
|
1011
|
+
* await timers.unset(timerId);
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
396
1014
|
declare const timers: {
|
|
397
1015
|
set: (delaySeconds: number, options: {
|
|
398
1016
|
inputPayload?: unknown;
|
|
@@ -402,6 +1020,23 @@ declare const timers: {
|
|
|
402
1020
|
}) => Promise<string>;
|
|
403
1021
|
unset: (id: string) => Promise<void>;
|
|
404
1022
|
};
|
|
1023
|
+
/**
|
|
1024
|
+
* UI service for managing custom block widgets.
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* ```typescript
|
|
1028
|
+
* // Set widget elements
|
|
1029
|
+
* await ui.widget.set({
|
|
1030
|
+
* elements: [
|
|
1031
|
+
* { type: "text", content: "Status: Active" },
|
|
1032
|
+
* { type: "button", label: "Refresh", action: "refresh" }
|
|
1033
|
+
* ]
|
|
1034
|
+
* });
|
|
1035
|
+
*
|
|
1036
|
+
* // Get current widget
|
|
1037
|
+
* const elements = await ui.widget.get();
|
|
1038
|
+
* ```
|
|
1039
|
+
*/
|
|
405
1040
|
declare const ui: {
|
|
406
1041
|
widget: {
|
|
407
1042
|
set: (options: {
|
|
@@ -411,6 +1046,50 @@ declare const ui: {
|
|
|
411
1046
|
get: (blockId?: any) => Promise<any[]>;
|
|
412
1047
|
};
|
|
413
1048
|
};
|
|
1049
|
+
/**
|
|
1050
|
+
* Define a Flows app with proper TypeScript typing and validation.
|
|
1051
|
+
*
|
|
1052
|
+
* This function provides type safety and IDE support while developing apps.
|
|
1053
|
+
* It should be the default export of your app module.
|
|
1054
|
+
*
|
|
1055
|
+
* @param app - The app schema defining blocks, configuration, and behaviors
|
|
1056
|
+
* @returns The same app schema for runtime use
|
|
1057
|
+
*
|
|
1058
|
+
* @example
|
|
1059
|
+
* ```typescript
|
|
1060
|
+
* export default defineApp({
|
|
1061
|
+
* name: "My App",
|
|
1062
|
+
* installationInstructions: "Configure your API credentials below.",
|
|
1063
|
+
* config: {
|
|
1064
|
+
* apiKey: {
|
|
1065
|
+
* name: "API Key",
|
|
1066
|
+
* type: "string",
|
|
1067
|
+
* required: true
|
|
1068
|
+
* }
|
|
1069
|
+
* },
|
|
1070
|
+
* blocks: {
|
|
1071
|
+
* processor: {
|
|
1072
|
+
* name: "Data Processor",
|
|
1073
|
+
* inputs: {
|
|
1074
|
+
* data: {
|
|
1075
|
+
* name: "Input Data",
|
|
1076
|
+
* onEvent: async (input) => {
|
|
1077
|
+
* // Your processing logic here
|
|
1078
|
+
* await events.emit(processedData);
|
|
1079
|
+
* }
|
|
1080
|
+
* }
|
|
1081
|
+
* },
|
|
1082
|
+
* outputs: {
|
|
1083
|
+
* result: {
|
|
1084
|
+
* name: "Processed Result",
|
|
1085
|
+
* default: true
|
|
1086
|
+
* }
|
|
1087
|
+
* }
|
|
1088
|
+
* }
|
|
1089
|
+
* }
|
|
1090
|
+
* });
|
|
1091
|
+
* ```
|
|
1092
|
+
*/
|
|
414
1093
|
declare function defineApp(app: AppSchema): AppSchema;
|
|
415
1094
|
|
|
416
|
-
export { type AppBlock, type AppBlockComponentInput, type AppBlockComponentOutput, type AppBlockConfigField, type AppBlockHTTPComponent, type AppBlockSchedule, type AppBlockSignal, type AppBlockUIComponent, type AppConfigField, type AppContext, type AppHTTPComponent, type AppHTTPEndpoint, type AppInput, type AppLifecycleCallbackOutput, type AppLifecycleStatus, type AppOnCreateOutput, type AppOnHTTPRequestInput, type AppOnInternalMessageInput, type AppOnTimerInput, type AppOnTriggerInput, type AppOnUIRequestInput, type AppSchedule, type AppSchema, type AppUIComponent, type EntityContext, type EntityHTTPEndpoint, type EntityInput, type EntityLifecycleCallbackOutput, type EntityLifecycleComponent, type EntityLifecycleStatus, type EntityOnHTTPRequestInput, type EntityOnInternalMessageInput, type EntityOnTimerInput, type EntityOnTriggerInput, type EntityOnUIRequestInput, type EntityView, type EntityViewType, type EventContext, type EventInput, type HTTPRequest, type ScheduleDefinition, type UIRequest, blocks, defineApp, events, getInvocationMetadata, http, kv, lifecycle, messaging,
|
|
1095
|
+
export { type AppBlock, type AppBlockComponentInput, type AppBlockComponentOutput, type AppBlockConfigField, type AppBlockHTTPComponent, type AppBlockSchedule, type AppBlockSignal, type AppBlockUIComponent, type AppConfigField, type AppContext, type AppHTTPComponent, type AppHTTPEndpoint, type AppInput, type AppLifecycleCallbackOutput, type AppLifecycleStatus, type AppOnCreateOutput, type AppOnHTTPRequestInput, type AppOnInternalMessageInput, type AppOnTimerInput, type AppOnTriggerInput, type AppOnUIRequestInput, type AppSchedule, type AppSchema, type AppUIComponent, type EntityContext, type EntityHTTPEndpoint, type EntityInput, type EntityLifecycleCallbackOutput, type EntityLifecycleComponent, type EntityLifecycleStatus, type EntityOnHTTPRequestInput, type EntityOnInternalMessageInput, type EntityOnTimerInput, type EntityOnTriggerInput, type EntityOnUIRequestInput, type EntityView, type EntityViewType, type EventContext, type EventInput, type HTTPRequest, type ScheduleDefinition, type UIRequest, blocks, defineApp, events, getInvocationMetadata, http, kv, lifecycle, messaging, timers, ui };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slflows/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"./v1": "./dist/v1/index.d.ts"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"pkgroll": "^2.
|
|
13
|
-
"rollup": "^4.
|
|
12
|
+
"pkgroll": "^2.13.1",
|
|
13
|
+
"rollup": "^4.44.1",
|
|
14
14
|
"rollup-plugin-dts": "^6.2.1",
|
|
15
15
|
"typescript": "^5.8.3"
|
|
16
16
|
},
|