@@ -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