@temporal-architect/claude-plugin 0.9.0
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/LICENSE +21 -0
- package/README.md +38 -0
- package/package.json +37 -0
- package/skills/MANIFEST.json +373 -0
- package/skills/MANIFEST.md +121 -0
- package/skills/temporal-architect/SKILL.md +99 -0
- package/skills/temporal-architect/reference/decomposition.md +78 -0
- package/skills/temporal-architect-author-go/README.md +16 -0
- package/skills/temporal-architect-author-go/SKILL.md +191 -0
- package/skills/temporal-architect-author-go/SUBAGENT_ADOPTION.md +161 -0
- package/skills/temporal-architect-author-go/reference/activity-call.md +73 -0
- package/skills/temporal-architect-author-go/reference/activity-def.md +54 -0
- package/skills/temporal-architect-author-go/reference/assignment.md +36 -0
- package/skills/temporal-architect-author-go/reference/await-all.md +104 -0
- package/skills/temporal-architect-author-go/reference/await-one.md +193 -0
- package/skills/temporal-architect-author-go/reference/await-timer.md +35 -0
- package/skills/temporal-architect-author-go/reference/close.md +71 -0
- package/skills/temporal-architect-author-go/reference/composite-patterns.md +176 -0
- package/skills/temporal-architect-author-go/reference/condition.md +56 -0
- package/skills/temporal-architect-author-go/reference/control-flow.md +151 -0
- package/skills/temporal-architect-author-go/reference/dependency-resolution.md +29 -0
- package/skills/temporal-architect-author-go/reference/detach.md +52 -0
- package/skills/temporal-architect-author-go/reference/heartbeat.md +84 -0
- package/skills/temporal-architect-author-go/reference/nexus-service-def.md +73 -0
- package/skills/temporal-architect-author-go/reference/nexus.md +35 -0
- package/skills/temporal-architect-author-go/reference/options.md +138 -0
- package/skills/temporal-architect-author-go/reference/promise.md +73 -0
- package/skills/temporal-architect-author-go/reference/proto-driven.md +197 -0
- package/skills/temporal-architect-author-go/reference/query-handler.md +34 -0
- package/skills/temporal-architect-author-go/reference/signal-handler.md +73 -0
- package/skills/temporal-architect-author-go/reference/three-layer-testing.md +173 -0
- package/skills/temporal-architect-author-go/reference/types.md +72 -0
- package/skills/temporal-architect-author-go/reference/update-handler.md +64 -0
- package/skills/temporal-architect-author-go/reference/worker.md +215 -0
- package/skills/temporal-architect-author-go/reference/workflow-call.md +37 -0
- package/skills/temporal-architect-author-go/reference/workflow-def.md +45 -0
- package/skills/temporal-architect-author-infra/README.md +16 -0
- package/skills/temporal-architect-author-infra/SKILL.md +132 -0
- package/skills/temporal-architect-author-infra/reference/tcld.md +112 -0
- package/skills/temporal-architect-author-infra/reference/terraform.md +125 -0
- package/skills/temporal-architect-design/README.md +16 -0
- package/skills/temporal-architect-design/SKILL.md +224 -0
- package/skills/temporal-architect-design/reference/LANGUAGE.md +5 -0
- package/skills/temporal-architect-design/reference/anti-patterns.md +332 -0
- package/skills/temporal-architect-design/reference/common-errors.md +88 -0
- package/skills/temporal-architect-design/reference/core-principles.md +52 -0
- package/skills/temporal-architect-design/reference/design-checklist.md +59 -0
- package/skills/temporal-architect-design/reference/namespaces.md +84 -0
- package/skills/temporal-architect-design/reference/notation-examples.md +304 -0
- package/skills/temporal-architect-design/reference/notation-reference.md +70 -0
- package/skills/temporal-architect-design/reference/primitives-reference.md +65 -0
- package/skills/temporal-architect-design/reference/project-discovery-subagent.md +80 -0
- package/skills/temporal-architect-design/reference/reverse-engineering.md +53 -0
- package/skills/temporal-architect-design/reference/twf-conventions.md +43 -0
- package/skills/temporal-architect-design/reference/workflow-boundaries.md +43 -0
- package/skills/temporal-architect-design/topics/activities-advanced.md +358 -0
- package/skills/temporal-architect-design/topics/activities-advanced.twf +107 -0
- package/skills/temporal-architect-design/topics/child-workflows.md +347 -0
- package/skills/temporal-architect-design/topics/child-workflows.twf +171 -0
- package/skills/temporal-architect-design/topics/long-running.md +230 -0
- package/skills/temporal-architect-design/topics/long-running.twf +100 -0
- package/skills/temporal-architect-design/topics/nexus.md +248 -0
- package/skills/temporal-architect-design/topics/nexus.twf +148 -0
- package/skills/temporal-architect-design/topics/patterns.md +469 -0
- package/skills/temporal-architect-design/topics/patterns.twf +346 -0
- package/skills/temporal-architect-design/topics/promises-conditions.md +179 -0
- package/skills/temporal-architect-design/topics/promises-conditions.twf +213 -0
- package/skills/temporal-architect-design/topics/signals-queries-updates.md +319 -0
- package/skills/temporal-architect-design/topics/signals-queries-updates.twf +234 -0
- package/skills/temporal-architect-design/topics/task-queues.md +205 -0
- package/skills/temporal-architect-design/topics/task-queues.twf +184 -0
- package/skills/temporal-architect-design/topics/testing.md +437 -0
- package/skills/temporal-architect-design/topics/testing.twf +177 -0
- package/skills/temporal-architect-design/topics/timers-scheduling.md +131 -0
- package/skills/temporal-architect-design/topics/timers-scheduling.twf +129 -0
- package/skills/temporal-architect-design/topics/versioning.md +434 -0
- package/skills/temporal-architect-design/topics/versioning.twf +174 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Source: patterns.md
|
|
2
|
+
# Patterns: process, entity, saga, fan-out/fan-in, pipeline, state machine, polling
|
|
3
|
+
|
|
4
|
+
# --- Process Workflow ---
|
|
5
|
+
|
|
6
|
+
workflow OrderFulfillment(order: Order) -> (OrderResult):
|
|
7
|
+
# Step 1: Validate
|
|
8
|
+
activity ValidateRetailOrder(order) -> validated
|
|
9
|
+
if (validated.success == false):
|
|
10
|
+
close fail(OrderResult{status: "invalid", error: validated.error})
|
|
11
|
+
|
|
12
|
+
# Step 2: Reserve
|
|
13
|
+
activity ReserveInventory(order.items) -> reservation
|
|
14
|
+
|
|
15
|
+
# Step 3: Charge
|
|
16
|
+
activity ProcessPayment(order.payment) -> payment
|
|
17
|
+
|
|
18
|
+
# Step 4: Fulfill
|
|
19
|
+
activity ShipRetailOrder(order, reservation)
|
|
20
|
+
|
|
21
|
+
# Step 5: Notify
|
|
22
|
+
activity SendConfirmation(order.customer)
|
|
23
|
+
|
|
24
|
+
close complete(OrderResult{status: "completed", trackingId: reservation.trackingId})
|
|
25
|
+
|
|
26
|
+
# --- Entity Workflow ---
|
|
27
|
+
|
|
28
|
+
workflow AccountEntity(accountId: string, account: Account):
|
|
29
|
+
signal Deposit(amount: decimal):
|
|
30
|
+
account.balance = account.balance + amount
|
|
31
|
+
|
|
32
|
+
signal Withdraw(amount: decimal):
|
|
33
|
+
if (account.balance >= amount):
|
|
34
|
+
account.balance = account.balance - amount
|
|
35
|
+
|
|
36
|
+
signal Close():
|
|
37
|
+
closePending = true
|
|
38
|
+
|
|
39
|
+
query GetBalance() -> (decimal):
|
|
40
|
+
return account.balance
|
|
41
|
+
|
|
42
|
+
update Transfer(amount: decimal, toAccount: string) -> (TransferResult):
|
|
43
|
+
if (account.balance < amount):
|
|
44
|
+
return TransferResult{success: false, error: "insufficient funds"}
|
|
45
|
+
account.balance = account.balance - amount
|
|
46
|
+
activity RecordAccountTransaction(accountId, "transfer")
|
|
47
|
+
return TransferResult{success: true}
|
|
48
|
+
|
|
49
|
+
if (account == null):
|
|
50
|
+
activity LoadAccount(accountId) -> account
|
|
51
|
+
|
|
52
|
+
eventCount = 0
|
|
53
|
+
closePending = false
|
|
54
|
+
|
|
55
|
+
for:
|
|
56
|
+
await one:
|
|
57
|
+
signal Deposit:
|
|
58
|
+
activity RecordAccountTransaction(accountId, "deposit")
|
|
59
|
+
signal Withdraw:
|
|
60
|
+
activity RecordAccountTransaction(accountId, "withdraw")
|
|
61
|
+
signal Close:
|
|
62
|
+
activity CloseAccount(accountId)
|
|
63
|
+
close complete
|
|
64
|
+
timer(24h):
|
|
65
|
+
activity DailyReconciliation(accountId)
|
|
66
|
+
|
|
67
|
+
eventCount = eventCount + 1
|
|
68
|
+
if (eventCount > 1000):
|
|
69
|
+
close continue_as_new(accountId, account)
|
|
70
|
+
|
|
71
|
+
# --- Saga Pattern (adapted without try/catch) ---
|
|
72
|
+
|
|
73
|
+
workflow BookingWorkflow(booking: Booking) -> (BookingResult):
|
|
74
|
+
# Step 1: Reserve flight
|
|
75
|
+
activity ReserveFlight(booking.flight) -> flight
|
|
76
|
+
|
|
77
|
+
# Step 2: Reserve hotel (compensate flight if this fails)
|
|
78
|
+
activity ReserveHotel(booking.hotel) -> hotel
|
|
79
|
+
if (hotel.failed):
|
|
80
|
+
activity CancelFlight(flight.id)
|
|
81
|
+
close fail(BookingResult{status: "hotel_failed"})
|
|
82
|
+
|
|
83
|
+
# Step 3: Reserve car (compensate flight + hotel if this fails)
|
|
84
|
+
activity ReserveCar(booking.car) -> car
|
|
85
|
+
if (car.failed):
|
|
86
|
+
activity CancelFlight(flight.id)
|
|
87
|
+
activity CancelHotel(hotel.id)
|
|
88
|
+
close fail(BookingResult{status: "car_failed"})
|
|
89
|
+
|
|
90
|
+
# Step 4: Charge payment (compensate all if this fails)
|
|
91
|
+
activity ChargeBookingPayment(booking.payment) -> payment
|
|
92
|
+
if (payment.failed):
|
|
93
|
+
workflow CompensateBooking(flight.id, hotel.id, car.id)
|
|
94
|
+
close fail(BookingResult{status: "payment_failed"})
|
|
95
|
+
|
|
96
|
+
# All succeeded
|
|
97
|
+
close complete(BookingResult{status: "confirmed"})
|
|
98
|
+
|
|
99
|
+
# --- Saga compensation workflow (called on failure) ---
|
|
100
|
+
|
|
101
|
+
workflow CompensateBooking(flight: string, hotel: string, car: string):
|
|
102
|
+
# Run compensations
|
|
103
|
+
activity CancelFlight(flight)
|
|
104
|
+
activity CancelHotel(hotel)
|
|
105
|
+
activity CancelCar(car)
|
|
106
|
+
close complete
|
|
107
|
+
|
|
108
|
+
# --- Fan-Out/Fan-In ---
|
|
109
|
+
|
|
110
|
+
workflow BatchProcessor(items: []Item) -> (BatchResult):
|
|
111
|
+
# Fan-out: start all processing in parallel
|
|
112
|
+
await all:
|
|
113
|
+
for (item in items):
|
|
114
|
+
activity ProcessBatchItem(item)
|
|
115
|
+
|
|
116
|
+
# Fan-in: aggregate results (SDK collects results from await all)
|
|
117
|
+
activity AggregateResults(items) -> aggregated
|
|
118
|
+
close complete(BatchResult{processed: aggregated.count})
|
|
119
|
+
|
|
120
|
+
# --- Pipeline ---
|
|
121
|
+
|
|
122
|
+
workflow DataPipeline(rawData: RawData) -> (ProcessedData):
|
|
123
|
+
# Stage 1: Ingest
|
|
124
|
+
activity Ingest(rawData) -> ingested
|
|
125
|
+
|
|
126
|
+
# Stage 2: Validate
|
|
127
|
+
activity Validate(ingested) -> validated
|
|
128
|
+
if (validated.valid == false):
|
|
129
|
+
close fail(ProcessedData{status: "invalid", errors: validated.errors})
|
|
130
|
+
|
|
131
|
+
# Stage 3: Transform
|
|
132
|
+
activity Transform(validated.data) -> transformed
|
|
133
|
+
|
|
134
|
+
# Stage 4: Enrich
|
|
135
|
+
activity Enrich(transformed) -> enriched
|
|
136
|
+
|
|
137
|
+
# Stage 5: Load
|
|
138
|
+
activity Load(enriched)
|
|
139
|
+
|
|
140
|
+
close complete(ProcessedData{status: "complete", recordCount: enriched.count})
|
|
141
|
+
|
|
142
|
+
# --- State Machine ---
|
|
143
|
+
|
|
144
|
+
workflow DocumentApproval(doc: Document) -> (ApprovalResult):
|
|
145
|
+
signal Submit():
|
|
146
|
+
phase = "pending_review"
|
|
147
|
+
activity NotifyReviewers(doc)
|
|
148
|
+
|
|
149
|
+
signal Approve():
|
|
150
|
+
phase = "approved"
|
|
151
|
+
|
|
152
|
+
signal Reject():
|
|
153
|
+
phase = "rejected"
|
|
154
|
+
|
|
155
|
+
signal RequestChanges():
|
|
156
|
+
phase = "changes_requested"
|
|
157
|
+
|
|
158
|
+
signal Withdraw():
|
|
159
|
+
phase = "withdrawn"
|
|
160
|
+
|
|
161
|
+
phase = "draft"
|
|
162
|
+
|
|
163
|
+
for:
|
|
164
|
+
switch (phase):
|
|
165
|
+
case "draft":
|
|
166
|
+
await one:
|
|
167
|
+
signal Submit:
|
|
168
|
+
timer(90d):
|
|
169
|
+
phase = "expired"
|
|
170
|
+
case "pending_review":
|
|
171
|
+
await one:
|
|
172
|
+
signal Approve:
|
|
173
|
+
signal Reject:
|
|
174
|
+
signal RequestChanges:
|
|
175
|
+
timer(30d):
|
|
176
|
+
phase = "expired"
|
|
177
|
+
case "changes_requested":
|
|
178
|
+
await one:
|
|
179
|
+
signal Submit:
|
|
180
|
+
signal Withdraw:
|
|
181
|
+
timer(30d):
|
|
182
|
+
phase = "expired"
|
|
183
|
+
case "approved":
|
|
184
|
+
activity PublishDocument(doc)
|
|
185
|
+
close complete(ApprovalResult{status: "approved"})
|
|
186
|
+
case "rejected":
|
|
187
|
+
activity ArchiveDocument(doc)
|
|
188
|
+
close complete(ApprovalResult{status: "rejected"})
|
|
189
|
+
case "withdrawn":
|
|
190
|
+
close complete(ApprovalResult{status: "withdrawn"})
|
|
191
|
+
case "expired":
|
|
192
|
+
close complete(ApprovalResult{status: "expired"})
|
|
193
|
+
|
|
194
|
+
# --- Polling ---
|
|
195
|
+
|
|
196
|
+
workflow AwaitResourceReady(resourceId: string) -> (Resource):
|
|
197
|
+
backoff = 5s
|
|
198
|
+
maxBackoff = 60s
|
|
199
|
+
|
|
200
|
+
for:
|
|
201
|
+
activity CheckResourceStatus(resourceId) -> status
|
|
202
|
+
|
|
203
|
+
if (status.ready):
|
|
204
|
+
activity GetResource(resourceId) -> resource
|
|
205
|
+
close complete(resource)
|
|
206
|
+
|
|
207
|
+
if (status.failed):
|
|
208
|
+
close fail(Resource{error: status.error})
|
|
209
|
+
|
|
210
|
+
# Wait with backoff, timeout via await one + timer
|
|
211
|
+
await one:
|
|
212
|
+
timer(backoff):
|
|
213
|
+
backoff = min(backoff * 2, maxBackoff)
|
|
214
|
+
timer(30m):
|
|
215
|
+
close fail(Resource{error: "timeout"})
|
|
216
|
+
|
|
217
|
+
# --- Supporting activities ---
|
|
218
|
+
|
|
219
|
+
activity ValidateRetailOrder(order: Order) -> (ValidateResult):
|
|
220
|
+
return validate(order)
|
|
221
|
+
|
|
222
|
+
activity ReserveInventory(items: []Item) -> (Reservation):
|
|
223
|
+
return reserve(items)
|
|
224
|
+
|
|
225
|
+
activity ProcessPayment(payment: Payment) -> (PaymentResult):
|
|
226
|
+
return charge(payment)
|
|
227
|
+
|
|
228
|
+
activity ShipRetailOrder(order: Order, reservation: Reservation):
|
|
229
|
+
ship(order, reservation)
|
|
230
|
+
|
|
231
|
+
activity SendConfirmation(customer: Customer):
|
|
232
|
+
notify(customer)
|
|
233
|
+
|
|
234
|
+
activity LoadAccount(accountId: string) -> (Account):
|
|
235
|
+
return db.load(accountId)
|
|
236
|
+
|
|
237
|
+
activity RecordAccountTransaction(accountId: string, txType: string):
|
|
238
|
+
db.record(accountId, txType)
|
|
239
|
+
|
|
240
|
+
activity CloseAccount(accountId: string):
|
|
241
|
+
db.close(accountId)
|
|
242
|
+
|
|
243
|
+
activity DailyReconciliation(accountId: string):
|
|
244
|
+
reconcile(accountId)
|
|
245
|
+
|
|
246
|
+
activity ReserveFlight(flight: FlightSpec) -> (FlightReservation):
|
|
247
|
+
return reserve(flight)
|
|
248
|
+
|
|
249
|
+
activity ReserveHotel(hotel: HotelSpec) -> (HotelReservation):
|
|
250
|
+
return reserve(hotel)
|
|
251
|
+
|
|
252
|
+
activity ReserveCar(car: CarSpec) -> (CarReservation):
|
|
253
|
+
return reserve(car)
|
|
254
|
+
|
|
255
|
+
activity ChargeBookingPayment(payment: Payment) -> (PaymentResult):
|
|
256
|
+
return charge(payment)
|
|
257
|
+
|
|
258
|
+
activity CancelFlight(flightId: string):
|
|
259
|
+
cancel(flightId)
|
|
260
|
+
|
|
261
|
+
activity CancelHotel(hotelId: string):
|
|
262
|
+
cancel(hotelId)
|
|
263
|
+
|
|
264
|
+
activity CancelCar(carId: string):
|
|
265
|
+
cancel(carId)
|
|
266
|
+
|
|
267
|
+
activity ProcessBatchItem(item: Item) -> (ItemResult):
|
|
268
|
+
return process(item)
|
|
269
|
+
|
|
270
|
+
activity AggregateResults(results: []ItemResult) -> (AggregateResult):
|
|
271
|
+
return aggregate(results)
|
|
272
|
+
|
|
273
|
+
activity Ingest(rawData: RawData) -> (IngestedData):
|
|
274
|
+
return ingest(rawData)
|
|
275
|
+
|
|
276
|
+
activity Validate(data: IngestedData) -> (ValidationResult):
|
|
277
|
+
return validate(data)
|
|
278
|
+
|
|
279
|
+
activity Transform(data: ValidatedData) -> (TransformedData):
|
|
280
|
+
return transform(data)
|
|
281
|
+
|
|
282
|
+
activity Enrich(data: TransformedData) -> (EnrichedData):
|
|
283
|
+
return enrich(data)
|
|
284
|
+
|
|
285
|
+
activity Load(data: EnrichedData):
|
|
286
|
+
load(data)
|
|
287
|
+
|
|
288
|
+
activity NotifyReviewers(doc: Document):
|
|
289
|
+
notify(doc.reviewers)
|
|
290
|
+
|
|
291
|
+
activity PublishDocument(doc: Document):
|
|
292
|
+
publish(doc)
|
|
293
|
+
|
|
294
|
+
activity ArchiveDocument(doc: Document):
|
|
295
|
+
archive(doc)
|
|
296
|
+
|
|
297
|
+
activity CheckResourceStatus(resourceId: string) -> (ResourceStatus):
|
|
298
|
+
return check(resourceId)
|
|
299
|
+
|
|
300
|
+
activity GetResource(resourceId: string) -> (Resource):
|
|
301
|
+
return fetch(resourceId)
|
|
302
|
+
|
|
303
|
+
# --- Worker and namespace ---
|
|
304
|
+
|
|
305
|
+
worker patternsWorker:
|
|
306
|
+
workflow OrderFulfillment
|
|
307
|
+
workflow AccountEntity
|
|
308
|
+
workflow BookingWorkflow
|
|
309
|
+
workflow CompensateBooking
|
|
310
|
+
workflow BatchProcessor
|
|
311
|
+
workflow DataPipeline
|
|
312
|
+
workflow DocumentApproval
|
|
313
|
+
workflow AwaitResourceReady
|
|
314
|
+
activity ValidateRetailOrder
|
|
315
|
+
activity ReserveInventory
|
|
316
|
+
activity ProcessPayment
|
|
317
|
+
activity ShipRetailOrder
|
|
318
|
+
activity SendConfirmation
|
|
319
|
+
activity LoadAccount
|
|
320
|
+
activity RecordAccountTransaction
|
|
321
|
+
activity CloseAccount
|
|
322
|
+
activity DailyReconciliation
|
|
323
|
+
activity ReserveFlight
|
|
324
|
+
activity ReserveHotel
|
|
325
|
+
activity ReserveCar
|
|
326
|
+
activity ChargeBookingPayment
|
|
327
|
+
activity CancelFlight
|
|
328
|
+
activity CancelHotel
|
|
329
|
+
activity CancelCar
|
|
330
|
+
activity ProcessBatchItem
|
|
331
|
+
activity AggregateResults
|
|
332
|
+
activity Ingest
|
|
333
|
+
activity Validate
|
|
334
|
+
activity Transform
|
|
335
|
+
activity Enrich
|
|
336
|
+
activity Load
|
|
337
|
+
activity NotifyReviewers
|
|
338
|
+
activity PublishDocument
|
|
339
|
+
activity ArchiveDocument
|
|
340
|
+
activity CheckResourceStatus
|
|
341
|
+
activity GetResource
|
|
342
|
+
|
|
343
|
+
namespace patterns:
|
|
344
|
+
worker patternsWorker
|
|
345
|
+
options:
|
|
346
|
+
task_queue: "patterns"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Promises and Conditions
|
|
2
|
+
|
|
3
|
+
> **Example:** [`promises-conditions.twf`](./promises-conditions.twf)
|
|
4
|
+
|
|
5
|
+
Deferred async operations and named boolean awaitables for workflow state.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
| Primitive | Purpose | Syntax |
|
|
10
|
+
|-----------|---------|--------|
|
|
11
|
+
| **Promise** | Start async operation, await later | `promise p <- activity Foo(x)` |
|
|
12
|
+
| **Condition** | Named boolean awaitable | `condition myCondition` (in `state:` block) |
|
|
13
|
+
| **Set / Unset** | Mutate a condition | `set myCondition` / `unset myCondition` |
|
|
14
|
+
| **State block** | Declare workflow state | `state:` (before handlers) |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Promises
|
|
19
|
+
|
|
20
|
+
A `promise` wraps any async operation (activity, workflow, timer, signal, update) for non-blocking execution. The `<-` operator visually distinguishes async declaration from sync result binding (`->`).
|
|
21
|
+
|
|
22
|
+
### Declaration
|
|
23
|
+
|
|
24
|
+
```twf
|
|
25
|
+
promise p <- activity ProcessItem(input)
|
|
26
|
+
promise report <- workflow BuildReport(data)
|
|
27
|
+
promise timeout <- timer(5m)
|
|
28
|
+
promise approved <- signal Approved
|
|
29
|
+
promise addr <- update ChangeAddress
|
|
30
|
+
promise pay <- nexus BillingEndpoint BillingService.ChargePayment(card)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Awaiting a Promise
|
|
34
|
+
|
|
35
|
+
Block until the promise resolves and bind the result:
|
|
36
|
+
|
|
37
|
+
```twf
|
|
38
|
+
await p -> result
|
|
39
|
+
await timeout
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Promises in `await one` (race)
|
|
43
|
+
|
|
44
|
+
```twf
|
|
45
|
+
await one:
|
|
46
|
+
p -> result:
|
|
47
|
+
close complete(Result{data: result})
|
|
48
|
+
timeout:
|
|
49
|
+
close fail("timed out")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> **The promise's operation is not cancelled when it loses.** If `timeout` wins, the operation backing `p` (the activity/workflow/nexus call started by `promise p <- ...`) keeps running until the workflow run ends — racing a promise against a timeout does **not** cancel the promise. If the losing operation must actually stop, add explicit cleanup; the race alone won't release it.
|
|
53
|
+
|
|
54
|
+
### Start-Now, Wait-Later Pattern
|
|
55
|
+
|
|
56
|
+
The primary use case for promises is starting operations without blocking, doing other work, then collecting results:
|
|
57
|
+
|
|
58
|
+
```twf
|
|
59
|
+
workflow ParallelProcessing(items: Items) -> (Result):
|
|
60
|
+
promise handleA <- activity ProcessA(items.a)
|
|
61
|
+
promise handleB <- activity ProcessB(items.b)
|
|
62
|
+
|
|
63
|
+
activity QuickSetup(items)
|
|
64
|
+
|
|
65
|
+
await handleA -> resultA
|
|
66
|
+
await handleB -> resultB
|
|
67
|
+
|
|
68
|
+
close complete(Result{a: resultA, b: resultB})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Async Duality
|
|
72
|
+
|
|
73
|
+
Every async operation has two forms:
|
|
74
|
+
|
|
75
|
+
| Form | Syntax | Behavior |
|
|
76
|
+
|------|--------|----------|
|
|
77
|
+
| **Blocking** | `activity Process(item) -> result` | Start and wait immediately |
|
|
78
|
+
| **Non-blocking** | `promise p <- activity Process(item)` | Start, continue, wait later |
|
|
79
|
+
|
|
80
|
+
This applies uniformly to: `activity`, `workflow`, `timer`, `signal`, `update`.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Conditions
|
|
85
|
+
|
|
86
|
+
A `condition` is a named boolean temporal primitive declared in the workflow `state:` block. It can be set, unset, and awaited.
|
|
87
|
+
|
|
88
|
+
### Declaration (in `state:` block only)
|
|
89
|
+
|
|
90
|
+
```twf
|
|
91
|
+
workflow Example():
|
|
92
|
+
state:
|
|
93
|
+
condition clusterStarted
|
|
94
|
+
condition thresholdReached
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Mutation
|
|
98
|
+
|
|
99
|
+
```twf
|
|
100
|
+
set clusterStarted
|
|
101
|
+
unset clusterStarted
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Awaiting a Condition
|
|
105
|
+
|
|
106
|
+
```twf
|
|
107
|
+
await clusterStarted
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Conditions in `await one` (race)
|
|
111
|
+
|
|
112
|
+
```twf
|
|
113
|
+
await one:
|
|
114
|
+
clusterStarted:
|
|
115
|
+
close complete(ClusterState{started: true})
|
|
116
|
+
timer(30d):
|
|
117
|
+
close fail("timeout")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Update Handler + Condition Pattern
|
|
121
|
+
|
|
122
|
+
The primary motivator for conditions: update handlers that wait on workflow state. The client blocks until the handler returns, and the handler waits for a condition that the main workflow body sets:
|
|
123
|
+
|
|
124
|
+
```twf
|
|
125
|
+
workflow ClusterManager(config: Config):
|
|
126
|
+
state:
|
|
127
|
+
condition clusterStarted
|
|
128
|
+
|
|
129
|
+
update WaitUntilStarted() -> (ClusterState):
|
|
130
|
+
await clusterStarted
|
|
131
|
+
return ClusterState{started: true}
|
|
132
|
+
|
|
133
|
+
activity ProvisionCluster(config)
|
|
134
|
+
activity StartCluster(config)
|
|
135
|
+
set clusterStarted
|
|
136
|
+
|
|
137
|
+
await signal Shutdown
|
|
138
|
+
close complete
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## State Block
|
|
144
|
+
|
|
145
|
+
The `state:` block declares workflow state including conditions and variable initializations. It must appear first in a workflow definition, before signal/query/update handlers.
|
|
146
|
+
|
|
147
|
+
```twf
|
|
148
|
+
workflow Example():
|
|
149
|
+
state:
|
|
150
|
+
condition myCondition
|
|
151
|
+
balance = 0
|
|
152
|
+
status = "pending"
|
|
153
|
+
|
|
154
|
+
signal Deposit(amount: decimal):
|
|
155
|
+
balance = balance + amount
|
|
156
|
+
if (balance >= 1000):
|
|
157
|
+
set myCondition
|
|
158
|
+
|
|
159
|
+
await myCondition
|
|
160
|
+
close complete
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Restrictions
|
|
164
|
+
|
|
165
|
+
- No temporal primitives inside `state:` block (it is purely declarative)
|
|
166
|
+
- `condition` declarations can only appear inside `state:` blocks
|
|
167
|
+
- `set`/`unset` targets must refer to conditions declared in the `state:` block
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Condition Considerations
|
|
172
|
+
|
|
173
|
+
| Consideration | Guidance |
|
|
174
|
+
|---------------|----------|
|
|
175
|
+
| **Boolean only** | Conditions are simple true/false values |
|
|
176
|
+
| **Declarative** | Must be declared in `state:` block before use |
|
|
177
|
+
| **Signal-driven** | Typically set/unset in signal or update handlers |
|
|
178
|
+
| **Reactive** | `await condition` unblocks when condition becomes true |
|
|
179
|
+
| **No expressions** | Conditions are named booleans, not arbitrary predicates |
|