@slflows/sdk 0.0.3 → 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 +680 -3
- 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 {
|
|
@@ -130,27 +201,61 @@ interface EntityView {
|
|
|
130
201
|
type: EntityViewType;
|
|
131
202
|
}
|
|
132
203
|
type EntityViewType = "default" | "fullScreen" | "regular";
|
|
204
|
+
/**
|
|
205
|
+
* Input context provided to app-level handlers.
|
|
206
|
+
* Contains app configuration and runtime information.
|
|
207
|
+
*/
|
|
133
208
|
interface AppInput {
|
|
209
|
+
/** App installation context and configuration */
|
|
134
210
|
app: AppContext;
|
|
135
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* App runtime context containing configuration and endpoints.
|
|
214
|
+
* Available in all app-level handlers (onSync, onDrain, onInternalMessage, etc.).
|
|
215
|
+
*/
|
|
136
216
|
interface AppContext {
|
|
217
|
+
/** App configuration values set during installation */
|
|
137
218
|
config: Record<string, any>;
|
|
138
|
-
|
|
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 */
|
|
139
222
|
http: AppHTTPEndpoint;
|
|
223
|
+
/** URL for managing this app installation */
|
|
140
224
|
installationUrl: string;
|
|
225
|
+
/** Signals exported by this app */
|
|
226
|
+
signals: Record<string, any>;
|
|
141
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* HTTP endpoint information for app-level HTTP handlers.
|
|
230
|
+
*/
|
|
142
231
|
interface AppHTTPEndpoint {
|
|
232
|
+
/** Base URL for this app's HTTP endpoints */
|
|
143
233
|
url: string;
|
|
144
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Input context provided to block-level handlers.
|
|
237
|
+
* Extends AppInput with block-specific context.
|
|
238
|
+
*/
|
|
145
239
|
interface EntityInput extends AppInput {
|
|
240
|
+
/** Block (entity) instance context and configuration */
|
|
146
241
|
block: EntityContext;
|
|
147
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Block (entity) runtime context containing configuration and state.
|
|
245
|
+
* Available in all block-level handlers (onEvent, onSync, onDrain, etc.).
|
|
246
|
+
*/
|
|
148
247
|
interface EntityContext {
|
|
248
|
+
/** Unique identifier for this block instance */
|
|
149
249
|
id: string;
|
|
250
|
+
/** Display name of this block instance */
|
|
150
251
|
name: string;
|
|
252
|
+
/** Description of this block instance */
|
|
151
253
|
description: string;
|
|
254
|
+
/** Block configuration values set in the flow */
|
|
152
255
|
config: Record<string, any>;
|
|
256
|
+
/** Lifecycle state information (null if block has no lifecycle) */
|
|
153
257
|
lifecycle: EntityLifecycleComponent | null;
|
|
258
|
+
/** HTTP endpoint information (null if block has no HTTP handler) */
|
|
154
259
|
http: EntityHTTPEndpoint | null;
|
|
155
260
|
}
|
|
156
261
|
interface EntityLifecycleComponent {
|
|
@@ -161,14 +266,31 @@ type EntityLifecycleStatus = "draft" | "in_progress" | "ready" | "drifted" | "fa
|
|
|
161
266
|
interface EntityHTTPEndpoint {
|
|
162
267
|
url: string;
|
|
163
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Input context provided to event handlers (onEvent).
|
|
271
|
+
* Extends EntityInput with event-specific information.
|
|
272
|
+
*/
|
|
164
273
|
interface EventInput extends EntityInput {
|
|
274
|
+
/** Event data and metadata */
|
|
165
275
|
event: EventContext;
|
|
166
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Event context containing the event data and metadata.
|
|
279
|
+
* Available in all onEvent handlers when processing incoming events.
|
|
280
|
+
*/
|
|
167
281
|
interface EventContext {
|
|
282
|
+
/** Unique identifier for this event */
|
|
168
283
|
id: string;
|
|
284
|
+
/** Configuration values for the specific input that received this event */
|
|
169
285
|
inputConfig: Record<string, any>;
|
|
286
|
+
/**
|
|
287
|
+
* Echo information for request-response patterns.
|
|
288
|
+
* Present when this event was emitted with echo: true.
|
|
289
|
+
*/
|
|
170
290
|
echo?: {
|
|
291
|
+
/** The event body that was echoed */
|
|
171
292
|
body: any;
|
|
293
|
+
/** The output key where the original event was emitted */
|
|
172
294
|
outputKey: string;
|
|
173
295
|
};
|
|
174
296
|
}
|
|
@@ -259,68 +381,209 @@ interface UIRequest {
|
|
|
259
381
|
payload?: any;
|
|
260
382
|
}
|
|
261
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Key-value pair for storage operations.
|
|
386
|
+
* Supports TTL (time-to-live) and optimistic locking for safe concurrent access.
|
|
387
|
+
*/
|
|
262
388
|
interface KVPair {
|
|
389
|
+
/** The storage key */
|
|
263
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
|
+
*/
|
|
264
395
|
value?: any;
|
|
396
|
+
/** Unix timestamp of when this key was last updated */
|
|
265
397
|
updatedAt?: number;
|
|
398
|
+
/** Time-to-live in seconds. If set, key will expire after this duration */
|
|
266
399
|
ttl?: number;
|
|
400
|
+
/** Optional lock information for atomic operations */
|
|
267
401
|
lock?: {
|
|
402
|
+
/** Unique lock identifier */
|
|
268
403
|
id: string;
|
|
404
|
+
/** Lock timeout in seconds */
|
|
269
405
|
timeout?: number;
|
|
270
406
|
};
|
|
271
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
|
+
*/
|
|
272
412
|
interface KVListInput {
|
|
413
|
+
/** Prefix to filter keys (e.g., "user:" to get all user-related keys) */
|
|
273
414
|
keyPrefix: string;
|
|
415
|
+
/** Starting key for pagination (from previous nextStartingKey) */
|
|
274
416
|
startingKey?: string;
|
|
275
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Output from key-value list operations with pagination support.
|
|
420
|
+
* Contains the retrieved key-value pairs and pagination information.
|
|
421
|
+
*/
|
|
276
422
|
interface KVListOutput {
|
|
423
|
+
/** Array of key-value pairs matching the prefix */
|
|
277
424
|
pairs: Array<KVPair>;
|
|
425
|
+
/** Next starting key for pagination (undefined if no more results) */
|
|
278
426
|
nextStartingKey?: string;
|
|
279
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Input parameters for listing blocks within the same app installation.
|
|
430
|
+
* Used to discover other blocks for coordination and messaging.
|
|
431
|
+
*/
|
|
280
432
|
interface ListBlocksInput {
|
|
433
|
+
/** Filter by specific block type IDs (omit to list all blocks) */
|
|
281
434
|
typeIds?: string[];
|
|
282
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Output from blocks.list() operation containing discovered blocks.
|
|
438
|
+
* Provides information about other blocks in the same app installation.
|
|
439
|
+
*/
|
|
283
440
|
interface ListBlocksOutput {
|
|
441
|
+
/** Array of blocks found in the app installation */
|
|
284
442
|
blocks: {
|
|
443
|
+
/** Unique identifier of the block instance */
|
|
285
444
|
id: string;
|
|
445
|
+
/** Type ID of the block (from the app's block definitions) */
|
|
286
446
|
typeId: string;
|
|
447
|
+
/** Current configuration values of the block */
|
|
287
448
|
config: Record<string, any>;
|
|
288
449
|
}[];
|
|
289
450
|
}
|
|
290
|
-
|
|
451
|
+
/**
|
|
452
|
+
* Options for emitting events to downstream blocks.
|
|
453
|
+
*/
|
|
291
454
|
interface EmitOptions {
|
|
455
|
+
/** The output key to emit on (defaults to default output) */
|
|
292
456
|
outputKey?: string;
|
|
457
|
+
/** Parent event ID for lineage tracking */
|
|
293
458
|
parentEventId?: string;
|
|
459
|
+
/** Additional parent event IDs for complex lineage */
|
|
294
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
|
+
*/
|
|
295
478
|
echo?: boolean;
|
|
479
|
+
/** Pending event ID to complete when emitting */
|
|
296
480
|
complete?: string;
|
|
297
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Options for creating a pending event.
|
|
484
|
+
*/
|
|
298
485
|
interface CreatePendingEventOptions {
|
|
486
|
+
/** Optional predicted event payload */
|
|
299
487
|
event?: Record<string, any>;
|
|
488
|
+
/** Which output this will emit on (optional) */
|
|
300
489
|
outputId?: string;
|
|
490
|
+
/** Parent event (auto-populated in handlers) */
|
|
301
491
|
parentEventId?: string;
|
|
492
|
+
/** Additional parent event IDs for complex lineage */
|
|
302
493
|
secondaryParentEventIds?: string[];
|
|
494
|
+
/** User-readable status message */
|
|
303
495
|
statusDescription: string | null;
|
|
304
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Options for updating an existing pending event.
|
|
499
|
+
* Used with updatePendingEvent() to modify pending event data or status.
|
|
500
|
+
*/
|
|
305
501
|
interface UpdatePendingEventOptions {
|
|
502
|
+
/** Updated event data (replaces previous event data) */
|
|
306
503
|
event?: Record<string, any>;
|
|
504
|
+
/** Updated output ID to emit on when completed */
|
|
307
505
|
outputId?: string;
|
|
506
|
+
/** Updated parent event ID for lineage tracking */
|
|
308
507
|
parentEventId?: string;
|
|
508
|
+
/** Updated additional parent event IDs */
|
|
309
509
|
secondaryParentEventIds?: string[];
|
|
510
|
+
/** Updated status description shown while pending */
|
|
310
511
|
statusDescription?: string | null;
|
|
311
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Options for creating user prompts in approval workflows.
|
|
515
|
+
* Used with createPrompt() to request manual user input or approval.
|
|
516
|
+
*/
|
|
312
517
|
interface PromptOptions {
|
|
518
|
+
/** Redirect configuration for the approval interface */
|
|
313
519
|
redirect: {
|
|
520
|
+
/** URL where users will be redirected for approval */
|
|
314
521
|
url: string;
|
|
522
|
+
/** HTTP method for the approval request */
|
|
315
523
|
method: "GET" | "POST";
|
|
524
|
+
/** Form fields to include with POST requests */
|
|
316
525
|
formFields?: Record<string, string>;
|
|
317
526
|
};
|
|
318
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Metadata about the current function invocation.
|
|
530
|
+
* Provides context for logging, debugging, and correlation.
|
|
531
|
+
*/
|
|
319
532
|
interface InvocationMetadata {
|
|
533
|
+
/** Unique identifier for this invocation */
|
|
320
534
|
invocationId: string;
|
|
535
|
+
/** Unix timestamp when the invocation started */
|
|
321
536
|
timestamp: number;
|
|
322
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
|
+
*/
|
|
323
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
|
+
*/
|
|
324
587
|
declare const events: {
|
|
325
588
|
emit: (event: any, options?: EmitOptions) => Promise<void>;
|
|
326
589
|
createPending: (options: CreatePendingEventOptions) => Promise<string>;
|
|
@@ -328,7 +591,112 @@ declare const events: {
|
|
|
328
591
|
cancelPending: (pendingEventId: string, reason: string) => Promise<void>;
|
|
329
592
|
completePending: (pendingEventId: string) => Promise<void>;
|
|
330
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
|
+
*/
|
|
331
698
|
declare const kv: {
|
|
699
|
+
/** Block-scoped key-value storage (isolated per block instance) */
|
|
332
700
|
block: {
|
|
333
701
|
getMany: (keys: string[]) => Promise<KVPair[]>;
|
|
334
702
|
get: (key: string) => Promise<KVPair>;
|
|
@@ -348,6 +716,7 @@ declare const kv: {
|
|
|
348
716
|
};
|
|
349
717
|
}) => Promise<boolean>;
|
|
350
718
|
};
|
|
719
|
+
/** App-scoped key-value storage (shared across all blocks in the app) */
|
|
351
720
|
app: {
|
|
352
721
|
getMany: (keys: string[]) => Promise<KVPair[]>;
|
|
353
722
|
get: (key: string) => Promise<KVPair>;
|
|
@@ -368,6 +737,27 @@ declare const kv: {
|
|
|
368
737
|
}) => Promise<boolean>;
|
|
369
738
|
};
|
|
370
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
|
+
*/
|
|
371
761
|
declare const messaging: {
|
|
372
762
|
sendToBlocks: (input: {
|
|
373
763
|
body: unknown;
|
|
@@ -377,9 +767,36 @@ declare const messaging: {
|
|
|
377
767
|
body: unknown;
|
|
378
768
|
}) => Promise<void>;
|
|
379
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
|
+
*/
|
|
380
784
|
declare const blocks: {
|
|
381
785
|
list: (input?: ListBlocksInput) => Promise<ListBlocksOutput>;
|
|
382
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
|
+
*/
|
|
383
800
|
declare const http: {
|
|
384
801
|
respond: (requestId: string, response: {
|
|
385
802
|
statusCode?: number;
|
|
@@ -387,6 +804,190 @@ declare const http: {
|
|
|
387
804
|
body?: unknown;
|
|
388
805
|
}) => Promise<void>;
|
|
389
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
|
+
*/
|
|
390
991
|
declare const lifecycle: {
|
|
391
992
|
sync: () => Promise<void>;
|
|
392
993
|
proceed: () => Promise<void>;
|
|
@@ -395,6 +996,21 @@ declare const lifecycle: {
|
|
|
395
996
|
delete: (promptId: string) => Promise<void>;
|
|
396
997
|
};
|
|
397
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
|
+
*/
|
|
398
1014
|
declare const timers: {
|
|
399
1015
|
set: (delaySeconds: number, options: {
|
|
400
1016
|
inputPayload?: unknown;
|
|
@@ -404,6 +1020,23 @@ declare const timers: {
|
|
|
404
1020
|
}) => Promise<string>;
|
|
405
1021
|
unset: (id: string) => Promise<void>;
|
|
406
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
|
+
*/
|
|
407
1040
|
declare const ui: {
|
|
408
1041
|
widget: {
|
|
409
1042
|
set: (options: {
|
|
@@ -413,6 +1046,50 @@ declare const ui: {
|
|
|
413
1046
|
get: (blockId?: any) => Promise<any[]>;
|
|
414
1047
|
};
|
|
415
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
|
+
*/
|
|
416
1093
|
declare function defineApp(app: AppSchema): AppSchema;
|
|
417
1094
|
|
|
418
|
-
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
|
},
|