@workflow/core 5.0.0-beta.35 → 5.0.0-beta.36
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/classify-error.d.ts.map +1 -1
- package/dist/classify-error.js +5 -2
- package/dist/create-hook.d.ts +41 -22
- package/dist/create-hook.d.ts.map +1 -1
- package/dist/create-hook.js +1 -1
- package/dist/describe-error.d.ts.map +1 -1
- package/dist/describe-error.js +12 -1
- package/dist/flushable-stream.d.ts +29 -0
- package/dist/flushable-stream.d.ts.map +1 -1
- package/dist/flushable-stream.js +228 -2
- package/dist/global.d.ts +2 -0
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +1 -1
- package/dist/private.d.ts +6 -17
- package/dist/private.d.ts.map +1 -1
- package/dist/private.js +1 -1
- package/dist/replay-payload-cache.d.ts +56 -0
- package/dist/replay-payload-cache.d.ts.map +1 -0
- package/dist/replay-payload-cache.js +137 -0
- package/dist/runtime/constants.d.ts +11 -0
- package/dist/runtime/constants.d.ts.map +1 -1
- package/dist/runtime/constants.js +26 -1
- package/dist/runtime/get-port-lazy.js +4 -4
- package/dist/runtime/helpers.d.ts +6 -4
- package/dist/runtime/helpers.d.ts.map +1 -1
- package/dist/runtime/helpers.js +23 -7
- package/dist/runtime/resume-hook.d.ts +4 -1
- package/dist/runtime/resume-hook.d.ts.map +1 -1
- package/dist/runtime/resume-hook.js +23 -20
- package/dist/runtime/step-executor.d.ts +39 -0
- package/dist/runtime/step-executor.d.ts.map +1 -1
- package/dist/runtime/step-executor.js +74 -4
- package/dist/runtime/step-handler.js +3 -3
- package/dist/runtime/suspension-handler.d.ts.map +1 -1
- package/dist/runtime/suspension-handler.js +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +372 -110
- package/dist/serialization.d.ts +44 -4
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +88 -54
- package/dist/step.d.ts.map +1 -1
- package/dist/step.js +10 -16
- package/dist/symbols.d.ts +13 -0
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +14 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/dist/workflow/abort-controller.d.ts.map +1 -1
- package/dist/workflow/abort-controller.js +3 -2
- package/dist/workflow/hook.d.ts.map +1 -1
- package/dist/workflow/hook.js +22 -7
- package/dist/workflow.d.ts +12 -9
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +16 -10
- package/docs/api-reference/create-hook.mdx +43 -2
- package/docs/api-reference/define-hook.mdx +26 -24
- package/docs/api-reference/fatal-error.mdx +29 -7
- package/docs/api-reference/fetch.mdx +3 -4
- package/docs/api-reference/sleep.mdx +1 -1
- package/docs/foundations/hooks.mdx +1 -1
- package/docs/foundations/idempotency.mdx +16 -9
- package/docs/how-it-works/encryption.mdx +3 -3
- package/docs/how-it-works/event-sourcing.mdx +6 -6
- package/docs/how-it-works/framework-integrations.mdx +2 -2
- package/package.json +5 -5
- package/dist/step-hydration-cache.d.ts +0 -148
- package/dist/step-hydration-cache.d.ts.map +0 -1
- package/dist/step-hydration-cache.js +0 -171
|
@@ -93,7 +93,7 @@ This API is provided as a convenience to easily use `fetch` in workflow, but oft
|
|
|
93
93
|
|
|
94
94
|
### Customizing Fetch Behavior
|
|
95
95
|
|
|
96
|
-
Here's an example of a custom fetch wrapper that provides more sophisticated error handling with custom retry logic:
|
|
96
|
+
Here's an example of a custom fetch wrapper that provides more sophisticated error handling with custom retry logic. Call `globalThis.fetch` inside your own `"use step"` function — calling the workflow `fetch` imported from `workflow` would nest a step inside a step:
|
|
97
97
|
|
|
98
98
|
```typescript lineNumbers
|
|
99
99
|
import { FatalError, RetryableError } from "workflow"
|
|
@@ -104,7 +104,7 @@ export async function customFetch(
|
|
|
104
104
|
) {
|
|
105
105
|
"use step"
|
|
106
106
|
|
|
107
|
-
const response = await fetch(url, init)
|
|
107
|
+
const response = await globalThis.fetch(url, init)
|
|
108
108
|
|
|
109
109
|
// Handle client errors (4xx) - don't retry
|
|
110
110
|
if (response.status >= 400 && response.status < 500) {
|
|
@@ -145,7 +145,6 @@ export async function customFetch(
|
|
|
145
145
|
|
|
146
146
|
This example demonstrates:
|
|
147
147
|
|
|
148
|
-
- Setting custom `maxRetries` to 5 retries (6 total attempts including the initial attempt).
|
|
149
148
|
- Throwing [`FatalError`](/docs/api-reference/workflow/fatal-error) for client errors (400-499) to prevent retries.
|
|
150
149
|
- Handling 429 rate limiting by reading the `Retry-After` header and using [`RetryableError`](/docs/api-reference/workflow/retryable-error).
|
|
151
|
-
- Allowing automatic retries for server errors (5xx)
|
|
150
|
+
- Allowing automatic retries for server errors (5xx) by throwing a plain `Error`.
|
|
@@ -14,7 +14,7 @@ Suspends a workflow for a specified duration or until an end date without consum
|
|
|
14
14
|
This is useful when you want to resume a workflow after some duration or date.
|
|
15
15
|
|
|
16
16
|
<Callout>
|
|
17
|
-
`sleep` is a
|
|
17
|
+
`sleep` is a built-in workflow runtime function (backed by a timer event in the event log, not a step) and should be called directly inside workflow functions.
|
|
18
18
|
</Callout>
|
|
19
19
|
|
|
20
20
|
```typescript lineNumbers
|
|
@@ -112,7 +112,7 @@ export async function orderWorkflow(orderId: string) {
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token
|
|
115
|
+
Calling `createHook()` on its own does not register the hook — registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the hook registration, then resolves with `null` once the hook is registered and ready to receive payloads, or with a `Run` handle for the run that owns the token (see [`HookConflictError`](/docs/errors/hook-conflict)). For `hook_conflict` events persisted by older worlds that did not record the owning run's ID, `getConflict()` rejects with `HookConflictError` instead of resolving with an incomplete handle. The conflicting run's accessors are durable steps, so the workflow can inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` — see [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies.
|
|
116
116
|
|
|
117
117
|
### Custom Tokens for Deterministic Hooks
|
|
118
118
|
|
|
@@ -96,7 +96,7 @@ export async function processOrder(orderId: string): Promise<OrderResult> {
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
The runtime creates the hook atomically. At most one
|
|
99
|
+
The runtime creates the hook atomically. At most one hook can own `order:${orderId}`, so duplicate workflow runs converge on one owner. A duplicate run observes `getConflict()` resolving with the owner's `Run` and returns before it reaches `chargeOrder()`. The conflicting run's accessors (`status`, `returnValue`, `cancel()`, …) are durable steps, so the duplicate run can do more than report the owner — see [conflict-handling strategies](#conflict-handling-strategies) below.
|
|
100
100
|
|
|
101
101
|
Outside the workflow, try to resume the hook first. If the hook is not registered yet, start the workflow and retry the resume until the new run creates the hook:
|
|
102
102
|
|
|
@@ -149,7 +149,7 @@ export async function POST(request: Request) {
|
|
|
149
149
|
This avoids creating a new run only after the first run has registered its hook. Because `start()` returns before the run body executes and calls `createHook()`, two concurrent requests can both observe "no hook yet" and each call `start()`. The race is resolved inside the workflow body, where the losing run observes `getConflict()` resolving with the active owner and returns without doing duplicate-sensitive work — and the route detects it by comparing the resumed hook's `runId` against the run it just started, without waiting for either run to finish. A native API for atomically starting a run and registering a hook is in the works. Until then, model recovery inside the workflow by checking `hook.getConflict()`.
|
|
150
150
|
</Callout>
|
|
151
151
|
|
|
152
|
-
This
|
|
152
|
+
This coordinates active runs by default: the token becomes available when its workflow ends. Set `experimental_minRetention` to keep it unavailable to late duplicates. After the workflow ends, the Hook can still be found with `getHookByToken()` until retention ends, but it cannot be resumed. See [`createHook()` minimum retention](/docs/api-reference/workflow/create-hook#keep-a-token-unavailable-after-the-run-ends) for examples and supported values.
|
|
153
153
|
|
|
154
154
|
### Conflict-handling strategies
|
|
155
155
|
|
|
@@ -182,7 +182,7 @@ export async function processOrder(orderId: string) {
|
|
|
182
182
|
}
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
**Inspect the owner before deciding.**
|
|
185
|
+
**Inspect the owner before deciding.** Reuse a completed owner's result, but reject other duplicates:
|
|
186
186
|
|
|
187
187
|
```typescript lineNumbers
|
|
188
188
|
import { createHook } from "workflow";
|
|
@@ -200,17 +200,17 @@ export async function processOrder(orderId: string) {
|
|
|
200
200
|
const conflict = await request.getConflict();
|
|
201
201
|
if (conflict) {
|
|
202
202
|
const status = await conflict.status; // [!code highlight]
|
|
203
|
-
if (status === "
|
|
204
|
-
return
|
|
203
|
+
if (status === "completed") {
|
|
204
|
+
return await conflict.returnValue;
|
|
205
205
|
}
|
|
206
|
-
|
|
206
|
+
return { status: "duplicate" as const, runId: conflict.runId };
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
return await processOwnedOrder(orderId);
|
|
210
210
|
}
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
**Signal the owner instead of doing the work.**
|
|
213
|
+
**Signal the owner instead of doing the work.** A conflict can refer to a finished run when `experimental_minRetention` is set, so check its status before sending data to its Hook:
|
|
214
214
|
|
|
215
215
|
```typescript lineNumbers
|
|
216
216
|
import { createHook } from "workflow";
|
|
@@ -230,16 +230,19 @@ export async function processOrder(orderId: string, confirmed: boolean) {
|
|
|
230
230
|
using request = createHook<OrderRequest>({ token });
|
|
231
231
|
|
|
232
232
|
const conflict = await request.getConflict();
|
|
233
|
-
if (conflict) {
|
|
233
|
+
if (conflict && ["pending", "running"].includes(await conflict.status)) {
|
|
234
234
|
await forwardToOwner(token, { confirmed }); // [!code highlight]
|
|
235
235
|
return { status: "forwarded" as const, runId: conflict.runId };
|
|
236
236
|
}
|
|
237
|
+
if (conflict) {
|
|
238
|
+
return { status: "duplicate" as const, runId: conflict.runId };
|
|
239
|
+
}
|
|
237
240
|
|
|
238
241
|
// ... own the token and do the work
|
|
239
242
|
}
|
|
240
243
|
```
|
|
241
244
|
|
|
242
|
-
**Supersede the owner.**
|
|
245
|
+
**Supersede the owner.** Without minimum retention, cancel the active run, then claim the released token. The retry loop covers the window where cancellation cleanup has not propagated yet:
|
|
243
246
|
|
|
244
247
|
```typescript lineNumbers
|
|
245
248
|
import { createHook } from "workflow";
|
|
@@ -272,6 +275,10 @@ export async function processOrderNewestWins(orderId: string) {
|
|
|
272
275
|
}
|
|
273
276
|
```
|
|
274
277
|
|
|
278
|
+
<Callout type="warn">
|
|
279
|
+
This pattern does not work with `experimental_minRetention`: cancelling the old run does not make its token available early.
|
|
280
|
+
</Callout>
|
|
281
|
+
|
|
275
282
|
If duplicate requests should only reuse the active run without sending data, use [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) as an advisory pre-check before calling `start()`. The workflow should still check `hook.getConflict()`, because the lookup and `start()` are not atomic.
|
|
276
283
|
|
|
277
284
|
Because this pattern uses hooks for idempotency, duplicate requests can also inject additional data and steer the existing run. The route example above uses `resumeHook()` for that: if the hook already exists, the duplicate request resumes the active workflow; if the hook is not registered yet, the route starts the workflow and retries `resumeHook()` so the payload is not dropped.
|
|
@@ -7,7 +7,7 @@ prerequisites:
|
|
|
7
7
|
- /docs/how-it-works/event-sourcing
|
|
8
8
|
related:
|
|
9
9
|
- /docs/observability
|
|
10
|
-
- /
|
|
10
|
+
- /worlds/vercel
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
<Callout>
|
|
@@ -38,7 +38,7 @@ Metadata such as workflow names, step names, entity IDs, timestamps, and lifecyc
|
|
|
38
38
|
|
|
39
39
|
Each workflow run is encrypted with its own unique key, provided by the `World` implementation via `getEncryptionKeyForRun()`. How the key is generated and stored is up to the `World`.
|
|
40
40
|
|
|
41
|
-
For example, the [Vercel World](/
|
|
41
|
+
For example, the [Vercel World](/worlds/vercel) provides unique keys per run and execution environment, ensuring that a given run can only decrypt data from that run itself.
|
|
42
42
|
|
|
43
43
|
### Encryption Algorithm
|
|
44
44
|
|
|
@@ -127,4 +127,4 @@ async function lookupRunKey(
|
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
The [Vercel World](/
|
|
130
|
+
The [Vercel World](/worlds/vercel) implementation uses HKDF derivation from a deployment-scoped key, but any consistent key management scheme will work.
|
|
@@ -121,15 +121,15 @@ flowchart TD
|
|
|
121
121
|
|
|
122
122
|
**Hook states:**
|
|
123
123
|
|
|
124
|
-
- `active`: Ready to receive payloads
|
|
125
|
-
- `disposed`: No longer accepting payloads
|
|
124
|
+
- `active`: Ready to receive payloads
|
|
125
|
+
- `disposed`: No longer accepting payloads
|
|
126
126
|
- `conflicted`: Hook creation failed because the token is already in use by another workflow
|
|
127
127
|
|
|
128
|
-
Unlike other entities, hooks don't have a `status` field—the states above are conceptual.
|
|
128
|
+
Unlike other entities, hooks don't have a `status` field—the states above are conceptual. When a `hook_disposed` event is created, the hook record is removed rather than updated.
|
|
129
129
|
|
|
130
|
-
While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token
|
|
130
|
+
While a hook is active, its token is reserved and cannot be used by other workflows. If a workflow attempts to create a hook with a token reserved by another run — either by an active hook or by `experimental_minRetention` after its run ended — a `hook_conflict` event is recorded instead of `hook_created`. Current worlds include the token and the run ID that owns it, though older persisted events or world implementations may only include the token. This causes `hook.getConflict()` to resolve with the conflicting run and the hook's payload promise to reject with a `HookConflictError`, which you can detect with `HookConflictError.is(error)`. See the [hook-conflict error](/docs/errors/hook-conflict) documentation for more details.
|
|
131
131
|
|
|
132
|
-
When a
|
|
132
|
+
When a workflow ends, its Hooks can no longer be resumed. They are normally removed and their tokens become available again. With `experimental_minRetention`, a Hook remains readable and its token remains unavailable until retention ends. A `hook_disposed` event removes the Hook and makes its token available immediately.
|
|
133
133
|
|
|
134
134
|
See [Hooks & Webhooks](/docs/foundations/hooks) for more on how hooks and webhooks work.
|
|
135
135
|
|
|
@@ -188,7 +188,7 @@ Events are categorized by the entity type they affect. Each event contains metad
|
|
|
188
188
|
| Event | Description |
|
|
189
189
|
|-------|-------------|
|
|
190
190
|
| `hook_created` | Creates a new hook in `active` state. Contains the hook token and optional metadata. |
|
|
191
|
-
| `hook_conflict` | Records that hook creation failed because
|
|
191
|
+
| `hook_conflict` | Records that hook creation failed because another run owns the token. Contains the token and, for current worlds, the owner's run ID. The hook is not created: `hook.getConflict()` resolves with the conflicting run, and awaiting the hook payload rejects with a `HookConflictError`. |
|
|
192
192
|
| `hook_received` | Records that a payload was delivered to the hook. The hook remains `active` and can receive more payloads. |
|
|
193
193
|
| `hook_disposed` | Deletes the hook from storage (conceptually transitioning to `disposed` state). The token is released for reuse by future workflows. |
|
|
194
194
|
|
|
@@ -6,7 +6,7 @@ summary: Build a custom framework integration using the Workflow SDK compiler an
|
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/foundations/workflows-and-steps
|
|
8
8
|
related:
|
|
9
|
-
- /
|
|
9
|
+
- /worlds/building-a-world
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
<Callout>
|
|
@@ -425,7 +425,7 @@ For self-hosted or non-Vercel deployments, you are responsible for securing the
|
|
|
425
425
|
- **Network-level security** — Deploy handlers behind a VPC, private network, or firewall rules so only your queue infrastructure can reach them
|
|
426
426
|
- **Rate limiting** — Add request validation and rate limiting to prevent abuse
|
|
427
427
|
|
|
428
|
-
Learn more about [building custom Worlds](/
|
|
428
|
+
Learn more about [building custom Worlds](/worlds/building-a-world).
|
|
429
429
|
|
|
430
430
|
## Testing Your Integration
|
|
431
431
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow/core",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.36",
|
|
4
4
|
"description": "Core runtime and engine for Workflow SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -98,12 +98,12 @@
|
|
|
98
98
|
"semver": "7.7.4",
|
|
99
99
|
"ulid": "~3.0.1",
|
|
100
100
|
"zod": "~4.3.6",
|
|
101
|
-
"@workflow/errors": "5.0.0-beta.
|
|
101
|
+
"@workflow/errors": "5.0.0-beta.12",
|
|
102
102
|
"@workflow/serde": "5.0.0-beta.2",
|
|
103
103
|
"@workflow/utils": "5.0.0-beta.6",
|
|
104
|
-
"@workflow/world": "5.0.0-beta.
|
|
105
|
-
"@workflow/world-local": "5.0.0-beta.
|
|
106
|
-
"@workflow/world-vercel": "5.0.0-beta.
|
|
104
|
+
"@workflow/world": "5.0.0-beta.22",
|
|
105
|
+
"@workflow/world-local": "5.0.0-beta.30",
|
|
106
|
+
"@workflow/world-vercel": "5.0.0-beta.32"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@opentelemetry/api": "1.9.0",
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-run memoization cache for hydrated step return values.
|
|
3
|
-
*
|
|
4
|
-
* ## Why
|
|
5
|
-
*
|
|
6
|
-
* The inline replay loop (`runtime.ts`) re-runs the workflow body from the top
|
|
7
|
-
* on every iteration, re-consuming the full event log each time. For every
|
|
8
|
-
* already-completed step, the step consumer (`step.ts`) re-runs
|
|
9
|
-
* `hydrateStepReturnValue` — which AES-GCM-decrypts and devalue-parses the
|
|
10
|
-
* serialized result — even though that exact result was already hydrated on
|
|
11
|
-
* every prior replay. For a sequential workflow of N steps, replay K hydrates
|
|
12
|
-
* K results, so the total work across a single invocation is O(N²)
|
|
13
|
-
* decrypt+parse operations.
|
|
14
|
-
*
|
|
15
|
-
* This cache makes a completed step's hydrated result available in O(1) on
|
|
16
|
-
* subsequent replays within the SAME invocation, turning the aggregate cost
|
|
17
|
-
* into O(N).
|
|
18
|
-
*
|
|
19
|
-
* ## Scope / lifetime
|
|
20
|
-
*
|
|
21
|
-
* The cache is owned by the inline loop in `runtime.ts` (one per workflow run
|
|
22
|
-
* invocation) and passed into `runWorkflow` so it survives across the loop's
|
|
23
|
-
* iterations but never leaks across unrelated runs or process-level
|
|
24
|
-
* invocations. A fresh `runWorkflow` / `WorkflowOrchestratorContext` is created
|
|
25
|
-
* each iteration, so the cache must live OUTSIDE the per-iteration context.
|
|
26
|
-
*
|
|
27
|
-
* ## Keying
|
|
28
|
-
*
|
|
29
|
-
* Entries are keyed by the persisted event's `eventId` — a stable,
|
|
30
|
-
* world-assigned identifier for the `step_completed` event whose serialized
|
|
31
|
-
* `result` is being hydrated. The same event (same `eventId`) carries the same
|
|
32
|
-
* immutable serialized bytes across every replay, so a hit is guaranteed to
|
|
33
|
-
* correspond to the identical input.
|
|
34
|
-
*
|
|
35
|
-
* ## Identity safety (why primitives only)
|
|
36
|
-
*
|
|
37
|
-
* `hydrateStepReturnValue` (devalue.parse) produces a FRESH object graph on
|
|
38
|
-
* every call, and each replay iteration runs in a FRESH workflow VM. The
|
|
39
|
-
* current (uncached) behavior therefore hands the workflow a brand-new value
|
|
40
|
-
* on every replay. If we cached and returned the SAME object reference across
|
|
41
|
-
* replays, workflow code that mutates a step result (`const r = await step();
|
|
42
|
-
* r.count++`) would observe the mutation from a previous replay on the next
|
|
43
|
-
* replay — a non-deterministic divergence. Structured-cloning on each hit is
|
|
44
|
-
* both lossy (revivers reconstruct stream handles, step-function proxies,
|
|
45
|
-
* Request/Response, and AbortController/AbortSignal class instances that don't
|
|
46
|
-
* survive a structured clone) and still O(size).
|
|
47
|
-
*
|
|
48
|
-
* So we only cache values for which returning the same reference on every
|
|
49
|
-
* replay is provably indistinguishable from re-hydrating: JavaScript
|
|
50
|
-
* primitives (string, number, boolean, bigint, symbol, null, undefined).
|
|
51
|
-
* Primitives are immutable and compared by value, so sharing the reference is
|
|
52
|
-
* byte-for-byte equivalent to re-parsing. Any non-primitive result falls
|
|
53
|
-
* through to a full re-hydrate every replay, preserving current behavior
|
|
54
|
-
* exactly. This trades some of the optimization away in the object-returning
|
|
55
|
-
* case in exchange for keeping deterministic replay airtight.
|
|
56
|
-
*
|
|
57
|
-
* ## Memory characteristic
|
|
58
|
-
*
|
|
59
|
-
* Cached entries hold the decrypted/devalue-parsed *plaintext* of a step
|
|
60
|
-
* result, which is retained for the rest of the invocation on top of the
|
|
61
|
-
* serialized bytes already held in `cachedEvents`. So the residual cost is:
|
|
62
|
-
*
|
|
63
|
-
* - **Scoped to one workflow-run invocation.** A fresh `Map` is created per
|
|
64
|
-
* invocation (in `runtime.ts`) and is unreachable / GC'd when the invocation
|
|
65
|
-
* returns. Nothing accumulates across runs or across process-level
|
|
66
|
-
* invocations.
|
|
67
|
-
* - **Bounded by the number of primitive-returning completed steps in that
|
|
68
|
-
* run** — at most one small entry per such step.
|
|
69
|
-
* - **Primitives only, and additionally byte-bounded.** Most primitives
|
|
70
|
-
* (numbers, booleans, null/undefined, symbols, short ids/strings) are tiny
|
|
71
|
-
* and fixed-size. The only primitive that can be large is a string (or a
|
|
72
|
-
* pathologically long bigint), so to keep the doubled-residency worst case
|
|
73
|
-
* bounded we *do not* memoize string/bigint results whose character length
|
|
74
|
-
* exceeds {@link MAX_MEMOIZED_PRIMITIVE_LENGTH}. A large string is cheap to
|
|
75
|
-
* re-hydrate relative to its footprint, so letting it fall through to the
|
|
76
|
-
* existing per-replay re-hydrate path costs little and caps peak retained
|
|
77
|
-
* memory.
|
|
78
|
-
*
|
|
79
|
-
* (This is a much weaker concern than a *process-wide* cache: the dominant
|
|
80
|
-
* residency — the full event log in `cachedEvents` — already exists for the
|
|
81
|
-
* same lifetime, and everything here is freed together with it when the
|
|
82
|
-
* invocation ends.)
|
|
83
|
-
*/
|
|
84
|
-
/**
|
|
85
|
-
* Upper bound, in characters, on a string/bigint primitive that may be
|
|
86
|
-
* memoized. Beyond this, the value falls through to a fresh re-hydrate on every
|
|
87
|
-
* replay so the cache never holds a large plaintext payload for the lifetime of
|
|
88
|
-
* the invocation. 4 KiB comfortably covers ids, counts, flags, and typical
|
|
89
|
-
* short string results while excluding the large-payload case the bound exists
|
|
90
|
-
* to guard. Other primitive types (number, boolean, symbol, null, undefined)
|
|
91
|
-
* are inherently small and are never length-checked.
|
|
92
|
-
*/
|
|
93
|
-
export declare const MAX_MEMOIZED_PRIMITIVE_LENGTH = 4096;
|
|
94
|
-
/**
|
|
95
|
-
* Returns true for values that are safe to memoize and return by reference
|
|
96
|
-
* across replays: JS primitives. Objects and functions are excluded because
|
|
97
|
-
* sharing a mutable reference across replays could change observable behavior.
|
|
98
|
-
*
|
|
99
|
-
* Strings and bigints are additionally bounded by length: a value longer than
|
|
100
|
-
* {@link MAX_MEMOIZED_PRIMITIVE_LENGTH} characters is treated as non-memoizable
|
|
101
|
-
* so the cache never retains a large plaintext payload for the whole invocation
|
|
102
|
-
* (see the module-level "Memory characteristic" docs). It re-hydrates fresh on
|
|
103
|
-
* every replay instead — cheap relative to its footprint.
|
|
104
|
-
*
|
|
105
|
-
* Note: `typeof null === 'object'`, so it is handled explicitly. `undefined`,
|
|
106
|
-
* `string`, `number`, `boolean`, `bigint`, and `symbol` are all primitives.
|
|
107
|
-
*/
|
|
108
|
-
export declare function isMemoizablePrimitive(value: unknown): boolean;
|
|
109
|
-
/**
|
|
110
|
-
* Cache of hydrated step return values for a single workflow run invocation.
|
|
111
|
-
*
|
|
112
|
-
* Keyed by `step_completed` event id; the value is the already-hydrated
|
|
113
|
-
* primitive result. Only successful, primitive hydrations are stored (see
|
|
114
|
-
* {@link getOrHydrateStepReturnValue}), so a non-`undefined` `has(eventId)`
|
|
115
|
-
* always means "this step completed with a memoizable primitive value".
|
|
116
|
-
*/
|
|
117
|
-
export type StepHydrationCache = Map<string, unknown>;
|
|
118
|
-
/**
|
|
119
|
-
* Create an empty per-invocation step hydration cache.
|
|
120
|
-
*/
|
|
121
|
-
export declare function createStepHydrationCache(): StepHydrationCache;
|
|
122
|
-
/**
|
|
123
|
-
* Return the hydrated step result for `eventId`, using `cache` as a per-run
|
|
124
|
-
* memo. On a hit, the cached primitive is returned without re-running the
|
|
125
|
-
* expensive decrypt + devalue-parse. On a miss, `hydrate()` runs and its
|
|
126
|
-
* result is memoized only when it is a small primitive (see the module docs for
|
|
127
|
-
* the identity-safety rationale and the length bound on string/bigint results).
|
|
128
|
-
*
|
|
129
|
-
* This always returns a `Promise` and `await`s `hydrate()` even on the miss
|
|
130
|
-
* path, so the caller's `await` inside its serial `promiseQueue` slot keeps the
|
|
131
|
-
* same scheduling on both hit and miss — a cache hit resolves through the exact
|
|
132
|
-
* same promise-chain position a re-hydrate would have, preserving the
|
|
133
|
-
* deterministic delivery order that `pendingDeliveries`, the delivery barriers,
|
|
134
|
-
* and `Promise.race`/`Promise.all` replay all depend on.
|
|
135
|
-
*
|
|
136
|
-
* `has(eventId)` is used rather than `get(eventId) !== undefined` so that a
|
|
137
|
-
* legitimately memoized `undefined` step result still registers as a hit.
|
|
138
|
-
*
|
|
139
|
-
* When `cache` or `eventId` is absent (lightweight test harnesses, or a context
|
|
140
|
-
* that predates this plumbing), this degrades to calling `hydrate()` directly
|
|
141
|
-
* with no memoization — identical to the previous behavior.
|
|
142
|
-
*
|
|
143
|
-
* Errors are intentionally never cached: a rejected hydrate propagates to the
|
|
144
|
-
* caller (which rejects the step promise) and the next replay re-attempts it,
|
|
145
|
-
* matching the uncached behavior and avoiding a parked rejected promise.
|
|
146
|
-
*/
|
|
147
|
-
export declare function getOrHydrateStepReturnValue(cache: StepHydrationCache | undefined, eventId: string | undefined, hydrate: () => Promise<unknown>): Promise<unknown>;
|
|
148
|
-
//# sourceMappingURL=step-hydration-cache.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"step-hydration-cache.d.ts","sourceRoot":"","sources":["../src/step-hydration-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkFG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAY7D;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,kBAAkB,CAE7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,kBAAkB,GAAG,SAAS,EACrC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC9B,OAAO,CAAC,OAAO,CAAC,CAkBlB"}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-run memoization cache for hydrated step return values.
|
|
3
|
-
*
|
|
4
|
-
* ## Why
|
|
5
|
-
*
|
|
6
|
-
* The inline replay loop (`runtime.ts`) re-runs the workflow body from the top
|
|
7
|
-
* on every iteration, re-consuming the full event log each time. For every
|
|
8
|
-
* already-completed step, the step consumer (`step.ts`) re-runs
|
|
9
|
-
* `hydrateStepReturnValue` — which AES-GCM-decrypts and devalue-parses the
|
|
10
|
-
* serialized result — even though that exact result was already hydrated on
|
|
11
|
-
* every prior replay. For a sequential workflow of N steps, replay K hydrates
|
|
12
|
-
* K results, so the total work across a single invocation is O(N²)
|
|
13
|
-
* decrypt+parse operations.
|
|
14
|
-
*
|
|
15
|
-
* This cache makes a completed step's hydrated result available in O(1) on
|
|
16
|
-
* subsequent replays within the SAME invocation, turning the aggregate cost
|
|
17
|
-
* into O(N).
|
|
18
|
-
*
|
|
19
|
-
* ## Scope / lifetime
|
|
20
|
-
*
|
|
21
|
-
* The cache is owned by the inline loop in `runtime.ts` (one per workflow run
|
|
22
|
-
* invocation) and passed into `runWorkflow` so it survives across the loop's
|
|
23
|
-
* iterations but never leaks across unrelated runs or process-level
|
|
24
|
-
* invocations. A fresh `runWorkflow` / `WorkflowOrchestratorContext` is created
|
|
25
|
-
* each iteration, so the cache must live OUTSIDE the per-iteration context.
|
|
26
|
-
*
|
|
27
|
-
* ## Keying
|
|
28
|
-
*
|
|
29
|
-
* Entries are keyed by the persisted event's `eventId` — a stable,
|
|
30
|
-
* world-assigned identifier for the `step_completed` event whose serialized
|
|
31
|
-
* `result` is being hydrated. The same event (same `eventId`) carries the same
|
|
32
|
-
* immutable serialized bytes across every replay, so a hit is guaranteed to
|
|
33
|
-
* correspond to the identical input.
|
|
34
|
-
*
|
|
35
|
-
* ## Identity safety (why primitives only)
|
|
36
|
-
*
|
|
37
|
-
* `hydrateStepReturnValue` (devalue.parse) produces a FRESH object graph on
|
|
38
|
-
* every call, and each replay iteration runs in a FRESH workflow VM. The
|
|
39
|
-
* current (uncached) behavior therefore hands the workflow a brand-new value
|
|
40
|
-
* on every replay. If we cached and returned the SAME object reference across
|
|
41
|
-
* replays, workflow code that mutates a step result (`const r = await step();
|
|
42
|
-
* r.count++`) would observe the mutation from a previous replay on the next
|
|
43
|
-
* replay — a non-deterministic divergence. Structured-cloning on each hit is
|
|
44
|
-
* both lossy (revivers reconstruct stream handles, step-function proxies,
|
|
45
|
-
* Request/Response, and AbortController/AbortSignal class instances that don't
|
|
46
|
-
* survive a structured clone) and still O(size).
|
|
47
|
-
*
|
|
48
|
-
* So we only cache values for which returning the same reference on every
|
|
49
|
-
* replay is provably indistinguishable from re-hydrating: JavaScript
|
|
50
|
-
* primitives (string, number, boolean, bigint, symbol, null, undefined).
|
|
51
|
-
* Primitives are immutable and compared by value, so sharing the reference is
|
|
52
|
-
* byte-for-byte equivalent to re-parsing. Any non-primitive result falls
|
|
53
|
-
* through to a full re-hydrate every replay, preserving current behavior
|
|
54
|
-
* exactly. This trades some of the optimization away in the object-returning
|
|
55
|
-
* case in exchange for keeping deterministic replay airtight.
|
|
56
|
-
*
|
|
57
|
-
* ## Memory characteristic
|
|
58
|
-
*
|
|
59
|
-
* Cached entries hold the decrypted/devalue-parsed *plaintext* of a step
|
|
60
|
-
* result, which is retained for the rest of the invocation on top of the
|
|
61
|
-
* serialized bytes already held in `cachedEvents`. So the residual cost is:
|
|
62
|
-
*
|
|
63
|
-
* - **Scoped to one workflow-run invocation.** A fresh `Map` is created per
|
|
64
|
-
* invocation (in `runtime.ts`) and is unreachable / GC'd when the invocation
|
|
65
|
-
* returns. Nothing accumulates across runs or across process-level
|
|
66
|
-
* invocations.
|
|
67
|
-
* - **Bounded by the number of primitive-returning completed steps in that
|
|
68
|
-
* run** — at most one small entry per such step.
|
|
69
|
-
* - **Primitives only, and additionally byte-bounded.** Most primitives
|
|
70
|
-
* (numbers, booleans, null/undefined, symbols, short ids/strings) are tiny
|
|
71
|
-
* and fixed-size. The only primitive that can be large is a string (or a
|
|
72
|
-
* pathologically long bigint), so to keep the doubled-residency worst case
|
|
73
|
-
* bounded we *do not* memoize string/bigint results whose character length
|
|
74
|
-
* exceeds {@link MAX_MEMOIZED_PRIMITIVE_LENGTH}. A large string is cheap to
|
|
75
|
-
* re-hydrate relative to its footprint, so letting it fall through to the
|
|
76
|
-
* existing per-replay re-hydrate path costs little and caps peak retained
|
|
77
|
-
* memory.
|
|
78
|
-
*
|
|
79
|
-
* (This is a much weaker concern than a *process-wide* cache: the dominant
|
|
80
|
-
* residency — the full event log in `cachedEvents` — already exists for the
|
|
81
|
-
* same lifetime, and everything here is freed together with it when the
|
|
82
|
-
* invocation ends.)
|
|
83
|
-
*/
|
|
84
|
-
/**
|
|
85
|
-
* Upper bound, in characters, on a string/bigint primitive that may be
|
|
86
|
-
* memoized. Beyond this, the value falls through to a fresh re-hydrate on every
|
|
87
|
-
* replay so the cache never holds a large plaintext payload for the lifetime of
|
|
88
|
-
* the invocation. 4 KiB comfortably covers ids, counts, flags, and typical
|
|
89
|
-
* short string results while excluding the large-payload case the bound exists
|
|
90
|
-
* to guard. Other primitive types (number, boolean, symbol, null, undefined)
|
|
91
|
-
* are inherently small and are never length-checked.
|
|
92
|
-
*/
|
|
93
|
-
export const MAX_MEMOIZED_PRIMITIVE_LENGTH = 4096;
|
|
94
|
-
/**
|
|
95
|
-
* Returns true for values that are safe to memoize and return by reference
|
|
96
|
-
* across replays: JS primitives. Objects and functions are excluded because
|
|
97
|
-
* sharing a mutable reference across replays could change observable behavior.
|
|
98
|
-
*
|
|
99
|
-
* Strings and bigints are additionally bounded by length: a value longer than
|
|
100
|
-
* {@link MAX_MEMOIZED_PRIMITIVE_LENGTH} characters is treated as non-memoizable
|
|
101
|
-
* so the cache never retains a large plaintext payload for the whole invocation
|
|
102
|
-
* (see the module-level "Memory characteristic" docs). It re-hydrates fresh on
|
|
103
|
-
* every replay instead — cheap relative to its footprint.
|
|
104
|
-
*
|
|
105
|
-
* Note: `typeof null === 'object'`, so it is handled explicitly. `undefined`,
|
|
106
|
-
* `string`, `number`, `boolean`, `bigint`, and `symbol` are all primitives.
|
|
107
|
-
*/
|
|
108
|
-
export function isMemoizablePrimitive(value) {
|
|
109
|
-
if (value === null)
|
|
110
|
-
return true;
|
|
111
|
-
const t = typeof value;
|
|
112
|
-
if (t === 'object' || t === 'function')
|
|
113
|
-
return false;
|
|
114
|
-
// Bound the only primitive types that can carry a large payload.
|
|
115
|
-
if (t === 'string') {
|
|
116
|
-
return value.length <= MAX_MEMOIZED_PRIMITIVE_LENGTH;
|
|
117
|
-
}
|
|
118
|
-
if (t === 'bigint') {
|
|
119
|
-
return value.toString().length <= MAX_MEMOIZED_PRIMITIVE_LENGTH;
|
|
120
|
-
}
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Create an empty per-invocation step hydration cache.
|
|
125
|
-
*/
|
|
126
|
-
export function createStepHydrationCache() {
|
|
127
|
-
return new Map();
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Return the hydrated step result for `eventId`, using `cache` as a per-run
|
|
131
|
-
* memo. On a hit, the cached primitive is returned without re-running the
|
|
132
|
-
* expensive decrypt + devalue-parse. On a miss, `hydrate()` runs and its
|
|
133
|
-
* result is memoized only when it is a small primitive (see the module docs for
|
|
134
|
-
* the identity-safety rationale and the length bound on string/bigint results).
|
|
135
|
-
*
|
|
136
|
-
* This always returns a `Promise` and `await`s `hydrate()` even on the miss
|
|
137
|
-
* path, so the caller's `await` inside its serial `promiseQueue` slot keeps the
|
|
138
|
-
* same scheduling on both hit and miss — a cache hit resolves through the exact
|
|
139
|
-
* same promise-chain position a re-hydrate would have, preserving the
|
|
140
|
-
* deterministic delivery order that `pendingDeliveries`, the delivery barriers,
|
|
141
|
-
* and `Promise.race`/`Promise.all` replay all depend on.
|
|
142
|
-
*
|
|
143
|
-
* `has(eventId)` is used rather than `get(eventId) !== undefined` so that a
|
|
144
|
-
* legitimately memoized `undefined` step result still registers as a hit.
|
|
145
|
-
*
|
|
146
|
-
* When `cache` or `eventId` is absent (lightweight test harnesses, or a context
|
|
147
|
-
* that predates this plumbing), this degrades to calling `hydrate()` directly
|
|
148
|
-
* with no memoization — identical to the previous behavior.
|
|
149
|
-
*
|
|
150
|
-
* Errors are intentionally never cached: a rejected hydrate propagates to the
|
|
151
|
-
* caller (which rejects the step promise) and the next replay re-attempts it,
|
|
152
|
-
* matching the uncached behavior and avoiding a parked rejected promise.
|
|
153
|
-
*/
|
|
154
|
-
export async function getOrHydrateStepReturnValue(cache, eventId, hydrate) {
|
|
155
|
-
if (!cache || eventId === undefined) {
|
|
156
|
-
return hydrate();
|
|
157
|
-
}
|
|
158
|
-
if (cache.has(eventId)) {
|
|
159
|
-
return cache.get(eventId);
|
|
160
|
-
}
|
|
161
|
-
const value = await hydrate();
|
|
162
|
-
// Only memoize values that are safe to return by reference across replays
|
|
163
|
-
// AND small enough to retain for the invocation. Non-primitives and
|
|
164
|
-
// oversized string/bigint values fall through and are re-hydrated fresh on
|
|
165
|
-
// every replay (see isMemoizablePrimitive / MAX_MEMOIZED_PRIMITIVE_LENGTH).
|
|
166
|
-
if (isMemoizablePrimitive(value)) {
|
|
167
|
-
cache.set(eventId, value);
|
|
168
|
-
}
|
|
169
|
-
return value;
|
|
170
|
-
}
|
|
171
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RlcC1oeWRyYXRpb24tY2FjaGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc3RlcC1oeWRyYXRpb24tY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrRkc7QUFFSDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sQ0FBQyxNQUFNLDZCQUE2QixHQUFHLElBQUksQ0FBQztBQUVsRDs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsTUFBTSxVQUFVLHFCQUFxQixDQUFDLEtBQWM7SUFDbEQsSUFBSSxLQUFLLEtBQUssSUFBSTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ2hDLE1BQU0sQ0FBQyxHQUFHLE9BQU8sS0FBSyxDQUFDO0lBQ3ZCLElBQUksQ0FBQyxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssVUFBVTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3JELGlFQUFpRTtJQUNqRSxJQUFJLENBQUMsS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUNuQixPQUFRLEtBQWdCLENBQUMsTUFBTSxJQUFJLDZCQUE2QixDQUFDO0lBQ25FLENBQUM7SUFDRCxJQUFJLENBQUMsS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUNuQixPQUFRLEtBQWdCLENBQUMsUUFBUSxFQUFFLENBQUMsTUFBTSxJQUFJLDZCQUE2QixDQUFDO0lBQzlFLENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUM7QUFZRDs7R0FFRztBQUNILE1BQU0sVUFBVSx3QkFBd0I7SUFDdEMsT0FBTyxJQUFJLEdBQUcsRUFBRSxDQUFDO0FBQ25CLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSwyQkFBMkIsQ0FDL0MsS0FBcUMsRUFDckMsT0FBMkIsRUFDM0IsT0FBK0I7SUFFL0IsSUFBSSxDQUFDLEtBQUssSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDcEMsT0FBTyxPQUFPLEVBQUUsQ0FBQztJQUNuQixDQUFDO0lBRUQsSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDdkIsT0FBTyxLQUFLLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzVCLENBQUM7SUFFRCxNQUFNLEtBQUssR0FBRyxNQUFNLE9BQU8sRUFBRSxDQUFDO0lBQzlCLDBFQUEwRTtJQUMxRSxvRUFBb0U7SUFDcEUsMkVBQTJFO0lBQzNFLDRFQUE0RTtJQUM1RSxJQUFJLHFCQUFxQixDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDakMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDNUIsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogUGVyLXJ1biBtZW1vaXphdGlvbiBjYWNoZSBmb3IgaHlkcmF0ZWQgc3RlcCByZXR1cm4gdmFsdWVzLlxuICpcbiAqICMjIFdoeVxuICpcbiAqIFRoZSBpbmxpbmUgcmVwbGF5IGxvb3AgKGBydW50aW1lLnRzYCkgcmUtcnVucyB0aGUgd29ya2Zsb3cgYm9keSBmcm9tIHRoZSB0b3BcbiAqIG9uIGV2ZXJ5IGl0ZXJhdGlvbiwgcmUtY29uc3VtaW5nIHRoZSBmdWxsIGV2ZW50IGxvZyBlYWNoIHRpbWUuIEZvciBldmVyeVxuICogYWxyZWFkeS1jb21wbGV0ZWQgc3RlcCwgdGhlIHN0ZXAgY29uc3VtZXIgKGBzdGVwLnRzYCkgcmUtcnVuc1xuICogYGh5ZHJhdGVTdGVwUmV0dXJuVmFsdWVgIOKAlCB3aGljaCBBRVMtR0NNLWRlY3J5cHRzIGFuZCBkZXZhbHVlLXBhcnNlcyB0aGVcbiAqIHNlcmlhbGl6ZWQgcmVzdWx0IOKAlCBldmVuIHRob3VnaCB0aGF0IGV4YWN0IHJlc3VsdCB3YXMgYWxyZWFkeSBoeWRyYXRlZCBvblxuICogZXZlcnkgcHJpb3IgcmVwbGF5LiBGb3IgYSBzZXF1ZW50aWFsIHdvcmtmbG93IG9mIE4gc3RlcHMsIHJlcGxheSBLIGh5ZHJhdGVzXG4gKiBLIHJlc3VsdHMsIHNvIHRoZSB0b3RhbCB3b3JrIGFjcm9zcyBhIHNpbmdsZSBpbnZvY2F0aW9uIGlzIE8oTsKyKVxuICogZGVjcnlwdCtwYXJzZSBvcGVyYXRpb25zLlxuICpcbiAqIFRoaXMgY2FjaGUgbWFrZXMgYSBjb21wbGV0ZWQgc3RlcCdzIGh5ZHJhdGVkIHJlc3VsdCBhdmFpbGFibGUgaW4gTygxKSBvblxuICogc3Vic2VxdWVudCByZXBsYXlzIHdpdGhpbiB0aGUgU0FNRSBpbnZvY2F0aW9uLCB0dXJuaW5nIHRoZSBhZ2dyZWdhdGUgY29zdFxuICogaW50byBPKE4pLlxuICpcbiAqICMjIFNjb3BlIC8gbGlmZXRpbWVcbiAqXG4gKiBUaGUgY2FjaGUgaXMgb3duZWQgYnkgdGhlIGlubGluZSBsb29wIGluIGBydW50aW1lLnRzYCAob25lIHBlciB3b3JrZmxvdyBydW5cbiAqIGludm9jYXRpb24pIGFuZCBwYXNzZWQgaW50byBgcnVuV29ya2Zsb3dgIHNvIGl0IHN1cnZpdmVzIGFjcm9zcyB0aGUgbG9vcCdzXG4gKiBpdGVyYXRpb25zIGJ1dCBuZXZlciBsZWFrcyBhY3Jvc3MgdW5yZWxhdGVkIHJ1bnMgb3IgcHJvY2Vzcy1sZXZlbFxuICogaW52b2NhdGlvbnMuIEEgZnJlc2ggYHJ1bldvcmtmbG93YCAvIGBXb3JrZmxvd09yY2hlc3RyYXRvckNvbnRleHRgIGlzIGNyZWF0ZWRcbiAqIGVhY2ggaXRlcmF0aW9uLCBzbyB0aGUgY2FjaGUgbXVzdCBsaXZlIE9VVFNJREUgdGhlIHBlci1pdGVyYXRpb24gY29udGV4dC5cbiAqXG4gKiAjIyBLZXlpbmdcbiAqXG4gKiBFbnRyaWVzIGFyZSBrZXllZCBieSB0aGUgcGVyc2lzdGVkIGV2ZW50J3MgYGV2ZW50SWRgIOKAlCBhIHN0YWJsZSxcbiAqIHdvcmxkLWFzc2lnbmVkIGlkZW50aWZpZXIgZm9yIHRoZSBgc3RlcF9jb21wbGV0ZWRgIGV2ZW50IHdob3NlIHNlcmlhbGl6ZWRcbiAqIGByZXN1bHRgIGlzIGJlaW5nIGh5ZHJhdGVkLiBUaGUgc2FtZSBldmVudCAoc2FtZSBgZXZlbnRJZGApIGNhcnJpZXMgdGhlIHNhbWVcbiAqIGltbXV0YWJsZSBzZXJpYWxpemVkIGJ5dGVzIGFjcm9zcyBldmVyeSByZXBsYXksIHNvIGEgaGl0IGlzIGd1YXJhbnRlZWQgdG9cbiAqIGNvcnJlc3BvbmQgdG8gdGhlIGlkZW50aWNhbCBpbnB1dC5cbiAqXG4gKiAjIyBJZGVudGl0eSBzYWZldHkgKHdoeSBwcmltaXRpdmVzIG9ubHkpXG4gKlxuICogYGh5ZHJhdGVTdGVwUmV0dXJuVmFsdWVgIChkZXZhbHVlLnBhcnNlKSBwcm9kdWNlcyBhIEZSRVNIIG9iamVjdCBncmFwaCBvblxuICogZXZlcnkgY2FsbCwgYW5kIGVhY2ggcmVwbGF5IGl0ZXJhdGlvbiBydW5zIGluIGEgRlJFU0ggd29ya2Zsb3cgVk0uIFRoZVxuICogY3VycmVudCAodW5jYWNoZWQpIGJlaGF2aW9yIHRoZXJlZm9yZSBoYW5kcyB0aGUgd29ya2Zsb3cgYSBicmFuZC1uZXcgdmFsdWVcbiAqIG9uIGV2ZXJ5IHJlcGxheS4gSWYgd2UgY2FjaGVkIGFuZCByZXR1cm5lZCB0aGUgU0FNRSBvYmplY3QgcmVmZXJlbmNlIGFjcm9zc1xuICogcmVwbGF5cywgd29ya2Zsb3cgY29kZSB0aGF0IG11dGF0ZXMgYSBzdGVwIHJlc3VsdCAoYGNvbnN0IHIgPSBhd2FpdCBzdGVwKCk7XG4gKiByLmNvdW50KytgKSB3b3VsZCBvYnNlcnZlIHRoZSBtdXRhdGlvbiBmcm9tIGEgcHJldmlvdXMgcmVwbGF5IG9uIHRoZSBuZXh0XG4gKiByZXBsYXkg4oCUIGEgbm9uLWRldGVybWluaXN0aWMgZGl2ZXJnZW5jZS4gU3RydWN0dXJlZC1jbG9uaW5nIG9uIGVhY2ggaGl0IGlzXG4gKiBib3RoIGxvc3N5IChyZXZpdmVycyByZWNvbnN0cnVjdCBzdHJlYW0gaGFuZGxlcywgc3RlcC1mdW5jdGlvbiBwcm94aWVzLFxuICogUmVxdWVzdC9SZXNwb25zZSwgYW5kIEFib3J0Q29udHJvbGxlci9BYm9ydFNpZ25hbCBjbGFzcyBpbnN0YW5jZXMgdGhhdCBkb24ndFxuICogc3Vydml2ZSBhIHN0cnVjdHVyZWQgY2xvbmUpIGFuZCBzdGlsbCBPKHNpemUpLlxuICpcbiAqIFNvIHdlIG9ubHkgY2FjaGUgdmFsdWVzIGZvciB3aGljaCByZXR1cm5pbmcgdGhlIHNhbWUgcmVmZXJlbmNlIG9uIGV2ZXJ5XG4gKiByZXBsYXkgaXMgcHJvdmFibHkgaW5kaXN0aW5ndWlzaGFibGUgZnJvbSByZS1oeWRyYXRpbmc6IEphdmFTY3JpcHRcbiAqIHByaW1pdGl2ZXMgKHN0cmluZywgbnVtYmVyLCBib29sZWFuLCBiaWdpbnQsIHN5bWJvbCwgbnVsbCwgdW5kZWZpbmVkKS5cbiAqIFByaW1pdGl2ZXMgYXJlIGltbXV0YWJsZSBhbmQgY29tcGFyZWQgYnkgdmFsdWUsIHNvIHNoYXJpbmcgdGhlIHJlZmVyZW5jZSBpc1xuICogYnl0ZS1mb3ItYnl0ZSBlcXVpdmFsZW50IHRvIHJlLXBhcnNpbmcuIEFueSBub24tcHJpbWl0aXZlIHJlc3VsdCBmYWxsc1xuICogdGhyb3VnaCB0byBhIGZ1bGwgcmUtaHlkcmF0ZSBldmVyeSByZXBsYXksIHByZXNlcnZpbmcgY3VycmVudCBiZWhhdmlvclxuICogZXhhY3RseS4gVGhpcyB0cmFkZXMgc29tZSBvZiB0aGUgb3B0aW1pemF0aW9uIGF3YXkgaW4gdGhlIG9iamVjdC1yZXR1cm5pbmdcbiAqIGNhc2UgaW4gZXhjaGFuZ2UgZm9yIGtlZXBpbmcgZGV0ZXJtaW5pc3RpYyByZXBsYXkgYWlydGlnaHQuXG4gKlxuICogIyMgTWVtb3J5IGNoYXJhY3RlcmlzdGljXG4gKlxuICogQ2FjaGVkIGVudHJpZXMgaG9sZCB0aGUgZGVjcnlwdGVkL2RldmFsdWUtcGFyc2VkICpwbGFpbnRleHQqIG9mIGEgc3RlcFxuICogcmVzdWx0LCB3aGljaCBpcyByZXRhaW5lZCBmb3IgdGhlIHJlc3Qgb2YgdGhlIGludm9jYXRpb24gb24gdG9wIG9mIHRoZVxuICogc2VyaWFsaXplZCBieXRlcyBhbHJlYWR5IGhlbGQgaW4gYGNhY2hlZEV2ZW50c2AuIFNvIHRoZSByZXNpZHVhbCBjb3N0IGlzOlxuICpcbiAqIC0gKipTY29wZWQgdG8gb25lIHdvcmtmbG93LXJ1biBpbnZvY2F0aW9uLioqIEEgZnJlc2ggYE1hcGAgaXMgY3JlYXRlZCBwZXJcbiAqICAgaW52b2NhdGlvbiAoaW4gYHJ1bnRpbWUudHNgKSBhbmQgaXMgdW5yZWFjaGFibGUgLyBHQydkIHdoZW4gdGhlIGludm9jYXRpb25cbiAqICAgcmV0dXJucy4gTm90aGluZyBhY2N1bXVsYXRlcyBhY3Jvc3MgcnVucyBvciBhY3Jvc3MgcHJvY2Vzcy1sZXZlbFxuICogICBpbnZvY2F0aW9ucy5cbiAqIC0gKipCb3VuZGVkIGJ5IHRoZSBudW1iZXIgb2YgcHJpbWl0aXZlLXJldHVybmluZyBjb21wbGV0ZWQgc3RlcHMgaW4gdGhhdFxuICogICBydW4qKiDigJQgYXQgbW9zdCBvbmUgc21hbGwgZW50cnkgcGVyIHN1Y2ggc3RlcC5cbiAqIC0gKipQcmltaXRpdmVzIG9ubHksIGFuZCBhZGRpdGlvbmFsbHkgYnl0ZS1ib3VuZGVkLioqIE1vc3QgcHJpbWl0aXZlc1xuICogICAobnVtYmVycywgYm9vbGVhbnMsIG51bGwvdW5kZWZpbmVkLCBzeW1ib2xzLCBzaG9ydCBpZHMvc3RyaW5ncykgYXJlIHRpbnlcbiAqICAgYW5kIGZpeGVkLXNpemUuIFRoZSBvbmx5IHByaW1pdGl2ZSB0aGF0IGNhbiBiZSBsYXJnZSBpcyBhIHN0cmluZyAob3IgYVxuICogICBwYXRob2xvZ2ljYWxseSBsb25nIGJpZ2ludCksIHNvIHRvIGtlZXAgdGhlIGRvdWJsZWQtcmVzaWRlbmN5IHdvcnN0IGNhc2VcbiAqICAgYm91bmRlZCB3ZSAqZG8gbm90KiBtZW1vaXplIHN0cmluZy9iaWdpbnQgcmVzdWx0cyB3aG9zZSBjaGFyYWN0ZXIgbGVuZ3RoXG4gKiAgIGV4Y2VlZHMge0BsaW5rIE1BWF9NRU1PSVpFRF9QUklNSVRJVkVfTEVOR1RIfS4gQSBsYXJnZSBzdHJpbmcgaXMgY2hlYXAgdG9cbiAqICAgcmUtaHlkcmF0ZSByZWxhdGl2ZSB0byBpdHMgZm9vdHByaW50LCBzbyBsZXR0aW5nIGl0IGZhbGwgdGhyb3VnaCB0byB0aGVcbiAqICAgZXhpc3RpbmcgcGVyLXJlcGxheSByZS1oeWRyYXRlIHBhdGggY29zdHMgbGl0dGxlIGFuZCBjYXBzIHBlYWsgcmV0YWluZWRcbiAqICAgbWVtb3J5LlxuICpcbiAqIChUaGlzIGlzIGEgbXVjaCB3ZWFrZXIgY29uY2VybiB0aGFuIGEgKnByb2Nlc3Mtd2lkZSogY2FjaGU6IHRoZSBkb21pbmFudFxuICogcmVzaWRlbmN5IOKAlCB0aGUgZnVsbCBldmVudCBsb2cgaW4gYGNhY2hlZEV2ZW50c2Ag4oCUIGFscmVhZHkgZXhpc3RzIGZvciB0aGVcbiAqIHNhbWUgbGlmZXRpbWUsIGFuZCBldmVyeXRoaW5nIGhlcmUgaXMgZnJlZWQgdG9nZXRoZXIgd2l0aCBpdCB3aGVuIHRoZVxuICogaW52b2NhdGlvbiBlbmRzLilcbiAqL1xuXG4vKipcbiAqIFVwcGVyIGJvdW5kLCBpbiBjaGFyYWN0ZXJzLCBvbiBhIHN0cmluZy9iaWdpbnQgcHJpbWl0aXZlIHRoYXQgbWF5IGJlXG4gKiBtZW1vaXplZC4gQmV5b25kIHRoaXMsIHRoZSB2YWx1ZSBmYWxscyB0aHJvdWdoIHRvIGEgZnJlc2ggcmUtaHlkcmF0ZSBvbiBldmVyeVxuICogcmVwbGF5IHNvIHRoZSBjYWNoZSBuZXZlciBob2xkcyBhIGxhcmdlIHBsYWludGV4dCBwYXlsb2FkIGZvciB0aGUgbGlmZXRpbWUgb2ZcbiAqIHRoZSBpbnZvY2F0aW9uLiA0IEtpQiBjb21mb3J0YWJseSBjb3ZlcnMgaWRzLCBjb3VudHMsIGZsYWdzLCBhbmQgdHlwaWNhbFxuICogc2hvcnQgc3RyaW5nIHJlc3VsdHMgd2hpbGUgZXhjbHVkaW5nIHRoZSBsYXJnZS1wYXlsb2FkIGNhc2UgdGhlIGJvdW5kIGV4aXN0c1xuICogdG8gZ3VhcmQuIE90aGVyIHByaW1pdGl2ZSB0eXBlcyAobnVtYmVyLCBib29sZWFuLCBzeW1ib2wsIG51bGwsIHVuZGVmaW5lZClcbiAqIGFyZSBpbmhlcmVudGx5IHNtYWxsIGFuZCBhcmUgbmV2ZXIgbGVuZ3RoLWNoZWNrZWQuXG4gKi9cbmV4cG9ydCBjb25zdCBNQVhfTUVNT0laRURfUFJJTUlUSVZFX0xFTkdUSCA9IDQwOTY7XG5cbi8qKlxuICogUmV0dXJucyB0cnVlIGZvciB2YWx1ZXMgdGhhdCBhcmUgc2FmZSB0byBtZW1vaXplIGFuZCByZXR1cm4gYnkgcmVmZXJlbmNlXG4gKiBhY3Jvc3MgcmVwbGF5czogSlMgcHJpbWl0aXZlcy4gT2JqZWN0cyBhbmQgZnVuY3Rpb25zIGFyZSBleGNsdWRlZCBiZWNhdXNlXG4gKiBzaGFyaW5nIGEgbXV0YWJsZSByZWZlcmVuY2UgYWNyb3NzIHJlcGxheXMgY291bGQgY2hhbmdlIG9ic2VydmFibGUgYmVoYXZpb3IuXG4gKlxuICogU3RyaW5ncyBhbmQgYmlnaW50cyBhcmUgYWRkaXRpb25hbGx5IGJvdW5kZWQgYnkgbGVuZ3RoOiBhIHZhbHVlIGxvbmdlciB0aGFuXG4gKiB7QGxpbmsgTUFYX01FTU9JWkVEX1BSSU1JVElWRV9MRU5HVEh9IGNoYXJhY3RlcnMgaXMgdHJlYXRlZCBhcyBub24tbWVtb2l6YWJsZVxuICogc28gdGhlIGNhY2hlIG5ldmVyIHJldGFpbnMgYSBsYXJnZSBwbGFpbnRleHQgcGF5bG9hZCBmb3IgdGhlIHdob2xlIGludm9jYXRpb25cbiAqIChzZWUgdGhlIG1vZHVsZS1sZXZlbCBcIk1lbW9yeSBjaGFyYWN0ZXJpc3RpY1wiIGRvY3MpLiBJdCByZS1oeWRyYXRlcyBmcmVzaCBvblxuICogZXZlcnkgcmVwbGF5IGluc3RlYWQg4oCUIGNoZWFwIHJlbGF0aXZlIHRvIGl0cyBmb290cHJpbnQuXG4gKlxuICogTm90ZTogYHR5cGVvZiBudWxsID09PSAnb2JqZWN0J2AsIHNvIGl0IGlzIGhhbmRsZWQgZXhwbGljaXRseS4gYHVuZGVmaW5lZGAsXG4gKiBgc3RyaW5nYCwgYG51bWJlcmAsIGBib29sZWFuYCwgYGJpZ2ludGAsIGFuZCBgc3ltYm9sYCBhcmUgYWxsIHByaW1pdGl2ZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc01lbW9pemFibGVQcmltaXRpdmUodmFsdWU6IHVua25vd24pOiBib29sZWFuIHtcbiAgaWYgKHZhbHVlID09PSBudWxsKSByZXR1cm4gdHJ1ZTtcbiAgY29uc3QgdCA9IHR5cGVvZiB2YWx1ZTtcbiAgaWYgKHQgPT09ICdvYmplY3QnIHx8IHQgPT09ICdmdW5jdGlvbicpIHJldHVybiBmYWxzZTtcbiAgLy8gQm91bmQgdGhlIG9ubHkgcHJpbWl0aXZlIHR5cGVzIHRoYXQgY2FuIGNhcnJ5IGEgbGFyZ2UgcGF5bG9hZC5cbiAgaWYgKHQgPT09ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuICh2YWx1ZSBhcyBzdHJpbmcpLmxlbmd0aCA8PSBNQVhfTUVNT0laRURfUFJJTUlUSVZFX0xFTkdUSDtcbiAgfVxuICBpZiAodCA9PT0gJ2JpZ2ludCcpIHtcbiAgICByZXR1cm4gKHZhbHVlIGFzIGJpZ2ludCkudG9TdHJpbmcoKS5sZW5ndGggPD0gTUFYX01FTU9JWkVEX1BSSU1JVElWRV9MRU5HVEg7XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5cbi8qKlxuICogQ2FjaGUgb2YgaHlkcmF0ZWQgc3RlcCByZXR1cm4gdmFsdWVzIGZvciBhIHNpbmdsZSB3b3JrZmxvdyBydW4gaW52b2NhdGlvbi5cbiAqXG4gKiBLZXllZCBieSBgc3RlcF9jb21wbGV0ZWRgIGV2ZW50IGlkOyB0aGUgdmFsdWUgaXMgdGhlIGFscmVhZHktaHlkcmF0ZWRcbiAqIHByaW1pdGl2ZSByZXN1bHQuIE9ubHkgc3VjY2Vzc2Z1bCwgcHJpbWl0aXZlIGh5ZHJhdGlvbnMgYXJlIHN0b3JlZCAoc2VlXG4gKiB7QGxpbmsgZ2V0T3JIeWRyYXRlU3RlcFJldHVyblZhbHVlfSksIHNvIGEgbm9uLWB1bmRlZmluZWRgIGBoYXMoZXZlbnRJZClgXG4gKiBhbHdheXMgbWVhbnMgXCJ0aGlzIHN0ZXAgY29tcGxldGVkIHdpdGggYSBtZW1vaXphYmxlIHByaW1pdGl2ZSB2YWx1ZVwiLlxuICovXG5leHBvcnQgdHlwZSBTdGVwSHlkcmF0aW9uQ2FjaGUgPSBNYXA8c3RyaW5nLCB1bmtub3duPjtcblxuLyoqXG4gKiBDcmVhdGUgYW4gZW1wdHkgcGVyLWludm9jYXRpb24gc3RlcCBoeWRyYXRpb24gY2FjaGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVTdGVwSHlkcmF0aW9uQ2FjaGUoKTogU3RlcEh5ZHJhdGlvbkNhY2hlIHtcbiAgcmV0dXJuIG5ldyBNYXAoKTtcbn1cblxuLyoqXG4gKiBSZXR1cm4gdGhlIGh5ZHJhdGVkIHN0ZXAgcmVzdWx0IGZvciBgZXZlbnRJZGAsIHVzaW5nIGBjYWNoZWAgYXMgYSBwZXItcnVuXG4gKiBtZW1vLiBPbiBhIGhpdCwgdGhlIGNhY2hlZCBwcmltaXRpdmUgaXMgcmV0dXJuZWQgd2l0aG91dCByZS1ydW5uaW5nIHRoZVxuICogZXhwZW5zaXZlIGRlY3J5cHQgKyBkZXZhbHVlLXBhcnNlLiBPbiBhIG1pc3MsIGBoeWRyYXRlKClgIHJ1bnMgYW5kIGl0c1xuICogcmVzdWx0IGlzIG1lbW9pemVkIG9ubHkgd2hlbiBpdCBpcyBhIHNtYWxsIHByaW1pdGl2ZSAoc2VlIHRoZSBtb2R1bGUgZG9jcyBmb3JcbiAqIHRoZSBpZGVudGl0eS1zYWZldHkgcmF0aW9uYWxlIGFuZCB0aGUgbGVuZ3RoIGJvdW5kIG9uIHN0cmluZy9iaWdpbnQgcmVzdWx0cykuXG4gKlxuICogVGhpcyBhbHdheXMgcmV0dXJucyBhIGBQcm9taXNlYCBhbmQgYGF3YWl0YHMgYGh5ZHJhdGUoKWAgZXZlbiBvbiB0aGUgbWlzc1xuICogcGF0aCwgc28gdGhlIGNhbGxlcidzIGBhd2FpdGAgaW5zaWRlIGl0cyBzZXJpYWwgYHByb21pc2VRdWV1ZWAgc2xvdCBrZWVwcyB0aGVcbiAqIHNhbWUgc2NoZWR1bGluZyBvbiBib3RoIGhpdCBhbmQgbWlzcyDigJQgYSBjYWNoZSBoaXQgcmVzb2x2ZXMgdGhyb3VnaCB0aGUgZXhhY3RcbiAqIHNhbWUgcHJvbWlzZS1jaGFpbiBwb3NpdGlvbiBhIHJlLWh5ZHJhdGUgd291bGQgaGF2ZSwgcHJlc2VydmluZyB0aGVcbiAqIGRldGVybWluaXN0aWMgZGVsaXZlcnkgb3JkZXIgdGhhdCBgcGVuZGluZ0RlbGl2ZXJpZXNgLCB0aGUgZGVsaXZlcnkgYmFycmllcnMsXG4gKiBhbmQgYFByb21pc2UucmFjZWAvYFByb21pc2UuYWxsYCByZXBsYXkgYWxsIGRlcGVuZCBvbi5cbiAqXG4gKiBgaGFzKGV2ZW50SWQpYCBpcyB1c2VkIHJhdGhlciB0aGFuIGBnZXQoZXZlbnRJZCkgIT09IHVuZGVmaW5lZGAgc28gdGhhdCBhXG4gKiBsZWdpdGltYXRlbHkgbWVtb2l6ZWQgYHVuZGVmaW5lZGAgc3RlcCByZXN1bHQgc3RpbGwgcmVnaXN0ZXJzIGFzIGEgaGl0LlxuICpcbiAqIFdoZW4gYGNhY2hlYCBvciBgZXZlbnRJZGAgaXMgYWJzZW50IChsaWdodHdlaWdodCB0ZXN0IGhhcm5lc3Nlcywgb3IgYSBjb250ZXh0XG4gKiB0aGF0IHByZWRhdGVzIHRoaXMgcGx1bWJpbmcpLCB0aGlzIGRlZ3JhZGVzIHRvIGNhbGxpbmcgYGh5ZHJhdGUoKWAgZGlyZWN0bHlcbiAqIHdpdGggbm8gbWVtb2l6YXRpb24g4oCUIGlkZW50aWNhbCB0byB0aGUgcHJldmlvdXMgYmVoYXZpb3IuXG4gKlxuICogRXJyb3JzIGFyZSBpbnRlbnRpb25hbGx5IG5ldmVyIGNhY2hlZDogYSByZWplY3RlZCBoeWRyYXRlIHByb3BhZ2F0ZXMgdG8gdGhlXG4gKiBjYWxsZXIgKHdoaWNoIHJlamVjdHMgdGhlIHN0ZXAgcHJvbWlzZSkgYW5kIHRoZSBuZXh0IHJlcGxheSByZS1hdHRlbXB0cyBpdCxcbiAqIG1hdGNoaW5nIHRoZSB1bmNhY2hlZCBiZWhhdmlvciBhbmQgYXZvaWRpbmcgYSBwYXJrZWQgcmVqZWN0ZWQgcHJvbWlzZS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldE9ySHlkcmF0ZVN0ZXBSZXR1cm5WYWx1ZShcbiAgY2FjaGU6IFN0ZXBIeWRyYXRpb25DYWNoZSB8IHVuZGVmaW5lZCxcbiAgZXZlbnRJZDogc3RyaW5nIHwgdW5kZWZpbmVkLFxuICBoeWRyYXRlOiAoKSA9PiBQcm9taXNlPHVua25vd24+XG4pOiBQcm9taXNlPHVua25vd24+IHtcbiAgaWYgKCFjYWNoZSB8fCBldmVudElkID09PSB1bmRlZmluZWQpIHtcbiAgICByZXR1cm4gaHlkcmF0ZSgpO1xuICB9XG5cbiAgaWYgKGNhY2hlLmhhcyhldmVudElkKSkge1xuICAgIHJldHVybiBjYWNoZS5nZXQoZXZlbnRJZCk7XG4gIH1cblxuICBjb25zdCB2YWx1ZSA9IGF3YWl0IGh5ZHJhdGUoKTtcbiAgLy8gT25seSBtZW1vaXplIHZhbHVlcyB0aGF0IGFyZSBzYWZlIHRvIHJldHVybiBieSByZWZlcmVuY2UgYWNyb3NzIHJlcGxheXNcbiAgLy8gQU5EIHNtYWxsIGVub3VnaCB0byByZXRhaW4gZm9yIHRoZSBpbnZvY2F0aW9uLiBOb24tcHJpbWl0aXZlcyBhbmRcbiAgLy8gb3ZlcnNpemVkIHN0cmluZy9iaWdpbnQgdmFsdWVzIGZhbGwgdGhyb3VnaCBhbmQgYXJlIHJlLWh5ZHJhdGVkIGZyZXNoIG9uXG4gIC8vIGV2ZXJ5IHJlcGxheSAoc2VlIGlzTWVtb2l6YWJsZVByaW1pdGl2ZSAvIE1BWF9NRU1PSVpFRF9QUklNSVRJVkVfTEVOR1RIKS5cbiAgaWYgKGlzTWVtb2l6YWJsZVByaW1pdGl2ZSh2YWx1ZSkpIHtcbiAgICBjYWNoZS5zZXQoZXZlbnRJZCwgdmFsdWUpO1xuICB9XG4gIHJldHVybiB2YWx1ZTtcbn1cbiJdfQ==
|