okengine 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +91 -0
- package/README.md +327 -0
- package/docs/spec/complete-example.md +1188 -0
- package/docs/spec/console.md +739 -0
- package/docs/spec/four-applications.md +1188 -0
- package/docs/spec/unified-theory.md +489 -0
- package/package.json +152 -0
- package/spec/manifest.v1.schema.json +624 -0
- package/src/auth/api-keys.ts +168 -0
- package/src/auth/attenuation.ts +116 -0
- package/src/auth/auth.test.ts +273 -0
- package/src/auth/cross-plane.ts +98 -0
- package/src/auth/index.ts +112 -0
- package/src/auth/invites.ts +66 -0
- package/src/auth/invoke-as.ts +49 -0
- package/src/auth/operator.ts +178 -0
- package/src/auth/planes.ts +102 -0
- package/src/auth/plugin.ts +44 -0
- package/src/auth/roles.ts +84 -0
- package/src/auth/sessions.ts +418 -0
- package/src/auth/tables.ts +131 -0
- package/src/cli/args.ts +50 -0
- package/src/cli/branch.ts +75 -0
- package/src/cli/build.ts +86 -0
- package/src/cli/client-add.test.ts +140 -0
- package/src/cli/client-add.ts +164 -0
- package/src/cli/completion.test.ts +58 -0
- package/src/cli/completion.ts +296 -0
- package/src/cli/dev-app-runner.ts +77 -0
- package/src/cli/dev.test.ts +435 -0
- package/src/cli/dev.ts +594 -0
- package/src/cli/doc-drift.test.ts +146 -0
- package/src/cli/doc-drift.ts +397 -0
- package/src/cli/docker-cli.test.ts +221 -0
- package/src/cli/docker.ts +127 -0
- package/src/cli/doctor-diff-examples.ts +92 -0
- package/src/cli/doctor-diff.test.ts +91 -0
- package/src/cli/doctor-diff.ts +274 -0
- package/src/cli/doctor-pii.test.ts +98 -0
- package/src/cli/doctor-pii.ts +151 -0
- package/src/cli/doctor.test.ts +150 -0
- package/src/cli/doctor.ts +223 -0
- package/src/cli/eval.ts +122 -0
- package/src/cli/exit.ts +19 -0
- package/src/cli/gates-list.test.ts +47 -0
- package/src/cli/gates-list.ts +84 -0
- package/src/cli/images.ts +277 -0
- package/src/cli/index.ts +99 -0
- package/src/cli/json-out.test.ts +129 -0
- package/src/cli/load-config.ts +71 -0
- package/src/cli/mcp-from-console.ts +50 -0
- package/src/cli/privacy-erase.ts +73 -0
- package/src/cli/registry.ts +455 -0
- package/src/cli/safe-defaults.test.ts +92 -0
- package/src/cli/schema.ts +196 -0
- package/src/cli/stack.ts +82 -0
- package/src/cli/start.ts +106 -0
- package/src/cli/upgrade.ts +105 -0
- package/src/cli/vault-cmd.ts +140 -0
- package/src/client/budget-entry.ts +34 -0
- package/src/client/budget.test.ts +17 -0
- package/src/client/create.test.ts +159 -0
- package/src/client/create.ts +152 -0
- package/src/client/errors.ts +60 -0
- package/src/client/index.ts +35 -0
- package/src/client/notes-contract.test.ts +289 -0
- package/src/client/transport.test.ts +123 -0
- package/src/client/transport.ts +254 -0
- package/src/client/types.ts +282 -0
- package/src/compiler/aot.test.ts +248 -0
- package/src/compiler/aot.ts +182 -0
- package/src/compiler/differential.test.ts +241 -0
- package/src/compiler/dynamic.ts +54 -0
- package/src/compiler/effects-infer.ts +541 -0
- package/src/compiler/emit.ts +40 -0
- package/src/compiler/extract.test.ts +177 -0
- package/src/compiler/extract.ts +1368 -0
- package/src/compiler/fixtures/raw/raw-unannotated.ts +28 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +24 -0
- package/src/compiler/fixtures/skyport/src/ai.ts +15 -0
- package/src/compiler/fixtures/skyport/src/app.ts +10 -0
- package/src/compiler/fixtures/skyport/src/channels.ts +6 -0
- package/src/compiler/fixtures/skyport/src/core.ts +3 -0
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +57 -0
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +11 -0
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +14 -0
- package/src/compiler/fixtures/skyport/src/flows/support/index.ts +20 -0
- package/src/compiler/fixtures/skyport/src/gates.ts +14 -0
- package/src/compiler/fixtures/skyport/src/journeys.ts +9 -0
- package/src/compiler/fixtures/skyport/src/schema.ts +3 -0
- package/src/compiler/fixtures/skyport/src/vault.ts +6 -0
- package/src/compiler/fixtures/skyport.expected.json +241 -0
- package/src/compiler/fixtures/triggers/five-triggers.ts +69 -0
- package/src/compiler/http-parse.ts +214 -0
- package/src/compiler/index.ts +67 -0
- package/src/compiler/response.ts +89 -0
- package/src/compiler/sucrose.ts +238 -0
- package/src/config/index.ts +167 -0
- package/src/console/budget.test.ts +73 -0
- package/src/console/index.ts +30 -0
- package/src/console/server/access.test.ts +370 -0
- package/src/console/server/access.ts +777 -0
- package/src/console/server/ai.test.ts +172 -0
- package/src/console/server/ai.ts +632 -0
- package/src/console/server/app.ts +294 -0
- package/src/console/server/auth-rate.test.ts +81 -0
- package/src/console/server/auth-rate.ts +72 -0
- package/src/console/server/bind.ts +25 -0
- package/src/console/server/channels.test.ts +179 -0
- package/src/console/server/channels.ts +469 -0
- package/src/console/server/claim.test.ts +66 -0
- package/src/console/server/claim.ts +134 -0
- package/src/console/server/clock.test.ts +239 -0
- package/src/console/server/clock.ts +390 -0
- package/src/console/server/console.test.ts +338 -0
- package/src/console/server/diff.test.ts +299 -0
- package/src/console/server/diff.ts +352 -0
- package/src/console/server/dry-run.audit.test.ts +118 -0
- package/src/console/server/flows-invoke.test.ts +148 -0
- package/src/console/server/flows.ts +3633 -0
- package/src/console/server/gates.test.ts +360 -0
- package/src/console/server/gates.ts +869 -0
- package/src/console/server/index.ts +141 -0
- package/src/console/server/live.test.ts +30 -0
- package/src/console/server/live.ts +109 -0
- package/src/console/server/plugin.ts +50 -0
- package/src/console/server/plugins.test.ts +131 -0
- package/src/console/server/plugins.ts +429 -0
- package/src/console/server/runs-pii.ts +98 -0
- package/src/console/server/security-headers.ts +97 -0
- package/src/console/server/security.gate.test.ts +286 -0
- package/src/console/server/serve.ts +319 -0
- package/src/console/server/signals.test.ts +88 -0
- package/src/console/server/signals.ts +282 -0
- package/src/console/server/state.ts +982 -0
- package/src/console/server/store.test.ts +233 -0
- package/src/console/server/store.ts +861 -0
- package/src/console/server/structural.ts +95 -0
- package/src/console/server/vault.test.ts +251 -0
- package/src/console/server/vault.ts +430 -0
- package/src/console/ui/access/AccessA11yView.tsx +237 -0
- package/src/console/ui/access/a11y.test.tsx +122 -0
- package/src/console/ui/access/acknowledgement.test.ts +13 -0
- package/src/console/ui/access/acknowledgement.ts +35 -0
- package/src/console/ui/access/blast-radius.test.ts +26 -0
- package/src/console/ui/access/blast-radius.ts +40 -0
- package/src/console/ui/access/confirmation.test.ts +38 -0
- package/src/console/ui/access/confirmation.ts +36 -0
- package/src/console/ui/access/fixture.ts +203 -0
- package/src/console/ui/access/grantable.test.ts +17 -0
- package/src/console/ui/access/grantable.ts +36 -0
- package/src/console/ui/access/hygiene.test.ts +12 -0
- package/src/console/ui/access/hygiene.ts +39 -0
- package/src/console/ui/access/index.ts +54 -0
- package/src/console/ui/access/provenance.test.ts +12 -0
- package/src/console/ui/access/provenance.ts +29 -0
- package/src/console/ui/access/search.test.ts +17 -0
- package/src/console/ui/access/search.ts +72 -0
- package/src/console/ui/access/types.ts +111 -0
- package/src/console/ui/ai/AiA11yView.tsx +265 -0
- package/src/console/ui/ai/a11y.test.tsx +99 -0
- package/src/console/ui/ai/findings.ts +41 -0
- package/src/console/ui/ai/fixture.ts +182 -0
- package/src/console/ui/ai/format.ts +90 -0
- package/src/console/ui/ai/group.test.ts +37 -0
- package/src/console/ui/ai/group.ts +88 -0
- package/src/console/ui/ai/index.ts +66 -0
- package/src/console/ui/ai/promotion.test.ts +77 -0
- package/src/console/ui/ai/promotion.ts +150 -0
- package/src/console/ui/ai/search.test.ts +40 -0
- package/src/console/ui/ai/search.ts +89 -0
- package/src/console/ui/ai/types.ts +139 -0
- package/src/console/ui/architecture/ArchitectureA11yView.tsx +163 -0
- package/src/console/ui/architecture/a11y.test.tsx +97 -0
- package/src/console/ui/architecture/boundary.ts +42 -0
- package/src/console/ui/architecture/declared.ts +165 -0
- package/src/console/ui/architecture/fixture.ts +74 -0
- package/src/console/ui/architecture/index.ts +84 -0
- package/src/console/ui/architecture/layers.ts +64 -0
- package/src/console/ui/architecture/layout.ts +68 -0
- package/src/console/ui/architecture/pathologies.ts +260 -0
- package/src/console/ui/architecture/search.ts +145 -0
- package/src/console/ui/architecture/traffic.ts +102 -0
- package/src/console/ui/architecture/types.ts +144 -0
- package/src/console/ui/architecture/view.test.ts +165 -0
- package/src/console/ui/architecture/view.ts +516 -0
- package/src/console/ui/channels/ChannelsA11yView.tsx +180 -0
- package/src/console/ui/channels/a11y.test.tsx +96 -0
- package/src/console/ui/channels/confirmation.test.ts +37 -0
- package/src/console/ui/channels/confirmation.ts +27 -0
- package/src/console/ui/channels/fallback.test.ts +15 -0
- package/src/console/ui/channels/fallback.ts +20 -0
- package/src/console/ui/channels/findings.ts +38 -0
- package/src/console/ui/channels/fixture.ts +134 -0
- package/src/console/ui/channels/group.ts +25 -0
- package/src/console/ui/channels/index.ts +57 -0
- package/src/console/ui/channels/locale.test.ts +17 -0
- package/src/console/ui/channels/locale.ts +31 -0
- package/src/console/ui/channels/mask.test.ts +14 -0
- package/src/console/ui/channels/mask.ts +12 -0
- package/src/console/ui/channels/search.ts +58 -0
- package/src/console/ui/channels/taxonomy.test.ts +17 -0
- package/src/console/ui/channels/taxonomy.ts +49 -0
- package/src/console/ui/channels/types.ts +117 -0
- package/src/console/ui/clock/ClockA11yView.tsx +182 -0
- package/src/console/ui/clock/a11y.test.tsx +99 -0
- package/src/console/ui/clock/confirmation.test.ts +32 -0
- package/src/console/ui/clock/confirmation.ts +34 -0
- package/src/console/ui/clock/findings.ts +40 -0
- package/src/console/ui/clock/fixture.ts +111 -0
- package/src/console/ui/clock/health.test.ts +25 -0
- package/src/console/ui/clock/health.ts +61 -0
- package/src/console/ui/clock/index.ts +49 -0
- package/src/console/ui/clock/search.test.ts +39 -0
- package/src/console/ui/clock/search.ts +76 -0
- package/src/console/ui/clock/timeline.test.ts +44 -0
- package/src/console/ui/clock/timeline.ts +41 -0
- package/src/console/ui/clock/types.ts +78 -0
- package/src/console/ui/clock/waiting-on.test.ts +34 -0
- package/src/console/ui/clock/waiting-on.ts +83 -0
- package/src/console/ui/confirmation.unify.test.ts +92 -0
- package/src/console/ui/diff/DiffA11yView.tsx +85 -0
- package/src/console/ui/diff/a11y.test.tsx +99 -0
- package/src/console/ui/diff/fixture.ts +85 -0
- package/src/console/ui/diff/group.test.ts +58 -0
- package/src/console/ui/diff/group.ts +97 -0
- package/src/console/ui/diff/index.ts +38 -0
- package/src/console/ui/diff/search.test.ts +32 -0
- package/src/console/ui/diff/search.ts +75 -0
- package/src/console/ui/diff/summary.ts +74 -0
- package/src/console/ui/diff/types.ts +48 -0
- package/src/console/ui/dist/assets/index-B71Yl_SS.js +10 -0
- package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +64 -0
- package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +1 -0
- package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +1 -0
- package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +1 -0
- package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +1 -0
- package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +1 -0
- package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +45 -0
- package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +1 -0
- package/src/console/ui/dist/assets/panel-overview-Bd48d9km.js +1 -0
- package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-BwsWqKeB.js +1 -0
- package/src/console/ui/dist/assets/panel-signals-9najbZY2.js +1 -0
- package/src/console/ui/dist/assets/panel-store-OHkP2pDp.js +1 -0
- package/src/console/ui/dist/assets/panel-traces-tn2JoY8U.js +1 -0
- package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +1 -0
- package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
- package/src/console/ui/dist/assets/style-Cnl7WLya.css +3 -0
- package/src/console/ui/dist/index.html +14 -0
- package/src/console/ui/flows/FlowsA11yView.tsx +155 -0
- package/src/console/ui/flows/a11y.test.tsx +105 -0
- package/src/console/ui/flows/buffer.test.ts +91 -0
- package/src/console/ui/flows/buffer.ts +118 -0
- package/src/console/ui/flows/confirmation.ts +131 -0
- package/src/console/ui/flows/contract.test.ts +89 -0
- package/src/console/ui/flows/contract.ts +353 -0
- package/src/console/ui/flows/fixture.ts +81 -0
- package/src/console/ui/flows/graph.test.ts +101 -0
- package/src/console/ui/flows/graph.ts +549 -0
- package/src/console/ui/flows/index.ts +87 -0
- package/src/console/ui/flows/save-as-test.test.ts +47 -0
- package/src/console/ui/flows/save-as-test.ts +99 -0
- package/src/console/ui/flows/search.ts +255 -0
- package/src/console/ui/flows/tiers.ts +140 -0
- package/src/console/ui/gates/GatesA11yView.tsx +192 -0
- package/src/console/ui/gates/a11y.test.tsx +116 -0
- package/src/console/ui/gates/audit.test.ts +40 -0
- package/src/console/ui/gates/audit.ts +63 -0
- package/src/console/ui/gates/denial.test.ts +36 -0
- package/src/console/ui/gates/denial.ts +51 -0
- package/src/console/ui/gates/findings.ts +19 -0
- package/src/console/ui/gates/fixture.ts +189 -0
- package/src/console/ui/gates/group.test.ts +30 -0
- package/src/console/ui/gates/group.ts +102 -0
- package/src/console/ui/gates/index.ts +47 -0
- package/src/console/ui/gates/search.test.ts +49 -0
- package/src/console/ui/gates/search.ts +115 -0
- package/src/console/ui/gates/types.ts +121 -0
- package/src/console/ui/overview/OverviewA11yView.tsx +152 -0
- package/src/console/ui/overview/a11y.test.tsx +113 -0
- package/src/console/ui/overview/busiest.ts +26 -0
- package/src/console/ui/overview/compose.ts +133 -0
- package/src/console/ui/overview/cost.test.ts +39 -0
- package/src/console/ui/overview/cost.ts +138 -0
- package/src/console/ui/overview/fixture.ts +161 -0
- package/src/console/ui/overview/golden.ts +57 -0
- package/src/console/ui/overview/index.ts +59 -0
- package/src/console/ui/overview/rank.test.ts +136 -0
- package/src/console/ui/overview/rank.ts +210 -0
- package/src/console/ui/overview/slo.test.ts +73 -0
- package/src/console/ui/overview/slo.ts +229 -0
- package/src/console/ui/overview/types.ts +121 -0
- package/src/console/ui/overview/verdict.ts +131 -0
- package/src/console/ui/plugins/PluginsA11yView.tsx +227 -0
- package/src/console/ui/plugins/a11y.test.tsx +97 -0
- package/src/console/ui/plugins/command.test.ts +22 -0
- package/src/console/ui/plugins/command.ts +27 -0
- package/src/console/ui/plugins/findings.ts +39 -0
- package/src/console/ui/plugins/fixture.ts +233 -0
- package/src/console/ui/plugins/group.test.ts +38 -0
- package/src/console/ui/plugins/group.ts +67 -0
- package/src/console/ui/plugins/index.ts +40 -0
- package/src/console/ui/plugins/search.ts +67 -0
- package/src/console/ui/plugins/types.ts +115 -0
- package/src/console/ui/runs/RunsA11yView.tsx +267 -0
- package/src/console/ui/runs/a11y.test.tsx +99 -0
- package/src/console/ui/runs/explain.test.ts +23 -0
- package/src/console/ui/runs/explain.ts +32 -0
- package/src/console/ui/runs/fixture.ts +189 -0
- package/src/console/ui/runs/group.test.ts +24 -0
- package/src/console/ui/runs/group.ts +85 -0
- package/src/console/ui/runs/histogram.test.ts +31 -0
- package/src/console/ui/runs/histogram.ts +85 -0
- package/src/console/ui/runs/index.ts +75 -0
- package/src/console/ui/runs/project.ts +122 -0
- package/src/console/ui/runs/query.test.ts +50 -0
- package/src/console/ui/runs/query.ts +283 -0
- package/src/console/ui/runs/search.test.ts +45 -0
- package/src/console/ui/runs/search.ts +141 -0
- package/src/console/ui/runs/trace-link.test.ts +30 -0
- package/src/console/ui/runs/trace-link.ts +99 -0
- package/src/console/ui/runs/types.ts +137 -0
- package/src/console/ui/shell/App.tsx +56 -0
- package/src/console/ui/shell/client.ts +1715 -0
- package/src/console/ui/shell/components/ui.tsx +110 -0
- package/src/console/ui/shell/index.html +12 -0
- package/src/console/ui/shell/layout/Shell.tsx +95 -0
- package/src/console/ui/shell/main.tsx +281 -0
- package/src/console/ui/shell/panels/Access.tsx +5 -0
- package/src/console/ui/shell/panels/Ai.tsx +5 -0
- package/src/console/ui/shell/panels/Architecture.tsx +5 -0
- package/src/console/ui/shell/panels/Channels.tsx +5 -0
- package/src/console/ui/shell/panels/Clock.tsx +5 -0
- package/src/console/ui/shell/panels/Diff.tsx +5 -0
- package/src/console/ui/shell/panels/Flows.tsx +5 -0
- package/src/console/ui/shell/panels/Gates.tsx +5 -0
- package/src/console/ui/shell/panels/Overview.tsx +6 -0
- package/src/console/ui/shell/panels/Plugins.tsx +5 -0
- package/src/console/ui/shell/panels/Runs.tsx +5 -0
- package/src/console/ui/shell/panels/Signals.tsx +5 -0
- package/src/console/ui/shell/panels/Store.tsx +5 -0
- package/src/console/ui/shell/panels/Traces.tsx +5 -0
- package/src/console/ui/shell/panels/Vault.tsx +5 -0
- package/src/console/ui/shell/panels/access/AccessPanel.tsx +813 -0
- package/src/console/ui/shell/panels/ai/AiPanel.tsx +563 -0
- package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +377 -0
- package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +454 -0
- package/src/console/ui/shell/panels/clock/ClockPanel.tsx +479 -0
- package/src/console/ui/shell/panels/diff/DiffPanel.tsx +275 -0
- package/src/console/ui/shell/panels/flows/ContractEditor.tsx +301 -0
- package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +350 -0
- package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +502 -0
- package/src/console/ui/shell/panels/gates/GatesPanel.tsx +482 -0
- package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +428 -0
- package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +413 -0
- package/src/console/ui/shell/panels/runs/RunsPanel.tsx +649 -0
- package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +840 -0
- package/src/console/ui/shell/panels/store/StorePanel.tsx +787 -0
- package/src/console/ui/shell/panels/traces/TracesPanel.tsx +539 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +449 -0
- package/src/console/ui/shell/plugin/Sandbox.tsx +29 -0
- package/src/console/ui/shell/setup/Wizard.tsx +189 -0
- package/src/console/ui/shell/styles.css +75 -0
- package/src/console/ui/signals/SignalsA11yView.tsx +276 -0
- package/src/console/ui/signals/a11y.test.tsx +99 -0
- package/src/console/ui/signals/confirmation.test.ts +39 -0
- package/src/console/ui/signals/confirmation.ts +63 -0
- package/src/console/ui/signals/dry-run.test.ts +18 -0
- package/src/console/ui/signals/dry-run.ts +36 -0
- package/src/console/ui/signals/durable.test.ts +22 -0
- package/src/console/ui/signals/durable.ts +41 -0
- package/src/console/ui/signals/findings.ts +33 -0
- package/src/console/ui/signals/fixture.ts +242 -0
- package/src/console/ui/signals/group.test.ts +32 -0
- package/src/console/ui/signals/group.ts +49 -0
- package/src/console/ui/signals/index.ts +52 -0
- package/src/console/ui/signals/monitor.test.ts +30 -0
- package/src/console/ui/signals/monitor.ts +79 -0
- package/src/console/ui/signals/schema-form.test.ts +34 -0
- package/src/console/ui/signals/schema-form.ts +112 -0
- package/src/console/ui/signals/search.ts +102 -0
- package/src/console/ui/signals/types.ts +71 -0
- package/src/console/ui/store/StoreA11yView.tsx +199 -0
- package/src/console/ui/store/a11y.test.tsx +99 -0
- package/src/console/ui/store/cache-view.test.ts +13 -0
- package/src/console/ui/store/cache-view.ts +36 -0
- package/src/console/ui/store/confirmation.test.ts +38 -0
- package/src/console/ui/store/confirmation.ts +55 -0
- package/src/console/ui/store/dry-run.test.ts +15 -0
- package/src/console/ui/store/dry-run.ts +29 -0
- package/src/console/ui/store/fixture.ts +163 -0
- package/src/console/ui/store/group.test.ts +21 -0
- package/src/console/ui/store/group.ts +41 -0
- package/src/console/ui/store/index.ts +40 -0
- package/src/console/ui/store/search.ts +78 -0
- package/src/console/ui/store/types.ts +65 -0
- package/src/console/ui/store/will-not-fire.test.ts +19 -0
- package/src/console/ui/store/will-not-fire.ts +40 -0
- package/src/console/ui/traces/TracesA11yView.tsx +200 -0
- package/src/console/ui/traces/a11y.test.tsx +97 -0
- package/src/console/ui/traces/chain.test.ts +61 -0
- package/src/console/ui/traces/chain.ts +144 -0
- package/src/console/ui/traces/critical-path.test.ts +24 -0
- package/src/console/ui/traces/critical-path.ts +59 -0
- package/src/console/ui/traces/filter.test.ts +78 -0
- package/src/console/ui/traces/filter.ts +112 -0
- package/src/console/ui/traces/fixture.ts +126 -0
- package/src/console/ui/traces/fold.test.ts +67 -0
- package/src/console/ui/traces/fold.ts +170 -0
- package/src/console/ui/traces/index.ts +64 -0
- package/src/console/ui/traces/mini.ts +65 -0
- package/src/console/ui/traces/replay.test.ts +24 -0
- package/src/console/ui/traces/replay.ts +35 -0
- package/src/console/ui/traces/sampling.test.ts +26 -0
- package/src/console/ui/traces/sampling.ts +66 -0
- package/src/console/ui/traces/search.ts +148 -0
- package/src/console/ui/traces/tier.ts +72 -0
- package/src/console/ui/traces/types.ts +125 -0
- package/src/console/ui/tsconfig.json +20 -0
- package/src/console/ui/vault/VaultA11yView.tsx +177 -0
- package/src/console/ui/vault/a11y.test.tsx +97 -0
- package/src/console/ui/vault/blast-radius.test.ts +35 -0
- package/src/console/ui/vault/blast-radius.ts +55 -0
- package/src/console/ui/vault/confirmation.test.ts +41 -0
- package/src/console/ui/vault/confirmation.ts +39 -0
- package/src/console/ui/vault/dormant.test.ts +37 -0
- package/src/console/ui/vault/dormant.ts +28 -0
- package/src/console/ui/vault/export-safe.test.ts +30 -0
- package/src/console/ui/vault/export-safe.ts +65 -0
- package/src/console/ui/vault/fixture.ts +75 -0
- package/src/console/ui/vault/group.test.ts +25 -0
- package/src/console/ui/vault/group.ts +50 -0
- package/src/console/ui/vault/index.ts +45 -0
- package/src/console/ui/vault/search.ts +62 -0
- package/src/console/ui/vault/types.ts +57 -0
- package/src/console/ui/vite.config.ts +84 -0
- package/src/console/xss.gate.test.ts +118 -0
- package/src/docker/compose.ts +226 -0
- package/src/docker/credentials.ts +25 -0
- package/src/docker/derive.ts +83 -0
- package/src/docker/docker.test.ts +185 -0
- package/src/docker/dockerfile.integration.test.ts +170 -0
- package/src/docker/dockerfile.ts +39 -0
- package/src/docker/helpers.ts +103 -0
- package/src/docker/index.ts +52 -0
- package/src/docker/pin.ts +90 -0
- package/src/docker/recipes/index.ts +33 -0
- package/src/docker/recipes/postgres.ts +18 -0
- package/src/docker/recipes/redis.ts +18 -0
- package/src/docker/stack.integration.test.ts +140 -0
- package/src/docker/stack.ts +62 -0
- package/src/docker/types.ts +134 -0
- package/src/drivers/ai-anthropic.ts +128 -0
- package/src/drivers/ai-mock.ts +82 -0
- package/src/drivers/ai-openai-compatible.ts +139 -0
- package/src/drivers/ai-providers.test.ts +163 -0
- package/src/drivers/ai-types.ts +83 -0
- package/src/drivers/bun-native-completeness.test.ts +155 -0
- package/src/drivers/channel-console.ts +118 -0
- package/src/drivers/channel-fcm.ts +96 -0
- package/src/drivers/channel-resend.ts +27 -0
- package/src/drivers/channel-smtp.ts +33 -0
- package/src/drivers/channel-types.ts +160 -0
- package/src/drivers/channel-unifonic.ts +79 -0
- package/src/drivers/channel-wa-cloud.ts +84 -0
- package/src/drivers/channel-webpush.ts +317 -0
- package/src/drivers/conformance.test.ts +89 -0
- package/src/drivers/conformance.ts +162 -0
- package/src/drivers/fs.ts +73 -0
- package/src/drivers/index.ts +194 -0
- package/src/drivers/kv-lua.ts +222 -0
- package/src/drivers/memory.ts +472 -0
- package/src/drivers/pgvector.ts +130 -0
- package/src/drivers/postgres.ts +221 -0
- package/src/drivers/redis.ts +254 -0
- package/src/drivers/s3.ts +129 -0
- package/src/drivers/signal-engine.ts +690 -0
- package/src/drivers/signal-memory.ts +27 -0
- package/src/drivers/signal-nats.ts +131 -0
- package/src/drivers/signal-postgres.ts +1089 -0
- package/src/drivers/signal-redis.ts +275 -0
- package/src/drivers/signal-types.ts +328 -0
- package/src/drivers/sqlite.ts +53 -0
- package/src/drivers/types.ts +334 -0
- package/src/drivers/vault-dotenv-parse.ts +56 -0
- package/src/drivers/vault-env.ts +101 -0
- package/src/drivers/vault-infisical.ts +61 -0
- package/src/drivers/vault-managed.ts +42 -0
- package/src/drivers/vault-memory.ts +38 -0
- package/src/drivers/vault-openbao.ts +80 -0
- package/src/drivers/vault-sops.ts +252 -0
- package/src/drivers/vault-types.ts +84 -0
- package/src/elements/ai/declare.ts +206 -0
- package/src/elements/ai/eval.ts +113 -0
- package/src/elements/ai/pii.ts +108 -0
- package/src/elements/ai/runtime.ts +540 -0
- package/src/elements/ai/schema.ts +194 -0
- package/src/elements/ai.test.ts +393 -0
- package/src/elements/ai.ts +56 -0
- package/src/elements/channel/consent.ts +81 -0
- package/src/elements/channel/costs.test.ts +62 -0
- package/src/elements/channel/costs.ts +130 -0
- package/src/elements/channel/declare.ts +141 -0
- package/src/elements/channel/email-auth.test.ts +47 -0
- package/src/elements/channel/email-auth.ts +109 -0
- package/src/elements/channel/locale.ts +97 -0
- package/src/elements/channel/mask.test.ts +19 -0
- package/src/elements/channel/mask.ts +60 -0
- package/src/elements/channel/mime.ts +23 -0
- package/src/elements/channel/outcomes.test.ts +40 -0
- package/src/elements/channel/outcomes.ts +126 -0
- package/src/elements/channel/receipts.ts +121 -0
- package/src/elements/channel/runtime.ts +500 -0
- package/src/elements/channel/suppression.ts +136 -0
- package/src/elements/channel.test.ts +232 -0
- package/src/elements/channel.ts +109 -0
- package/src/elements/clock/actions.ts +170 -0
- package/src/elements/clock/declare.ts +58 -0
- package/src/elements/clock/dst.ts +197 -0
- package/src/elements/clock/durable.ts +140 -0
- package/src/elements/clock/duration.ts +29 -0
- package/src/elements/clock/health.test.ts +103 -0
- package/src/elements/clock/health.ts +112 -0
- package/src/elements/clock/leader.ts +127 -0
- package/src/elements/clock/reconcile.ts +241 -0
- package/src/elements/clock/runtime.ts +243 -0
- package/src/elements/clock/schedule.ts +262 -0
- package/src/elements/clock/time-travel.ts +53 -0
- package/src/elements/clock.test.ts +365 -0
- package/src/elements/clock.ts +80 -0
- package/src/elements/gate/declare.ts +134 -0
- package/src/elements/gate/permissions.ts +104 -0
- package/src/elements/gate/runtime.ts +177 -0
- package/src/elements/gate/strategies.ts +388 -0
- package/src/elements/gate.test.ts +145 -0
- package/src/elements/gate.ts +41 -0
- package/src/elements/index.ts +169 -0
- package/src/elements/signal/chaos-child.ts +47 -0
- package/src/elements/signal/declare.ts +77 -0
- package/src/elements/signal/dry-run-replay.test.ts +95 -0
- package/src/elements/signal/dry-run-write-isolation.test.ts +94 -0
- package/src/elements/signal/reconcile.test.ts +23 -0
- package/src/elements/signal/reconcile.ts +122 -0
- package/src/elements/signal/runtime.ts +94 -0
- package/src/elements/signal.test.ts +457 -0
- package/src/elements/signal.ts +26 -0
- package/src/elements/store/cache.ts +197 -0
- package/src/elements/store/classify.ts +125 -0
- package/src/elements/store/declare.ts +200 -0
- package/src/elements/store/files-policy.test.ts +31 -0
- package/src/elements/store/files-policy.ts +70 -0
- package/src/elements/store/replica.ts +68 -0
- package/src/elements/store/runtime.ts +393 -0
- package/src/elements/store/sql-condition.ts +205 -0
- package/src/elements/store/sql-session.ts +579 -0
- package/src/elements/store/table.ts +282 -0
- package/src/elements/store.test.ts +370 -0
- package/src/elements/store.ts +99 -0
- package/src/elements/vault/declare.ts +143 -0
- package/src/elements/vault/fingerprint.ts +30 -0
- package/src/elements/vault/redact.ts +52 -0
- package/src/elements/vault/runtime.ts +461 -0
- package/src/elements/vault.test.ts +296 -0
- package/src/elements/vault.ts +44 -0
- package/src/index.ts +183 -0
- package/src/kernel/adopt-routes.ts +194 -0
- package/src/kernel/app.ts +1068 -0
- package/src/kernel/auth-resolve.ts +93 -0
- package/src/kernel/boot.test.ts +173 -0
- package/src/kernel/boot.ts +439 -0
- package/src/kernel/budget-entry.ts +55 -0
- package/src/kernel/budget.test.ts +19 -0
- package/src/kernel/call.test.ts +83 -0
- package/src/kernel/capability.ts +113 -0
- package/src/kernel/dry-run.test.ts +64 -0
- package/src/kernel/dry-run.ts +171 -0
- package/src/kernel/effects.test.ts +89 -0
- package/src/kernel/effects.ts +140 -0
- package/src/kernel/errors.registry.test.ts +39 -0
- package/src/kernel/errors.ts +225 -0
- package/src/kernel/flow.test.ts +154 -0
- package/src/kernel/flow.ts +290 -0
- package/src/kernel/fx.test.ts +245 -0
- package/src/kernel/fx.ts +996 -0
- package/src/kernel/hook-timing.test.ts +72 -0
- package/src/kernel/hook-timing.ts +190 -0
- package/src/kernel/hooks.test.ts +181 -0
- package/src/kernel/hooks.ts +275 -0
- package/src/kernel/index.ts +278 -0
- package/src/kernel/journal.ts +405 -0
- package/src/kernel/journey.ts +75 -0
- package/src/kernel/on.ts +74 -0
- package/src/kernel/pipeline.test.ts +349 -0
- package/src/kernel/pipeline.ts +245 -0
- package/src/kernel/plug.ts +82 -0
- package/src/kernel/plugin/capabilities.test.ts +84 -0
- package/src/kernel/plugin/conflicts.test.ts +106 -0
- package/src/kernel/plugin/decorate.test.ts +111 -0
- package/src/kernel/plugin/scoping.test.ts +138 -0
- package/src/kernel/plugin.ts +485 -0
- package/src/kernel/rate-limit.ts +25 -0
- package/src/kernel/registry.ts +398 -0
- package/src/kernel/router.test.ts +114 -0
- package/src/kernel/router.ts +556 -0
- package/src/kernel/routing-budget.test.ts +19 -0
- package/src/kernel/run-telemetry.ts +70 -0
- package/src/kernel/triggers.ts +324 -0
- package/src/kernel/unit.ts +88 -0
- package/src/manifest/diff.test.ts +866 -0
- package/src/manifest/diff.ts +1670 -0
- package/src/manifest/fixtures/base.manifest.json +112 -0
- package/src/manifest/fixtures/skyport.excerpt.json +51 -0
- package/src/manifest/fixtures/skyport.manifest.json +153 -0
- package/src/manifest/index.ts +27 -0
- package/src/manifest/types.ts +358 -0
- package/src/manifest/undeclared.test.ts +82 -0
- package/src/manifest/undeclared.ts +65 -0
- package/src/manifest/validate.test.ts +105 -0
- package/src/manifest/validate.ts +174 -0
- package/src/mcp/authorization.ts +340 -0
- package/src/mcp/confirmation.ts +174 -0
- package/src/mcp/data.ts +90 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/injection.gate.test.ts +176 -0
- package/src/mcp/mcp.test.ts +348 -0
- package/src/mcp/protocol.ts +172 -0
- package/src/mcp/server.ts +283 -0
- package/src/mcp/session.ts +139 -0
- package/src/mcp/tools.ts +391 -0
- package/src/plugins/catalogue.test.ts +48 -0
- package/src/plugins/catalogue.ts +113 -0
- package/src/plugins/node-import-scan.test.ts +28 -0
- package/src/plugins/node-import-scan.ts +98 -0
- package/src/plugins/supply-chain.test.ts +48 -0
- package/src/plugins/supply-chain.ts +460 -0
- package/src/release/index.ts +30 -0
- package/src/release/limits.ts +20 -0
- package/src/release/measure.ts +443 -0
- package/src/release/publish.ts +56 -0
- package/src/runs/bench.test.ts +93 -0
- package/src/runs/collect.ts +144 -0
- package/src/runs/drivers/clickhouse.ts +108 -0
- package/src/runs/drivers/files.ts +201 -0
- package/src/runs/drivers/memory.ts +94 -0
- package/src/runs/drivers/postgres.ts +110 -0
- package/src/runs/duckdb.ts +99 -0
- package/src/runs/index.ts +82 -0
- package/src/runs/outlier.ts +180 -0
- package/src/runs/parquet.ts +210 -0
- package/src/runs/privacy.ts +49 -0
- package/src/runs/runs.test.ts +324 -0
- package/src/runs/runtime.ts +229 -0
- package/src/runs/shred.ts +243 -0
- package/src/runs/telemetry.ts +10 -0
- package/src/runs/types.ts +179 -0
- package/src/runtime/bun.ts +140 -0
- package/src/runtime/cold-start.bench.ts +20 -0
- package/src/runtime/index.ts +46 -0
- package/src/runtime/primitives.ts +242 -0
- package/src/runtime/security.test.ts +170 -0
- package/src/runtime/security.ts +168 -0
- package/src/runtime/serve.test.ts +223 -0
- package/src/runtime/types.ts +217 -0
- package/src/runtime/web-standard.ts +77 -0
- package/src/test/create-test-app.test.ts +161 -0
- package/src/test/create-test-app.ts +533 -0
- package/src/test/index.ts +21 -0
- package/src/test/provisions.integration.test.ts +174 -0
- package/src/upgrade/codemods.test.ts +58 -0
- package/src/upgrade/codemods.ts +83 -0
- package/src/validation/index.ts +23 -0
- package/src/validation/standard-schema.test.ts +96 -0
- package/src/validation/standard-schema.ts +316 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DST ambiguity detection — expression + zone together (console §9.6).
|
|
3
|
+
*
|
|
4
|
+
* A daily 02:00 job in a DST-observing zone runs twice or never on
|
|
5
|
+
* transition days. The warning appears only when the schedule actually
|
|
6
|
+
* falls in the ambiguous window.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Result when a schedule hits a DST gap or overlap. */
|
|
10
|
+
export interface DstAmbiguity {
|
|
11
|
+
/** `"gap"` — spring forward (time skipped); `"overlap"` — fall back (time repeats). */
|
|
12
|
+
readonly kind: "gap" | "overlap";
|
|
13
|
+
/** IANA timezone. */
|
|
14
|
+
readonly timezone: string;
|
|
15
|
+
/** Cron expression that was checked. */
|
|
16
|
+
readonly cron: string;
|
|
17
|
+
/** Local wall time (`HH:MM`) that is ambiguous. */
|
|
18
|
+
readonly localTime: string;
|
|
19
|
+
/** Example transition day (ISO date) where the issue occurs. */
|
|
20
|
+
readonly on: string;
|
|
21
|
+
/** Human-readable reason. */
|
|
22
|
+
readonly reason: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect whether a cron expression + timezone falls in a DST ambiguous window.
|
|
27
|
+
*
|
|
28
|
+
* Supports simple forms `M H * * *` (and `M H * * DOW`). Complex lists /
|
|
29
|
+
* steps / ranges return `null` (no spurious always-on warning).
|
|
30
|
+
*
|
|
31
|
+
* @param cron - Five-field cron expression
|
|
32
|
+
* @param timezone - IANA timezone
|
|
33
|
+
* @param from - Optional epoch-ms scan start (defaults to now)
|
|
34
|
+
*/
|
|
35
|
+
export function detectDstAmbiguity(
|
|
36
|
+
cron: string,
|
|
37
|
+
timezone: string,
|
|
38
|
+
from: number = Date.now(),
|
|
39
|
+
): DstAmbiguity | null {
|
|
40
|
+
if (timezone === "UTC" || timezone === "Etc/UTC" || timezone === "GMT") {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const parsed = parseSimpleDaily(cron);
|
|
45
|
+
if (!parsed) return null;
|
|
46
|
+
|
|
47
|
+
const { minute, hour } = parsed;
|
|
48
|
+
const start = new Date(from);
|
|
49
|
+
let prevOffset: number | null = null;
|
|
50
|
+
|
|
51
|
+
// Walk day-by-day; only classify on days where the noon offset jumps
|
|
52
|
+
// (DST transition). Keeps the warning precise without a full-year grind.
|
|
53
|
+
for (let day = 0; day < 450; day++) {
|
|
54
|
+
const probe = Date.UTC(
|
|
55
|
+
start.getUTCFullYear(),
|
|
56
|
+
start.getUTCMonth(),
|
|
57
|
+
start.getUTCDate() + day,
|
|
58
|
+
12,
|
|
59
|
+
0,
|
|
60
|
+
0,
|
|
61
|
+
);
|
|
62
|
+
const offset = offsetMinutesAt(probe, timezone);
|
|
63
|
+
if (prevOffset !== null && offset !== prevOffset) {
|
|
64
|
+
// Transition occurred between yesterday noon and today noon —
|
|
65
|
+
// check both civil dates around the jump.
|
|
66
|
+
for (const delta of [-1, 0] as const) {
|
|
67
|
+
const t = new Date(probe + delta * 86_400_000);
|
|
68
|
+
const y = t.getUTCFullYear();
|
|
69
|
+
const mo = t.getUTCMonth() + 1;
|
|
70
|
+
const d = t.getUTCDate();
|
|
71
|
+
const dateStr = `${y}-${pad(mo)}-${pad(d)}`;
|
|
72
|
+
const kind = classifyLocalInstant(dateStr, hour, minute, timezone);
|
|
73
|
+
if (kind === "gap" || kind === "overlap") {
|
|
74
|
+
const localTime = `${pad(hour)}:${pad(minute)}`;
|
|
75
|
+
return {
|
|
76
|
+
kind,
|
|
77
|
+
timezone,
|
|
78
|
+
cron: cron.trim(),
|
|
79
|
+
localTime,
|
|
80
|
+
on: dateStr,
|
|
81
|
+
reason:
|
|
82
|
+
kind === "gap"
|
|
83
|
+
? `${localTime} in ${timezone} is skipped on ${dateStr} (DST spring forward)`
|
|
84
|
+
: `${localTime} in ${timezone} occurs twice on ${dateStr} (DST fall back)`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
prevOffset = offset;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Parsed simple daily cron. */
|
|
95
|
+
interface SimpleDaily {
|
|
96
|
+
readonly minute: number;
|
|
97
|
+
readonly hour: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseSimpleDaily(cron: string): SimpleDaily | null {
|
|
101
|
+
const parts = cron.trim().split(/\s+/);
|
|
102
|
+
if (parts.length < 5) return null;
|
|
103
|
+
const minute = parts[0]!;
|
|
104
|
+
const hour = parts[1]!;
|
|
105
|
+
const dom = parts[2]!;
|
|
106
|
+
const mon = parts[3]!;
|
|
107
|
+
// Only flag when the schedule pins a specific local wall-clock time.
|
|
108
|
+
if (!/^\d+$/.test(minute) || !/^\d+$/.test(hour)) return null;
|
|
109
|
+
if (dom !== "*" && !/^\d+$/.test(dom)) return null;
|
|
110
|
+
if (mon !== "*" && !/^\d+$/.test(mon)) return null;
|
|
111
|
+
const m = Number(minute);
|
|
112
|
+
const h = Number(hour);
|
|
113
|
+
if (m < 0 || m > 59 || h < 0 || h > 23) return null;
|
|
114
|
+
return { minute: m, hour: h };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Classify a local civil time in `timezone` on `YYYY-MM-DD`.
|
|
119
|
+
*
|
|
120
|
+
* - `ok` — exactly one UTC instant
|
|
121
|
+
* - `gap` — no UTC instant (spring forward)
|
|
122
|
+
* - `overlap` — two UTC instants (fall back)
|
|
123
|
+
*/
|
|
124
|
+
function classifyLocalInstant(
|
|
125
|
+
dateStr: string,
|
|
126
|
+
hour: number,
|
|
127
|
+
minute: number,
|
|
128
|
+
timezone: string,
|
|
129
|
+
): "ok" | "gap" | "overlap" {
|
|
130
|
+
const matches: number[] = [];
|
|
131
|
+
const base = Date.parse(`${dateStr}T00:00:00.000Z`);
|
|
132
|
+
if (Number.isNaN(base)) return "ok";
|
|
133
|
+
|
|
134
|
+
for (let offsetMin = -14 * 60; offsetMin <= 14 * 60; offsetMin += 15) {
|
|
135
|
+
const utc = base + hour * 3_600_000 + minute * 60_000 - offsetMin * 60_000;
|
|
136
|
+
const local = formatInZone(utc, timezone);
|
|
137
|
+
if (
|
|
138
|
+
local.date === dateStr &&
|
|
139
|
+
local.hour === hour &&
|
|
140
|
+
local.minute === minute
|
|
141
|
+
) {
|
|
142
|
+
if (!matches.includes(utc)) matches.push(utc);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (matches.length === 0) return "gap";
|
|
147
|
+
if (matches.length >= 2) return "overlap";
|
|
148
|
+
return "ok";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** UTC offset (minutes) of `timezone` at `utcMs`. */
|
|
152
|
+
function offsetMinutesAt(utcMs: number, timezone: string): number {
|
|
153
|
+
const local = formatInZone(utcMs, timezone);
|
|
154
|
+
// Reconstruct a UTC ms for the same wall clock as if it were UTC, then diff.
|
|
155
|
+
const asUtc = Date.UTC(
|
|
156
|
+
Number(local.date.slice(0, 4)),
|
|
157
|
+
Number(local.date.slice(5, 7)) - 1,
|
|
158
|
+
Number(local.date.slice(8, 10)),
|
|
159
|
+
local.hour,
|
|
160
|
+
local.minute,
|
|
161
|
+
0,
|
|
162
|
+
);
|
|
163
|
+
return Math.round((asUtc - utcMs) / 60_000);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface ZonedParts {
|
|
167
|
+
readonly date: string;
|
|
168
|
+
readonly hour: number;
|
|
169
|
+
readonly minute: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function formatInZone(utcMs: number, timezone: string): ZonedParts {
|
|
173
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
174
|
+
timeZone: timezone,
|
|
175
|
+
year: "numeric",
|
|
176
|
+
month: "2-digit",
|
|
177
|
+
day: "2-digit",
|
|
178
|
+
hour: "2-digit",
|
|
179
|
+
minute: "2-digit",
|
|
180
|
+
hourCycle: "h23",
|
|
181
|
+
});
|
|
182
|
+
const parts = fmt.formatToParts(new Date(utcMs));
|
|
183
|
+
const get = (type: Intl.DateTimeFormatPartTypes): string =>
|
|
184
|
+
parts.find((p) => p.type === type)?.value ?? "0";
|
|
185
|
+
const year = get("year");
|
|
186
|
+
const month = get("month");
|
|
187
|
+
const day = get("day");
|
|
188
|
+
return {
|
|
189
|
+
date: `${year}-${month}-${day}`,
|
|
190
|
+
hour: Number(get("hour")),
|
|
191
|
+
minute: Number(get("minute")),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function pad(n: number): string {
|
|
196
|
+
return n < 10 ? `0${n}` : String(n);
|
|
197
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable flow runner — journals every fx call when `durable: true`.
|
|
3
|
+
*
|
|
4
|
+
* Workflows are not a separate API: an ordinary flow with one option.
|
|
5
|
+
* A process killed between steps resumes at the failed step; the prior
|
|
6
|
+
* step never re-runs (four-applications · Provisions).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { AnyFlowDef } from "../../kernel/flow.ts";
|
|
10
|
+
import {
|
|
11
|
+
createFxContext,
|
|
12
|
+
type CreateFxOptions,
|
|
13
|
+
type Fx,
|
|
14
|
+
} from "../../kernel/fx.ts";
|
|
15
|
+
import {
|
|
16
|
+
createJournal,
|
|
17
|
+
isJournalSuspend,
|
|
18
|
+
type Journal,
|
|
19
|
+
type JournalSession,
|
|
20
|
+
type JournalStore,
|
|
21
|
+
} from "../../kernel/journal.ts";
|
|
22
|
+
/** Outcome of one durable attempt. */
|
|
23
|
+
export type DurableResult<O = unknown> =
|
|
24
|
+
| {
|
|
25
|
+
readonly status: "completed";
|
|
26
|
+
readonly runId: string;
|
|
27
|
+
readonly output: O;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
readonly status: "sleeping";
|
|
31
|
+
readonly runId: string;
|
|
32
|
+
readonly wakeAt: number;
|
|
33
|
+
readonly label: string;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
readonly status: "failed";
|
|
37
|
+
readonly runId: string;
|
|
38
|
+
readonly error: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Options for {@link runDurable}. */
|
|
42
|
+
export interface RunDurableOptions {
|
|
43
|
+
/** Flow with `durable: true` (or forced via this runner). */
|
|
44
|
+
readonly flow: AnyFlowDef;
|
|
45
|
+
/** Input payload. */
|
|
46
|
+
readonly input?: unknown;
|
|
47
|
+
/** Journal store (memory or file). */
|
|
48
|
+
readonly journalStore: JournalStore;
|
|
49
|
+
/** Resume an existing run id (crash recovery). */
|
|
50
|
+
readonly runId?: string;
|
|
51
|
+
/** Injectable clock. */
|
|
52
|
+
readonly now?: () => number;
|
|
53
|
+
/** Extra fx options (secrets, store runtime, …). */
|
|
54
|
+
readonly fx?: Omit<
|
|
55
|
+
CreateFxOptions,
|
|
56
|
+
"flow" | "effects" | "capability" | "now" | "journal"
|
|
57
|
+
>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Execute (or resume) a durable flow against a journal.
|
|
62
|
+
*
|
|
63
|
+
* @param options - Flow, input, journal, clock
|
|
64
|
+
*/
|
|
65
|
+
export async function runDurable<O = unknown>(
|
|
66
|
+
options: RunDurableOptions,
|
|
67
|
+
): Promise<DurableResult<O>> {
|
|
68
|
+
const now = options.now ?? (() => Date.now());
|
|
69
|
+
const journal: Journal = createJournal({
|
|
70
|
+
store: options.journalStore,
|
|
71
|
+
now,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (options.runId) {
|
|
75
|
+
const existing = await options.journalStore.get(options.runId);
|
|
76
|
+
if (
|
|
77
|
+
existing?.wakeAt !== undefined &&
|
|
78
|
+
now() < existing.wakeAt
|
|
79
|
+
) {
|
|
80
|
+
const sleepEntry = [...existing.entries]
|
|
81
|
+
.reverse()
|
|
82
|
+
.find((e) => e.kind === "sleep");
|
|
83
|
+
return {
|
|
84
|
+
status: "sleeping",
|
|
85
|
+
runId: existing.id,
|
|
86
|
+
wakeAt: existing.wakeAt,
|
|
87
|
+
label: sleepEntry && sleepEntry.kind === "sleep" ? sleepEntry.label : "",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const session: JournalSession = options.runId
|
|
93
|
+
? await journal.resume(options.runId)
|
|
94
|
+
: await journal.start(options.flow.name, options.input);
|
|
95
|
+
|
|
96
|
+
if (session.run.status === "sleeping") {
|
|
97
|
+
session.run.status = "running";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { fx } = createFxContext({
|
|
101
|
+
...options.fx,
|
|
102
|
+
flow: options.flow.name,
|
|
103
|
+
effects: options.flow.effects,
|
|
104
|
+
now,
|
|
105
|
+
journal: session,
|
|
106
|
+
durable: true,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const output = await options.flow.do(options.input as never, fx);
|
|
111
|
+
await session.commit("completed", { output });
|
|
112
|
+
return {
|
|
113
|
+
status: "completed",
|
|
114
|
+
runId: session.runId,
|
|
115
|
+
output: output as O,
|
|
116
|
+
};
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (isJournalSuspend(err)) {
|
|
119
|
+
return {
|
|
120
|
+
status: "sleeping",
|
|
121
|
+
runId: session.runId,
|
|
122
|
+
wakeAt: err.wakeAt,
|
|
123
|
+
label: err.label,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
127
|
+
await session.commit("failed", { error: message });
|
|
128
|
+
return {
|
|
129
|
+
status: "failed",
|
|
130
|
+
runId: session.runId,
|
|
131
|
+
error: message,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Bind a journal session onto an existing {@link Fx} factory path —
|
|
138
|
+
* re-exported type helper for tests that build fx directly.
|
|
139
|
+
*/
|
|
140
|
+
export type { JournalSession, Fx };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration / interval parsing for the Clock element.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse a short duration string (`"7d"`, `"2m"`, `"30s"`, `"1h"`, `"200ms"`)
|
|
7
|
+
* to milliseconds. Returns `0` when the string is not a duration.
|
|
8
|
+
*
|
|
9
|
+
* @param duration - Duration string
|
|
10
|
+
*/
|
|
11
|
+
export function parseDurationMs(duration: string): number {
|
|
12
|
+
const match = /^(\d+)(ms|s|m|h|d)$/.exec(duration.trim());
|
|
13
|
+
if (!match) return 0;
|
|
14
|
+
const n = Number(match[1]);
|
|
15
|
+
switch (match[2]) {
|
|
16
|
+
case "ms":
|
|
17
|
+
return n;
|
|
18
|
+
case "s":
|
|
19
|
+
return n * 1000;
|
|
20
|
+
case "m":
|
|
21
|
+
return n * 60_000;
|
|
22
|
+
case "h":
|
|
23
|
+
return n * 3_600_000;
|
|
24
|
+
case "d":
|
|
25
|
+
return n * 86_400_000;
|
|
26
|
+
default:
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron health + next-occurrence helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { cronHealth } from "./health.ts";
|
|
7
|
+
import { nextOccurrences } from "./schedule.ts";
|
|
8
|
+
import type { CronRow } from "./reconcile.ts";
|
|
9
|
+
|
|
10
|
+
function row(partial: Partial<CronRow> & { name: string }): CronRow {
|
|
11
|
+
return {
|
|
12
|
+
timezone: "UTC",
|
|
13
|
+
overridable: false,
|
|
14
|
+
status: "active",
|
|
15
|
+
...partial,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("cronHealth", () => {
|
|
20
|
+
test("every — overdue with missed runs and catchUp one", () => {
|
|
21
|
+
const now = 10_000;
|
|
22
|
+
const h = cronHealth(
|
|
23
|
+
row({
|
|
24
|
+
name: "tick",
|
|
25
|
+
effectiveEvery: "1s",
|
|
26
|
+
declaredEvery: "1s",
|
|
27
|
+
lastRunAt: 0,
|
|
28
|
+
nextRunAt: 1_000,
|
|
29
|
+
}),
|
|
30
|
+
now,
|
|
31
|
+
);
|
|
32
|
+
expect(h.overdue).toBe(true);
|
|
33
|
+
expect(h.missedRuns).toBe(10);
|
|
34
|
+
expect(h.catchUp).toBe("one");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("not overdue when nextRunAt is in the future", () => {
|
|
38
|
+
const now = 1_000;
|
|
39
|
+
const h = cronHealth(
|
|
40
|
+
row({
|
|
41
|
+
name: "ok",
|
|
42
|
+
effectiveEvery: "1h",
|
|
43
|
+
lastRunAt: 0,
|
|
44
|
+
nextRunAt: 3_600_000,
|
|
45
|
+
}),
|
|
46
|
+
now,
|
|
47
|
+
);
|
|
48
|
+
expect(h.overdue).toBe(false);
|
|
49
|
+
expect(h.missedRuns).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("projects lease holder fields", () => {
|
|
53
|
+
const h = cronHealth(
|
|
54
|
+
row({
|
|
55
|
+
name: "leased",
|
|
56
|
+
effectiveEvery: "1h",
|
|
57
|
+
leaderInstanceId: "inst-a",
|
|
58
|
+
leaderLeaseUntil: 99_000,
|
|
59
|
+
lastRunAt: 0,
|
|
60
|
+
nextRunAt: 3_600_000,
|
|
61
|
+
}),
|
|
62
|
+
1_000,
|
|
63
|
+
);
|
|
64
|
+
expect(h.leaderInstanceId).toBe("inst-a");
|
|
65
|
+
expect(h.leaderLeaseUntil).toBe(99_000);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("nextOccurrences", () => {
|
|
70
|
+
test("every — next 24h of fires", () => {
|
|
71
|
+
const from = 0;
|
|
72
|
+
const until = 10_000;
|
|
73
|
+
const fires = nextOccurrences(
|
|
74
|
+
row({
|
|
75
|
+
name: "tick",
|
|
76
|
+
effectiveEvery: "2s",
|
|
77
|
+
lastRunAt: 0,
|
|
78
|
+
}),
|
|
79
|
+
from,
|
|
80
|
+
until,
|
|
81
|
+
);
|
|
82
|
+
expect(fires[0]).toBe(2_000);
|
|
83
|
+
expect(fires.length).toBe(4);
|
|
84
|
+
expect(fires[fires.length - 1]).toBe(8_000);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("simple daily cron in UTC", () => {
|
|
88
|
+
// 2024-01-01 00:00 UTC
|
|
89
|
+
const from = Date.UTC(2024, 0, 1, 0, 0, 0);
|
|
90
|
+
const until = from + 48 * 3_600_000;
|
|
91
|
+
const fires = nextOccurrences(
|
|
92
|
+
row({
|
|
93
|
+
name: "nightly",
|
|
94
|
+
effectiveCron: "0 2 * * *",
|
|
95
|
+
timezone: "UTC",
|
|
96
|
+
}),
|
|
97
|
+
from,
|
|
98
|
+
until,
|
|
99
|
+
);
|
|
100
|
+
expect(fires.length).toBeGreaterThanOrEqual(1);
|
|
101
|
+
expect(fires.every((t) => t >= from && t < until)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron health — four numbers for the Console Clock panel (console §9.6).
|
|
3
|
+
*
|
|
4
|
+
* Catch-up policy is `"one"`: the runtime fires at most once per lease when
|
|
5
|
+
* overdue (`ClockRuntime.tick` / `fire`). Not a Store column.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parseDurationMs } from "./duration.ts";
|
|
9
|
+
import { effectiveSchedule, type CronRow } from "./reconcile.ts";
|
|
10
|
+
import {
|
|
11
|
+
countMissedOccurrences,
|
|
12
|
+
previousOccurrence,
|
|
13
|
+
} from "./schedule.ts";
|
|
14
|
+
|
|
15
|
+
/** Catch-up policy matching ClockRuntime physics. */
|
|
16
|
+
export type CatchUpPolicy = "one";
|
|
17
|
+
|
|
18
|
+
/** Four-number cron health (+ lease holder). */
|
|
19
|
+
export interface CronHealth {
|
|
20
|
+
/** Scheduled vs actual (`lastRunAt - expected`); `null` when unknown. */
|
|
21
|
+
readonly driftMs: number | null;
|
|
22
|
+
/** Whether the schedule is past due. */
|
|
23
|
+
readonly overdue: boolean;
|
|
24
|
+
/** Runs missed while the app was down (slots between last fire and now). */
|
|
25
|
+
readonly missedRuns: number;
|
|
26
|
+
/** Runtime catch-up policy (`"one"`). */
|
|
27
|
+
readonly catchUp: CatchUpPolicy;
|
|
28
|
+
/** Instance holding the leader lease, if any. */
|
|
29
|
+
readonly leaderInstanceId?: string;
|
|
30
|
+
/** Lease expiry epoch-ms. */
|
|
31
|
+
readonly leaderLeaseUntil?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compute cron health from a reconciled Store row.
|
|
36
|
+
*
|
|
37
|
+
* @param row - `oke_crons` row
|
|
38
|
+
* @param now - Current epoch-ms
|
|
39
|
+
*/
|
|
40
|
+
export function cronHealth(row: CronRow, now: number): CronHealth {
|
|
41
|
+
const overdue = isOverdue(row, now);
|
|
42
|
+
const missedRuns = overdue
|
|
43
|
+
? countMissedFromRow(row, now)
|
|
44
|
+
: 0;
|
|
45
|
+
const driftMs = computeDrift(row, now);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
driftMs,
|
|
49
|
+
overdue,
|
|
50
|
+
missedRuns,
|
|
51
|
+
catchUp: "one",
|
|
52
|
+
leaderInstanceId: row.leaderInstanceId,
|
|
53
|
+
leaderLeaseUntil: row.leaderLeaseUntil,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param row - Store row
|
|
59
|
+
* @param now - Clock
|
|
60
|
+
*/
|
|
61
|
+
function isOverdue(row: CronRow, now: number): boolean {
|
|
62
|
+
if (row.status !== "active") return false;
|
|
63
|
+
if (row.nextRunAt !== undefined) return row.nextRunAt < now;
|
|
64
|
+
const sched = effectiveSchedule(row);
|
|
65
|
+
if (sched.every) {
|
|
66
|
+
const interval = parseDurationMs(sched.every);
|
|
67
|
+
if (interval <= 0) return false;
|
|
68
|
+
if (row.lastRunAt === undefined) return true;
|
|
69
|
+
return row.lastRunAt + interval < now;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function countMissedFromRow(row: CronRow, now: number): number {
|
|
75
|
+
const sched = effectiveSchedule(row);
|
|
76
|
+
if (sched.every) {
|
|
77
|
+
const interval = parseDurationMs(sched.every);
|
|
78
|
+
if (interval <= 0) return 0;
|
|
79
|
+
if (row.lastRunAt === undefined) return 1;
|
|
80
|
+
const elapsed = now - row.lastRunAt;
|
|
81
|
+
return Math.max(0, Math.floor(elapsed / interval));
|
|
82
|
+
}
|
|
83
|
+
if (row.lastRunAt !== undefined) {
|
|
84
|
+
return countMissedOccurrences(row, row.lastRunAt, now);
|
|
85
|
+
}
|
|
86
|
+
if (row.nextRunAt !== undefined && row.nextRunAt < now) return 1;
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function computeDrift(row: CronRow, now: number): number | null {
|
|
91
|
+
if (row.lastRunAt === undefined) return null;
|
|
92
|
+
const expected = previousOccurrence(row, row.lastRunAt);
|
|
93
|
+
if (expected === null) {
|
|
94
|
+
// Fallback: for `every`, expected is lastRunAt floored to interval from origin.
|
|
95
|
+
const sched = effectiveSchedule(row);
|
|
96
|
+
if (sched.every) {
|
|
97
|
+
const interval = parseDurationMs(sched.every);
|
|
98
|
+
if (interval <= 0) return null;
|
|
99
|
+
// If nextRunAt is set, expected previous = nextRunAt - interval.
|
|
100
|
+
if (row.nextRunAt !== undefined) {
|
|
101
|
+
return row.lastRunAt - (row.nextRunAt - interval);
|
|
102
|
+
}
|
|
103
|
+
// No better anchor — drift unknown unless overdue.
|
|
104
|
+
if (row.lastRunAt + interval < now) {
|
|
105
|
+
return now - (row.lastRunAt + interval);
|
|
106
|
+
}
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return row.lastRunAt - expected;
|
|
112
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron leader election — N instances, one run (unified-theory · Clone axis).
|
|
3
|
+
*
|
|
4
|
+
* Lease lives on the reconciled cron row so the Console can show which
|
|
5
|
+
* instance holds it (console §9.6).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Cron row fields the lease mutates. */
|
|
9
|
+
export interface LeaseTarget {
|
|
10
|
+
/** Schedule name. */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** Instance currently holding the lease, if any. */
|
|
13
|
+
leaderInstanceId?: string;
|
|
14
|
+
/** Absolute lease expiry epoch-ms. */
|
|
15
|
+
leaderLeaseUntil?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Store surface required by {@link tryAcquireLease}. */
|
|
19
|
+
export interface LeaseStore {
|
|
20
|
+
/**
|
|
21
|
+
* @param name - Cron name
|
|
22
|
+
*/
|
|
23
|
+
get(name: string): Promise<LeaseTarget | undefined>;
|
|
24
|
+
/**
|
|
25
|
+
* @param row - Updated row
|
|
26
|
+
*/
|
|
27
|
+
put(row: LeaseTarget): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Optional atomic acquire — preferred when present (memory / sql).
|
|
30
|
+
*
|
|
31
|
+
* @param name - Cron name
|
|
32
|
+
* @param instanceId - Candidate instance
|
|
33
|
+
* @param now - Current epoch-ms
|
|
34
|
+
* @param leaseMs - Lease TTL
|
|
35
|
+
*/
|
|
36
|
+
acquireLease?(
|
|
37
|
+
name: string,
|
|
38
|
+
instanceId: string,
|
|
39
|
+
now: number,
|
|
40
|
+
leaseMs: number,
|
|
41
|
+
): Promise<boolean>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Options for {@link tryAcquireLease}. */
|
|
45
|
+
export interface AcquireLeaseOptions {
|
|
46
|
+
/** Cron name. */
|
|
47
|
+
readonly name: string;
|
|
48
|
+
/** This instance's id. */
|
|
49
|
+
readonly instanceId: string;
|
|
50
|
+
/** Current epoch-ms. */
|
|
51
|
+
readonly now: number;
|
|
52
|
+
/** Lease TTL in milliseconds. */
|
|
53
|
+
readonly leaseMs: number;
|
|
54
|
+
/** Backing store. */
|
|
55
|
+
readonly store: LeaseStore;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Try to acquire (or renew) the leader lease for a cron.
|
|
60
|
+
*
|
|
61
|
+
* Returns `true` when this instance holds the lease after the call —
|
|
62
|
+
* only then may it execute the schedule.
|
|
63
|
+
*
|
|
64
|
+
* @param options - Name, instance, clock, store
|
|
65
|
+
*/
|
|
66
|
+
export async function tryAcquireLease(
|
|
67
|
+
options: AcquireLeaseOptions,
|
|
68
|
+
): Promise<boolean> {
|
|
69
|
+
if (options.store.acquireLease) {
|
|
70
|
+
return options.store.acquireLease(
|
|
71
|
+
options.name,
|
|
72
|
+
options.instanceId,
|
|
73
|
+
options.now,
|
|
74
|
+
options.leaseMs,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const row = await options.store.get(options.name);
|
|
79
|
+
if (!row) return false;
|
|
80
|
+
|
|
81
|
+
const held =
|
|
82
|
+
row.leaderLeaseUntil !== undefined &&
|
|
83
|
+
row.leaderLeaseUntil > options.now &&
|
|
84
|
+
row.leaderInstanceId !== undefined &&
|
|
85
|
+
row.leaderInstanceId !== options.instanceId;
|
|
86
|
+
|
|
87
|
+
if (held) return false;
|
|
88
|
+
|
|
89
|
+
const next: LeaseTarget = {
|
|
90
|
+
...row,
|
|
91
|
+
leaderInstanceId: options.instanceId,
|
|
92
|
+
leaderLeaseUntil: options.now + options.leaseMs,
|
|
93
|
+
};
|
|
94
|
+
await options.store.put(next);
|
|
95
|
+
|
|
96
|
+
// Re-read: lose the race if another writer won.
|
|
97
|
+
const confirmed = await options.store.get(options.name);
|
|
98
|
+
return (
|
|
99
|
+
confirmed?.leaderInstanceId === options.instanceId &&
|
|
100
|
+
(confirmed.leaderLeaseUntil ?? 0) > options.now
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Release the lease when held by `instanceId` (best-effort).
|
|
106
|
+
*
|
|
107
|
+
* @param store - Lease store
|
|
108
|
+
* @param name - Cron name
|
|
109
|
+
* @param instanceId - Instance that should release
|
|
110
|
+
* @param now - Current epoch-ms
|
|
111
|
+
*/
|
|
112
|
+
export async function releaseLease(
|
|
113
|
+
store: LeaseStore,
|
|
114
|
+
name: string,
|
|
115
|
+
instanceId: string,
|
|
116
|
+
now: number,
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const row = await store.get(name);
|
|
119
|
+
if (!row) return;
|
|
120
|
+
if (row.leaderInstanceId !== instanceId) return;
|
|
121
|
+
if (row.leaderLeaseUntil !== undefined && row.leaderLeaseUntil <= now) return;
|
|
122
|
+
await store.put({
|
|
123
|
+
...row,
|
|
124
|
+
leaderInstanceId: undefined,
|
|
125
|
+
leaderLeaseUntil: undefined,
|
|
126
|
+
});
|
|
127
|
+
}
|