Skip to content

Commit 8cfcc23

Browse files
committed
playground example updates
1 parent 9b3af43 commit 8cfcc23

File tree

1 file changed

+281
-5
lines changed

1 file changed

+281
-5
lines changed

playground/src/main.ts

Lines changed: 281 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,16 @@ const EXAMPLE_CATEGORIES: ExampleCategory[] = [
5959
{
6060
label: 'Control Flow',
6161
keys: [
62-
'sequential', 'if-else', 'early-return', 'while-loop', 'for-each',
63-
'try-catch', 'switch-case', 'nested-conditions', 'and-or-conditions',
64-
'dynamic-wait', 'wait-and-continue', 'parallel',
62+
'sequential', 'if-else', 'early-return', 'while-loop', 'do-while-loop', 'for-each',
63+
'try-catch', 'typed-error-handling', 'switch-case', 'nested-conditions', 'and-or-conditions',
64+
'dynamic-wait', 'wait-and-continue', 'parallel', 'steps-map-closure', 'steps-sequential',
65+
'deferred-await', 'retry-timeout',
6566
],
6667
},
6768
{
6869
label: 'Services',
6970
keys: [
70-
'multi-service', 'sqs-queue', 'eventbridge', 'dynamodb-crud',
71+
'multi-service', 'sqs-queue', 'eventbridge', 'dynamodb-crud', 'dynamodb-query-scan',
7172
's3-operations', 'secrets-manager', 'ssm-params', 'nested-step-function',
7273
'lambda-patterns', 'aws-sdk-escape-hatch', 'ecs-task', 'bedrock-model',
7374
'batch-job', 'glue-etl', 'codebuild-project', 'athena-query',
@@ -77,7 +78,7 @@ const EXAMPLE_CATEGORIES: ExampleCategory[] = [
7778
label: 'JS Features',
7879
keys: [
7980
'intrinsics', 'js-operators', 'string-interpolation', 'constants',
80-
'js-patterns', 'context-object', 'multi-step-function',
81+
'js-patterns', 'spread-merge', 'context-object', 'multi-step-function',
8182
],
8283
},
8384
{
@@ -207,6 +208,34 @@ export const whileLoop = Steps.createFunction(
207208
return { completed: true, progress: status.progress };
208209
},
209210
);
211+
` }] },
212+
213+
'do-while-loop': { description: 'Do-while loop — body executes at least once before condition check.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
214+
import { Lambda } from './runtime/services/Lambda';
215+
216+
// Do-While Loop
217+
// Unlike while, the body executes at least once before checking
218+
// the condition. Useful for polling patterns where you need to
219+
// act first, then decide whether to continue.
220+
221+
const pollFn = Lambda<{ jobId: string }, { status: string; retryable: boolean }>(
222+
'arn:aws:lambda:us-east-1:123:function:Poll',
223+
);
224+
const processFn = Lambda<{ jobId: string }, { result: string }>(
225+
'arn:aws:lambda:us-east-1:123:function:Process',
226+
);
227+
228+
export const doWhileLoop = Steps.createFunction(
229+
async (context: SimpleStepContext, input: { jobId: string }) => {
230+
let poll;
231+
do {
232+
poll = await pollFn.call({ jobId: input.jobId });
233+
} while (poll.status !== 'ready');
234+
235+
const result = await processFn.call({ jobId: input.jobId });
236+
return { result: result.result };
237+
},
238+
);
210239
` }] },
211240

212241
'for-each': { description: 'Iterate over items with for-of, compiles to Map state.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
@@ -243,6 +272,47 @@ export const tryCatch = Steps.createFunction(
243272
}
244273
},
245274
);
275+
` }] },
276+
277+
'typed-error-handling': { description: 'Custom StepException subclasses with instanceof chains for typed error handling.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext, StepException } from './runtime/index';
278+
import { Lambda } from './runtime/services/Lambda';
279+
280+
// Typed Error Handling
281+
// Define custom error classes extending StepException.
282+
// The compiler maps instanceof checks to Catch rules with
283+
// ErrorEquals matching the class name.
284+
285+
class OrderNotFound extends StepException {}
286+
class PaymentFailed extends StepException {}
287+
288+
const processFn = Lambda<{ orderId: string }, { total: number }>(
289+
'arn:aws:lambda:us-east-1:123:function:ProcessOrder',
290+
);
291+
const refundFn = Lambda<{ orderId: string }, void>(
292+
'arn:aws:lambda:us-east-1:123:function:Refund',
293+
);
294+
const notifyFn = Lambda<{ message: string }, void>(
295+
'arn:aws:lambda:us-east-1:123:function:Notify',
296+
);
297+
298+
export const typedErrors = Steps.createFunction(
299+
async (context: SimpleStepContext, input: { orderId: string }) => {
300+
try {
301+
const result = await processFn.call({ orderId: input.orderId });
302+
return { status: 'success', total: result.total };
303+
} catch (e) {
304+
if (e instanceof OrderNotFound) {
305+
await notifyFn.call({ message: 'Order not found' });
306+
return { status: 'not_found' };
307+
}
308+
if (e instanceof PaymentFailed) {
309+
await refundFn.call({ orderId: input.orderId });
310+
return { status: 'payment_failed' };
311+
}
312+
return { status: 'unknown_error' };
313+
}
314+
},
315+
);
246316
` }] },
247317

248318
'switch-case': { description: 'Multi-branch switch/case, compiles to chained Choice states.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
@@ -363,6 +433,144 @@ export const parallel = Steps.createFunction(
363433
return { order: order, payment: payment };
364434
},
365435
);
436+
` }] },
437+
438+
'steps-map-closure': { description: 'Steps.map() with result capture, closures, and maxConcurrency. Steps.items() for for...of with options.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
439+
import { Lambda } from './runtime/services/Lambda';
440+
441+
// Steps.map() — Functional Map API
442+
//
443+
// Three advantages over plain for...of:
444+
// 1. Result capture — collect iteration results into a variable
445+
// 2. Closures — access outer await results inside the callback
446+
// 3. MaxConcurrency — limit parallel execution
447+
//
448+
// Steps.items() — For...of with options
449+
// Wrap arrays for for...of with maxConcurrency support.
450+
451+
const getConfig = Lambda<{ env: string }, { prefix: string }>(
452+
'arn:aws:lambda:us-east-1:123:function:GetConfig',
453+
);
454+
const processItem = Lambda<{ item: string; prefix: string }, { processed: boolean }>(
455+
'arn:aws:lambda:us-east-1:123:function:ProcessItem',
456+
);
457+
458+
export const stepsMapClosure = Steps.createFunction(
459+
async (context: SimpleStepContext, input: { items: string[]; env: string }) => {
460+
// Fetch config (outer scope)
461+
const config = await getConfig.call({ env: input.env });
462+
463+
// Steps.map: closure over config.prefix, capture results, limit concurrency
464+
const results = await Steps.map(input.items, async (item) => {
465+
return await processItem.call({ item, prefix: config.prefix });
466+
}, { maxConcurrency: 5 });
467+
468+
// Steps.items: for...of with maxConcurrency
469+
for (const item of Steps.items(input.items, { maxConcurrency: 3 })) {
470+
await processItem.call({ item, prefix: config.prefix });
471+
}
472+
473+
return { results };
474+
},
475+
);
476+
` }] },
477+
478+
'steps-sequential': { description: 'Steps.sequential() — iterate items one at a time (MaxConcurrency: 1).', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
479+
import { Lambda } from './runtime/services/Lambda';
480+
481+
// Sequential Iteration
482+
//
483+
// Steps.sequential(arr) wraps an array for use with for...of,
484+
// compiling to a Map state with MaxConcurrency: 1.
485+
// Items are processed strictly one at a time.
486+
487+
const processFn = Lambda<{ item: string }, { ok: boolean }>(
488+
'arn:aws:lambda:us-east-1:123:function:ProcessItem',
489+
);
490+
491+
export const stepsSequential = Steps.createFunction(
492+
async (context: SimpleStepContext, input: { items: string[] }) => {
493+
// MaxConcurrency: 1 — items processed one at a time
494+
for (const item of Steps.sequential(input.items)) {
495+
await processFn.call({ item });
496+
}
497+
498+
return { done: true };
499+
},
500+
);
501+
` }] },
502+
503+
'deferred-await': { description: 'Fire-then-await pattern — compiler batches deferred awaits into a single Parallel state.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
504+
import { Lambda } from './runtime/services/Lambda';
505+
506+
// Deferred Await — Natural Parallelism
507+
//
508+
// Start multiple service calls without awaiting, then collect
509+
// results later. The compiler detects this pattern and batches
510+
// the awaits into a single Parallel state.
511+
512+
const getOrder = Lambda<{ orderId: string }, { status: string; total: number }>(
513+
'arn:aws:lambda:us-east-1:123:function:GetOrder',
514+
);
515+
const getPayment = Lambda<{ orderId: string }, { paid: boolean; method: string }>(
516+
'arn:aws:lambda:us-east-1:123:function:GetPayment',
517+
);
518+
519+
export const deferredAwait = Steps.createFunction(
520+
async (context: SimpleStepContext, input: { orderId: string }) => {
521+
// Start both calls — not awaited yet (no state emitted)
522+
const orderPromise = getOrder.call({ orderId: input.orderId });
523+
const paymentPromise = getPayment.call({ orderId: input.orderId });
524+
525+
// Await both — compiler batches into a single Parallel state
526+
const order = await orderPromise;
527+
const payment = await paymentPromise;
528+
529+
return { order, payment };
530+
},
531+
);
532+
` }] },
533+
534+
'retry-timeout': { description: 'Retry policies, timeouts, heartbeat, and typed error matching on service calls.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext, TimeoutError } from './runtime/index';
535+
import { Lambda } from './runtime/services/Lambda';
536+
537+
// Retry, Timeout, and Heartbeat
538+
//
539+
// Service calls accept options for retry policies, execution timeouts,
540+
// and heartbeat intervals. These compile directly to ASL Task fields.
541+
// Catch blocks with instanceof check compile to typed Catch rules.
542+
543+
const longTask = Lambda<{ jobId: string }, { status: string }>(
544+
'arn:aws:lambda:us-east-1:123:function:LongRunningTask',
545+
);
546+
const alertService = Lambda<{ message: string }, void>(
547+
'arn:aws:lambda:us-east-1:123:function:AlertService',
548+
);
549+
550+
export const retryTimeout = Steps.createFunction(
551+
async (context: SimpleStepContext, input: { jobId: string }) => {
552+
try {
553+
const result = await longTask.call({ jobId: input.jobId }, {
554+
timeoutSeconds: 300,
555+
heartbeatSeconds: 60,
556+
retry: {
557+
errorEquals: ['States.TaskFailed', 'States.Timeout'],
558+
intervalSeconds: 5,
559+
maxAttempts: 3,
560+
backoffRate: 2,
561+
},
562+
});
563+
return { status: result.status };
564+
} catch (e) {
565+
if (e instanceof TimeoutError) {
566+
await alertService.call({ message: 'Task timed out' });
567+
return { status: 'timeout' };
568+
}
569+
await alertService.call({ message: 'Task failed' });
570+
return { status: 'error' };
571+
}
572+
},
573+
);
366574
` }] },
367575

368576
// ── Services ──────────────────────────────────────────────────────────
@@ -449,6 +657,42 @@ export const dynamoDbCrud = Steps.createFunction(
449657
return { updated: true };
450658
},
451659
);
660+
` }] },
661+
662+
'dynamodb-query-scan': { description: 'DynamoDB query, scan, and updateItem operations.', services: ['DynamoDB'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
663+
import { DynamoDB } from './runtime/services/DynamoDB';
664+
665+
// DynamoDB Query, Scan, and Update
666+
//
667+
// Beyond basic CRUD (get/put/delete), DynamoDB supports
668+
// query (by key condition), scan (full table), and updateItem.
669+
670+
const ordersDb = new DynamoDB('OrdersTable');
671+
672+
export const dynamoDbQueryScan = Steps.createFunction(
673+
async (context: SimpleStepContext, input: { userId: string; minAmount: number }) => {
674+
// Query — retrieve items by partition key
675+
const orders = await ordersDb.query<{ Items: any[] }>({
676+
KeyConditionExpression: 'userId = :uid',
677+
ExpressionAttributeValues: { ':uid': input.userId },
678+
});
679+
680+
// Scan — full table scan with filter
681+
const highValue = await ordersDb.scan<{ Items: any[] }>({
682+
FilterExpression: 'amount > :min',
683+
ExpressionAttributeValues: { ':min': input.minAmount },
684+
});
685+
686+
// UpdateItem — update specific attributes
687+
await ordersDb.updateItem({
688+
Key: { userId: input.userId },
689+
UpdateExpression: 'SET lastQueried = :now',
690+
ExpressionAttributeValues: { ':now': 'today' },
691+
});
692+
693+
return { orders, highValue };
694+
},
695+
);
452696
` }] },
453697

454698
's3-operations': { description: 'S3 put and get with automatic Bucket injection.', services: ['S3'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
@@ -759,6 +1003,38 @@ export const jsPatterns = Steps.createFunction(
7591003
return { total, message, parts, parsed, serialized, hasItem, count, id };
7601004
},
7611005
);
1006+
` }] },
1007+
1008+
'spread-merge': { description: 'Object spread compiles to States.JsonMerge; Steps.merge() for deep merge.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';
1009+
import { Lambda } from './runtime/services/Lambda';
1010+
1011+
// Object Spread / JsonMerge
1012+
//
1013+
// Object spread syntax { ...a, ...b } compiles to States.JsonMerge
1014+
// (or $merge in JSONata mode). Steps.merge() provides explicit
1015+
// merge with optional deep merge flag.
1016+
1017+
const getDefaults = Lambda<{ type: string }, { color: string; size: string; priority: number }>(
1018+
'arn:aws:lambda:us-east-1:123:function:GetDefaults',
1019+
);
1020+
const getOverrides = Lambda<{ userId: string }, { color: string; label: string }>(
1021+
'arn:aws:lambda:us-east-1:123:function:GetOverrides',
1022+
);
1023+
1024+
export const spreadMerge = Steps.createFunction(
1025+
async (context: SimpleStepContext, input: { type: string; userId: string }) => {
1026+
const defaults = await getDefaults.call({ type: input.type });
1027+
const overrides = await getOverrides.call({ userId: input.userId });
1028+
1029+
// Spread → States.JsonMerge: overrides win on conflict
1030+
const combined = { ...defaults, ...overrides };
1031+
1032+
// Steps.merge() — explicit merge (deep: true for nested objects)
1033+
const deepMerged = Steps.merge(defaults, overrides, true);
1034+
1035+
return { combined, deepMerged };
1036+
},
1037+
);
7621038
` }] },
7631039

7641040
'context-object': { description: 'Access execution metadata via the context parameter.', services: ['Lambda'], files: [{ name: 'workflow.ts', content: `import { Steps, SimpleStepContext } from './runtime/index';

0 commit comments

Comments
 (0)