diff --git a/.github/workflows/build-and-push-typescript.yml b/.github/workflows/build-and-push-typescript.yml new file mode 100644 index 0000000..5d0db44 --- /dev/null +++ b/.github/workflows/build-and-push-typescript.yml @@ -0,0 +1,55 @@ +# Pushes the TypeScript Instruqt track definition on merge to main. +# +# CI deliberately does NOT build the sandbox image, for the same reason as the +# Kotlin caller: the image lives at ghcr.io/nadvolod/edu-nexus-typescript-sandbox, +# a personal namespace this repo's default GITHUB_TOKEN cannot write to. A build +# job would fail on every merge. Rather than leave CI permanently red, the image +# is built by hand and pinned by digest in typescript/instruqt/config.yml: +# +# cd typescript +# docker buildx build --platform linux/amd64 -f sandbox/Dockerfile \ +# -t ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest --push . +# docker buildx imagetools inspect ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest \ +# --format "{{.Manifest.Digest}}" +# # paste that digest into typescript/instruqt/config.yml, then commit +# +# Because the digest is pinned, a source change only reaches attendees once that +# re-pin is committed, and the re-pin lives under typescript/instruqt/. That is +# why this workflow watches instruqt/ alone: it is the only path that can change +# what the track actually serves. +# +# To let CI own the image later, move it to ghcr.io/temporalio/... (or add a PAT +# with write:packages) and restore build_image below. +# +# Required once: a repo secret named INSTRUQT_TOKEN (Instruqt org API key). +name: Publish TypeScript Nexus track + +on: + push: + branches: [main] + paths: + - "typescript/instruqt/**" + - ".github/workflows/_track-ci.yml" + - ".github/workflows/build-and-push-typescript.yml" + workflow_dispatch: + +jobs: + ci: + # packages: write looks wrong next to build_image: false, but it is required. + # A caller's permissions block is the CEILING for every job in the called + # workflow, and GitHub validates that ceiling when it loads the workflow, + # before any `if:` is evaluated. _track-ci.yml's build-image job asks for + # packages: write, so a read-only ceiling fails the whole run at startup with + # zero jobs, even though build-image never runs. Do not narrow this back. + permissions: + contents: read + packages: write + uses: ./.github/workflows/_track-ci.yml + with: + track_dir: "typescript" + # Referenced for completeness. Unused while build_image is false. + image: ghcr.io/nadvolod/edu-nexus-typescript-sandbox + # Never build here: see the note at the top of this file. + build_image: false + push_track: true + secrets: inherit diff --git a/.gitignore b/.gitignore index 6faa36b..2e8842c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ build/ # VS Code Kotlin language server output bin/ + +# Node (typescript/) +node_modules/ +lib/ diff --git a/INSTRUQT.md b/INSTRUQT.md index 34e2d85..8066baf 100644 --- a/INSTRUQT.md +++ b/INSTRUQT.md @@ -46,6 +46,61 @@ fixed code-server for us. Confirmed working. Only one tab can be first, so a second embedded app (our Solution editor) may still need a refresh. Accept it, or serve both views from one instance. +### Use ONE editor tab, not an Exercise tab and a Solution tab + +This is the fix for the 0x0 iframe problem above rather than a workaround for it. + +Two editor tabs means two code-server iframes, and Instruqt boots every service tab's +iframe at challenge start including the hidden ones. Only the first tab has real +dimensions, so the second reliably needs a manual refresh. Making the Solution tab +position 5 does not help; it just moves which one is broken. + +Point a single tab at the directory that contains BOTH trees: + +```yaml +- title: Exercise + type: service + path: /?folder=/root/workshop # holds exercise/ and solution/ + port: 8080 +``` + +One iframe, nothing hidden, no refresh. It also drops the tab count from seven to six. + +Three things move with it, and two fail silently: + +- **`tab-N` is a position.** Removing a tab renumbers every tab after it. Re-verify + every `[button label="..."](tab-N)` by label, not by arithmetic. +- **File paths in assignments become ambiguous.** With `solution/` visible, "open + `payments/workflows.ts`" matches two files, and a learner editing the solution copy + watches their changes do nothing. Write paths in full: + `exercise/src/payments/workflows.ts`. +- **Any readiness probe that polls `?folder=` must use the new path.** Ours still + pointed at the old folder, so setup would have reported the editor not ready forever. + +The cost is that source sits two levels deeper in the tree. Worth it; the refresh bug +was hit by every attendee, the extra clicks are hit once. + +### Feedback is lab chrome, not a tab you add + +Do not add a Feedback entry to every challenge's `tabs:` list. It renumbers every tab +after it and breaks every `[button](tab-N)` in the track, for a tab Instruqt already +provides. + +Turn it on in `track.yml` instead: + +```yaml +lab_config: + feedback_tab_enabled: true # Feedback tab in the lab UI + feedback_recap_enabled: true # prompt at the end of the track +``` + +Verified against `temporal-community/ai-agents-workshop-v4`, which sets both and has no +Feedback entry in any challenge's `tabs:`. Because it is chrome, `tab-N` indices are +untouched. + +There is no `tab-N` to link to, so a per-challenge call to action references it by name: +"the **Feedback** tab". + ### Reordering tabs breaks every button, silently `tab-N` is a **zero-indexed position, not an id**. Move a tab and every @@ -324,6 +379,60 @@ grep -rn '^ *==*$' */assignment.md lines in a `kotlin,nocopy` fence and then say what they mean. Costs four lines of assignment, removes all the guessing. +### Implementation steps belong in the TODO, not the assignment + +The assignment names the file and the TODO, and says why the change matters. The TODO +comment, three lines from the cursor, says how. When both say how you have two copies to +keep in sync, and the learner reads the same recipe twice. + +This does not contradict "Show the line, do not describe it" above, and the difference is +worth stating precisely: + +- Code the learner is meant to **read** belongs in the assignment. Pasting it stops them + hunting through a file for the two lines you meant. +- Code the learner is meant to **write** belongs in the TODO. That is the recipe, and the + recipe goes next to the cursor. + +What it looks like when it leaks, from challenge 4 of the TypeScript track: + +``` +Then delete the proxy above it and its `complianceActivities` import. +Remove `...complianceActivities` from the `activities` object, and delete its import. +``` + +Both were already in the TODO comment. Grep for the shape — an imperative aimed at a +named symbol: + +```bash +grep -nE '^(Remove|Delete|Add|Replace|Change) `' */assignment.md +``` + +The assignment keeps the part the TODO cannot carry: why the change matters, and what to +notice once it works. + +### One action, one lettered TODO, sited on the code it changes + +A TODO that says "replace this call, then delete the declaration above, then write the +function at the bottom" makes the learner hold three locations in their head and scroll +between them. They finish one, lose the list, and re-read the whole block. + +Split by action, letter them so they still read as one idea, and put each comment on the +code it acts on: + +``` +TODO 4a on the proxy to delete +TODO 4b on the call site to replace +TODO 4c on the function to write +``` + +Letters rather than new numbers when the parts are one concept — here, putting the Nexus +boundary in. New numbers imply separate concepts and inflate the count. + +The failure this fixes is real and was reported from a live sandbox: a note reading +"TODO 4(a) deletes this proxy" sat on the proxy while the instructions for 4(a) were +thirty lines away at the call site, so the two halves of one instruction pointed at each +other. + ### A long edge label will cover your diagram Node text and edge labels are drawn in different layers, so a label wider than the gap diff --git a/README.md b/README.md index cffa139..5345c07 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,20 @@ See the [Nexus documentation](https://docs.temporal.io/nexus) to explore more. You can launch an exercise environment for this tutorial using GitHub Codespaces by following [this](codespaces.md) walkthrough. +## Language tracks + +Two hands-on lab versions of this material exist as Instruqt tracks, in addition to the +Java tutorial under `java/`: + +| Language | Directory | Track slug | Sandbox image | +|---|---|---|---| +| Kotlin | `kotlin/` | `nexus-kotlin-decouple-monolith` | `ghcr.io/nadvolod/edu-nexus-kotlin-sandbox` | +| TypeScript | `typescript/` | `nexus-typescript-decouple-monolith` | `ghcr.io/nadvolod/edu-nexus-typescript-sandbox` | + +The slide decks are **not** in this repo. They live in +[`temporalio/temporal-devdays-ts`](https://github.com/temporalio/temporal-devdays-ts) +under `decks/`, which is the index and deck library for TypeScript DevDays content. + ## Kotlin Instruqt track A hands-on lab version of this material, in Kotlin, lives under `kotlin/`. It runs as @@ -205,4 +219,202 @@ even though `build-image` is disabled. `Workflow.getLogger` from `main`, which throws off a Workflow thread. - **`track.yml` and every `assignment.md` frontmatter are rewritten on push.** Comments are stripped and keys reordered. Keep durable explanation here, not in those files. - `config.yml` is left alone. \ No newline at end of file + `config.yml` is left alone. + +## TypeScript Instruqt track + +The same material in TypeScript, under `typescript/`, on the Temporal TypeScript SDK. +Same five challenges, same narrative, same 90 minute shape. + +``` +typescript/ +├── decouple-monolith/ +│ ├── exercise/ npm + ts-node, TODOs 1 to 7 open +│ └── solution/ complete +├── sandbox/ +│ ├── Dockerfile node:22-bookworm + Temporal CLI + code-server +│ └── serve-diagrams.mjs static server for the diagram tab +├── diagrams/ architecture diagram, served on 8090 +├── .dockerignore keeps node_modules out of the build context +└── instruqt/ + ├── track.yml, config.yml, track_scripts/ + └── 01-run-the-monolith/ ... 05-durability-and-human-review/ +``` + +**Nexus is GA in the TypeScript SDK as of v1.23.0**, the version this workshop pins — +specifically for calling Operations from Workflows and for Workflow-backed Operation +handlers, which is exactly what the lab builds (SDK release notes, PR #2299). + +Two things are easy to get wrong here, so they are stated in the deck and in +`shared/nexus-service.ts` rather than left to be discovered: + +- `nexus-rpc` is a **separately versioned** package, still at `0.0.3`, and it does tag + `service()`, `operation()` and `serviceHandler()` `@experimental`. An attendee hovering + `nexus.service` will see that and reasonably conclude Nexus is experimental. It is not; + the contract package is. +- **Standalone Nexus Operations** — running an Operation with no caller Workflow, via + `client.nexus.createServiceClient()` — *are* still Pre-release and need a special CLI + build. This workshop does not use them. + +### Three lessons that do not survive the port + +These are real design differences between the SDKs, not translation choices. Recorded +here for whoever maintains the port. **The workshop itself is standalone and never +mentions another language** — the assignments teach each of these on its own terms, so do +not reintroduce a comparison into attendee-facing material. + +- **Challenge 2 ends with a deliberately FAILING typecheck.** `ServiceHandlerFor` is + a non-optional mapped type, so declaring an Operation obliges a matching handler at + compile time. Kotlin fails at Worker startup with `Missing handlers for service + operations`; TypeScript refuses to compile: + + ``` + TS2345: Type '{}' is missing the following properties from type + 'ServiceHandlerFor<...>': checkCompliance, submitReview + ``` + + So `02/solve-workshop` deliberately does not typecheck. The build is red until + challenge 3 fills the handlers in, and that is the lesson, not a defect. + +- **Challenge 3's proof of registration inverts.** There is no `Nexus Poller` line to + look for. A Worker *without* `nexusServices` logs `No Nexus services registered, not + polling for Nexus tasks`; a correctly wired one logs nothing. Absence is the signal, so + the assignment tells learners to go looking for it deliberately. + +- **The Endpoint moves to the call site.** Java and Kotlin configure it on the Worker via + `NexusServiceOptions`, which is what makes the Kotlin lesson "notice you do NOT write + the Endpoint name here." In TypeScript, `wf.createNexusServiceClient({ service, + endpoint })` takes it inside the Workflow. The decoupling argument still holds one + level in — the Workflow knows a name, the Registry knows the address — and + `COMPLIANCE_ENDPOINT` is a single constant in `shared/types.ts`. + +### Challenges + +Five TODOs, numbered 1 to 5, plus one worked example. + +`checkCompliance` in `compliance/nexus-handler.ts` is **given, not a TODO**. It is the +harder of the two Nexus Operations and the one whose shape carries the lesson, so a +learner reads it as a worked example and then writes `submitReview`, which is the +opposite shape on the same Service. That contrast is what challenge 3 is for, and it +lands better from a working example than from two blank functions. + +| # | Slug | Format | Files touched | TODOs | +|---|------|--------|---------------|-------| +| 01 | run-the-monolith | observe | none | — | +| 02 | the-shared-contract | code | `shared/nexus-service.ts` | 1 | +| 03 | the-compliance-side | code | `compliance/nexus-handler.ts`, `compliance/worker.ts`, plus the Endpoint CLI | 2, 3 (+ worked example) | + +TODO 4 is lettered rather than split into separate numbers, because its three parts are +one idea: put the Nexus boundary in. 4a deletes the Activity proxy, 4b replaces the call +that used it, 4c writes the review caller. Each comment sits on the code it changes. + +4b and 4c build the same `createNexusServiceClient` with different numbers — `'10 minutes'` +for `checkCompliance`, `'10 seconds'` for `submitReview` — and that pair is the lesson: +async Operations budget for the whole call including retries, sync Operations must answer +inside the handler deadline. +| 04 | the-payments-side | code | `payments/workflows.ts`, `payments/worker.ts` | 4a, 4b, 4c, 5 | +| 05 | durability-and-human-review | observe | none | — | + +### Tabs + +Six, not seven. There is **one** editor tab, rooted at `/root/workshop` so both project +folders are in the same file tree: + +| Index | Tab | Type | +|-------|-----|------| +| tab-0 | Exercise | service, port 8080 (code-server, shows `exercise/` and `solution/`) | +| tab-1 | Temporal UI | service, port 8233 | +| tab-2 | Terminal | terminal | +| tab-3 | Payments Worker | terminal | +| tab-4 | Compliance Worker | terminal | +| tab-5 | Monolith Architecture | service, port 8090 (`node /opt/serve-diagrams.mjs`) | + +A separate Solution tab was removed on purpose. Instruqt loads every service tab's iframe +at challenge start including hidden ones, and code-server cannot lay itself out in a 0x0 +iframe — which is why the Kotlin track's Solution tab "may still need one refresh". One +editor instance with one visible iframe removes that failure mode rather than documenting +it. + +The cost is that source paths are now two levels deeper, and `solution/` is visible while +a learner works. Both are handled by writing paths in full: assignments say +`exercise/src/payments/workflows.ts`, never `payments/workflows.ts`, so nobody edits the +solution copy by accident. `node_modules` and lockfiles are hidden via `files.exclude`. + +The `tab-N`-is-a-position rule still applies: these are zero-indexed positions, not ids, +so reordering means remapping every button. + +### What the sandbox image bakes in + +`node:22-bookworm`, the Temporal CLI, code-server, both trees at `/opt/workshop` with +`node_modules` installed, and the diagram. Both trees are typechecked at build time, so +a broken port fails the image build rather than the workshop. + +Three deliberate differences from the Kotlin image: + +- **`memory: 4096`, not 8192.** That budget existed for two JVMs and a Gradle daemon. +- **No language extension is installed.** The Kotlin image pinned a syntax-only extension + to keep a language server from competing with two JVMs. Here the built-in TypeScript + service is wanted: it renders the challenge-2 compile error as a live red squiggle. +- **Track setup symlinks `node_modules` instead of copying it.** It is 292 MB per tree, + so copying both would move ~600 MB before the attendee can type. Consequence: + `npm install` inside the lab cannot write to `node_modules`. The workshop never needs + it. + +`.dockerignore` is load-bearing, not tidiness. The build context is `typescript/` and the +Dockerfile copies `decouple-monolith/`. Without the ignore the build ships ~600 MB of +host-built `node_modules` that are also the wrong architecture — the host is arm64, the +image amd64, and `@temporalio` ships a native core bridge. Context drops from 584 MB to +0.3 MB. + +### Publishing + +```bash +cd typescript/instruqt && instruqt track validate +``` + +Build for `linux/amd64`. An arm64 image fails to start on Instruqt with an empty log: + +```bash +cd typescript +docker buildx build --platform linux/amd64 \ + -f sandbox/Dockerfile -t ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest . +docker buildx imagetools inspect ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest \ + --format "{{.Manifest.Digest}}" +``` + +Same rule as Kotlin: **the image is pinned by digest, so any change under +`typescript/decouple-monolith/` requires a rebuild AND a re-pin.** Only changes under +`typescript/instruqt/` ship without one. + +**The GHCR package must be public.** A newly published package is private by default and +Instruqt pulls anonymously, which surfaces as `Could not find the image` with nothing +else to go on. Check it without credentials: + +```bash +tok=$(curl -s "https://ghcr.io/token?scope=repository:nadvolod/edu-nexus-typescript-sandbox:pull&service=ghcr.io" | jq -r .token) +curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $tok" \ + https://ghcr.io/v2/nadvolod/edu-nexus-typescript-sandbox/manifests/latest +``` + +200 is public, 403 is private. GitHub exposes no REST endpoint for package visibility, so +flipping it is a web-UI action. + +### TypeScript-specific gotchas + +- **`proxyActivities` silently types away synchronous Activities.** An Activity that is + not `async` becomes `NotAnActivityMethod`, and the failure appears at the *call site* in + the Workflow as `Type 'Symbol' has no call signatures` — which is verbatim the error the + SDK's own JSDoc quotes. Every Activity here is `async` for that reason. +- **A `setHandler` validator must take the same arguments as its handler.** A zero-arg + validator makes TypeScript infer `Args = []` and select the wrong `setHandler` overload, + producing an error that points at the definition rather than at the validator. +- **Workflow type names are camelCase in visibility queries.** Challenge 04's check queries + `WorkflowType="paymentProcessingWorkflow"`, the exported function name, not a class name. +- **Running the Kotlin and TypeScript workshops against one dev server does not work.** + Both poll `compliance-risk` in `compliance-namespace`. A stray Kotlin Worker will pick + up a TypeScript Workflow task and fail it with `Unknown workflow type + "complianceWorkflow". Known types are [ComplianceWorkflow]`, costing a Workflow-task + retry. Harmless in a sandbox, confusing locally. +- **`track.yml` and every `assignment.md` frontmatter are rewritten on publish**, exactly + as on the Kotlin track. Comments are stripped and keys reordered. Keep durable + explanation here. diff --git a/typescript/.dockerignore b/typescript/.dockerignore new file mode 100644 index 0000000..0b4c473 --- /dev/null +++ b/typescript/.dockerignore @@ -0,0 +1,7 @@ +# node_modules is reinstalled inside the image with `npm ci`, so shipping the +# host copy would send ~600 MB of build context and then be thrown away. +# It would also be the wrong architecture: the host is arm64, the image amd64, +# and @temporalio ships a native core bridge binary. +**/node_modules +**/lib +**/.DS_Store diff --git a/typescript/decouple-monolith/exercise/package-lock.json b/typescript/decouple-monolith/exercise/package-lock.json new file mode 100644 index 0000000..199db8c --- /dev/null +++ b/typescript/decouple-monolith/exercise/package-lock.json @@ -0,0 +1,2349 @@ +{ + "name": "decouple-monolith-exercise", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "decouple-monolith-exercise", + "version": "1.0.0", + "dependencies": { + "@temporalio/activity": "^1.23.0", + "@temporalio/client": "^1.23.0", + "@temporalio/nexus": "^1.23.0", + "@temporalio/worker": "^1.23.0", + "@temporalio/workflow": "^1.23.0", + "nexus-rpc": "^0.0.3" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/node": "^22.9.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.68.1.tgz", + "integrity": "sha512-V5oZ4Gt9WJKyQef0n9cAd0N9qjSkIBm3E4MYsgNIWBk5aINCDPKxMPo1i29rBxqiT4Ixf1epklqV9VJMKIxwlw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.68.1.tgz", + "integrity": "sha512-HCG72UioncuO7Gw09XNVG+S85e3cq2hrUC/mexBrsWsa3mI7eePkkqWie3uVYbtsb64OR9YGQs5SqaufDRYBcg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.68.1.tgz", + "integrity": "sha512-R5D9mWtqdURzcOWj1vdXr3APCwX0xchtFT+kmW7fXLNDifWdDrnh26jSID8pdnUfFBxTyfHtFtTL/NWKzIH7kQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/fs-print": "4.68.1", + "@jsonjoy.com/fs-snapshot": "4.68.1", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.68.1.tgz", + "integrity": "sha512-HK1BTksysokNZxNspqDH0yPaqN9YgR/AYIlYiIaU2Ys4BOk5CdybI7r6BgiZuiiPiV8n4sK/kZdice7Znpy2Kw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.68.1.tgz", + "integrity": "sha512-lpKmU4X9e/oh8GIuAI7EXaS5QiLNM3KD15CkdhfS6PYmrGvoJqKQcyEfnLgnnaGslh/PFUMYSIZBCf2ejJGw8g==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.68.1.tgz", + "integrity": "sha512-/GxfW1DWm9SCdkfbvqevLO/P5duobQfmKkHXxdMIDbcZMQeAgooAstIfZhkXpATzq9QbCQsnoWFM/dGHdZfndw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "glob-to-regex.js": "^1.0.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.68.1.tgz", + "integrity": "sha512-oGeZOGPYKK9v1CgeVeEDsLomH1lCnslSpqUN5GmPzrmAVGQlsmsdcXNA2O4lV8Y4xkuSuynx2ITBkUHJVaTbow==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.68.1", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.68.1.tgz", + "integrity": "sha512-XZfP0FDZN32bbc4t2bZN2qRrYHg5AktJnzk22HRoKGK4BprrbNRH2k5ceSNS/kupKYcofCs+O841+xaAbjnxwQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@swc/core": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.16.1.tgz", + "integrity": "sha512-nUaeu91O5QZKrQdaDCHd402ogUIoNOOjpkZNq0UomWK0G6gDaGmLhvddF1/3BXf5O8aLyo6ZPY/aMDWvaJQ/hg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.28" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.16.1", + "@swc/core-darwin-x64": "1.16.1", + "@swc/core-linux-arm-gnueabihf": "1.16.1", + "@swc/core-linux-arm64-gnu": "1.16.1", + "@swc/core-linux-arm64-musl": "1.16.1", + "@swc/core-linux-ppc64-gnu": "1.16.1", + "@swc/core-linux-s390x-gnu": "1.16.1", + "@swc/core-linux-x64-gnu": "1.16.1", + "@swc/core-linux-x64-musl": "1.16.1", + "@swc/core-win32-arm64-msvc": "1.16.1", + "@swc/core-win32-ia32-msvc": "1.16.1", + "@swc/core-win32-x64-msvc": "1.16.1" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.1.tgz", + "integrity": "sha512-zlJblJ8ncErD43lKdxjbUaUskJQf+LxiPXYcWXD8/8ZMV+7uuAT+CwjciLXpyZBd5Pq/S726bMpeeAwSeL1hhg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.1.tgz", + "integrity": "sha512-IN0BmPWb0YAh/17mmlWB/HDBtTw2MfuW4hulf/tQAgTQBRH17l+z499bNJLK6LizSjqs0P7V+jU38Zj+vJC1DA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.1.tgz", + "integrity": "sha512-EYgrx2YOCQ2Twz2S793kqNjPkpvYVUPzzR95bIb7by+VQcyaai4lZZ2iz/tZvcFVKSNcN3/JTKwx+aBn2ZL52A==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.1.tgz", + "integrity": "sha512-moyKm0YZlHdHohzm1YwgAyesqnE853rO0REMfJLFAova51wF9BNi+3ZW2PeS7Vqvn6HeJuepLpAHbBdZctxpHA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.1.tgz", + "integrity": "sha512-kKGBO9wdapiSzuf5ZzZ2fYtlu1BNSYtIIUxvH1ir/gcelTOREEHGDCLTDFx/2Knf878nU11A40z7LxwasEFxqA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.1.tgz", + "integrity": "sha512-nZ6qahtLxC3PM54cWOQZHxt4lTCF/3J4LIoWWzz6v7A+rLs8Dx54anYQf7mH3eIi8KlNpgKci/ie8ZSqFN8O7A==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.1.tgz", + "integrity": "sha512-4ji5PNzhYq193Z4/4xUaSoNJza6iCkDJSzhetrbB6KOYxsr+kxtQr8ePWhMJUiMt6JUWtXaZ1PYT8FhtED+nGA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.1.tgz", + "integrity": "sha512-VJQxqrisHV+B394IgrOu8YsIIXZgffnf5tO+yc9Z/hoUpuZEvuQTjWwlnpZdpyD+0nx6LTD1/3k646JYm43yJA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.1.tgz", + "integrity": "sha512-r9oV1mwxxsIGcLV1IQ/tw76MW3doatKze1QFWuC+a7QqJUkhY/bKTSVk6NpKKUGm2LDsE33Va8VqSClfA7vSiQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.1.tgz", + "integrity": "sha512-6huNRessoBLxWEqBm5zJXyCQ27TO7anvkdiuQ5MDO4CJni0nOXEqKtV9RllQ2TdyENKKsUMXVnIfW2hIXx/R5Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.1.tgz", + "integrity": "sha512-OVKJFUzphrGmsh+BGtcZDesx0YryV7/Yvy5XGgTqnrZfjnyfcr5uaqYQugCckdIlupc5Vs3XtDjRAj12z4ZPlw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.16.1.tgz", + "integrity": "sha512-Bt+VIhWYCGk4urklnkkteLUOeLv1VxigwTCeB/xC6rBZxY6IIKdDwCJf6on3E3SUGsIqmQS6QqtuJQc1VxF4Aw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@temporalio/activity": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/activity/-/activity-1.23.0.tgz", + "integrity": "sha512-BVeiizVD08yk0Ls6MhKCK+g3fPIWbVM9MLx+o990KwhCaZtRM6o1+du1/qfIIShHl6YjHk+H0ewkx2x14QQ6bA==", + "license": "MIT", + "dependencies": { + "@temporalio/client": "1.23.0", + "@temporalio/common": "1.23.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/client": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/client/-/client-1.23.0.tgz", + "integrity": "sha512-E9nXZPiKDGEDbPpRi0EyQ9i1x70fEIG26aMXZO88kKslKZnEKNPphs06xIic7bhuDoSrPsgVfdDap6o3l6IO2A==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.23.0", + "@temporalio/proto": "1.23.0", + "abort-controller": "^3.0.0", + "long": "^5.3.2", + "nexus-rpc": "^0.0.3", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/common": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/common/-/common-1.23.0.tgz", + "integrity": "sha512-TaZYb/ruzgzJ8jd42EqRUCI4ispLZETt2Jigc9szES6q9edDmfncRw143HyvPi7b8n9naohYmA/IxTe9COH/dg==", + "license": "MIT", + "dependencies": { + "@temporalio/proto": "1.23.0", + "long": "^5.3.2", + "ms": "3.0.0-canary.1", + "nexus-rpc": "^0.0.3", + "protobufjs": "^8.7.1" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/common/node_modules/protobufjs": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", + "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@temporalio/core-bridge": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/core-bridge/-/core-bridge-1.23.0.tgz", + "integrity": "sha512-sid14odlYnci9WgB+iDLyxFbzkq+qBbRPp+CW9U1v+DLRfHlFJwpznQ1AZl4mHjtAZRGQFvDsDG0XNncl9ESSA==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.23.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/nexus": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/nexus/-/nexus-1.23.0.tgz", + "integrity": "sha512-mObGNp3thR+mTXdtKXpUvFpsdjEI9xpVjewhblKXao7Nqv6xrs7RpFiLdKfJhkEvDyfRgIk/dH5RkiCbZ58I9w==", + "license": "MIT", + "dependencies": { + "@temporalio/client": "1.23.0", + "@temporalio/common": "1.23.0", + "@temporalio/proto": "1.23.0", + "long": "^5.3.2", + "nexus-rpc": "^0.0.3" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/proto": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.23.0.tgz", + "integrity": "sha512-90JWnYEs1wyq97/SXkQGknRVMr/BCV9sZ0pOnK3fHTedVinfvz3/PBVFkeAJyU3CJqMQGnzRs9IIfsBTT1icrA==", + "license": "MIT", + "dependencies": { + "long": "^5.3.2", + "protobufjs": "^8.7.1" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/proto/node_modules/protobufjs": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", + "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@temporalio/worker": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/worker/-/worker-1.23.0.tgz", + "integrity": "sha512-k1MrK10lD1s7U66PFqAwGlQMRsVrfU5nCB+CjIAg5RyL7JpOzidDCz/E/jB2HobXzZnxtMcDBfBHh/BYUoyhxw==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@swc/core": "^1.3.102", + "@temporalio/activity": "1.23.0", + "@temporalio/client": "1.23.0", + "@temporalio/common": "1.23.0", + "@temporalio/core-bridge": "1.23.0", + "@temporalio/nexus": "1.23.0", + "@temporalio/proto": "1.23.0", + "@temporalio/workflow": "1.23.0", + "heap-js": "^2.6.0", + "memfs": "^4.6.0", + "nexus-rpc": "^0.0.3", + "protobufjs": "^8.7.1", + "rxjs": "^7.8.1", + "source-map": "^0.7.4", + "source-map-loader": "^5.0.0", + "supports-color": "^8.1.1", + "swc-loader": "^0.2.3", + "unionfs": "^4.5.1", + "webpack": "^5.108.4" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/worker/node_modules/protobufjs": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", + "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@temporalio/workflow": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/workflow/-/workflow-1.23.0.tgz", + "integrity": "sha512-dHQnYmxU/QN7VfTmdfCfpdfTS1w0hoW/NQ7Zefr55QnqLwZw+E0Vn6S4fAbJrpfHrsAmezUZQFVLi+Z4Sq+jzA==", + "license": "MIT", + "dependencies": { + "@temporalio/common": "1.23.0", + "@temporalio/proto": "1.23.0", + "nexus-rpc": "^0.0.3" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.6", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.6.tgz", + "integrity": "sha512-/5thavHAnWDZwH2H6eEn/T8pBEBhtuKaWGMslsj82kJfvzQgOgEMK7X4goBbIUjgVtAzPTtM9Ml64LA0uxO6Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", + "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "license": "Unlicense" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/heap-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/heap-js/-/heap-js-2.7.1.tgz", + "integrity": "sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/memfs": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.68.1.tgz", + "integrity": "sha512-OD+IDRUvIxu3QHL+nFm9gdyugInD27FDJ+sl4B5QgomPHXMlbw+GP918P8VNKu2FkNlVeqBkpzkwROpamVifRw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-fsa": "4.68.1", + "@jsonjoy.com/fs-node": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-to-fsa": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/fs-print": "4.68.1", + "@jsonjoy.com/fs-snapshot": "4.68.1", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.8.0.tgz", + "integrity": "sha512-2cT9+goJfBhtMz+gJqejSf09ClgmYhPhccRb/fb0ztVbixt0BkO8mRuI26FoPPuUNkRk6iEDryWAIouc5W8eJA==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.3", + "terser": "^5.51.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "license": "MIT", + "engines": { + "node": ">=12.13" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/nexus-rpc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/nexus-rpc/-/nexus-rpc-0.0.3.tgz", + "integrity": "sha512-ksIBeYHDv6FHAR1h3Vwk0kQ6REhdo1STqDHXMzu/yn/VN/XzgOor2IUjewG6paoA7IgxlIPZoOU/PD78PC6HTg==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/swc-loader": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.7.tgz", + "integrity": "sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w==", + "license": "MIT", + "dependencies": { + "@swc/counter": "^0.1.3" + }, + "peerDependencies": { + "@swc/core": "^1.2.147", + "webpack": ">=2" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/thingies": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz", + "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unionfs": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/unionfs/-/unionfs-4.6.0.tgz", + "integrity": "sha512-fJAy3gTHjFi5S3TP5EGdjs/OUMFFvI/ady3T8qVuZfkv8Qi8prV/Q8BuFEgODJslhZTT2z2qdD2lGdee9qjEnA==", + "dependencies": { + "fs-monkey": "^1.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.110.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.110.1.tgz", + "integrity": "sha512-gInQB+jxXxgnZyvPwuzT5NGQmECDqeu85oxcrjinrYHqPoBex0hCAN2SFTJVyPVrK0Pq9E44VFP+e89fAc10/w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.24.4", + "es-module-lexer": "^2.1.0", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.7.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/typescript/decouple-monolith/exercise/package.json b/typescript/decouple-monolith/exercise/package.json new file mode 100644 index 0000000..a683709 --- /dev/null +++ b/typescript/decouple-monolith/exercise/package.json @@ -0,0 +1,26 @@ +{ + "name": "decouple-monolith-exercise", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "tsc --build", + "payments-worker": "ts-node src/payments/worker.ts", + "compliance-worker": "ts-node src/compliance/worker.ts", + "starter": "ts-node src/payments/starter.ts", + "review-starter": "ts-node src/payments/review-starter.ts" + }, + "dependencies": { + "@temporalio/activity": "^1.23.0", + "@temporalio/client": "^1.23.0", + "@temporalio/nexus": "^1.23.0", + "@temporalio/worker": "^1.23.0", + "@temporalio/workflow": "^1.23.0", + "nexus-rpc": "^0.0.3" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/node": "^22.9.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } +} diff --git a/typescript/decouple-monolith/exercise/src/compliance/activities.ts b/typescript/decouple-monolith/exercise/src/compliance/activities.ts new file mode 100644 index 0000000..9e18362 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/compliance/activities.ts @@ -0,0 +1,12 @@ +// [GIVEN] Compliance team Activity. +// +// In the monolith, paymentProcessingWorkflow calls this Activity directly — which means +// Compliance's code runs inside the Payments Worker process. After decoupling, the +// Activity stays here but is called from the Compliance Worker, inside the Workflow that +// the Nexus handler starts. +import { checkCompliance as runCheck } from './checker'; +import { ComplianceRequest, ComplianceResult } from '../shared/types'; + +export async function checkCompliance(request: ComplianceRequest): Promise { + return runCheck(request); +} diff --git a/typescript/decouple-monolith/exercise/src/compliance/checker.ts b/typescript/decouple-monolith/exercise/src/compliance/checker.ts new file mode 100644 index 0000000..1879a3a --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/compliance/checker.ts @@ -0,0 +1,59 @@ +// [GIVEN] Rule-based compliance checker. Deterministic, no API keys needed. +// Same logic whether it runs as an Activity or behind a Nexus handler. +// +// Rules: +// - OFAC sanctioned country on either side -> HIGH risk, blocked +// - Amount over $50,000 -> HIGH risk, blocked (TXN-C hits this) +// - Over $10,000 or an unusual jurisdiction -> MEDIUM risk (TXN-B hits this) +// - Everything else -> LOW risk, approved +import { ComplianceRequest, ComplianceResult } from '../shared/types'; + +const SANCTIONED = new Set(['North Korea', 'Iran', 'Cuba', 'Syria', 'Venezuela']); +const COMMON = new Set(['US', 'UK', 'Canada', 'Germany', 'France', 'Japan', 'Australia']); + +export function checkCompliance(request: ComplianceRequest): ComplianceResult { + console.log( + `[ComplianceChecker] Evaluating ${request.transactionId}` + + ` | $${request.amount.toFixed(2)}` + + ` | ${request.senderCountry} -> ${request.receiverCountry}`, + ); + + if (SANCTIONED.has(request.receiverCountry) || SANCTIONED.has(request.senderCountry)) { + return { + transactionId: request.transactionId, + approved: false, + riskLevel: 'HIGH', + explanation: + 'Destination/source country is OFAC-sanctioned. Transaction blocked per regulatory requirements.', + }; + } + + if (request.amount > 50_000) { + return { + transactionId: request.transactionId, + approved: false, + riskLevel: 'HIGH', + explanation: + 'Transaction amount exceeds $50,000 threshold. Requires enhanced due diligence review.', + }; + } + + const isInternational = request.senderCountry !== request.receiverCountry; + const isUnusualJurisdiction = isInternational && !COMMON.has(request.receiverCountry); + + if (request.amount > 10_000 || isUnusualJurisdiction) { + return { + transactionId: request.transactionId, + approved: true, + riskLevel: 'MEDIUM', + explanation: 'International transfer above $10K threshold. Approved with AML monitoring note.', + }; + } + + return { + transactionId: request.transactionId, + approved: true, + riskLevel: 'LOW', + explanation: 'Routine domestic/standard international transfer. No regulatory concerns.', + }; +} diff --git a/typescript/decouple-monolith/exercise/src/compliance/nexus-handler.ts b/typescript/decouple-monolith/exercise/src/compliance/nexus-handler.ts new file mode 100644 index 0000000..3030b9c --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/compliance/nexus-handler.ts @@ -0,0 +1,91 @@ +// The Compliance team's Nexus handler. Payments depends on the contract in +// shared/nexus-service.ts. This file is the code that answers those calls, and only +// the Compliance team owns it. +// +// One Operation is written for you as a worked example. The other one is yours, and it +// is the opposite shape. Read the example first — the contrast between them is the +// whole point of this challenge. +// +// Two things you will need: +// +// temporalNexus.getClient() +// A Temporal Client, already connected to this team's Namespace. +// +// workflowIdFor(transactionId) +// The Workflow ID. The SAME id in both Operations: one creates that Workflow, the +// other looks up the one already running. +// +// Stuck? "Ask AI" on https://docs.temporal.io: +// "how do I send a Workflow Update from a Nexus Operation handler in TypeScript?" +import * as nexus from 'nexus-rpc'; +import * as temporalNexus from '@temporalio/nexus'; +import { complianceService } from '../shared/nexus-service'; +import { ComplianceRequest, ComplianceResult, ReviewRequest } from '../shared/types'; +import { complianceWorkflow, reviewUpdate } from './workflows'; + +/** Same ID both times: one Operation creates the Workflow, the other looks it up. */ +const workflowIdFor = (transactionId: string) => `compliance-${transactionId}`; + +export const complianceServiceHandler = nexus.serviceHandler(complianceService, { + // ── WORKED EXAMPLE — read this, then write TODO 2 below ────────────────────────── + // + // checkCompliance is the SLOW call. A risky payment has to be approved by a person, + // so an answer can be minutes or hours away. This Operation must not sit and wait. + // + // So it does not return an answer at all. It starts a Workflow and returns a + // REFERENCE to it. Temporal holds that reference and collects the result whenever the + // Workflow finishes, however long that takes. + // + // That is what WorkflowRunOperationHandler is for. Note the shape: + // + // - it wraps a function of (ctx, input) + // - that function calls temporalNexus.startWorkflow(ctx, , { ... }) + // - the Workflow ID is business-meaningful, so a retried Operation re-attaches to + // the same compliance check instead of starting a second one + // + // A plain async function would NOT work here. That is a synchronous Operation: it is + // cut off at the 10-second handler deadline, and each retry would start another + // compliance check for the same payment. + checkCompliance: new temporalNexus.WorkflowRunOperationHandler( + async (ctx, input: ComplianceRequest) => + temporalNexus.startWorkflow(ctx, complianceWorkflow, { + args: [input], + workflowId: workflowIdFor(input.transactionId), + // Task queue defaults to the queue this Operation is handled on. + }), + ), + + // ── TODO 2 ────────────────────────────────────────────────────────────────────── + // Implement submitReview, the other half of the pattern. + // + // This is the FAST call, and it is the opposite shape to the example above. By the + // time it arrives, the compliance check is already running and parked waiting for a + // decision. All this Operation does is find that Workflow and hand it the yes or no. + // That takes milliseconds, so it can answer inside the call. + // + // Which means: NOT a WorkflowRunOperationHandler. A plain async function is right + // here. Slow work starts a Workflow and returns a reference; fast work answers in the + // call. Same Service, two Operations, two shapes. + // + // submitReview: async (_ctx, input: ReviewRequest): Promise => { + // // 1. get a Client with temporalNexus.getClient() + // // 2. get a handle to the running Workflow, using workflowIdFor(...) + // // 3. return await handle.executeUpdate(reviewUpdate, { args: [...] }) + // }, + // + // `reviewUpdate` is already imported at the top of this file. It takes two arguments, + // in this order: whether the reviewer approved, and their explanation. + // + // Until you write this, the build stays red — the contract declared two Operations and + // this handler only answers one of them. + // + // One thing to know before you start. You do not annotate the handler's parameters: + // their types come from the contract, which is what makes the Service a contract at + // all. So if TODO 1 is still unfinished, the contract is empty, TypeScript has nothing + // to infer from, and you get: + // + // error TS7006: Parameter '_ctx' implicitly has an 'any' type. + // + // That error points at this file, but the fix is in shared/nexus-service.ts. Finish + // TODO 1 first. +}); diff --git a/typescript/decouple-monolith/exercise/src/compliance/worker.ts b/typescript/decouple-monolith/exercise/src/compliance/worker.ts new file mode 100644 index 0000000..49aaa56 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/compliance/worker.ts @@ -0,0 +1,52 @@ +// The Compliance team's Worker. This process is the only place their code runs. +// +// The connection and the Worker itself are set up for you. What is missing is the part +// that decides what this Worker can actually do: see TODO 3. +import { NativeConnection, Worker } from '@temporalio/worker'; +import * as activities from './activities'; +import { complianceServiceHandler } from './nexus-handler'; +import { COMPLIANCE_NAMESPACE, COMPLIANCE_TASK_QUEUE } from '../shared/types'; + +async function run() { + const connection = await NativeConnection.connect({ address: 'localhost:7233' }); + try { + const worker = await Worker.create({ + connection, + namespace: COMPLIANCE_NAMESPACE, + // MUST match the --target-task-queue you give the Nexus Endpoint, or the Endpoint + // routes work to a queue nobody is polling. + taskQueue: COMPLIANCE_TASK_QUEUE, + workflowsPath: require.resolve('./workflows'), + activities, + + // ── TODO 3 ──────────────────────────────────────────────────────────────── + // A Worker only handles work it has been told about. This one already knows about + // its Workflows (workflowsPath) and its Activities (activities). What it does not + // yet know about is the Nexus handler — the thing that makes this team callable by + // Payments at all. + // + // Add one more option here, naming `complianceServiceHandler`, which is already + // imported above. + // + // Leave it out and the Worker still starts. Payments' calls just go unanswered + // forever. That is the failure you are guarding against. + // + // Stuck? "Ask AI" on https://docs.temporal.io: + // "what do I register on a TypeScript Worker that handles Nexus Operations?" + }); + + console.log('========================================================='); + console.log(` Compliance Worker started on: ${COMPLIANCE_TASK_QUEUE}`); + console.log(` Namespace: ${COMPLIANCE_NAMESPACE}`); + console.log('========================================================='); + + await worker.run(); + } finally { + await connection.close(); + } +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/typescript/decouple-monolith/exercise/src/compliance/workflows.ts b/typescript/decouple-monolith/exercise/src/compliance/workflows.ts new file mode 100644 index 0000000..f1baa4e --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/compliance/workflows.ts @@ -0,0 +1,75 @@ +// [GIVEN] Compliance Workflow. Pre-built for this exercise. +// +// Runs an automated compliance check and, for MEDIUM-risk transactions, waits for a +// human reviewer to approve or deny via a Workflow Update. +// +// LOW risk -> auto-approved, returns immediately +// HIGH risk -> auto-denied, returns immediately +// MEDIUM -> pauses, waits for the review Update +// +// The 10-second sleep is intentional. It gives you a window to stop the Compliance +// Worker with Ctrl+C and restart it, so you can watch the Nexus Operation survive the +// outage. The timer runs on the server, so nothing is lost when the Worker goes away. +import * as wf from '@temporalio/workflow'; +import type * as activities from './activities'; +import { ComplianceRequest, ComplianceResult } from '../shared/types'; + +const { checkCompliance } = wf.proxyActivities({ + startToCloseTimeout: '30 seconds', +}); + +/** Update sent by the Compliance team's sync Nexus handler when a human decides. */ +export const reviewUpdate = wf.defineUpdate('review'); + +export async function complianceWorkflow(request: ComplianceRequest): Promise { + let reviewResult: ComplianceResult | undefined; + let autoResult: ComplianceResult | undefined; + + wf.setHandler( + reviewUpdate, + (approved, explanation) => { + const result: ComplianceResult = { + transactionId: request.transactionId, + approved, + riskLevel: 'MEDIUM', + explanation, + }; + reviewResult = result; + return result; + }, + { + validator: (_approved: boolean, _explanation: string) => { + if (autoResult?.riskLevel !== 'MEDIUM') throw new Error('Workflow is not awaiting review'); + if (reviewResult !== undefined) throw new Error('Review already submitted'); + }, + }, + ); + + // Step 1: automated compliance check. + const auto = await checkCompliance(request); + autoResult = auto; + + // Durable delay. Stop the Compliance Worker mid-sleep, restart it, and the Workflow + // resumes on its own. + await wf.sleep('10 seconds'); + + // Step 2: LOW or HIGH risk returns immediately. + if (auto.riskLevel !== 'MEDIUM') return auto; + + // Step 3: MEDIUM risk waits for human review. + // + // Bounded on purpose. An unbounded wait would leave this Workflow Running forever + // when nobody reviews. Eight minutes is deliberately shorter than the caller's + // 10-minute scheduleToCloseTimeout, so the handler resolves and reports a decision + // rather than letting the caller time out first. + const reviewed = await wf.condition(() => reviewResult !== undefined, '8 minutes'); + if (!reviewed) { + return { + transactionId: request.transactionId, + approved: false, + riskLevel: 'MEDIUM', + explanation: 'No reviewer responded within 8 minutes. Not approved.', + }; + } + return reviewResult!; +} diff --git a/typescript/decouple-monolith/exercise/src/payments/activities.ts b/typescript/decouple-monolith/exercise/src/payments/activities.ts new file mode 100644 index 0000000..ac67272 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/payments/activities.ts @@ -0,0 +1,15 @@ +// [GIVEN] Payments team Activities. Thin wrappers around the gateway. +// +// An Activity is just an exported function. They must be async: proxyActivities only accepts +// Promise-returning functions, and a synchronous one is silently typed away, surfacing +// later as "Type 'Symbol' has no call signatures" at the call site in the Workflow. +import * as gateway from './gateway'; +import { PaymentRequest } from '../shared/types'; + +export async function validatePayment(request: PaymentRequest): Promise { + return gateway.validatePayment(request); +} + +export async function executePayment(request: PaymentRequest): Promise { + return gateway.executePayment(request); +} diff --git a/typescript/decouple-monolith/exercise/src/payments/gateway.ts b/typescript/decouple-monolith/exercise/src/payments/gateway.ts new file mode 100644 index 0000000..f775915 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/payments/gateway.ts @@ -0,0 +1,46 @@ +// [GIVEN] Simulated payment gateway. In production this would call Stripe, PayPal, SWIFT. +// +// The 10% failure rate is deliberate: it shows Temporal retrying an Activity for free. +// Because of it, never assert on a clean first attempt — assert on final Workflow state. +import { PaymentRequest } from '../shared/types'; + +const money = (n: number) => `$${n.toFixed(2)}`; +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +export function validatePayment(request: PaymentRequest): boolean { + if (request.amount <= 0) { + console.log(`[PaymentGateway] REJECTED: Invalid amount for ${request.transactionId}`); + return false; + } + if (!request.senderAccount || !request.receiverAccount) { + console.log(`[PaymentGateway] REJECTED: Missing account info for ${request.transactionId}`); + return false; + } + console.log(`[PaymentGateway] Validation passed for ${request.transactionId}`); + return true; +} + +export async function executePayment(request: PaymentRequest): Promise { + console.log( + `[PaymentGateway] Processing ${request.transactionId}` + + ` | ${money(request.amount)}` + + ` | ${request.senderCountry} -> ${request.receiverCountry}`, + ); + + await sleep(500 + Math.random() * 500); + + // Simulate occasional gateway failures. Temporal retries automatically. + if (Math.random() < 0.1) { + throw new Error( + `Payment gateway timeout for ${request.transactionId}, connection to banking network failed`, + ); + } + + // Derived from the transaction ID, not the clock, so a retry of this Activity returns + // the SAME confirmation number instead of minting a second one. With a 10% failure + // rate above, a timestamp here would make retries look like distinct payments. + // Idempotency is what makes an at-least-once retry safe. + const confirmationNumber = `CONF-${request.transactionId}`; + console.log(`[PaymentGateway] Payment executed: ${confirmationNumber}`); + return confirmationNumber; +} diff --git a/typescript/decouple-monolith/exercise/src/payments/review-starter.ts b/typescript/decouple-monolith/exercise/src/payments/review-starter.ts new file mode 100644 index 0000000..e8061dc --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/payments/review-starter.ts @@ -0,0 +1,51 @@ +// [GIVEN] Starts a reviewCallerWorkflow to approve TXN-B via Nexus. +// +// Rather than calling 'temporal workflow update execute' directly, this triggers a caller +// Workflow that invokes the submitReview Nexus Operation. The Compliance team's sync +// handler forwards the decision to the running complianceWorkflow as an Update. +// +// Flip `approved` to false below to see the denial path. +import { Client, Connection } from '@temporalio/client'; +import { reviewCallerWorkflow } from './workflows'; +import { PAYMENTS_NAMESPACE, PAYMENTS_TASK_QUEUE, ReviewRequest } from '../shared/types'; + +async function run() { + console.log('=========================================================='); + console.log(' REVIEW STARTER - Submitting review for TXN-B via Nexus'); + console.log('==========================================================\n'); + + const connection = await Connection.connect({ address: 'localhost:7233' }); + const client = new Client({ connection, namespace: PAYMENTS_NAMESPACE }); + + const request: ReviewRequest = { + transactionId: 'TXN-B', + approved: true, + explanation: 'Approved after manual review', + }; + + console.log(' Submitting review for TXN-B via Nexus...'); + console.log(` Approved: ${request.approved}`); + console.log(` Explanation: ${request.explanation}\n`); + + const result = await client.workflow.execute(reviewCallerWorkflow, { + taskQueue: PAYMENTS_TASK_QUEUE, + workflowId: `review-${request.transactionId}`, + args: [request], + }); + + console.log(` Review result: ${result.approved ? 'APPROVED' : 'DENIED'}`); + console.log(` Risk level: ${result.riskLevel}`); + console.log(` Explanation: ${result.explanation}\n`); + console.log(' TXN-B review submitted. The payment Workflow will now complete.'); + console.log('=========================================================='); + + await connection.close(); +} + +run().then( + () => process.exit(0), + (err) => { + console.error(err); + process.exit(1); + }, +); diff --git a/typescript/decouple-monolith/exercise/src/payments/starter.ts b/typescript/decouple-monolith/exercise/src/payments/starter.ts new file mode 100644 index 0000000..492620b --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/payments/starter.ts @@ -0,0 +1,131 @@ +// [GIVEN] Starts three payment Workflows. +// +// All three start before any result is collected. That matters once Nexus is in play: +// TXN-B is MEDIUM risk, so its handler Workflow pauses for human review and would +// otherwise block TXN-C behind it. Starting concurrently lets TXN-A and TXN-C finish +// while TXN-B sits visibly pending, which is the point of the human-in-the-loop path. +// +// The starter does not know or care whether compliance runs locally or via Nexus. +import { Client, Connection } from '@temporalio/client'; +import { paymentProcessingWorkflow } from './workflows'; +import { PAYMENTS_NAMESPACE, PAYMENTS_TASK_QUEUE, PaymentRequest, PaymentResult } from '../shared/types'; + +// How long to wait before calling a Workflow "pending review". TXN-A and TXN-C finish in +// roughly 12 seconds (the handler Workflow sleeps for 10 of those). +const COLLECT_DEADLINE_MS = 25_000; + +const TRANSACTIONS: PaymentRequest[] = [ + { + transactionId: 'TXN-A', amount: 250.0, currency: 'USD', + senderCountry: 'US', receiverCountry: 'US', + description: 'Routine supplier payment', senderAccount: 'ACC-001', receiverAccount: 'ACC-002', + }, + { + transactionId: 'TXN-B', amount: 12_000.0, currency: 'USD', + senderCountry: 'US', receiverCountry: 'UK', + description: 'International consulting fee', senderAccount: 'ACC-003', receiverAccount: 'ACC-004', + }, + { + transactionId: 'TXN-C', amount: 75_000.0, currency: 'USD', + senderCountry: 'US', receiverCountry: 'US', + description: 'Large capital transfer', senderAccount: 'ACC-005', receiverAccount: 'ACC-006', + }, +]; + +type Outcome = + | { state: 'done'; result: PaymentResult } + | { state: 'failed'; error: string } + | { state: 'pending' }; + +async function run() { + console.log('=========================================================='); + console.log(' PAYMENT STARTER - Decouple Monolith'); + console.log(' Running 3 transactions through Temporal'); + console.log('==========================================================\n'); + + const connection = await Connection.connect({ address: 'localhost:7233' }); + const client = new Client({ connection, namespace: PAYMENTS_NAMESPACE }); + + // Start all three without awaiting results, so a pause in one cannot hold up the others. + const started = await Promise.all( + TRANSACTIONS.map(async (txn) => { + const handle = await client.workflow.start(paymentProcessingWorkflow, { + taskQueue: PAYMENTS_TASK_QUEUE, + workflowId: `payment-${txn.transactionId}`, + args: [txn], + }); + console.log(` Started: payment-${txn.transactionId}`); + console.log( + ` Amount: $${txn.amount.toFixed(2)} | Route: ${txn.senderCountry} -> ${txn.receiverCountry}`, + ); + return { txn, handle }; + }), + ); + + console.log('\n All 3 started. Collecting results...\n'); + + // Wait on all three against a single shared deadline. Waiting one at a time would let + // TXN-B's review pause burn the whole budget, so TXN-C would be reported as pending + // even though it finished seconds earlier. + const outcomes = new Map(started.map(({ txn }) => [txn.transactionId, { state: 'pending' }])); + await Promise.race([ + Promise.allSettled( + started.map(async ({ txn, handle }) => { + try { + outcomes.set(txn.transactionId, { state: 'done', result: await handle.result() }); + } catch (err) { + outcomes.set(txn.transactionId, { state: 'failed', error: (err as Error).message }); + } + }), + ), + new Promise((resolve) => setTimeout(resolve, COLLECT_DEADLINE_MS)), + ]); + + let pendingReview = 0; + for (const { txn } of started) { + const outcome = outcomes.get(txn.transactionId)!; + console.log('----------------------------------------------------'); + console.log(` ${txn.transactionId}`); + + if (outcome.state === 'pending') { + pendingReview++; + console.log(' Result: STILL RUNNING'); + console.log(' Reason: either it is parked for human review (MEDIUM risk), or the'); + console.log(' Compliance handler is not answering right now.'); + console.log(' Action: check the Temporal UI. If it is parked for review, run'); + console.log(" 'npm run review-starter'."); + } else if (outcome.state === 'failed') { + console.log(' Result: FAILED'); + console.log(` Error: ${outcome.error}`); + } else { + const r = outcome.result; + console.log(` Result: ${r.status}`); + console.log(` Risk: ${r.riskLevel ?? 'N/A'}`); + console.log(` Reason: ${r.explanation ?? 'N/A'}`); + if (r.confirmationNumber) console.log(` Conf#: ${r.confirmationNumber}`); + if (r.error) console.log(` Error: ${r.error}`); + } + console.log(); + } + + console.log('=========================================================='); + if (pendingReview > 0) { + console.log(` ${pendingReview} transaction(s) have not resolved yet.`); + console.log(" Parked for review? Run 'npm run review-starter'."); + console.log(' Compliance Worker down? Start it and they resume on their own.'); + } else { + console.log(' All 3 transactions resolved!'); + } + console.log(' Check Temporal UI: http://localhost:8233'); + console.log('=========================================================='); + + await connection.close(); +} + +run().then( + () => process.exit(0), + (err) => { + console.error(err); + process.exit(1); + }, +); diff --git a/typescript/decouple-monolith/exercise/src/payments/worker.ts b/typescript/decouple-monolith/exercise/src/payments/worker.ts new file mode 100644 index 0000000..2629cc5 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/payments/worker.ts @@ -0,0 +1,51 @@ +// MONOLITH VERSION. One Worker, one deployment, one blast radius. +// +// Right now this single process runs everything: +// - paymentProcessingWorkflow (Payments team) +// - payment activities (Payments team) +// - compliance activities (Compliance team's code, running in Payments' process) +// +// That last line is the problem. A bug in compliance code takes payments down with it. +// +// Do TODO 5 last, after TODO 4 has moved the Workflow onto a Nexus client. +import { NativeConnection, Worker } from '@temporalio/worker'; +import * as paymentActivities from './activities'; +import * as complianceActivities from '../compliance/activities'; +import { PAYMENTS_NAMESPACE, PAYMENTS_TASK_QUEUE } from '../shared/types'; + +async function run() { + const connection = await NativeConnection.connect({ address: 'localhost:7233' }); + try { + const worker = await Worker.create({ + connection, + namespace: PAYMENTS_NAMESPACE, + taskQueue: PAYMENTS_TASK_QUEUE, + workflowsPath: require.resolve('./workflows'), + + // ── TODO 5 ──────────────────────────────────────────────────────────────── + // Once the Workflow calls Compliance over Nexus, this Worker has no reason to run + // the Compliance team's code. Remove `...complianceActivities` from the object + // below, and delete the import at the top of this file. + // + // That deletion is the decoupling. Everything before it was wiring; this is the + // line where the two teams stop sharing a process. + activities: { ...paymentActivities, ...complianceActivities }, + }); + + console.log('========================================================='); + console.log(` Payments Worker started on: ${PAYMENTS_TASK_QUEUE}`); + console.log(` Namespace: ${PAYMENTS_NAMESPACE}`); + console.log(' Registered: paymentProcessingWorkflow, payment activities'); + console.log(' compliance activities (monolith, will decouple)'); + console.log('========================================================='); + + await worker.run(); + } finally { + await connection.close(); + } +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/typescript/decouple-monolith/exercise/src/payments/workflows.ts b/typescript/decouple-monolith/exercise/src/payments/workflows.ts new file mode 100644 index 0000000..d3a13e3 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/payments/workflows.ts @@ -0,0 +1,114 @@ +// MONOLITH VERSION. This already works — you run it in challenge 01. +// +// Three steps, all on one Worker: +// Step 1: validatePayment (payment Activity) +// Step 2: checkCompliance (compliance Activity) <- this one crosses a team boundary +// Step 3: executePayment (payment Activity) +import * as wf from '@temporalio/workflow'; +import type * as paymentActivities from './activities'; +import type * as complianceActivities from '../compliance/activities'; +import { + ComplianceRequest, + ComplianceResult, + PaymentRequest, + PaymentResult, + ReviewRequest, +} from '../shared/types'; + +const { validatePayment, executePayment } = wf.proxyActivities({ + startToCloseTimeout: '30 seconds', + retry: { initialInterval: '1 second', backoffCoefficient: 2 }, +}); + +// ── TODO 4a ───────────────────────────────────────────────────────────────────────── +// Delete this proxy, and the `complianceActivities` import at the top of the file. +// +// This is the coupling. A call on this proxy schedules an Activity, and Activities run +// on THIS Worker — so the Compliance team's code executes inside the Payments process. +// TODO 4b replaces the one place that calls it. +const { checkCompliance } = wf.proxyActivities({ + startToCloseTimeout: '30 seconds', +}); + +export async function paymentProcessingWorkflow(request: PaymentRequest): Promise { + const logger = wf.log; + + // Step 1: validate (Payments team). + if (!(await validatePayment(request))) { + return { + success: false, + transactionId: request.transactionId, + status: 'REJECTED', + error: 'Payment validation failed', + }; + } + logger.info(`Step 1 passed: validation OK for ${request.transactionId}`); + + // Step 2: compliance check. + const compReq: ComplianceRequest = { + transactionId: request.transactionId, + amount: request.amount, + senderCountry: request.senderCountry, + receiverCountry: request.receiverCountry, + description: request.description, + }; + + logger.info(`Step 2: calling compliance check for ${request.transactionId}`); + + // ── TODO 4b ─────────────────────────────────────────────────────────────────── + // Replace this Activity call with a Nexus call: + // + // const compliance = wf.createNexusServiceClient({ + // service: complianceService, // '../shared/nexus-service' + // endpoint: COMPLIANCE_ENDPOINT, // '../shared/types' + // }); + // const result = await compliance.executeOperation('checkCompliance', compReq, { + // scheduleToCloseTimeout: '10 minutes', + // }); + // + // Ten minutes covers the whole call including retries, which is what lets it outlive + // the Compliance Worker going away. You prove that in challenge 5. + // + // Note what the Workflow names — a contract and an Endpoint. No Namespace, no Task + // Queue, no address. The Registry resolves those, so this Workflow does not change + // when Compliance moves. + const result: ComplianceResult = await checkCompliance(compReq); + + logger.info(`Compliance result: ${result.riskLevel} | approved=${result.approved}`); + + // A declined payment is a business outcome, not a failure. The Workflow completes + // successfully and reports the decision. + if (!result.approved) { + return { + success: false, + transactionId: request.transactionId, + status: 'DECLINED_COMPLIANCE', + riskLevel: result.riskLevel, + explanation: result.explanation, + }; + } + + // Step 3: execute, only if compliance approved. + logger.info(`Step 3: executing payment for ${request.transactionId}`); + const confirmationNumber = await executePayment(request); + + return { + success: true, + transactionId: request.transactionId, + status: 'COMPLETED', + riskLevel: result.riskLevel, + explanation: result.explanation, + confirmationNumber, + }; +} + +// ── TODO 4c ───────────────────────────────────────────────────────────────────────── +// Same client as 4b, but call 'submitReview' with a scheduleToCloseTimeout of +// '10 seconds'. +// +// Seconds, not minutes: submitReview is a SYNCHRONOUS Operation, so the handler has to +// answer inside the Nexus handler deadline. Routing the review through the Endpoint is +// what keeps the boundary — neither team learns the other's Workflow IDs. +export async function reviewCallerWorkflow(_request: ReviewRequest): Promise { + throw new Error('TODO 4c: submit the review decision through the Nexus Endpoint'); +} diff --git a/typescript/decouple-monolith/exercise/src/shared/nexus-service.ts b/typescript/decouple-monolith/exercise/src/shared/nexus-service.ts new file mode 100644 index 0000000..25afaf8 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/shared/nexus-service.ts @@ -0,0 +1,31 @@ +// The shared contract between the Payments and Compliance teams. Like an OpenAPI spec, +// but durable. Payments builds a client from it; Compliance implements a handler for it. +// +// Both teams import THIS file. It is the only thing they share. +// +// Looking things up during this lab: start at +// https://docs.temporal.io/develop/typescript/nexus/feature-guide +// Faster still, the "Ask AI" button on https://docs.temporal.io answers in plain +// language. Try "how do I define a Nexus Service contract in TypeScript?" Get used to +// it now — the later TODOs are harder. +// +// You may see `@experimental` when you hover `nexus.service` or `nexus.operation`. That +// is `nexus-rpc`, the contract package, which is versioned separately and still pre-1.0. +// Nexus itself is GA in the Temporal TypeScript SDK as of v1.23.0. +import * as nexus from 'nexus-rpc'; +import { ComplianceRequest, ComplianceResult, ReviewRequest } from './types'; + +// ── TODO 1 ──────────────────────────────────────────────────────────────────────── +// Declare the two Operations this Service offers. Each one is a name paired with +// nexus.operation(), which carries no implementation — only the types +// that both teams agree on. +// +// checkCompliance ComplianceRequest -> ComplianceResult +// submitReview ReviewRequest -> ComplianceResult +// +// Add them one at a time and watch what happens: the moment you declare an Operation +// here, TypeScript demands a matching handler in compliance/nexus-handler.ts. The +// contract is enforced at compile time, which is the whole point of sharing it. +export const complianceService = nexus.service('ComplianceNexusService', { + // your Operations go here +}); diff --git a/typescript/decouple-monolith/exercise/src/shared/types.ts b/typescript/decouple-monolith/exercise/src/shared/types.ts new file mode 100644 index 0000000..49cf8c8 --- /dev/null +++ b/typescript/decouple-monolith/exercise/src/shared/types.ts @@ -0,0 +1,59 @@ +// [GIVEN] The data that crosses team boundaries. +// +// Plain interfaces, and that is all they need to be. The default data converter is +// JSON, so a structural type is enough to cross the Nexus boundary in both directions. + +/** A payment transaction to be processed. */ +export interface PaymentRequest { + transactionId: string; + amount: number; + currency: string; + senderCountry: string; + receiverCountry: string; + description: string; + senderAccount: string; + receiverAccount: string; +} + +/** The outcome of a payment Workflow. */ +export interface PaymentResult { + success: boolean; + transactionId: string; + status: 'COMPLETED' | 'REJECTED' | 'DECLINED_COMPLIANCE' | 'FAILED'; + riskLevel?: RiskLevel; + explanation?: string; + confirmationNumber?: string; + error?: string; +} + +export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH'; + +/** Input to the compliance check. Sent by Payments to Compliance. */ +export interface ComplianceRequest { + transactionId: string; + amount: number; + senderCountry: string; + receiverCountry: string; + description: string; +} + +/** Result of a compliance check. Returned by Compliance to Payments. */ +export interface ComplianceResult { + transactionId: string; + approved: boolean; + riskLevel: RiskLevel; + explanation: string; +} + +/** A human review decision, submitted through Nexus. */ +export interface ReviewRequest { + transactionId: string; + approved: boolean; + explanation: string; +} + +export const PAYMENTS_TASK_QUEUE = 'payments-processing'; +export const COMPLIANCE_TASK_QUEUE = 'compliance-risk'; +export const PAYMENTS_NAMESPACE = 'payments-namespace'; +export const COMPLIANCE_NAMESPACE = 'compliance-namespace'; +export const COMPLIANCE_ENDPOINT = 'compliance-endpoint'; diff --git a/typescript/decouple-monolith/exercise/tsconfig.json b/typescript/decouple-monolith/exercise/tsconfig.json new file mode 100644 index 0000000..f0e6b74 --- /dev/null +++ b/typescript/decouple-monolith/exercise/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "version": "5.6.3", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "strict": true + }, + "include": ["src/**/*.ts"] +} diff --git a/typescript/decouple-monolith/solution/package-lock.json b/typescript/decouple-monolith/solution/package-lock.json new file mode 100644 index 0000000..fc6a38e --- /dev/null +++ b/typescript/decouple-monolith/solution/package-lock.json @@ -0,0 +1,2349 @@ +{ + "name": "decouple-monolith-solution", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "decouple-monolith-solution", + "version": "1.0.0", + "dependencies": { + "@temporalio/activity": "^1.23.0", + "@temporalio/client": "^1.23.0", + "@temporalio/nexus": "^1.23.0", + "@temporalio/worker": "^1.23.0", + "@temporalio/workflow": "^1.23.0", + "nexus-rpc": "^0.0.3" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/node": "^22.9.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.68.1.tgz", + "integrity": "sha512-V5oZ4Gt9WJKyQef0n9cAd0N9qjSkIBm3E4MYsgNIWBk5aINCDPKxMPo1i29rBxqiT4Ixf1epklqV9VJMKIxwlw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.68.1.tgz", + "integrity": "sha512-HCG72UioncuO7Gw09XNVG+S85e3cq2hrUC/mexBrsWsa3mI7eePkkqWie3uVYbtsb64OR9YGQs5SqaufDRYBcg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.68.1.tgz", + "integrity": "sha512-R5D9mWtqdURzcOWj1vdXr3APCwX0xchtFT+kmW7fXLNDifWdDrnh26jSID8pdnUfFBxTyfHtFtTL/NWKzIH7kQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/fs-print": "4.68.1", + "@jsonjoy.com/fs-snapshot": "4.68.1", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.68.1.tgz", + "integrity": "sha512-HK1BTksysokNZxNspqDH0yPaqN9YgR/AYIlYiIaU2Ys4BOk5CdybI7r6BgiZuiiPiV8n4sK/kZdice7Znpy2Kw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.68.1.tgz", + "integrity": "sha512-lpKmU4X9e/oh8GIuAI7EXaS5QiLNM3KD15CkdhfS6PYmrGvoJqKQcyEfnLgnnaGslh/PFUMYSIZBCf2ejJGw8g==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.68.1.tgz", + "integrity": "sha512-/GxfW1DWm9SCdkfbvqevLO/P5duobQfmKkHXxdMIDbcZMQeAgooAstIfZhkXpATzq9QbCQsnoWFM/dGHdZfndw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "glob-to-regex.js": "^1.0.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.68.1.tgz", + "integrity": "sha512-oGeZOGPYKK9v1CgeVeEDsLomH1lCnslSpqUN5GmPzrmAVGQlsmsdcXNA2O4lV8Y4xkuSuynx2ITBkUHJVaTbow==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.68.1", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.68.1.tgz", + "integrity": "sha512-XZfP0FDZN32bbc4t2bZN2qRrYHg5AktJnzk22HRoKGK4BprrbNRH2k5ceSNS/kupKYcofCs+O841+xaAbjnxwQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@swc/core": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.16.1.tgz", + "integrity": "sha512-nUaeu91O5QZKrQdaDCHd402ogUIoNOOjpkZNq0UomWK0G6gDaGmLhvddF1/3BXf5O8aLyo6ZPY/aMDWvaJQ/hg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.28" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.16.1", + "@swc/core-darwin-x64": "1.16.1", + "@swc/core-linux-arm-gnueabihf": "1.16.1", + "@swc/core-linux-arm64-gnu": "1.16.1", + "@swc/core-linux-arm64-musl": "1.16.1", + "@swc/core-linux-ppc64-gnu": "1.16.1", + "@swc/core-linux-s390x-gnu": "1.16.1", + "@swc/core-linux-x64-gnu": "1.16.1", + "@swc/core-linux-x64-musl": "1.16.1", + "@swc/core-win32-arm64-msvc": "1.16.1", + "@swc/core-win32-ia32-msvc": "1.16.1", + "@swc/core-win32-x64-msvc": "1.16.1" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.1.tgz", + "integrity": "sha512-zlJblJ8ncErD43lKdxjbUaUskJQf+LxiPXYcWXD8/8ZMV+7uuAT+CwjciLXpyZBd5Pq/S726bMpeeAwSeL1hhg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.1.tgz", + "integrity": "sha512-IN0BmPWb0YAh/17mmlWB/HDBtTw2MfuW4hulf/tQAgTQBRH17l+z499bNJLK6LizSjqs0P7V+jU38Zj+vJC1DA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.1.tgz", + "integrity": "sha512-EYgrx2YOCQ2Twz2S793kqNjPkpvYVUPzzR95bIb7by+VQcyaai4lZZ2iz/tZvcFVKSNcN3/JTKwx+aBn2ZL52A==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.1.tgz", + "integrity": "sha512-moyKm0YZlHdHohzm1YwgAyesqnE853rO0REMfJLFAova51wF9BNi+3ZW2PeS7Vqvn6HeJuepLpAHbBdZctxpHA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.1.tgz", + "integrity": "sha512-kKGBO9wdapiSzuf5ZzZ2fYtlu1BNSYtIIUxvH1ir/gcelTOREEHGDCLTDFx/2Knf878nU11A40z7LxwasEFxqA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.1.tgz", + "integrity": "sha512-nZ6qahtLxC3PM54cWOQZHxt4lTCF/3J4LIoWWzz6v7A+rLs8Dx54anYQf7mH3eIi8KlNpgKci/ie8ZSqFN8O7A==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.1.tgz", + "integrity": "sha512-4ji5PNzhYq193Z4/4xUaSoNJza6iCkDJSzhetrbB6KOYxsr+kxtQr8ePWhMJUiMt6JUWtXaZ1PYT8FhtED+nGA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.1.tgz", + "integrity": "sha512-VJQxqrisHV+B394IgrOu8YsIIXZgffnf5tO+yc9Z/hoUpuZEvuQTjWwlnpZdpyD+0nx6LTD1/3k646JYm43yJA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.1.tgz", + "integrity": "sha512-r9oV1mwxxsIGcLV1IQ/tw76MW3doatKze1QFWuC+a7QqJUkhY/bKTSVk6NpKKUGm2LDsE33Va8VqSClfA7vSiQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.1.tgz", + "integrity": "sha512-6huNRessoBLxWEqBm5zJXyCQ27TO7anvkdiuQ5MDO4CJni0nOXEqKtV9RllQ2TdyENKKsUMXVnIfW2hIXx/R5Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.1.tgz", + "integrity": "sha512-OVKJFUzphrGmsh+BGtcZDesx0YryV7/Yvy5XGgTqnrZfjnyfcr5uaqYQugCckdIlupc5Vs3XtDjRAj12z4ZPlw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.16.1.tgz", + "integrity": "sha512-Bt+VIhWYCGk4urklnkkteLUOeLv1VxigwTCeB/xC6rBZxY6IIKdDwCJf6on3E3SUGsIqmQS6QqtuJQc1VxF4Aw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@temporalio/activity": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/activity/-/activity-1.23.0.tgz", + "integrity": "sha512-BVeiizVD08yk0Ls6MhKCK+g3fPIWbVM9MLx+o990KwhCaZtRM6o1+du1/qfIIShHl6YjHk+H0ewkx2x14QQ6bA==", + "license": "MIT", + "dependencies": { + "@temporalio/client": "1.23.0", + "@temporalio/common": "1.23.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/client": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/client/-/client-1.23.0.tgz", + "integrity": "sha512-E9nXZPiKDGEDbPpRi0EyQ9i1x70fEIG26aMXZO88kKslKZnEKNPphs06xIic7bhuDoSrPsgVfdDap6o3l6IO2A==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.23.0", + "@temporalio/proto": "1.23.0", + "abort-controller": "^3.0.0", + "long": "^5.3.2", + "nexus-rpc": "^0.0.3", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/common": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/common/-/common-1.23.0.tgz", + "integrity": "sha512-TaZYb/ruzgzJ8jd42EqRUCI4ispLZETt2Jigc9szES6q9edDmfncRw143HyvPi7b8n9naohYmA/IxTe9COH/dg==", + "license": "MIT", + "dependencies": { + "@temporalio/proto": "1.23.0", + "long": "^5.3.2", + "ms": "3.0.0-canary.1", + "nexus-rpc": "^0.0.3", + "protobufjs": "^8.7.1" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/common/node_modules/protobufjs": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", + "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@temporalio/core-bridge": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/core-bridge/-/core-bridge-1.23.0.tgz", + "integrity": "sha512-sid14odlYnci9WgB+iDLyxFbzkq+qBbRPp+CW9U1v+DLRfHlFJwpznQ1AZl4mHjtAZRGQFvDsDG0XNncl9ESSA==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.23.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/nexus": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/nexus/-/nexus-1.23.0.tgz", + "integrity": "sha512-mObGNp3thR+mTXdtKXpUvFpsdjEI9xpVjewhblKXao7Nqv6xrs7RpFiLdKfJhkEvDyfRgIk/dH5RkiCbZ58I9w==", + "license": "MIT", + "dependencies": { + "@temporalio/client": "1.23.0", + "@temporalio/common": "1.23.0", + "@temporalio/proto": "1.23.0", + "long": "^5.3.2", + "nexus-rpc": "^0.0.3" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/proto": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.23.0.tgz", + "integrity": "sha512-90JWnYEs1wyq97/SXkQGknRVMr/BCV9sZ0pOnK3fHTedVinfvz3/PBVFkeAJyU3CJqMQGnzRs9IIfsBTT1icrA==", + "license": "MIT", + "dependencies": { + "long": "^5.3.2", + "protobufjs": "^8.7.1" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/proto/node_modules/protobufjs": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", + "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@temporalio/worker": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/worker/-/worker-1.23.0.tgz", + "integrity": "sha512-k1MrK10lD1s7U66PFqAwGlQMRsVrfU5nCB+CjIAg5RyL7JpOzidDCz/E/jB2HobXzZnxtMcDBfBHh/BYUoyhxw==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@swc/core": "^1.3.102", + "@temporalio/activity": "1.23.0", + "@temporalio/client": "1.23.0", + "@temporalio/common": "1.23.0", + "@temporalio/core-bridge": "1.23.0", + "@temporalio/nexus": "1.23.0", + "@temporalio/proto": "1.23.0", + "@temporalio/workflow": "1.23.0", + "heap-js": "^2.6.0", + "memfs": "^4.6.0", + "nexus-rpc": "^0.0.3", + "protobufjs": "^8.7.1", + "rxjs": "^7.8.1", + "source-map": "^0.7.4", + "source-map-loader": "^5.0.0", + "supports-color": "^8.1.1", + "swc-loader": "^0.2.3", + "unionfs": "^4.5.1", + "webpack": "^5.108.4" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@temporalio/worker/node_modules/protobufjs": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", + "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@temporalio/workflow": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@temporalio/workflow/-/workflow-1.23.0.tgz", + "integrity": "sha512-dHQnYmxU/QN7VfTmdfCfpdfTS1w0hoW/NQ7Zefr55QnqLwZw+E0Vn6S4fAbJrpfHrsAmezUZQFVLi+Z4Sq+jzA==", + "license": "MIT", + "dependencies": { + "@temporalio/common": "1.23.0", + "@temporalio/proto": "1.23.0", + "nexus-rpc": "^0.0.3" + }, + "engines": { + "node": ">= 20.3.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.6", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.6.tgz", + "integrity": "sha512-/5thavHAnWDZwH2H6eEn/T8pBEBhtuKaWGMslsj82kJfvzQgOgEMK7X4goBbIUjgVtAzPTtM9Ml64LA0uxO6Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", + "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "license": "Unlicense" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/heap-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/heap-js/-/heap-js-2.7.1.tgz", + "integrity": "sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/memfs": { + "version": "4.68.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.68.1.tgz", + "integrity": "sha512-OD+IDRUvIxu3QHL+nFm9gdyugInD27FDJ+sl4B5QgomPHXMlbw+GP918P8VNKu2FkNlVeqBkpzkwROpamVifRw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-fsa": "4.68.1", + "@jsonjoy.com/fs-node": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-to-fsa": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/fs-print": "4.68.1", + "@jsonjoy.com/fs-snapshot": "4.68.1", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.8.0.tgz", + "integrity": "sha512-2cT9+goJfBhtMz+gJqejSf09ClgmYhPhccRb/fb0ztVbixt0BkO8mRuI26FoPPuUNkRk6iEDryWAIouc5W8eJA==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.3", + "terser": "^5.51.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "license": "MIT", + "engines": { + "node": ">=12.13" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/nexus-rpc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/nexus-rpc/-/nexus-rpc-0.0.3.tgz", + "integrity": "sha512-ksIBeYHDv6FHAR1h3Vwk0kQ6REhdo1STqDHXMzu/yn/VN/XzgOor2IUjewG6paoA7IgxlIPZoOU/PD78PC6HTg==", + "license": "MIT", + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/swc-loader": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.7.tgz", + "integrity": "sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w==", + "license": "MIT", + "dependencies": { + "@swc/counter": "^0.1.3" + }, + "peerDependencies": { + "@swc/core": "^1.2.147", + "webpack": ">=2" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/thingies": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz", + "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unionfs": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/unionfs/-/unionfs-4.6.0.tgz", + "integrity": "sha512-fJAy3gTHjFi5S3TP5EGdjs/OUMFFvI/ady3T8qVuZfkv8Qi8prV/Q8BuFEgODJslhZTT2z2qdD2lGdee9qjEnA==", + "dependencies": { + "fs-monkey": "^1.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.110.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.110.1.tgz", + "integrity": "sha512-gInQB+jxXxgnZyvPwuzT5NGQmECDqeu85oxcrjinrYHqPoBex0hCAN2SFTJVyPVrK0Pq9E44VFP+e89fAc10/w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.24.4", + "es-module-lexer": "^2.1.0", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.7.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/typescript/decouple-monolith/solution/package.json b/typescript/decouple-monolith/solution/package.json new file mode 100644 index 0000000..24c65eb --- /dev/null +++ b/typescript/decouple-monolith/solution/package.json @@ -0,0 +1,26 @@ +{ + "name": "decouple-monolith-solution", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "tsc --build", + "payments-worker": "ts-node src/payments/worker.ts", + "compliance-worker": "ts-node src/compliance/worker.ts", + "starter": "ts-node src/payments/starter.ts", + "review-starter": "ts-node src/payments/review-starter.ts" + }, + "dependencies": { + "@temporalio/activity": "^1.23.0", + "@temporalio/client": "^1.23.0", + "@temporalio/nexus": "^1.23.0", + "@temporalio/worker": "^1.23.0", + "@temporalio/workflow": "^1.23.0", + "nexus-rpc": "^0.0.3" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/node": "^22.9.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } +} diff --git a/typescript/decouple-monolith/solution/src/compliance/activities.ts b/typescript/decouple-monolith/solution/src/compliance/activities.ts new file mode 100644 index 0000000..9e18362 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/compliance/activities.ts @@ -0,0 +1,12 @@ +// [GIVEN] Compliance team Activity. +// +// In the monolith, paymentProcessingWorkflow calls this Activity directly — which means +// Compliance's code runs inside the Payments Worker process. After decoupling, the +// Activity stays here but is called from the Compliance Worker, inside the Workflow that +// the Nexus handler starts. +import { checkCompliance as runCheck } from './checker'; +import { ComplianceRequest, ComplianceResult } from '../shared/types'; + +export async function checkCompliance(request: ComplianceRequest): Promise { + return runCheck(request); +} diff --git a/typescript/decouple-monolith/solution/src/compliance/checker.ts b/typescript/decouple-monolith/solution/src/compliance/checker.ts new file mode 100644 index 0000000..1879a3a --- /dev/null +++ b/typescript/decouple-monolith/solution/src/compliance/checker.ts @@ -0,0 +1,59 @@ +// [GIVEN] Rule-based compliance checker. Deterministic, no API keys needed. +// Same logic whether it runs as an Activity or behind a Nexus handler. +// +// Rules: +// - OFAC sanctioned country on either side -> HIGH risk, blocked +// - Amount over $50,000 -> HIGH risk, blocked (TXN-C hits this) +// - Over $10,000 or an unusual jurisdiction -> MEDIUM risk (TXN-B hits this) +// - Everything else -> LOW risk, approved +import { ComplianceRequest, ComplianceResult } from '../shared/types'; + +const SANCTIONED = new Set(['North Korea', 'Iran', 'Cuba', 'Syria', 'Venezuela']); +const COMMON = new Set(['US', 'UK', 'Canada', 'Germany', 'France', 'Japan', 'Australia']); + +export function checkCompliance(request: ComplianceRequest): ComplianceResult { + console.log( + `[ComplianceChecker] Evaluating ${request.transactionId}` + + ` | $${request.amount.toFixed(2)}` + + ` | ${request.senderCountry} -> ${request.receiverCountry}`, + ); + + if (SANCTIONED.has(request.receiverCountry) || SANCTIONED.has(request.senderCountry)) { + return { + transactionId: request.transactionId, + approved: false, + riskLevel: 'HIGH', + explanation: + 'Destination/source country is OFAC-sanctioned. Transaction blocked per regulatory requirements.', + }; + } + + if (request.amount > 50_000) { + return { + transactionId: request.transactionId, + approved: false, + riskLevel: 'HIGH', + explanation: + 'Transaction amount exceeds $50,000 threshold. Requires enhanced due diligence review.', + }; + } + + const isInternational = request.senderCountry !== request.receiverCountry; + const isUnusualJurisdiction = isInternational && !COMMON.has(request.receiverCountry); + + if (request.amount > 10_000 || isUnusualJurisdiction) { + return { + transactionId: request.transactionId, + approved: true, + riskLevel: 'MEDIUM', + explanation: 'International transfer above $10K threshold. Approved with AML monitoring note.', + }; + } + + return { + transactionId: request.transactionId, + approved: true, + riskLevel: 'LOW', + explanation: 'Routine domestic/standard international transfer. No regulatory concerns.', + }; +} diff --git a/typescript/decouple-monolith/solution/src/compliance/nexus-handler.ts b/typescript/decouple-monolith/solution/src/compliance/nexus-handler.ts new file mode 100644 index 0000000..1fdeac8 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/compliance/nexus-handler.ts @@ -0,0 +1,45 @@ +// The Compliance team's Nexus handler. Payments depends on the contract in +// shared/nexus-service.ts. This file is the code that answers those calls, and only +// the Compliance team owns it. +import * as nexus from 'nexus-rpc'; +import * as temporalNexus from '@temporalio/nexus'; +import { complianceService } from '../shared/nexus-service'; +import { ComplianceRequest, ComplianceResult, ReviewRequest } from '../shared/types'; +import { complianceWorkflow, reviewUpdate } from './workflows'; + +/** Same ID both times: one Operation creates the Workflow, the other looks it up. */ +const workflowIdFor = (transactionId: string) => `compliance-${transactionId}`; + +export const complianceServiceHandler = nexus.serviceHandler(complianceService, { + // The SLOW call. A risky payment may need a person to approve it, so an answer can be + // minutes or hours away. This Operation must not sit and wait for it. + // + // WorkflowRunOperationHandler starts a Workflow and returns immediately, handing back + // a reference. Temporal holds that reference and collects the result whenever the + // Workflow finishes. + // + // A plain async function would NOT work here: a synchronous Operation is cut off at + // the 10-second handler deadline, and when Temporal retried the failed call it would + // start a SECOND compliance check for the same payment. + checkCompliance: new temporalNexus.WorkflowRunOperationHandler( + async (ctx, input: ComplianceRequest) => + temporalNexus.startWorkflow(ctx, complianceWorkflow, { + args: [input], + // Business-meaningful and deduped: a retried Operation attaches to the same + // compliance check rather than starting a second one. + workflowId: workflowIdFor(input.transactionId), + // Task queue defaults to the queue this Operation is handled on. + }), + ), + + // The FAST call. By now the compliance check is running and parked waiting for a + // decision. All this does is find that Workflow and hand it the yes or no — a few + // milliseconds — so a synchronous handler is the right shape. + submitReview: async (_ctx, input: ReviewRequest): Promise => { + const client = temporalNexus.getClient(); + const handle = client.workflow.getHandle(workflowIdFor(input.transactionId)); + return await handle.executeUpdate(reviewUpdate, { + args: [input.approved, input.explanation], + }); + }, +}); diff --git a/typescript/decouple-monolith/solution/src/compliance/worker.ts b/typescript/decouple-monolith/solution/src/compliance/worker.ts new file mode 100644 index 0000000..ed10624 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/compliance/worker.ts @@ -0,0 +1,37 @@ +// The Compliance team's Worker. This process is the only place their code runs. +import { NativeConnection, Worker } from '@temporalio/worker'; +import * as activities from './activities'; +import { complianceServiceHandler } from './nexus-handler'; +import { COMPLIANCE_NAMESPACE, COMPLIANCE_TASK_QUEUE } from '../shared/types'; + +async function run() { + const connection = await NativeConnection.connect({ address: 'localhost:7233' }); + try { + const worker = await Worker.create({ + connection, + namespace: COMPLIANCE_NAMESPACE, + // MUST match the --target-task-queue given to the Nexus Endpoint, or the Endpoint + // routes work to a queue nobody is polling. + taskQueue: COMPLIANCE_TASK_QUEUE, + workflowsPath: require.resolve('./workflows'), + activities, + // Leave this out and the Worker still starts — Payments' calls just go unanswered + // forever. That is the failure this guards against. + nexusServices: [complianceServiceHandler], + }); + + console.log('========================================================='); + console.log(` Compliance Worker started on: ${COMPLIANCE_TASK_QUEUE}`); + console.log(` Namespace: ${COMPLIANCE_NAMESPACE}`); + console.log('========================================================='); + + await worker.run(); + } finally { + await connection.close(); + } +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/typescript/decouple-monolith/solution/src/compliance/workflows.ts b/typescript/decouple-monolith/solution/src/compliance/workflows.ts new file mode 100644 index 0000000..f1baa4e --- /dev/null +++ b/typescript/decouple-monolith/solution/src/compliance/workflows.ts @@ -0,0 +1,75 @@ +// [GIVEN] Compliance Workflow. Pre-built for this exercise. +// +// Runs an automated compliance check and, for MEDIUM-risk transactions, waits for a +// human reviewer to approve or deny via a Workflow Update. +// +// LOW risk -> auto-approved, returns immediately +// HIGH risk -> auto-denied, returns immediately +// MEDIUM -> pauses, waits for the review Update +// +// The 10-second sleep is intentional. It gives you a window to stop the Compliance +// Worker with Ctrl+C and restart it, so you can watch the Nexus Operation survive the +// outage. The timer runs on the server, so nothing is lost when the Worker goes away. +import * as wf from '@temporalio/workflow'; +import type * as activities from './activities'; +import { ComplianceRequest, ComplianceResult } from '../shared/types'; + +const { checkCompliance } = wf.proxyActivities({ + startToCloseTimeout: '30 seconds', +}); + +/** Update sent by the Compliance team's sync Nexus handler when a human decides. */ +export const reviewUpdate = wf.defineUpdate('review'); + +export async function complianceWorkflow(request: ComplianceRequest): Promise { + let reviewResult: ComplianceResult | undefined; + let autoResult: ComplianceResult | undefined; + + wf.setHandler( + reviewUpdate, + (approved, explanation) => { + const result: ComplianceResult = { + transactionId: request.transactionId, + approved, + riskLevel: 'MEDIUM', + explanation, + }; + reviewResult = result; + return result; + }, + { + validator: (_approved: boolean, _explanation: string) => { + if (autoResult?.riskLevel !== 'MEDIUM') throw new Error('Workflow is not awaiting review'); + if (reviewResult !== undefined) throw new Error('Review already submitted'); + }, + }, + ); + + // Step 1: automated compliance check. + const auto = await checkCompliance(request); + autoResult = auto; + + // Durable delay. Stop the Compliance Worker mid-sleep, restart it, and the Workflow + // resumes on its own. + await wf.sleep('10 seconds'); + + // Step 2: LOW or HIGH risk returns immediately. + if (auto.riskLevel !== 'MEDIUM') return auto; + + // Step 3: MEDIUM risk waits for human review. + // + // Bounded on purpose. An unbounded wait would leave this Workflow Running forever + // when nobody reviews. Eight minutes is deliberately shorter than the caller's + // 10-minute scheduleToCloseTimeout, so the handler resolves and reports a decision + // rather than letting the caller time out first. + const reviewed = await wf.condition(() => reviewResult !== undefined, '8 minutes'); + if (!reviewed) { + return { + transactionId: request.transactionId, + approved: false, + riskLevel: 'MEDIUM', + explanation: 'No reviewer responded within 8 minutes. Not approved.', + }; + } + return reviewResult!; +} diff --git a/typescript/decouple-monolith/solution/src/payments/activities.ts b/typescript/decouple-monolith/solution/src/payments/activities.ts new file mode 100644 index 0000000..ac67272 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/payments/activities.ts @@ -0,0 +1,15 @@ +// [GIVEN] Payments team Activities. Thin wrappers around the gateway. +// +// An Activity is just an exported function. They must be async: proxyActivities only accepts +// Promise-returning functions, and a synchronous one is silently typed away, surfacing +// later as "Type 'Symbol' has no call signatures" at the call site in the Workflow. +import * as gateway from './gateway'; +import { PaymentRequest } from '../shared/types'; + +export async function validatePayment(request: PaymentRequest): Promise { + return gateway.validatePayment(request); +} + +export async function executePayment(request: PaymentRequest): Promise { + return gateway.executePayment(request); +} diff --git a/typescript/decouple-monolith/solution/src/payments/gateway.ts b/typescript/decouple-monolith/solution/src/payments/gateway.ts new file mode 100644 index 0000000..f775915 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/payments/gateway.ts @@ -0,0 +1,46 @@ +// [GIVEN] Simulated payment gateway. In production this would call Stripe, PayPal, SWIFT. +// +// The 10% failure rate is deliberate: it shows Temporal retrying an Activity for free. +// Because of it, never assert on a clean first attempt — assert on final Workflow state. +import { PaymentRequest } from '../shared/types'; + +const money = (n: number) => `$${n.toFixed(2)}`; +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +export function validatePayment(request: PaymentRequest): boolean { + if (request.amount <= 0) { + console.log(`[PaymentGateway] REJECTED: Invalid amount for ${request.transactionId}`); + return false; + } + if (!request.senderAccount || !request.receiverAccount) { + console.log(`[PaymentGateway] REJECTED: Missing account info for ${request.transactionId}`); + return false; + } + console.log(`[PaymentGateway] Validation passed for ${request.transactionId}`); + return true; +} + +export async function executePayment(request: PaymentRequest): Promise { + console.log( + `[PaymentGateway] Processing ${request.transactionId}` + + ` | ${money(request.amount)}` + + ` | ${request.senderCountry} -> ${request.receiverCountry}`, + ); + + await sleep(500 + Math.random() * 500); + + // Simulate occasional gateway failures. Temporal retries automatically. + if (Math.random() < 0.1) { + throw new Error( + `Payment gateway timeout for ${request.transactionId}, connection to banking network failed`, + ); + } + + // Derived from the transaction ID, not the clock, so a retry of this Activity returns + // the SAME confirmation number instead of minting a second one. With a 10% failure + // rate above, a timestamp here would make retries look like distinct payments. + // Idempotency is what makes an at-least-once retry safe. + const confirmationNumber = `CONF-${request.transactionId}`; + console.log(`[PaymentGateway] Payment executed: ${confirmationNumber}`); + return confirmationNumber; +} diff --git a/typescript/decouple-monolith/solution/src/payments/review-starter.ts b/typescript/decouple-monolith/solution/src/payments/review-starter.ts new file mode 100644 index 0000000..e8061dc --- /dev/null +++ b/typescript/decouple-monolith/solution/src/payments/review-starter.ts @@ -0,0 +1,51 @@ +// [GIVEN] Starts a reviewCallerWorkflow to approve TXN-B via Nexus. +// +// Rather than calling 'temporal workflow update execute' directly, this triggers a caller +// Workflow that invokes the submitReview Nexus Operation. The Compliance team's sync +// handler forwards the decision to the running complianceWorkflow as an Update. +// +// Flip `approved` to false below to see the denial path. +import { Client, Connection } from '@temporalio/client'; +import { reviewCallerWorkflow } from './workflows'; +import { PAYMENTS_NAMESPACE, PAYMENTS_TASK_QUEUE, ReviewRequest } from '../shared/types'; + +async function run() { + console.log('=========================================================='); + console.log(' REVIEW STARTER - Submitting review for TXN-B via Nexus'); + console.log('==========================================================\n'); + + const connection = await Connection.connect({ address: 'localhost:7233' }); + const client = new Client({ connection, namespace: PAYMENTS_NAMESPACE }); + + const request: ReviewRequest = { + transactionId: 'TXN-B', + approved: true, + explanation: 'Approved after manual review', + }; + + console.log(' Submitting review for TXN-B via Nexus...'); + console.log(` Approved: ${request.approved}`); + console.log(` Explanation: ${request.explanation}\n`); + + const result = await client.workflow.execute(reviewCallerWorkflow, { + taskQueue: PAYMENTS_TASK_QUEUE, + workflowId: `review-${request.transactionId}`, + args: [request], + }); + + console.log(` Review result: ${result.approved ? 'APPROVED' : 'DENIED'}`); + console.log(` Risk level: ${result.riskLevel}`); + console.log(` Explanation: ${result.explanation}\n`); + console.log(' TXN-B review submitted. The payment Workflow will now complete.'); + console.log('=========================================================='); + + await connection.close(); +} + +run().then( + () => process.exit(0), + (err) => { + console.error(err); + process.exit(1); + }, +); diff --git a/typescript/decouple-monolith/solution/src/payments/starter.ts b/typescript/decouple-monolith/solution/src/payments/starter.ts new file mode 100644 index 0000000..492620b --- /dev/null +++ b/typescript/decouple-monolith/solution/src/payments/starter.ts @@ -0,0 +1,131 @@ +// [GIVEN] Starts three payment Workflows. +// +// All three start before any result is collected. That matters once Nexus is in play: +// TXN-B is MEDIUM risk, so its handler Workflow pauses for human review and would +// otherwise block TXN-C behind it. Starting concurrently lets TXN-A and TXN-C finish +// while TXN-B sits visibly pending, which is the point of the human-in-the-loop path. +// +// The starter does not know or care whether compliance runs locally or via Nexus. +import { Client, Connection } from '@temporalio/client'; +import { paymentProcessingWorkflow } from './workflows'; +import { PAYMENTS_NAMESPACE, PAYMENTS_TASK_QUEUE, PaymentRequest, PaymentResult } from '../shared/types'; + +// How long to wait before calling a Workflow "pending review". TXN-A and TXN-C finish in +// roughly 12 seconds (the handler Workflow sleeps for 10 of those). +const COLLECT_DEADLINE_MS = 25_000; + +const TRANSACTIONS: PaymentRequest[] = [ + { + transactionId: 'TXN-A', amount: 250.0, currency: 'USD', + senderCountry: 'US', receiverCountry: 'US', + description: 'Routine supplier payment', senderAccount: 'ACC-001', receiverAccount: 'ACC-002', + }, + { + transactionId: 'TXN-B', amount: 12_000.0, currency: 'USD', + senderCountry: 'US', receiverCountry: 'UK', + description: 'International consulting fee', senderAccount: 'ACC-003', receiverAccount: 'ACC-004', + }, + { + transactionId: 'TXN-C', amount: 75_000.0, currency: 'USD', + senderCountry: 'US', receiverCountry: 'US', + description: 'Large capital transfer', senderAccount: 'ACC-005', receiverAccount: 'ACC-006', + }, +]; + +type Outcome = + | { state: 'done'; result: PaymentResult } + | { state: 'failed'; error: string } + | { state: 'pending' }; + +async function run() { + console.log('=========================================================='); + console.log(' PAYMENT STARTER - Decouple Monolith'); + console.log(' Running 3 transactions through Temporal'); + console.log('==========================================================\n'); + + const connection = await Connection.connect({ address: 'localhost:7233' }); + const client = new Client({ connection, namespace: PAYMENTS_NAMESPACE }); + + // Start all three without awaiting results, so a pause in one cannot hold up the others. + const started = await Promise.all( + TRANSACTIONS.map(async (txn) => { + const handle = await client.workflow.start(paymentProcessingWorkflow, { + taskQueue: PAYMENTS_TASK_QUEUE, + workflowId: `payment-${txn.transactionId}`, + args: [txn], + }); + console.log(` Started: payment-${txn.transactionId}`); + console.log( + ` Amount: $${txn.amount.toFixed(2)} | Route: ${txn.senderCountry} -> ${txn.receiverCountry}`, + ); + return { txn, handle }; + }), + ); + + console.log('\n All 3 started. Collecting results...\n'); + + // Wait on all three against a single shared deadline. Waiting one at a time would let + // TXN-B's review pause burn the whole budget, so TXN-C would be reported as pending + // even though it finished seconds earlier. + const outcomes = new Map(started.map(({ txn }) => [txn.transactionId, { state: 'pending' }])); + await Promise.race([ + Promise.allSettled( + started.map(async ({ txn, handle }) => { + try { + outcomes.set(txn.transactionId, { state: 'done', result: await handle.result() }); + } catch (err) { + outcomes.set(txn.transactionId, { state: 'failed', error: (err as Error).message }); + } + }), + ), + new Promise((resolve) => setTimeout(resolve, COLLECT_DEADLINE_MS)), + ]); + + let pendingReview = 0; + for (const { txn } of started) { + const outcome = outcomes.get(txn.transactionId)!; + console.log('----------------------------------------------------'); + console.log(` ${txn.transactionId}`); + + if (outcome.state === 'pending') { + pendingReview++; + console.log(' Result: STILL RUNNING'); + console.log(' Reason: either it is parked for human review (MEDIUM risk), or the'); + console.log(' Compliance handler is not answering right now.'); + console.log(' Action: check the Temporal UI. If it is parked for review, run'); + console.log(" 'npm run review-starter'."); + } else if (outcome.state === 'failed') { + console.log(' Result: FAILED'); + console.log(` Error: ${outcome.error}`); + } else { + const r = outcome.result; + console.log(` Result: ${r.status}`); + console.log(` Risk: ${r.riskLevel ?? 'N/A'}`); + console.log(` Reason: ${r.explanation ?? 'N/A'}`); + if (r.confirmationNumber) console.log(` Conf#: ${r.confirmationNumber}`); + if (r.error) console.log(` Error: ${r.error}`); + } + console.log(); + } + + console.log('=========================================================='); + if (pendingReview > 0) { + console.log(` ${pendingReview} transaction(s) have not resolved yet.`); + console.log(" Parked for review? Run 'npm run review-starter'."); + console.log(' Compliance Worker down? Start it and they resume on their own.'); + } else { + console.log(' All 3 transactions resolved!'); + } + console.log(' Check Temporal UI: http://localhost:8233'); + console.log('=========================================================='); + + await connection.close(); +} + +run().then( + () => process.exit(0), + (err) => { + console.error(err); + process.exit(1); + }, +); diff --git a/typescript/decouple-monolith/solution/src/payments/worker.ts b/typescript/decouple-monolith/solution/src/payments/worker.ts new file mode 100644 index 0000000..f879433 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/payments/worker.ts @@ -0,0 +1,36 @@ +// DECOUPLED VERSION. This Worker runs Payments code and nothing else. +// +// Compare against the monolith it replaced, which also registered the Compliance team's +// Activity — meaning a bug in compliance code at 3 AM took payments down with it. +import { NativeConnection, Worker } from '@temporalio/worker'; +import * as activities from './activities'; +import { PAYMENTS_NAMESPACE, PAYMENTS_TASK_QUEUE } from '../shared/types'; + +async function run() { + const connection = await NativeConnection.connect({ address: 'localhost:7233' }); + try { + const worker = await Worker.create({ + connection, + namespace: PAYMENTS_NAMESPACE, + taskQueue: PAYMENTS_TASK_QUEUE, + workflowsPath: require.resolve('./workflows'), + activities, + }); + + console.log('========================================================='); + console.log(` Payments Worker started on: ${PAYMENTS_TASK_QUEUE}`); + console.log(` Namespace: ${PAYMENTS_NAMESPACE}`); + console.log(' Registered: paymentProcessingWorkflow, reviewCallerWorkflow'); + console.log(' payment activities only — compliance is remote'); + console.log('========================================================='); + + await worker.run(); + } finally { + await connection.close(); + } +} + +run().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/typescript/decouple-monolith/solution/src/payments/workflows.ts b/typescript/decouple-monolith/solution/src/payments/workflows.ts new file mode 100644 index 0000000..1f6eb82 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/payments/workflows.ts @@ -0,0 +1,110 @@ +// DECOUPLED VERSION. Compliance is now called across a Nexus boundary. +// +// Three steps: +// Step 1: validatePayment (Activity, this Worker) +// Step 2: checkCompliance (Nexus Operation, the Compliance team's Worker) +// Step 3: executePayment (Activity, this Worker) +import * as wf from '@temporalio/workflow'; +import type * as activities from './activities'; +import { complianceService } from '../shared/nexus-service'; +import { + COMPLIANCE_ENDPOINT, + ComplianceRequest, + ComplianceResult, + PaymentRequest, + PaymentResult, + ReviewRequest, +} from '../shared/types'; + +const { validatePayment, executePayment } = wf.proxyActivities({ + startToCloseTimeout: '30 seconds', + retry: { initialInterval: '1 second', backoffCoefficient: 2 }, +}); + +export async function paymentProcessingWorkflow(request: PaymentRequest): Promise { + const logger = wf.log; + + // Step 1: validate (Payments team). + if (!(await validatePayment(request))) { + return { + success: false, + transactionId: request.transactionId, + status: 'REJECTED', + error: 'Payment validation failed', + }; + } + logger.info(`Step 1 passed: validation OK for ${request.transactionId}`); + + // Step 2: compliance check, across the team boundary. + // + // This client is what replaced the Activity stub. Calls on it do not run code in this + // process — they go out through the Endpoint to whichever Worker owns the Service. + // + // The 10 minutes is the budget for the whole call, retries included. It is what lets + // the Operation survive the Compliance Worker going away instead of failing the moment + // it does. + const compliance = wf.createNexusServiceClient({ + service: complianceService, + endpoint: COMPLIANCE_ENDPOINT, + }); + + const compReq: ComplianceRequest = { + transactionId: request.transactionId, + amount: request.amount, + senderCountry: request.senderCountry, + receiverCountry: request.receiverCountry, + description: request.description, + }; + + logger.info(`Step 2: calling compliance check for ${request.transactionId}`); + const result: ComplianceResult = await compliance.executeOperation('checkCompliance', compReq, { + scheduleToCloseTimeout: '10 minutes', + }); + + logger.info(`Compliance result: ${result.riskLevel} | approved=${result.approved}`); + + // A declined payment is a business outcome, not a failure. The Workflow completes + // successfully and reports the decision. + if (!result.approved) { + return { + success: false, + transactionId: request.transactionId, + status: 'DECLINED_COMPLIANCE', + riskLevel: result.riskLevel, + explanation: result.explanation, + }; + } + + // Step 3: execute, only if compliance approved. + logger.info(`Step 3: executing payment for ${request.transactionId}`); + const confirmationNumber = await executePayment(request); + + return { + success: true, + transactionId: request.transactionId, + status: 'COMPLETED', + riskLevel: result.riskLevel, + explanation: result.explanation, + confirmationNumber, + }; +} + +/** + * [GIVEN] Sends a compliance review decision through Nexus. + * + * Routing the review through the Endpoint respects the team boundary: neither team needs + * to know the other's Workflow IDs or internal method names. + * + * submitReview is a synchronous Operation, so the Compliance handler must finish inside + * the 10-second handler deadline. That is why the timeout here is 10 seconds, not the + * 10 minutes used for the async check. + */ +export async function reviewCallerWorkflow(request: ReviewRequest): Promise { + const compliance = wf.createNexusServiceClient({ + service: complianceService, + endpoint: COMPLIANCE_ENDPOINT, + }); + return await compliance.executeOperation('submitReview', request, { + scheduleToCloseTimeout: '10 seconds', + }); +} diff --git a/typescript/decouple-monolith/solution/src/shared/nexus-service.ts b/typescript/decouple-monolith/solution/src/shared/nexus-service.ts new file mode 100644 index 0000000..f8781ef --- /dev/null +++ b/typescript/decouple-monolith/solution/src/shared/nexus-service.ts @@ -0,0 +1,14 @@ +// The shared contract between the Payments and Compliance teams. Like an OpenAPI spec, +// but durable. Payments builds a client from it; Compliance implements a handler for it. +// +// Both teams import THIS file. It is the only thing they share. +import * as nexus from 'nexus-rpc'; +import { ComplianceRequest, ComplianceResult, ReviewRequest } from './types'; + +export const complianceService = nexus.service('ComplianceNexusService', { + /** Slow path. Backed by a Workflow, because a risky payment may need a human. */ + checkCompliance: nexus.operation(), + + /** Fast path. A human decision handed to an already-running compliance check. */ + submitReview: nexus.operation(), +}); diff --git a/typescript/decouple-monolith/solution/src/shared/types.ts b/typescript/decouple-monolith/solution/src/shared/types.ts new file mode 100644 index 0000000..49cf8c8 --- /dev/null +++ b/typescript/decouple-monolith/solution/src/shared/types.ts @@ -0,0 +1,59 @@ +// [GIVEN] The data that crosses team boundaries. +// +// Plain interfaces, and that is all they need to be. The default data converter is +// JSON, so a structural type is enough to cross the Nexus boundary in both directions. + +/** A payment transaction to be processed. */ +export interface PaymentRequest { + transactionId: string; + amount: number; + currency: string; + senderCountry: string; + receiverCountry: string; + description: string; + senderAccount: string; + receiverAccount: string; +} + +/** The outcome of a payment Workflow. */ +export interface PaymentResult { + success: boolean; + transactionId: string; + status: 'COMPLETED' | 'REJECTED' | 'DECLINED_COMPLIANCE' | 'FAILED'; + riskLevel?: RiskLevel; + explanation?: string; + confirmationNumber?: string; + error?: string; +} + +export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH'; + +/** Input to the compliance check. Sent by Payments to Compliance. */ +export interface ComplianceRequest { + transactionId: string; + amount: number; + senderCountry: string; + receiverCountry: string; + description: string; +} + +/** Result of a compliance check. Returned by Compliance to Payments. */ +export interface ComplianceResult { + transactionId: string; + approved: boolean; + riskLevel: RiskLevel; + explanation: string; +} + +/** A human review decision, submitted through Nexus. */ +export interface ReviewRequest { + transactionId: string; + approved: boolean; + explanation: string; +} + +export const PAYMENTS_TASK_QUEUE = 'payments-processing'; +export const COMPLIANCE_TASK_QUEUE = 'compliance-risk'; +export const PAYMENTS_NAMESPACE = 'payments-namespace'; +export const COMPLIANCE_NAMESPACE = 'compliance-namespace'; +export const COMPLIANCE_ENDPOINT = 'compliance-endpoint'; diff --git a/typescript/decouple-monolith/solution/tsconfig.json b/typescript/decouple-monolith/solution/tsconfig.json new file mode 100644 index 0000000..f0e6b74 --- /dev/null +++ b/typescript/decouple-monolith/solution/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "version": "5.6.3", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "strict": true + }, + "include": ["src/**/*.ts"] +} diff --git a/typescript/diagrams/monolith-architecture.html b/typescript/diagrams/monolith-architecture.html new file mode 100644 index 0000000..b8312bd --- /dev/null +++ b/typescript/diagrams/monolith-architecture.html @@ -0,0 +1,494 @@ + + + + + +Challenge 1 · The Payments Monolith + + + +
+
+

Challenge 1 · Before Nexus

+

The Payments Monolith

+

Two teams. One process. Step through the flow with the arrows, or click any block for its file, + signature and role.

+
+ +
+ + + step — / — + +
+ + Three payments, two teams, one Worker. +
+
+ +
+ Zoom + + 100% + + + + +
+ +
+
+
+
+
+
+ +
+
+ + + + diff --git a/typescript/instruqt/01-run-the-monolith/assignment.md b/typescript/instruqt/01-run-the-monolith/assignment.md new file mode 100644 index 0000000..39b8e76 --- /dev/null +++ b/typescript/instruqt/01-run-the-monolith/assignment.md @@ -0,0 +1,227 @@ +--- +slug: run-the-monolith +id: nwdcc4mlob8f +type: challenge +title: 1. Run the Monolith +teaser: Three payments, two teams, one Worker. Find the coupling before you fix it. +notes: +- type: text + contents: |- + # What breaks when two teams share one Worker? + + A payment cannot execute until Compliance clears it. Both teams' code runs in + the same process, on the same Task Queue, in the same Namespace. + + It works. That is the problem. Nobody notices the coupling until the night + a compliance bug takes payments down with it. +- type: text + contents: |- + # Ziggy is warming up your sandbox + + A Temporal dev server is starting, and two Namespaces are being created: + one for Payments, one for Compliance. + + Right now only one of them has anything in it. +tabs: +- id: jy7gspk7rzja + title: Exercise + type: service + hostname: workshop + path: /?folder=/root/workshop + port: 8080 +- id: osmynelncext + title: Temporal UI + type: service + hostname: workshop + path: / + port: 8233 +- id: bhx6tjsdyrva + title: Terminal + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: utwhvn6xykly + title: Payments Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: xcqorbmjolyq + title: Compliance Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: kaxawqn9yd2g + title: Monolith Architecture + type: service + hostname: workshop + path: /monolith-architecture.html + port: 8090 +difficulty: basic +timelimit: 900 +enhanced_loading: null +--- + +# The Setup + +You work at a bank. Every payment takes three steps. + +1. Validate the payment. Payments team. +2. Check compliance. Compliance team. +3. Execute the payment. Payments team. + +Step 2 is not optional. Nothing moves until Compliance says yes. + +The blue buttons below are clickable. Click any one to jump to that tab. + +# Start the Worker + +Click the [button label="Payments Worker" background="#444CE7"](tab-3) tab and start it: + +```bash,run +npm run payments-worker +``` + +The Worker bundles your Workflow code with webpack on startup, so the first run takes a +second longer than you might expect. Read the banner it prints: + +```bash,nocopy + Registered: paymentProcessingWorkflow, payment activities + compliance activities (monolith, will decouple) +``` + +One Worker. Both teams' code. That last line is the coupling, written down. + +# Run Three Payments + +Click the [button label="Terminal" background="#444CE7"](tab-2) tab: + +```bash,run +npm run starter +``` + +Three transactions go through. Watch what comes back: + +```bash,nocopy + TXN-A Result: COMPLETED Risk: LOW + TXN-B Result: COMPLETED Risk: MEDIUM + TXN-C Result: DECLINED_COMPLIANCE Risk: HIGH +``` + +# See It in the Temporal UI + +The terminal gave you three lines. The server has the whole story. + +Click the [button label="Temporal UI" background="#444CE7"](tab-1) tab. Check that the +Namespace selector at the top reads `payments-namespace`. + +Three Workflows, every one of them **Completed**. Click refresh if you do not see them yet. + +| Workflow ID | Amount | What happened | +|---|---|---| +| `payment-TXN-A` | $250 | paid | +| `payment-TXN-B` | $12,000 | paid | +| `payment-TXN-C` | $75,000 | declined by Compliance | + +Open `payment-TXN-C`, find the **Input and Results** panel, and open **Results**: + +```json,nocopy +{ + "explanation": "Transaction amount exceeds $50,000 threshold. Requires enhanced due diligence review.", + "riskLevel": "HIGH", + "status": "DECLINED_COMPLIANCE", + "success": false, + "transactionId": "TXN-C" +} +``` + +Read that closely. `success` is `false`, there is no `error` field at all, and the Workflow +status is **Completed** rather than Failed. + +Compliance looked at a $75,000 payment and said no. That is the system working. A declined +payment is an answer, so the Workflow returned it and finished normally. Keep the two ideas +apart: a declined payment is a business outcome, a failed Workflow is a defect. You will +watch this same result travel across a team boundary in challenge 4, and it should still +look exactly like this. + +# Review the Monolith Architecture + +Before you hunt for the coupling in code, look at the shape of what you just ran. + +Click the [button label="Monolith Architecture" background="#444CE7"](tab-5) tab. + +Two shaded zones, one per team. Payments is blue, Compliance is amber. Every box carries +its file name and the function that matters. + +Click **Walk the flow**, then move with the **left** and **right** arrows on screen or on +your keyboard. Eight steps. + +Two of them are the point of this challenge: + +- **Step 5 of 8** is the Workflow calling the Compliance team's Activity. Watch the arrow + cross from the blue zone into the amber one while staying inside a single process. +- **Step 8 of 8** is the consequence. Compliance owns a Namespace with nothing in it. + +Then read the dashed outline that wraps both zones. It is labeled +`payments/worker.ts · one Node process · one Task Queue payments-processing`. That is the +coupling stated as plainly as it can be: both teams' code, one process, one queue. Every +box inside that outline is polled by the same Worker. + +**Fit** brings everything back on screen. **Reset view** undoes any zooming. + +# Find the Coupling + +Click the [button label="Exercise" background="#444CE7"](tab-0) tab and open +`exercise/src/payments/worker.ts`. + +Look at what this one Worker registers. One object, two different teams: + +```ts,nocopy +activities: { ...paymentActivities, ...complianceActivities }, +``` + +`paymentActivities` belongs to Payments. `complianceActivities` belongs to Compliance. +They sit on one line in one file, which means the same process, the same deployment, and +the same blast radius. Compliance cannot ship a fix without Payments shipping too. + +That second spread is the one you delete in challenge 4. That deletion is the decoupling. + +Now open `exercise/src/payments/workflows.ts` and find step 2: + +```ts,nocopy +const result: ComplianceResult = await checkCompliance(compReq); +``` + +An in-process Activity call across a team boundary that should not be in-process. + +# See the Empty Namespace + +The diagram claims Compliance owns a Namespace with nothing in it. Confirm it. + +Click the [button label="Temporal UI" background="#444CE7"](tab-1) tab and switch the +Namespace selector to `compliance-namespace`. + +No Workflows. Compliance has a Namespace of its own and nothing of theirs runs in it, +because their code is executing inside the Payments Worker. + +If you look under **Workers** you will see one Go Worker on a `temporal-sys-` Task Queue. +Ignore it — Temporal Server is written in Go and runs an internal Worker in every +Namespace for its own background work. No Worker of the Compliance team's exists yet. +You start one in challenge 3. + +Switch back to `payments-namespace`, open `payment-TXN-C` again, and read the Event +History. Find **Activity Task Scheduled** for the compliance check. That single event is +what you spend the rest of this lab replacing. + +Click **Check** when your three transactions have finished. + +# What You Know Now + +- One Worker runs both teams. One bug takes down both. +- The compliance check is an ordinary Activity call. +- A declined payment completes. It does not fail. +- Compliance has a Namespace, and it is empty. + +--- + +**Please share your feedback so we can make better content for you.** The **Feedback** +tab takes a few seconds, and it is the only way we find out which parts of this landed. diff --git a/typescript/instruqt/01-run-the-monolith/check-workshop b/typescript/instruqt/01-run-the-monolith/check-workshop new file mode 100755 index 0000000..dbefe2d --- /dev/null +++ b/typescript/instruqt/01-run-the-monolith/check-workshop @@ -0,0 +1,15 @@ +#!/bin/bash +# ABOUTME: Instant pass. This is a live instructor-led workshop, so the +# instructor is the feedback loop and progression is never gated on a script. +# +# Two reasons this is deliberate rather than lazy: +# 1. Skip runs solve-workshop, which copies solution files over the learner's +# work. Gating progression pushes stuck learners toward destroying their +# own code to move on. +# 2. Check-click latency costs real minutes across a room of 20 in a 40 minute +# lab block. +# +# Challenge 04 is the exception: a mistyped Nexus Endpoint hangs silently, so +# that one keeps a diagnostic. Everything the removed checks used to explain now +# lives in the assignment bodies. +exit 0 diff --git a/typescript/instruqt/01-run-the-monolith/cleanup-workshop b/typescript/instruqt/01-run-the-monolith/cleanup-workshop new file mode 100755 index 0000000..1275b4e --- /dev/null +++ b/typescript/instruqt/01-run-the-monolith/cleanup-workshop @@ -0,0 +1,7 @@ +#!/bin/bash +# ABOUTME: Challenge 01 cleanup. One pkill per pattern. +set -euo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "npm run payments-worker" || true +sleep 1 diff --git a/typescript/instruqt/01-run-the-monolith/setup-workshop b/typescript/instruqt/01-run-the-monolith/setup-workshop new file mode 100755 index 0000000..eb9bbc7 --- /dev/null +++ b/typescript/instruqt/01-run-the-monolith/setup-workshop @@ -0,0 +1,26 @@ +#!/bin/bash +# ABOUTME: Challenge 01 setup. Clears stragglers so the monolith run is clean. +# The working tree is staged once at the track level and accumulates edits +# across challenges, so nothing is wiped here. +set -euxo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true + +# Restore the exercise tree if a solve script has already replaced it with the +# solution. The solve scripts for later challenges do `cp -R solution/src +# exercise/src`, and nothing else ever puts the TODOs back. Without this, using +# Skip once leaves the sandbox permanently solved: challenge 01 would print the +# decoupled Worker banner instead of the monolith one, and every TODO would be +# gone before the attendee reached it. +# +# Only restores a tree that has NO TODO markers left, which means it was +# overwritten wholesale. A pristine or partly-completed tree is left alone, so an +# attendee who wanders back to challenge 01 does not lose their work. +CONTRACT=/root/workshop/exercise/src/shared/nexus-service.ts +if [ -f "$CONTRACT" ] && ! grep -q "TODO" "$CONTRACT"; then + echo "==> Exercise tree was already solved. Restoring the TODOs." + rm -rf /root/workshop/exercise/src + cp -R /opt/workshop/exercise/src /root/workshop/exercise/src +fi +true diff --git a/typescript/instruqt/01-run-the-monolith/solve-workshop b/typescript/instruqt/01-run-the-monolith/solve-workshop new file mode 100755 index 0000000..b5106bc --- /dev/null +++ b/typescript/instruqt/01-run-the-monolith/solve-workshop @@ -0,0 +1,18 @@ +#!/bin/bash +# ABOUTME: Challenge 01 solve. Runs the monolith end to end so the next +# challenge inherits three completed payment Workflows. +set -euxo pipefail + +cd /root/workshop/exercise + +pkill -f "payments/worker.ts" || true +sleep 1 + +nohup npm run payments-worker > /tmp/payments-worker.log 2>&1 & +for i in $(seq 1 120); do + grep -q "Payments Worker started" /tmp/payments-worker.log && break + sleep 1 +done + +npm run starter > /tmp/starter.log 2>&1 || true +cat /tmp/starter.log diff --git a/typescript/instruqt/02-the-shared-contract/assignment.md b/typescript/instruqt/02-the-shared-contract/assignment.md new file mode 100644 index 0000000..87331fb --- /dev/null +++ b/typescript/instruqt/02-the-shared-contract/assignment.md @@ -0,0 +1,125 @@ +--- +slug: the-shared-contract +id: ynazxaoijxil +type: challenge +title: 2. The Shared Contract +teaser: Write the interface both teams depend on. One file, two Operations. +notes: +- type: text + contents: |- + # How do two teams agree on a call neither one owns? + + Payments needs a compliance decision. Compliance owns the logic and refuses + to hand over its database. + + An HTTP client would work until the network blips. Then you are writing + retries, timeouts, and callback infrastructure by hand. +- type: text + contents: |- + # The four Nexus pieces + + Service is the contract. Operation is a method on it. Endpoint is the routing + rule. Registry is where Endpoints live. + + You write the Service now. The other three come next. +tabs: +- id: fsl9wnyvzlt2 + title: Exercise + type: service + hostname: workshop + path: /?folder=/root/workshop + port: 8080 +- id: 7iygieqqyuko + title: Temporal UI + type: service + hostname: workshop + path: / + port: 8233 +- id: nljg80a3snn1 + title: Terminal + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: ymi443zqbrnc + title: Payments Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: znmrv8zywaej + title: Compliance Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: 1wdriwhm5mby + title: Monolith Architecture + type: service + hostname: workshop + path: /monolith-architecture.html + port: 8090 +difficulty: basic +timelimit: 900 +enhanced_loading: null +--- + +# One Contract, Two Teams + +A Nexus Service is a contract both teams compile against. Payments builds a client from +it. Compliance implements a handler for it. Neither team sees the other's code. + +It lives in `shared/` on purpose. Neither team owns it alone. + +# Write It + +Click the [button label="Exercise" background="#444CE7"](tab-0) tab, open +`exercise/src/shared/nexus-service.ts`, and follow TODO 1. + +Two Operations. Each is a name paired with `nexus.operation()`, which +carries no implementation — only the types both teams agree on. + +The editor saves as you type. There is no save button. + +# Now Break the Build on Purpose + +Click the [button label="Terminal" background="#444CE7"](tab-2) tab: + +```bash,run +npx tsc --noEmit +``` + +**This is supposed to fail.** Read what it says: + +```bash,nocopy +src/compliance/nexus-handler.ts(29,81): error TS2345: Argument of type +'{ checkCompliance: WorkflowRunOperationHandler<...>; }' is not assignable to +parameter of type 'ServiceHandlerFor<...>'. + Property 'submitReview' is missing in type + '{ checkCompliance: WorkflowRunOperationHandler<...>; }' but required in type + 'ServiceHandlerFor<...>'. +``` + +You declared two Operations, and TypeScript immediately went looking for the handlers +that answer them. One of them, `checkCompliance`, is already written for you. The other +is not, so it refuses to compile and names the one that is missing. + +That is the contract doing its job, and it is worth sitting with for a second. You have +not run anything. No Worker has started. No call has been made. The compiler already knows +Compliance owes Payments two implementations, purely from the shape of the Service. + +Notice *when* it told you: at compile time, before anything ran. A Service with an +unanswered Operation cannot reach a running Worker, because the code carrying the gap does +not build. + +You fix this in challenge 3, by writing the handlers. Leave it red. + +Click **Check** when the two Operations are declared. + +# What You Know Now + +- `nexus.service()` names the contract; `nexus.operation()` declares each call. +- The contract is shared. The implementation is not. +- Declaring an Operation obliges someone to handle it, and the compiler enforces that. + +--- + +**Please share your feedback so we can make better content for you.** The **Feedback** +tab takes a few seconds, and it is the only way we find out which parts of this landed. diff --git a/typescript/instruqt/02-the-shared-contract/check-workshop b/typescript/instruqt/02-the-shared-contract/check-workshop new file mode 100755 index 0000000..dbefe2d --- /dev/null +++ b/typescript/instruqt/02-the-shared-contract/check-workshop @@ -0,0 +1,15 @@ +#!/bin/bash +# ABOUTME: Instant pass. This is a live instructor-led workshop, so the +# instructor is the feedback loop and progression is never gated on a script. +# +# Two reasons this is deliberate rather than lazy: +# 1. Skip runs solve-workshop, which copies solution files over the learner's +# work. Gating progression pushes stuck learners toward destroying their +# own code to move on. +# 2. Check-click latency costs real minutes across a room of 20 in a 40 minute +# lab block. +# +# Challenge 04 is the exception: a mistyped Nexus Endpoint hangs silently, so +# that one keeps a diagnostic. Everything the removed checks used to explain now +# lives in the assignment bodies. +exit 0 diff --git a/typescript/instruqt/02-the-shared-contract/cleanup-workshop b/typescript/instruqt/02-the-shared-contract/cleanup-workshop new file mode 100755 index 0000000..1dda6d9 --- /dev/null +++ b/typescript/instruqt/02-the-shared-contract/cleanup-workshop @@ -0,0 +1,7 @@ +#!/bin/bash +# ABOUTME: Challenge 02 cleanup. One pkill per pattern. +set -euo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "npm run payments-worker" || true +sleep 1 diff --git a/typescript/instruqt/02-the-shared-contract/setup-workshop b/typescript/instruqt/02-the-shared-contract/setup-workshop new file mode 100755 index 0000000..22d51ca --- /dev/null +++ b/typescript/instruqt/02-the-shared-contract/setup-workshop @@ -0,0 +1,8 @@ +#!/bin/bash +# ABOUTME: Challenge 02 setup. Clears the monolith Worker from challenge 01. +# The working tree accumulates edits across challenges, so nothing is wiped. +set -euxo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true +true diff --git a/typescript/instruqt/02-the-shared-contract/solve-workshop b/typescript/instruqt/02-the-shared-contract/solve-workshop new file mode 100755 index 0000000..67b3283 --- /dev/null +++ b/typescript/instruqt/02-the-shared-contract/solve-workshop @@ -0,0 +1,12 @@ +#!/bin/bash +# ABOUTME: Challenge 02 solve. Copies only this challenge's file from the +# solution so a skipped challenge does not hand the learner the rest of the lab. +# +# No typecheck here, deliberately. Declaring the two Operations makes +# the contract complete, and TypeScript then requires matching handlers that do +# not exist until challenge 03 — so `tsc` FAILS at the end of this challenge by +# design. See the assignment. +set -euxo pipefail + +cp /root/workshop/solution/src/shared/nexus-service.ts \ + /root/workshop/exercise/src/shared/nexus-service.ts diff --git a/typescript/instruqt/03-the-compliance-side/assignment.md b/typescript/instruqt/03-the-compliance-side/assignment.md new file mode 100644 index 0000000..396fbae --- /dev/null +++ b/typescript/instruqt/03-the-compliance-side/assignment.md @@ -0,0 +1,219 @@ +--- +slug: the-compliance-side +id: lwb2jpje3u96 +type: challenge +title: 3. The Compliance Side +teaser: Implement both handlers, register them on a Worker, and create the Endpoint. +notes: +- type: text + contents: |- + # A compliance check can wait on a human. How do you put that behind an RPC? + + Some checks clear in milliseconds. A $12,000 international transfer waits for + an officer to click approve, and that officer is at lunch. + + A synchronous handler has ten seconds. Lunch takes longer. +- type: text + contents: |- + # This is the hardest challenge in the lab + + Two handlers, two different shapes, plus the Worker registration and the + Endpoint. + + The finished code sits in solution/ in the same file tree. Use it if you + stall. Learning the shape beats staring at a blank function. +tabs: +- id: gok4feicejlo + title: Exercise + type: service + hostname: workshop + path: /?folder=/root/workshop + port: 8080 +- id: rhie9uzgyhfq + title: Temporal UI + type: service + hostname: workshop + path: / + port: 8233 +- id: hcqbwck0rvo9 + title: Terminal + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: jjlm3enbxo4t + title: Payments Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: xy2tk6mpbv0t + title: Compliance Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: ujvrlheqjxnv + title: Monolith Architecture + type: service + hostname: workshop + path: /monolith-architecture.html + port: 8090 +difficulty: intermediate +timelimit: 1800 +enhanced_loading: null +--- + +# Everything Compliance Owns + +Three pieces, all on this team's side of the boundary. + +1. The handlers that answer Nexus calls. +2. The Worker that runs them. +3. The Endpoint that tells Temporal where to route. + +# Read the Worked Example First + +Click the [button label="Exercise" background="#444CE7"](tab-0) tab and open +`exercise/src/compliance/nexus-handler.ts`. + +`checkCompliance` is already written. Read it before you write anything, because the +Operation you are about to write is the other half of the same pattern, and the contrast +between the two is the whole point of this challenge. + +| Operation | Shape | Why | +|---|---|---| +| `checkCompliance` *(given)* | `new temporalNexus.WorkflowRunOperationHandler(...)` | Slow. Starts a Workflow and returns a reference to it. | +| `submitReview` *(yours)* | a plain `async (ctx, input) => {...}` | Fast. Answers inside the call, in milliseconds. | + +Notice why the given one has to be that shape. Written as a plain async function it would +be a synchronous Operation: cut off at the ten second handler deadline, and every retry +would start a **second** compliance check for the same payment. + +# Write the Other Handler + +Follow TODO 2 in the same file. + +`submitReview` is the fast path, so it is the plain async function. It gets a Client, +finds the Workflow the example started, and hands it the decision. Slow work starts a +Workflow and returns a reference; fast work answers in the call. + +When you have written it, the `TS2345` error from challenge 2 disappears. + +# Register the Handler + +Open `exercise/src/compliance/worker.ts` and follow TODO 3. + +The Worker already knows its Workflows and Activities. One option is missing: the one +that makes this team callable by Payments at all. + +# Typecheck + +Click the [button label="Terminal" background="#444CE7"](tab-2) tab: + +```bash,run +npx tsc --noEmit +``` + +Silence means it passed. The Payments side still calls the Activity proxy, which is +perfectly valid — you change that in challenge 4. + +### If you see an error about `_ctx` + +```bash,nocopy +error TS7006: Parameter '_ctx' implicitly has an 'any' type. +``` + +That error points at `nexus-handler.ts`, but the fix is in `exercise/src/shared/nexus-service.ts`. + +You never annotate a handler's parameter types — they are inferred **from the contract**. +So if TODO 1 from challenge 2 is unfinished, the Service has no Operations, there is +nothing to infer from, and the parameter falls back to `any`, which `strict` rejects. + +Go back and declare the two Operations. Then this file compiles unchanged. + +It is worth noticing what just happened: the contract is not documentation sitting beside +the code. Delete it and the handlers stop type checking. + +# Create the Endpoint + +The Endpoint is the routing rule: a name, a target Namespace, and a target Task Queue. +It is the one piece of Nexus that lives outside your code. + +```bash,run +temporal operator nexus endpoint create --name compliance-endpoint --target-namespace compliance-namespace --target-task-queue compliance-risk +``` + +`--target-task-queue` must match `COMPLIANCE_TASK_QUEUE` in `exercise/src/shared/types.ts` exactly. +If it points at a queue no Worker is polling, the calls are never answered. + +# Start the Worker + +Click the [button label="Compliance Worker" background="#444CE7"](tab-4) tab: + +```bash,run +npm run compliance-worker +``` + +```bash,nocopy +========================================================= + Compliance Worker started on: compliance-risk + Namespace: compliance-namespace +========================================================= +[INFO] Worker state changed { state: 'RUNNING' } +``` + +The banner only tells you the process is alive. It prints whether or not you registered +anything, so it is not proof. Two things are. + +**One: a line that should NOT be there.** Scroll the Worker output and look for this: + +```bash,nocopy +[INFO] No Nexus services registered, not polling for Nexus tasks +``` + +If you see it, your handler is not registered even though the Worker started happily. +When `nexusServices` is set, that line does not appear at all. Absence is the signal here, +which is exactly the kind of thing that is easy to miss — so look for it deliberately. + +**Two: the Temporal UI.** Click the +[button label="Temporal UI" background="#444CE7"](tab-1) tab, switch the Namespace +selector to `compliance-namespace`, and open **Workers** in the left menu. + +You will see **two** Workers, and only one of them is yours: + +| Task Queue | SDK | Whose | +|---|---|---| +| `compliance-risk` | TypeScript | **yours** — the one you just started | +| `temporal-sys-per-ns-tq` | Go | Temporal's own | + +The Go one is not something you started and not something you can break. Temporal Server +is written in Go and runs an internal Worker in **every** Namespace to handle its own +background work — batch operations, schedules, deletion. It was there before you began +and it will show `Tasks Processed: 0` all day. Anything on `temporal-sys-` is the system +talking to itself. + +Yours is the one on `compliance-risk`, listed as **Running**. That is Compliance running +in its own Namespace for the first time — in challenge 1 there was no Worker of yours +here at all. + +If something went wrong: + +| What you see | What it means | +|---|---| +| `TS2345 ... missing ... checkCompliance` | A handler is missing from the object. | +| `No Nexus services registered` | You skipped `nexusServices` on `Worker.create`. | +| Worker starts, calls never answered | The Task Queue does not match the Endpoint's target. | + +Click **Check** once it is up. Challenge 4 starts both Workers fresh, so you can +stop this one with **Ctrl+C** afterwards. + +# What You Know Now + +- Slow work starts a Workflow and returns. Fast work answers in the call. +- `WorkflowRunOperationHandler` makes a retry re-attach instead of starting a duplicate. +- A Worker starts fine with nothing registered. A clean banner proves nothing. +- The Endpoint maps a name to a Namespace and a Task Queue. +- Compliance now runs in its own Namespace, with its own Worker. + +--- + +**Please share your feedback so we can make better content for you.** The **Feedback** +tab takes a few seconds, and it is the only way we find out which parts of this landed. diff --git a/typescript/instruqt/03-the-compliance-side/check-workshop b/typescript/instruqt/03-the-compliance-side/check-workshop new file mode 100755 index 0000000..dbefe2d --- /dev/null +++ b/typescript/instruqt/03-the-compliance-side/check-workshop @@ -0,0 +1,15 @@ +#!/bin/bash +# ABOUTME: Instant pass. This is a live instructor-led workshop, so the +# instructor is the feedback loop and progression is never gated on a script. +# +# Two reasons this is deliberate rather than lazy: +# 1. Skip runs solve-workshop, which copies solution files over the learner's +# work. Gating progression pushes stuck learners toward destroying their +# own code to move on. +# 2. Check-click latency costs real minutes across a room of 20 in a 40 minute +# lab block. +# +# Challenge 04 is the exception: a mistyped Nexus Endpoint hangs silently, so +# that one keeps a diagnostic. Everything the removed checks used to explain now +# lives in the assignment bodies. +exit 0 diff --git a/typescript/instruqt/03-the-compliance-side/cleanup-workshop b/typescript/instruqt/03-the-compliance-side/cleanup-workshop new file mode 100755 index 0000000..e745fd3 --- /dev/null +++ b/typescript/instruqt/03-the-compliance-side/cleanup-workshop @@ -0,0 +1,10 @@ +#!/bin/bash +# ABOUTME: Challenge 03 cleanup. Clean slate. Challenge 04 starts both Workers +# itself rather than depending on a process surviving the transition. +set -euo pipefail + +pkill -f "compliance/worker.ts" || true +pkill -f "payments/worker.ts" || true +pkill -f "npm run compliance-worker" || true +pkill -f "npm run payments-worker" || true +sleep 1 diff --git a/typescript/instruqt/03-the-compliance-side/setup-workshop b/typescript/instruqt/03-the-compliance-side/setup-workshop new file mode 100755 index 0000000..647024c --- /dev/null +++ b/typescript/instruqt/03-the-compliance-side/setup-workshop @@ -0,0 +1,7 @@ +#!/bin/bash +# ABOUTME: Challenge 03 setup. Clears Workers from earlier challenges. +set -euxo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true +true diff --git a/typescript/instruqt/03-the-compliance-side/solve-workshop b/typescript/instruqt/03-the-compliance-side/solve-workshop new file mode 100755 index 0000000..f8c9517 --- /dev/null +++ b/typescript/instruqt/03-the-compliance-side/solve-workshop @@ -0,0 +1,39 @@ +#!/bin/bash +# ABOUTME: Challenge 03 solve. Copies the Compliance side from the solution and +# creates the Endpoint, because the learner creates it by hand in this challenge. +# Skipping without it would leave challenge 04 unroutable. +set -euxo pipefail + +SRC=/root/workshop/solution/src +DST=/root/workshop/exercise/src + +# The contract too, in case challenge 02 was skipped without solving. +cp "$SRC/shared/nexus-service.ts" "$DST/shared/" +cp "$SRC/compliance/nexus-handler.ts" "$DST/compliance/" +cp "$SRC/compliance/worker.ts" "$DST/compliance/" + +temporal operator nexus endpoint create \ + --name compliance-endpoint \ + --target-namespace compliance-namespace \ + --target-task-queue compliance-risk \ + --address 127.0.0.1:7233 || true + +cd /root/workshop/exercise + +# The Compliance side now typechecks on its own. The Payments side still calls +# the Activity proxy, which is valid, so the whole tree compiles here. +npx tsc --noEmit + +# Prove the wiring boots, then stop it. Challenge 04 starts its own Workers. +pkill -f "compliance/worker.ts" || true +sleep 1 +nohup npm run compliance-worker > /tmp/compliance-worker.log 2>&1 & +for i in $(seq 1 120); do + grep -q "Compliance Worker started" /tmp/compliance-worker.log && break + sleep 1 +done +# The banner alone proves nothing. A Worker with no Nexus Service registered +# logs that it is skipping Nexus polling, so assert that line is ABSENT. +grep -q "Compliance Worker started" /tmp/compliance-worker.log +! grep -q "No Nexus services registered" /tmp/compliance-worker.log +pkill -f "compliance/worker.ts" || true diff --git a/typescript/instruqt/04-the-payments-side/assignment.md b/typescript/instruqt/04-the-payments-side/assignment.md new file mode 100644 index 0000000..5d5df4f --- /dev/null +++ b/typescript/instruqt/04-the-payments-side/assignment.md @@ -0,0 +1,190 @@ +--- +slug: the-payments-side +id: xbklygolip1u +type: challenge +title: 4. The Payments Side +teaser: Swap the Activity proxy for a Nexus client, point it at the Endpoint, and + delete the coupling. +notes: +- type: text + contents: |- + # How much code changes when you cross a team boundary? + + The compliance check is about to leave this process entirely. Different + Namespace, different Task Queue, different deployment, different team. + + Guess how many lines change at the call site. +- type: text + contents: |- + # One deletion is the whole point + + Somewhere in the Payments Worker, one spread registers Compliance code inside + the Payments process. + + Deleting it is the decoupling. Everything else is wiring. +tabs: +- id: jt0nbvtyxv2o + title: Exercise + type: service + hostname: workshop + path: /?folder=/root/workshop + port: 8080 +- id: xrdqvojuovik + title: Temporal UI + type: service + hostname: workshop + path: / + port: 8233 +- id: ak56yyflojys + title: Terminal + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: auuddyhhaflq + title: Payments Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: gmgtqjw8yg6r + title: Compliance Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: slczt2wyuij1 + title: Monolith Architecture + type: service + hostname: workshop + path: /monolith-architecture.html + port: 8090 +difficulty: intermediate +timelimit: 1800 +enhanced_loading: null +--- + +# Swap the Proxy for a Nexus Client + +Click the [button label="Exercise" background="#444CE7"](tab-0) tab, open +`exercise/src/payments/workflows.ts`, and follow TODO 4a, 4b and 4c. Each sits on the +code it changes: the proxy to delete, the call to replace, and the review caller to +write. + +The call site barely changes — same operation name, same input, same result type. What +changes is everything underneath it: the compliance check leaves this process entirely. + +What is worth your attention is the pair of timeouts the two parts use. +`checkCompliance` is **asynchronous** — its budget covers the entire call including +retries, which is what lets it outlive the Compliance Worker going away, as you prove in +challenge 5. `submitReview` is **synchronous** — the handler must answer inside the Nexus +handler deadline. One Service, two Operations, two very different budgets. + +# Where the Endpoint Name Lives + +Look at what you just wrote, in both places. The Workflow names two things: a **contract** +and an **Endpoint**. It does not name a Namespace, a Task Queue, a hostname, or a port. + +That is the boundary. An Endpoint is a name the Registry resolves into an address. Move +Compliance to a different Namespace or a different Task Queue tomorrow, update the +Endpoint, and not one line of this Workflow changes. + +The name itself is a single constant, `COMPLIANCE_ENDPOINT` in +`exercise/src/shared/types.ts`, so there is exactly one place to edit if it is renamed. + +# Delete the Coupling + +Open `exercise/src/payments/worker.ts` and follow TODO 5. + +That deletion is the decoupling. Everything else was wiring: once it is gone the Payments +Worker has no way to run the Compliance team's code, even by accident. + +# Run It Decoupled + +Two Workers now, one per team. Start Compliance first. + +Click the [button label="Compliance Worker" background="#444CE7"](tab-4) tab: + +```bash,run +npm run compliance-worker +``` + +Click the [button label="Payments Worker" background="#444CE7"](tab-3) tab: + +```bash,run +npm run payments-worker +``` + +Its banner now reads `payment activities only — compliance is remote`. And this time +you should see the Nexus line on the Payments side: + +```bash,nocopy +[INFO] No Nexus services registered, not polling for Nexus tasks +``` + +That is correct here. Payments is the **caller**, not the handler. It makes Nexus calls; +it answers none. Only the Compliance Worker should be missing that line. + +Click the [button label="Terminal" background="#444CE7"](tab-2) tab: + +```bash,run +npm run starter +``` + +# Read the Result Carefully + +```bash,nocopy + TXN-A Result: COMPLETED Risk: LOW + TXN-B Result: STILL RUNNING MEDIUM risk, waiting on a human + TXN-C Result: DECLINED_COMPLIANCE Risk: HIGH +``` + +TXN-B changed. In the monolith it completed instantly, because compliance was a plain +Activity that returned a MEDIUM verdict and moved on. Now the check runs inside a real +Workflow that parks for human review. Same transaction. Same business rules. The +durable boundary made the wait visible. + +TXN-A and TXN-C are also slower than they were in challenge 1: the handler Workflow sleeps +for ten seconds before returning, which is the window you use in challenge 5. If the +starter reports TXN-A as still running, give it a moment and check the UI — it finishes. + +Leave TXN-B parked. Challenge 5 releases it. + +# If It Hangs + +A payment Workflow stuck in `Running` with nothing happening means the Endpoint name +does not match. Check the Payments Worker tab for: + +```bash,nocopy +INVALID_ARGUMENT: BadScheduleNexusOperationAttributes: endpoint "..." not found +``` + +A wrong Endpoint name does not fail the Workflow. The server rejects the command and +the Workflow task retries forever. Silence, not an error. + +```bash,run +temporal operator nexus endpoint list +``` + +# See the Boundary + +Click the [button label="Temporal UI" background="#444CE7"](tab-1) tab. + +In `payments-namespace`, open the newest `paymentProcessingWorkflow` and find +**Nexus Operation Scheduled** and **Nexus Operation Completed** in the Event History. Click +refresh if the list looks stale. + +Now switch the Namespace selector to `compliance-namespace`. Three +`complianceWorkflow` executions are there. That Namespace was empty in challenge 1. + +Click **Check**. + +# What You Know Now + +- Swapping an Activity proxy for a Nexus client is a one call change at the call site. +- In TypeScript the Workflow names both the contract and the Endpoint; the Registry + resolves that name to a Namespace and Task Queue. +- One Service can carry a slow async Operation and a fast sync one, with different budgets. +- The work moved to another Namespace, and the business logic never changed. + +--- + +**Please share your feedback so we can make better content for you.** The **Feedback** +tab takes a few seconds, and it is the only way we find out which parts of this landed. diff --git a/typescript/instruqt/04-the-payments-side/check-workshop b/typescript/instruqt/04-the-payments-side/check-workshop new file mode 100755 index 0000000..6d785aa --- /dev/null +++ b/typescript/instruqt/04-the-payments-side/check-workshop @@ -0,0 +1,54 @@ +#!/bin/bash +# ABOUTME: Challenge 04 diagnostic. The only check in the track that can fail. +# +# It is a safety net, not a gate. It exists for exactly one failure mode: a +# mistyped or missing Nexus Endpoint name. Verified behavior: that does NOT fail +# the Workflow. The server rejects the ScheduleNexusOperation command at +# command-report time with +# +# INVALID_ARGUMENT: BadScheduleNexusOperationAttributes: endpoint "..." not found +# +# which happens outside Workflow code, so no try/catch can see it. The Workflow +# sits Running forever with repeated WORKFLOW_TASK_FAILED events and the starter +# prints nothing useful. Silence, not an error. Without this check that becomes +# an instructor support queue during the tightest part of the lab. +# +# Note the Workflow type is camelCase: the type name is the exported function name. +# +# Anything else passes. If nothing is stuck, there is nothing to report. +set -uo pipefail + +ADDR="127.0.0.1:7233" + +RUNNING=$(temporal workflow list \ + --address "$ADDR" --namespace payments-namespace \ + --query 'WorkflowType="paymentProcessingWorkflow" AND ExecutionStatus="Running"' \ + --output json 2>/dev/null | jq -r '.[].execution.workflowId' 2>/dev/null) + +for WID in $RUNNING; do + ATTEMPT=$(temporal workflow describe --address "$ADDR" --namespace payments-namespace \ + --workflow-id "$WID" --output json 2>/dev/null \ + | jq -r '.pendingWorkflowTask.attempt // 0' 2>/dev/null) + + # A healthy Workflow waiting on a Nexus Operation sits at attempt 1. Repeated + # Workflow task failures mean the command itself is being rejected. + if [ "${ATTEMPT:-0}" -gt 2 ]; then + fail-message "$WID is stuck retrying its Workflow task (attempt $ATTEMPT). + +That is the signature of an Endpoint name that does not match. The Workflow is not +failing, it is retrying a command the server keeps rejecting. + +Check the Payments Worker tab for: + + INVALID_ARGUMENT: BadScheduleNexusOperationAttributes: endpoint \"...\" not found + +Then confirm the COMPLIANCE_ENDPOINT value in src/shared/types.ts matches the +Endpoint you created. In TypeScript the Endpoint is named at the call site, in +src/payments/workflows.ts, not on the Worker: + + temporal operator nexus endpoint list" + exit 1 + fi +done + +exit 0 diff --git a/typescript/instruqt/04-the-payments-side/cleanup-workshop b/typescript/instruqt/04-the-payments-side/cleanup-workshop new file mode 100755 index 0000000..89c2a1f --- /dev/null +++ b/typescript/instruqt/04-the-payments-side/cleanup-workshop @@ -0,0 +1,9 @@ +#!/bin/bash +# ABOUTME: Challenge 04 cleanup. Challenge 05 starts its own Workers. +set -euo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true +pkill -f "npm run payments-worker" || true +pkill -f "npm run compliance-worker" || true +sleep 1 diff --git a/typescript/instruqt/04-the-payments-side/setup-workshop b/typescript/instruqt/04-the-payments-side/setup-workshop new file mode 100755 index 0000000..a693929 --- /dev/null +++ b/typescript/instruqt/04-the-payments-side/setup-workshop @@ -0,0 +1,7 @@ +#!/bin/bash +# ABOUTME: Challenge 04 setup. Clean slate. This challenge starts both Workers. +set -euxo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true +true diff --git a/typescript/instruqt/04-the-payments-side/solve-workshop b/typescript/instruqt/04-the-payments-side/solve-workshop new file mode 100755 index 0000000..011ee32 --- /dev/null +++ b/typescript/instruqt/04-the-payments-side/solve-workshop @@ -0,0 +1,31 @@ +#!/bin/bash +# ABOUTME: Challenge 04 solve. Applies the whole solution, starts both Workers, +# and runs the starter so challenge 05 inherits a parked TXN-B. +set -euxo pipefail + +rm -rf /root/workshop/exercise/src +cp -R /root/workshop/solution/src /root/workshop/exercise/src + +temporal operator nexus endpoint create \ + --name compliance-endpoint \ + --target-namespace compliance-namespace \ + --target-task-queue compliance-risk \ + --address 127.0.0.1:7233 || true + +cd /root/workshop/exercise +npx tsc --noEmit + +pkill -f "compliance/worker.ts" || true +pkill -f "payments/worker.ts" || true +sleep 1 + +nohup npm run compliance-worker > /tmp/compliance-worker.log 2>&1 & +nohup npm run payments-worker > /tmp/payments-worker.log 2>&1 & +for i in $(seq 1 120); do + grep -q "Compliance Worker started" /tmp/compliance-worker.log 2>/dev/null \ + && grep -q "Payments Worker started" /tmp/payments-worker.log 2>/dev/null && break + sleep 1 +done + +npm run starter > /tmp/starter.log 2>&1 || true +cat /tmp/starter.log diff --git a/typescript/instruqt/05-durability-and-human-review/assignment.md b/typescript/instruqt/05-durability-and-human-review/assignment.md new file mode 100644 index 0000000..f0253f9 --- /dev/null +++ b/typescript/instruqt/05-durability-and-human-review/assignment.md @@ -0,0 +1,180 @@ +--- +slug: durability-and-human-review +id: hcijwjx63wn2 +type: challenge +title: 5. Break It, Then Finish It +teaser: Take the Compliance Worker down mid-payment. Watch the payment wait instead + of fail. +notes: +- type: text + contents: |- + # The Compliance team is deploying. What happens to payments in flight? + + You just moved compliance into another team's process. That team ships on + Fridays. + + An HTTP call would return a connection error and you would be writing + retry logic. This is not an HTTP call. +- type: text + contents: |- + # Ziggy the tardigrade survives being frozen, boiled, and shot into space + + It parks its metabolism and picks up where it left off. + + Your payment Workflow is about to do the same thing. +tabs: +- id: uxvwrjemsoea + title: Exercise + type: service + hostname: workshop + path: /?folder=/root/workshop + port: 8080 +- id: kirogybfummw + title: Temporal UI + type: service + hostname: workshop + path: / + port: 8233 +- id: gsmserb7horg + title: Terminal + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: fxetrm2uuagt + title: Payments Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: xcwthyvjsofb + title: Compliance Worker + type: terminal + hostname: workshop + workdir: /root/workshop/exercise +- id: dwrcskq7wzji + title: Monolith Architecture + type: service + hostname: workshop + path: /monolith-architecture.html + port: 8090 +difficulty: basic +timelimit: 900 +enhanced_loading: null +--- + +# Start Both Teams + +Click the [button label="Compliance Worker" background="#444CE7"](tab-4) tab: + +```bash,run +npm run compliance-worker +``` + +Click the [button label="Payments Worker" background="#444CE7"](tab-3) tab: + +```bash,run +npm run payments-worker +``` + +# Take Compliance Down + +Go back to the [button label="Compliance Worker" background="#444CE7"](tab-4) tab and +press **Ctrl+C**. + +Compliance is now offline. Payments has no idea. + +# Send Payments Anyway + +Click the [button label="Terminal" background="#444CE7"](tab-2) tab: + +```bash,run +npm run starter +``` + +Nothing resolves. The starter reports every transaction as still running and exits after +25 seconds. + +# Look at What Did Not Happen + +Click the [button label="Temporal UI" background="#444CE7"](tab-1) tab. In +`payments-namespace`, click the newest payment Workflow. + +Status is **Running**, not Failed. The Event History shows **Nexus Operation Scheduled** +with no completion. The Operation is waiting for a handler that does not exist yet. + +No connection error. No retry loop you had to write. The caller's +`scheduleToCloseTimeout` of ten minutes is the entire outage budget, and you set it in +one line back in challenge 4. + +# Bring Compliance Back + +Click the [button label="Compliance Worker" background="#444CE7"](tab-4) tab: + +```bash,run +npm run compliance-worker +``` + +Watch the Temporal UI. The pending Operations get picked up and TXN-A completes, +TXN-C is declined for HIGH risk, exactly as if nothing had happened. + +This can take up to a minute. The Operation retries with backoff while the handler +is down, so it does not resume the instant the Worker returns. + +**The payments never failed. They waited.** + +# Release the Parked Payment + +TXN-B is still parked. MEDIUM risk needs a human, and you are the human. + +Wait until TXN-A shows **Completed** before running this. The review is a sync +Operation with a ten second budget, and it fails if `compliance-TXN-B` has not started +yet. Run it again if it does. + +Click the [button label="Terminal" background="#444CE7"](tab-2) tab: + +```bash,run +npm run review-starter +``` + +```bash,nocopy + Review result: APPROVED + Risk level: MEDIUM + Explanation: Approved after manual review +``` + +That call went out over Nexus too, through `submitReview`, the sync handler you wrote +in challenge 3 and the caller you wrote in challenge 4. Sync because it talks to a +Workflow already running and returns immediately. The async Operation starts new work; +the sync one sends a message to work that is already running. + +Open `payment-TXN-B` in the UI one more time. It is **Completed**, with a confirmation +number, and the explanation is the one you just typed in. + +Click **Check**. + +# What You Built + +You started with one Worker running both teams' code and ended with two services that +deploy independently. + +| Piece | What it did | +|---|---| +| `nexus.service()` / `nexus.operation()` | The contract both teams compile against | +| `nexus.serviceHandler()` | The handler only Compliance owns | +| `WorkflowRunOperationHandler` | Backed a long check with a Workflow, retry safe | +| a plain `async` handler | Steered a running Workflow through the boundary | +| `wf.createNexusServiceClient()` | Replaced the Activity proxy at the call site | +| `nexusServices: [...]` on the Worker | Made Compliance answerable at all | +| Nexus Endpoint | The routing rule, the only piece outside the code | + +The business logic never changed. The call site barely changed. Compliance moved to +its own Namespace, its own Task Queue, and its own deployment schedule, and Payments +kept working through an outage while it happened. + +Everything you just used is generally available: Nexus went GA in the TypeScript SDK in +v1.23.0, for calling Operations from Workflows and for Workflow-backed Operation +handlers. You can take this shape to production as it stands. + +--- + +**Please share your feedback so we can make better content for you.** The **Feedback** +tab takes a few seconds, and it is the only way we find out which parts of this landed. diff --git a/typescript/instruqt/05-durability-and-human-review/check-workshop b/typescript/instruqt/05-durability-and-human-review/check-workshop new file mode 100755 index 0000000..dbefe2d --- /dev/null +++ b/typescript/instruqt/05-durability-and-human-review/check-workshop @@ -0,0 +1,15 @@ +#!/bin/bash +# ABOUTME: Instant pass. This is a live instructor-led workshop, so the +# instructor is the feedback loop and progression is never gated on a script. +# +# Two reasons this is deliberate rather than lazy: +# 1. Skip runs solve-workshop, which copies solution files over the learner's +# work. Gating progression pushes stuck learners toward destroying their +# own code to move on. +# 2. Check-click latency costs real minutes across a room of 20 in a 40 minute +# lab block. +# +# Challenge 04 is the exception: a mistyped Nexus Endpoint hangs silently, so +# that one keeps a diagnostic. Everything the removed checks used to explain now +# lives in the assignment bodies. +exit 0 diff --git a/typescript/instruqt/05-durability-and-human-review/cleanup-workshop b/typescript/instruqt/05-durability-and-human-review/cleanup-workshop new file mode 100755 index 0000000..5021e34 --- /dev/null +++ b/typescript/instruqt/05-durability-and-human-review/cleanup-workshop @@ -0,0 +1,9 @@ +#!/bin/bash +# ABOUTME: Challenge 05 cleanup. One pkill per pattern. +set -euo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true +pkill -f "npm run payments-worker" || true +pkill -f "npm run compliance-worker" || true +sleep 1 diff --git a/typescript/instruqt/05-durability-and-human-review/setup-workshop b/typescript/instruqt/05-durability-and-human-review/setup-workshop new file mode 100755 index 0000000..c617167 --- /dev/null +++ b/typescript/instruqt/05-durability-and-human-review/setup-workshop @@ -0,0 +1,18 @@ +#!/bin/bash +# ABOUTME: Challenge 05 setup. Clean slate so the outage demo is deterministic. +# +# Terminates leftovers from challenge 04, including the parked TXN-B. Without +# this, re-running the starter collides with the still-Running payment-TXN-B and +# the whole demo fails on "Workflow execution already started". +set -euxo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true + +ADDR=127.0.0.1:7233 +temporal workflow terminate --address "$ADDR" --namespace payments-namespace \ + --query 'ExecutionStatus="Running"' --reason "challenge 05 reset" --yes >/dev/null 2>&1 || true +temporal workflow terminate --address "$ADDR" --namespace compliance-namespace \ + --query 'ExecutionStatus="Running"' --reason "challenge 05 reset" --yes >/dev/null 2>&1 || true +sleep 2 +true diff --git a/typescript/instruqt/05-durability-and-human-review/solve-workshop b/typescript/instruqt/05-durability-and-human-review/solve-workshop new file mode 100755 index 0000000..5dc3e86 --- /dev/null +++ b/typescript/instruqt/05-durability-and-human-review/solve-workshop @@ -0,0 +1,61 @@ +#!/bin/bash +# ABOUTME: Challenge 05 solve. Runs the outage demo end to end: start payments, +# leave compliance down, send payments, bring compliance up, then review. +set -euxo pipefail + +rm -rf /root/workshop/exercise/src +cp -R /root/workshop/solution/src /root/workshop/exercise/src + +temporal operator nexus endpoint create \ + --name compliance-endpoint \ + --target-namespace compliance-namespace \ + --target-task-queue compliance-risk \ + --address 127.0.0.1:7233 || true + +cd /root/workshop/exercise +npx tsc --noEmit + +pkill -f "compliance/worker.ts" || true +pkill -f "payments/worker.ts" || true +sleep 1 + +# Payments up, Compliance deliberately down. +nohup npm run payments-worker > /tmp/payments-worker.log 2>&1 & +for i in $(seq 1 120); do + grep -q "Payments Worker started" /tmp/payments-worker.log 2>/dev/null && break + sleep 1 +done + +# Operations get scheduled with no handler polling. The caller waits. +npm run starter > /tmp/starter.log 2>&1 || true + +# Bring Compliance back. The pending Operations get picked up. +nohup npm run compliance-worker > /tmp/compliance-worker.log 2>&1 & +for i in $(seq 1 120); do + grep -q "Compliance Worker started" /tmp/compliance-worker.log 2>/dev/null && break + sleep 1 +done + +# Wait for the handler Workflow to actually exist before reviewing it. +# +# A fixed sleep is not enough. After an outage the Nexus Operation retries with +# backoff, so compliance-TXN-B can appear well after the Worker is back. Reviewing +# too early hits the sync Operation's 10s scheduleToCloseTimeout and fails. +ADDR=127.0.0.1:7233 +for i in $(seq 1 120); do + STATE=$(temporal workflow describe --address "$ADDR" --namespace compliance-namespace \ + --workflow-id compliance-TXN-B --output json 2>/dev/null \ + | jq -r '.workflowExecutionInfo.status // empty' 2>/dev/null) + [ "$STATE" = "WORKFLOW_EXECUTION_STATUS_RUNNING" ] && break + sleep 1 +done + +# The review Update is rejected until the automated check has produced a MEDIUM +# verdict, so retry rather than assuming the first attempt lands. +for attempt in 1 2 3 4 5; do + if npm run review-starter > /tmp/review.log 2>&1; then + break + fi + sleep 5 +done +cat /tmp/review.log diff --git a/typescript/instruqt/config.yml b/typescript/instruqt/config.yml new file mode 100644 index 0000000..32347c8 --- /dev/null +++ b/typescript/instruqt/config.yml @@ -0,0 +1,17 @@ +version: "3" +containers: +- name: workshop + # Pinned by digest, not :latest. A moving tag let a sandbox boot a cached older + # image that had no code-server in it, so the editor tabs showed "Please wait" + # forever. Re-pin this after every image rebuild with: + # docker buildx imagetools inspect ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest --format "{{.Manifest.Digest}}" + image: ghcr.io/nadvolod/edu-nexus-typescript-sandbox@sha256:79ea7e304f7374b5c0db5bc574555294f75778a7a0a895511e5581b1822951d0 + shell: /bin/bash + # Sized for two Node Workers, a Temporal dev server, and one code-server. + memory: 4096 + # Every port a challenge tab points at MUST be listed here. Instruqt's proxy + # cannot reach an undeclared port, and the tab shows "Please wait" forever. + ports: + - 8233 # Temporal UI + - 8080 # code-server, both editor tabs + - 8090 # architecture diagram (node serve-diagrams.mjs) diff --git a/typescript/instruqt/track.yml b/typescript/instruqt/track.yml new file mode 100644 index 0000000..a4f2875 --- /dev/null +++ b/typescript/instruqt/track.yml @@ -0,0 +1,55 @@ +slug: nexus-typescript-decouple-monolith +id: fxx1wuc1irwu +title: Decouple a Monolith with Temporal Nexus (TypeScript) +teaser: Split a payments monolith into two independently deployable services connected + by Nexus. +description: |- + Payments and Compliance share one Worker, one Namespace, one blast radius. A bug + in compliance code at 3 AM takes payments down with it. + + You will put a Nexus boundary between them without changing a line of business + logic. Define the shared Service contract, implement the handler on the Compliance + side, register the Endpoint, then swap the caller from an Activity proxy to a Nexus + client. One call changes at the call site. The architecture underneath does not. + + Then break it on purpose. Stop the Compliance Worker mid-operation and watch the + payment Workflow wait instead of fail. + + TypeScript on the Temporal TypeScript SDK, where Nexus went generally available in + v1.23.0. Five challenges, three of them hands-on. +icon: "" +tags: +- nexus +- temporal +- node +- typescript +owner: temporal +developers: +- nikolay.advolodkin@temporal.io +skipping_enabled: true +idle_timeout: 3600 +timelimit: 7200 +lab_config: + extend_ttl: 900 + sidebar_enabled: true + feedback_recap_enabled: true + feedback_tab_enabled: true + loadingMessages: + - Waking Ziggy the tardigrade... + - Starting the Temporal dev server... + - Creating two Namespaces, one per team... + - Warming the npm cache so your first run is not your slowest... + - Typechecking TypeScript against the Temporal SDK... + - Tardigrades survive vacuum. Your Workflows survive Worker crashes... + - Checking that nobody is polling the Compliance Task Queue yet... + - Opening the code editor... + - Loading the Temporal Web UI... + - Ziggy found some lichen. Almost ready... + theme: + name: modern-dark + override_challenge_layout: true + hideStopButton: false + default_layout: AssignmentLeft + default_layout_sidebar_size: 33 +checksum: "2211482467216494180" +enhanced_loading: false diff --git a/typescript/instruqt/track_scripts/cleanup-workshop b/typescript/instruqt/track_scripts/cleanup-workshop new file mode 100755 index 0000000..5cb845d --- /dev/null +++ b/typescript/instruqt/track_scripts/cleanup-workshop @@ -0,0 +1,16 @@ +#!/bin/bash +# ABOUTME: Track-level cleanup. Runs once when the track is torn down. +# One pkill per pattern. Alternation does not work on macOS and the scripts +# are authored there. +# +# Two patterns per process: `npm run x` leaves an npm parent as well as the node +# child that actually holds the port, and killing only one leaves the other. +set -euo pipefail + +pkill -f "payments/worker.ts" || true +pkill -f "compliance/worker.ts" || true +pkill -f "payments/starter.ts" || true +pkill -f "payments/review-starter.ts" || true +pkill -f "npm run payments-worker" || true +pkill -f "npm run compliance-worker" || true +pkill -f "temporal server" || true diff --git a/typescript/instruqt/track_scripts/setup-workshop b/typescript/instruqt/track_scripts/setup-workshop new file mode 100755 index 0000000..0e7f517 --- /dev/null +++ b/typescript/instruqt/track_scripts/setup-workshop @@ -0,0 +1,154 @@ +#!/bin/bash +# ABOUTME: Track-level setup. Runs once when the attendee enters the track. +# Starts the editors, stages the workshop source, starts the dev server, and +# creates both Namespaces. +# +# Ordering matters. The editors start BEFORE any Temporal work and are never +# gated on it. An earlier version started them after the dev server health +# check, which had an `exit 1` on timeout: a slow Temporal start took the +# Exercise and Solution tabs down with it and the attendee saw "Please wait". +# The editors have nothing to do with Temporal, so they no longer wait for it. +# +# The Nexus Endpoint is deliberately NOT created here. Attendees create it +# themselves in challenge 03, which is where the Registry stops being abstract. +set -uo pipefail + +# 1. Wait for Instruqt host bootstrap. +until [ -f /opt/instruqt/bootstrap/host-bootstrap-completed ]; do + sleep 1 +done + +# 2. Stage the writable working tree. /opt/workshop stays read-only by convention. +# +# Source only. node_modules is 292 MB per tree, so copying both would move +# ~600 MB before the attendee can type anything. It is baked into the image and +# never edited, so the writable tree symlinks to it instead. ts-node and tsc +# resolve through the symlink normally. +# +# Consequence: `npm install` inside the lab cannot write to node_modules. The +# workshop never needs it. +if [ ! -d /root/workshop ]; then + for t in exercise solution; do + mkdir -p "/root/workshop/$t" + cp -r "/opt/workshop/$t/src" "/root/workshop/$t/src" + cp "/opt/workshop/$t/package.json" \ + "/opt/workshop/$t/package-lock.json" \ + "/opt/workshop/$t/tsconfig.json" "/root/workshop/$t/" + ln -s "/opt/workshop/$t/node_modules" "/root/workshop/$t/node_modules" + done +fi + +# 3. Start ONE code-server, rooted at the whole workshop tree. +# +# One instance, not two. Both editor tabs point at this single server and select +# their directory with ?folder=, which is exactly the shape the one Temporal track +# known to run code-server successfully (temporal-ai-agents-python-v4) uses. +# +# Why it matters beyond tidiness: Instruqt loads every service tab's iframe at +# challenge start, including tabs that are not visible. code-server cannot compute +# its layout in a 0x0 iframe and the workbench never finishes booting, which is +# the "blank until you refresh" symptom (console shows "Unable to figure out +# browser width and height"). Two instances meant two hidden iframes racing that +# boot at once. One halves it, and frees about 200 MB. +# +# Port 8080 on purpose. 8443 is the conventional alternate-HTTPS port and +# Instruqt's proxy could not reach code-server there. Every port a tab points at +# must also be listed under `ports:` in config.yml. +if ! curl -sf -o /dev/null "http://127.0.0.1:8080/" 2>/dev/null; then + nohup code-server \ + --bind-addr "0.0.0.0:8080" \ + --auth none \ + --disable-telemetry \ + --disable-update-check \ + --user-data-dir /root/.cs-exercise \ + /root/workshop > /tmp/code-server-8080.log 2>&1 & + disown || true +fi + +# 3b. Serve the architecture diagram on 8090. +# +# A small Node static server is baked into the image for this. +# Must also be listed under `ports:` in config.yml or the tab cannot reach it. +if ! curl -sf -o /dev/null "http://127.0.0.1:8090/" 2>/dev/null; then + nohup node /opt/serve-diagrams.mjs > /tmp/diagrams.log 2>&1 & + disown || true +fi + +# 4. Start the Temporal dev server in the background. +# --ip 0.0.0.0 so the Instruqt service tab can reach the Web UI on 8233. +if ! curl -sf http://127.0.0.1:8233 >/dev/null 2>&1; then + nohup temporal server start-dev \ + --ip 0.0.0.0 \ + --port 7233 \ + --ui-port 8233 \ + --db-filename /root/temporal.db \ + --log-level warn \ + > /tmp/temporal-server.log 2>&1 & + disown || true +fi + +# 5. Wait for Temporal health. Log loudly on failure, but do NOT exit: killing +# the script here would leave later setup undone for no benefit, and the +# editors are already up. +TEMPORAL_UP=0 +for i in $(seq 1 120); do + if temporal operator cluster health --address 127.0.0.1:7233 >/dev/null 2>&1; then + TEMPORAL_UP=1 + break + fi + sleep 1 +done +if [ "$TEMPORAL_UP" -ne 1 ]; then + echo "==> WARNING: Temporal dev server did not become healthy in 120s." + tail -30 /tmp/temporal-server.log 2>/dev/null +fi + +# 6. Pre-create both Namespaces. One per team, which is the whole point. +temporal operator namespace create --namespace payments-namespace \ + --address 127.0.0.1:7233 >/dev/null 2>&1 || true +temporal operator namespace create --namespace compliance-namespace \ + --address 127.0.0.1:7233 >/dev/null 2>&1 || true + +for i in $(seq 1 30); do + if temporal operator namespace describe --namespace payments-namespace \ + --address 127.0.0.1:7233 >/dev/null 2>&1 \ + && temporal operator namespace describe --namespace compliance-namespace \ + --address 127.0.0.1:7233 >/dev/null 2>&1; then + break + fi + sleep 1 +done + +# 7. Wait for the editor to be genuinely ready. Never fatal. +# +# Diagnosed by Instruqt support. The old check was +# curl -sf http://127.0.0.1:8080/ +# which passes the moment the HTTP listener binds, because bare "/" 302-redirects +# and `curl -sf` treats a 3xx as success. Setup then reported "ready" while the +# workbench was still booting. +# +# Their suggested fix polls /healthz for '"status":"ready"'. That string does not +# exist in code-server 4.96.4: /healthz returns "expired" or "alive", and "alive" +# only means an HTTP request arrived recently, which our own polling would cause. +# Waiting on it would never pass. Verified by measuring the live endpoint. +# +# So we check the two things that ARE true only once the workbench is up: the +# exact URL a tab opens returns 200 (bare "/" still 302s at that point), and +# code-server logs that the extension host is running. +EX_FOLDER=/root/workshop + +code_server_ready() { + [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 \ + "http://127.0.0.1:8080/?folder=${EX_FOLDER}" 2>/dev/null)" = "200" ] \ + && grep -q "Extension host agent started" /tmp/code-server-8080.log 2>/dev/null +} + +for i in $(seq 1 90); do + code_server_ready && break + sleep 1 +done + +code_server_ready \ + || { echo "==> WARNING: code-server 8080 not ready."; tail -20 /tmp/code-server-8080.log 2>/dev/null; } + +echo "==> Track setup complete." diff --git a/typescript/sandbox/Dockerfile b/typescript/sandbox/Dockerfile new file mode 100644 index 0000000..3c6ac0a --- /dev/null +++ b/typescript/sandbox/Dockerfile @@ -0,0 +1,110 @@ +# Sandbox image for the TypeScript Nexus workshop. +# Build context is typescript/ so this can COPY decouple-monolith/. +# +# docker buildx build --platform linux/amd64 \ +# -f sandbox/Dockerfile -t ghcr.io/nadvolod/edu-nexus-typescript-sandbox:latest --push . +# +# linux/amd64 is required. An arm64 image (the Apple Silicon default) fails to +# start on Instruqt with an empty log and no error. + +FROM node:22-bookworm + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl wget git vim nano less unzip ca-certificates bash-completion jq procps \ + && rm -rf /var/lib/apt/lists/* + +# --- Temporal CLI ------------------------------------------------------------- +ARG TEMPORAL_CLI_VERSION=1.7.2 +RUN curl -sSL \ + "https://github.com/temporalio/cli/releases/download/v${TEMPORAL_CLI_VERSION}/temporal_cli_${TEMPORAL_CLI_VERSION}_linux_amd64.tar.gz" \ + | tar -xz -C /usr/local/bin temporal \ + && chmod +x /usr/local/bin/temporal + +# --- code-server (VS Code in the browser) ------------------------------------- +# The native Instruqt `type: code` tab is "a simple code editor" with no +# documented language support. code-server gives real VS Code. +# +# No language extension is installed, deliberately: TypeScript support is built +# into VS Code. The language service is wanted here — it is what surfaces the +# compile error in challenge 2 as a red squiggle, live, which is the lesson. +ARG CODE_SERVER_VERSION=4.96.4 +RUN curl -fsSL https://code-server.dev/install.sh | sh -s -- --version ${CODE_SERVER_VERSION} + +# Seed settings into BOTH user-data-dirs. This is load-bearing, not cosmetic. +# +# security.workspace.trust.enabled MUST be false. Workspace Trust is on by +# default, and code-server renders a blank page instead of the workbench until +# the folder is trusted. That was the cause of the editor tabs showing white. +# +# Each instance needs its own copy because each runs with its own +# --user-data-dir, which is what stops them sharing a last-opened workspace. +RUN for d in /root/.cs-exercise /root/.cs-solution; do \ + mkdir -p "$d/User" && printf '%s\n' \ + '{' \ + ' "security.workspace.trust.enabled": false,' \ + ' "workbench.colorTheme": "Default Dark+",' \ + ' "workbench.startupEditor": "none",' \ + ' "editor.fontSize": 14,' \ + ' "editor.tabSize": 2,' \ + ' "telemetry.telemetryLevel": "off",' \ + ' "files.autoSave": "afterDelay",' \ + ' "files.autoSaveDelay": 300,' \ + ' "typescript.tsdk": "/opt/workshop/exercise/node_modules/typescript/lib",' \ + ' "typescript.disableAutomaticTypeAcquisition": true,' \ + ' "typescript.inlayHints.parameterNames.enabled": "literals",' \ + ' "typescript.inlayHints.functionLikeReturnTypes.enabled": true,' \ + ' "typescript.inlayHints.propertyDeclarationTypes.enabled": true,' \ + ' "typescript.preferences.importModuleSpecifier": "relative",' \ + ' "javascript.suggestionActions.enabled": false,' \ + ' "editor.stickyScroll.enabled": true,' \ + ' "editor.minimap.enabled": false,' \ + ' "editor.inlayHints.fontSize": 11,' \ + ' "workbench.editor.enablePreview": false,' \ + ' "explorer.compactFolders": false,' \ + ' "files.exclude": { "**/node_modules": true, "**/package-lock.json": true },' \ + ' "errorLens.enabledDiagnosticLevels": ["error", "warning"],' \ + ' "errorLens.messageMaxChars": 0' \ + '}' > "$d/User/settings.json"; \ + done + +# --- Editor extensions --------------------------------------------------------- +# Error Lens renders diagnostics inline at the end of the offending line instead of +# only underlining them. That is not decoration here: challenge 2 ends on a +# deliberate compile error, and the whole lesson is that the contract is enforced +# before anything runs. An error the learner has to hover to read is a lesson +# half-delivered. +# +# code-server resolves extensions from Open VSX, not the VS Code marketplace. +# Installed into the default extensions dir so the runtime invocation needs no +# --extensions-dir flag. +RUN code-server --install-extension usernamehw.errorlens || \ + echo "==> WARNING: Error Lens did not install; diagnostics will still underline, just not inline." + +# --- Architecture diagram ----------------------------------------------------- +# Lives outside the challenge folders on purpose: Instruqt parses every file +# inside a challenge directory as a lifecycle script. +COPY diagrams/ /opt/diagrams/ +COPY sandbox/serve-diagrams.mjs /opt/serve-diagrams.mjs + +# --- Bake the workshop source ------------------------------------------------- +COPY decouple-monolith/ /opt/workshop/ + +# --- Pre-warm npm ------------------------------------------------------------- +# Installs both trees so the first command in the lab is fast, and typechecks +# them so a broken port fails the image build instead of the workshop. +# +# Both trees must typecheck. The exercise's open TODOs are comments and a +# throwing stub, so it compiles cleanly with the work still undone. Keep these +# strict: a failure here is a real defect, not an expected state. +WORKDIR /opt/workshop/solution +RUN npm ci && npx tsc --noEmit + +WORKDIR /opt/workshop/exercise +RUN npm ci && npx tsc --noEmit + +# --- Shell environment -------------------------------------------------------- +RUN echo 'export TEMPORAL_ADDRESS="${TEMPORAL_ADDRESS:-127.0.0.1:7233}"' >> /root/.bashrc \ + && echo 'export WORKSHOP_REPO=/root/workshop' >> /root/.bashrc + +WORKDIR /root +CMD ["/bin/bash"] diff --git a/typescript/sandbox/serve-diagrams.mjs b/typescript/sandbox/serve-diagrams.mjs new file mode 100644 index 0000000..0768c2c --- /dev/null +++ b/typescript/sandbox/serve-diagrams.mjs @@ -0,0 +1,35 @@ +// Minimal static file server for the architecture diagram tab. +// +// The Kotlin image used `jwebserver`, which ships with the JDK. There is no JDK +// here, and adding python3 or an npm package for one static directory is more +// moving parts than this. Node's own http module is enough. +import { createServer } from 'node:http'; +import { readFile } from 'node:fs/promises'; +import { extname, join, normalize } from 'node:path'; + +const ROOT = '/opt/diagrams'; +const PORT = 8090; +const TYPES = { + '.html': 'text/html; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.webp': 'image/webp', +}; + +createServer(async (req, res) => { + // Strip the query string, then normalize before joining so ../ cannot escape ROOT. + const rel = normalize(decodeURIComponent((req.url ?? '/').split('?')[0])).replace(/^(\.\.[/\\])+/, ''); + const path = join(ROOT, rel === '/' ? 'monolith-architecture.html' : rel); + if (!path.startsWith(ROOT)) { + res.writeHead(403).end('forbidden'); + return; + } + try { + const body = await readFile(path); + res.writeHead(200, { 'content-type': TYPES[extname(path)] ?? 'application/octet-stream' }).end(body); + } catch { + res.writeHead(404).end('not found'); + } +}).listen(PORT, '0.0.0.0', () => console.log(`diagrams on ${PORT} from ${ROOT}`));