Commit Graph

126 Commits

Author SHA1 Message Date
clockwork-labs-bot 84cfe5a920 Fix TypeScript table handle casing (#5286)
## Compatibility note

This PR changes the generated TypeScript table/view handles for
snake_case module accessors to the target-language TypeScript accessor
casing, but keeps the old snake_case handles as deprecated aliases.

Generated TS clients now expose the intended TypeScript accessor casing:

- `ctx.db.databaseTree`
- `tables.loggedOutPlayer`
- `tables.myProgress`

The old snake_case handles continue to work and are marked deprecated in
the generated type surface:

- `ctx.db.database_tree` still works as an alias for
`ctx.db.databaseTree`
- `tables.logged_out_player` still works as an alias for
`tables.loggedOutPlayer`
- `tables.my_progress` still works as an alias for `tables.myProgress`

The database canonical names stay unchanged. For example, the generated
TypeScript handle is camelCase, but the table metadata still has `name:
'logged_out_player'`.

## Remaining API breakage risk

This should be non-breaking for normal generated TypeScript client
usage, including:

- direct table access through `conn.db.snake_case`
- callback contexts like `ctx.db.snake_case`
- exported query builders like `tables.snake_case`
- type inference for deprecated aliases

There are still a few edge cases where users may notice an API shape
change:

- Code that enumerates generated table handles with `Object.keys`,
`Object.entries`, `for...in`, or similar will now see both the
target-language handle and the deprecated snake_case alias.
- Code with pathological table/view accessor collisions may not get
every possible alias. The normal case is `logged_out_player` ->
generated handle `loggedOutPlayer` plus deprecated alias
`logged_out_player`. Pathological cases are shapes like:
- `foo_bar` and `fooBar`: both want the generated TypeScript handle
`fooBar`, so generated clients cannot provide two distinct
`tables.fooBar` entries.
- `foo_bar` and some other table/view whose target-language handle is
already `foo_bar`: the deprecated `foo_bar` alias for `foo_bar` ->
`fooBar` would shadow the other generated handle, so the generator skips
that alias.
- Code that compares the exact generated `index.ts` text, emitted
declaration text, or public type names will see new helper/types such as
`DbView`, `Tables`, and the alias metadata.

TypeScript modules themselves are not expected to break from this unless
they consume generated TypeScript client bindings or depend on exact
generated client object keys.

## Terminology

The casing proposal uses **canonical name** for the database/internal
name. That is language-independent. It uses **accessor name** for the
source/module/client-facing identifier that codegen derives
language-specific handles from.

This PR keeps database canonical names unchanged. It changes the
generated TypeScript accessor handles to match TypeScript casing while
retaining the old generated handles as deprecated aliases.

## Why

The TypeScript code generator was using `table.accessor_name` and
`view.accessor_name` directly as object keys in `tablesSchema`. That
preserved the raw module accessor spelling instead of applying the
target-language `Case::Camel` conversion for TypeScript handles. Per the
casing policy, client codegen should use the server accessor name as its
source and apply the target-language conversion.

## What changed

- Convert generated TypeScript table and view handle keys with
`Case::Camel`.
- Generate deprecated snake_case aliases for table/view handles when the
old generated handle differs from the target-language TypeScript handle.
- Keep runtime table metadata and database canonical names unchanged.
- Avoid duplicate runtime table definitions.
- Add a type-only aliased schema so callback contexts infer deprecated
aliases too.
- Add regression coverage for TypeScript-cased handles and deprecated
aliases.
- Update checked-in generated TS bindings and references that change
under this fix.

Generated code in this repo that changes as a result:

- `crates/bindings-typescript/test-app/src/module_bindings/index.ts`
-
`crates/bindings-typescript/test-react-router-app/src/module_bindings/index.ts`
-
`crates/bindings-typescript/test-solid-router/src/module_bindings/index.ts`
-
`crates/bindings-typescript/case-conversion-test-client/src/module_bindings/index.ts`
- `demo/Blackholio/client-ts/src/module_bindings/index.ts`
- `templates/hangman-react-ts/src/module_bindings/index.ts`
- `templates/money-exchange-react-ts/src/module_bindings/index.ts`
- `crates/codegen/tests/snapshots/codegen__codegen_typescript.snap`

I also updated the corresponding client/test references to the generated
TypeScript-cased handles.

## Verification

- `cargo fmt --all --check`
- `cargo test -p spacetimedb-codegen typescript`
- `pnpm --dir crates/bindings-typescript test --
tests/client_query.test.ts tests/db_connection.test.ts`
- `pnpm --dir crates/bindings-typescript exec vitest run
--typecheck.enabled tests/client_query.test.ts
tests/db_connection.test.ts`
- `git diff --check`

The explicit Vitest typecheck command still reports existing global
typecheck errors from unrelated files loaded by the test project,
including missing `spacetimesys@2.x` ambient modules and existing test
type issues, but the touched alias tests report `Type Errors no errors`
and the earlier generated-code `declare override` issue is gone.

---------

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: Ryan <r.ekhoff@clockworklabs.io>
2026-06-26 13:01:35 +00:00
Ryan a08663c7b9 Version bump 2.7.0 (#5399)
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

- Bumps version to 2.7.0

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

None

# Expected complexity level and risk

- 1 - this is just a version bump

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] Version number is correct (`2.7.0`)
- [x] BSL license file has been updated with the new date and version
number
2026-06-22 04:08:56 +00:00
joshua-spacetime 156d515afe Remove Promise.withResolvers and replace with deferred pattern (#5384)
# Description of Changes

Removes all references to `Promise.withResolvers` from the codebase
since it's not supported universally and replaces it with the classic
pre-ES2024 deferred pattern. See #5031 and #5342.

Also adds a lint to avoid re-introducing it in the future.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

...
2026-06-18 14:21:12 +00:00
Ryan 31fd1c8c33 Version bump 2.6.0 (#5326)
# Description of Changes

* Bump version to 2.6.0

# API and ABI breaking changes

None

# Expected complexity level and risk

* 1 - this is just a version bump

# Testing

- [X] Version number is correct (`2.6.0`)
- [X] BSL license file has been updated with the new date and version
number
2026-06-16 01:11:36 +00:00
Zeke Foppa fbee3fcbc1 Consolidate template versions (#5228)
# Description of Changes

It turns out that different templates have different behavior for
whether they get a major+minor version constraint, a major+minor+patch
version constraint, or, surprisingly, just a major version constraint.
See https://github.com/clockworklabs/SpacetimeDB/issues/5229 for a bit
more detail.

This PR brings them all in line to major+minor.

This fixes a bug where we could release a newer version of the
server+CLI, but not the crates, and that would cause the CLI to
initialize some templates to expect a version number that did not exist.

**I am not 100% sure that this doesn't have surprise consequences**,
since this is a weird situation in the first place.

# API and ABI breaking changes

None

# Expected complexity level and risk

2

# Testing
spot tests, but more importantly some template smoketests have been
added to check that the version constraints are now `major.minor` on
some representative templates.

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
2026-06-10 21:10:00 +00:00
Ryan 0abf20b959 Bump version to 2.5.0 (#5258)
# Description of Changes

Bump version to `2.5.0` for new release

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1

# Testing

None
2026-06-10 02:56:58 +00:00
Zeke Foppa 31699135d0 Bump the rest of the package versions to 2.4.1 (#5227)
# Description of Changes

The previous PR only bumped the Rust packages since that's what we were
releasing. Since we may release more, we can now bump the rest of the
versions.

(This is harmless if we don't end up releasing the other packages
anyway; I could have just done this in the first place).

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

CI only

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-06-04 19:26:30 +00:00
Zeke Foppa 10ebb2ba42 Bump versions to 2.4.1 (#5216)
# Description of Changes

In preparation for upcoming release.

**This only bumps the Rust+CLI versions**

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

existing CI

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-06-04 04:28:06 +00:00
Muhammad Amin Saffari Taheri d16f824217 Add SolidJS integration with docs quickstart (#5052)
# Description of Changes

This PR adds support for Solid.
Related issue: https://github.com/clockworklabs/SpacetimeDB/issues/4820

The initial implementation was with an LLM, since Solid and React are
very similar. Then I improved on it.
The docs though are completely written by the LLM, I just read through
them once to make sure there aren't any problems.

# API and ABI breaking changes

There are no API or ABI changes. Just added support for Solid.

# Expected complexity level and risk

I'd say around 1 or 2. I haven't tested it too much except for the
example that I added.

# Testing

I'll be testing it more with the test app I'm planing to build.
- [ ] I've added the test solid router similar to test react router, and
it was working, but it'll be nice if someone else can verify as well.
- [ ] I'll be testing it more with the test app I'm planing to build

---------

Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
2026-06-03 22:23:36 +00:00
clockwork-tien d79a769a67 Bump Angular SDK version in angular-ts template (#5139)
# Description of Changes
- Bump Angular to ^21.2.12 in angular-ts template + sdk devDeps

<!-- Please describe your change, mention any related tickets, and so on
here. -->

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk
1

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [ ] <!-- maybe a test you want to do -->
- [ ] <!-- maybe a test you want a reviewer to do, so they can check it
off when they're satisfied. -->

---------

Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
2026-06-03 04:14:44 +00:00
Jason Larabie 99b5ef67f5 Add launchpad tag to llm-chat and chat-react (#5178)
# Description of Changes

Add the missing tag for `llm-chat-ts` and `chat-react-ts` to add them to
`Launchpad` on the templates page.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1 - small template config

# Testing

Double checked both still work with `spacetime init` locally to create
the project with latest template change.
2026-06-02 21:09:50 +00:00
Ryan b3547448cf Version bump 2.4.0 (#5162)
# Description of Changes

* Bumps version to 2.4.0

# API and ABI breaking changes

None

# Expected complexity level and risk

* 1 - this is just a version bump

# Testing

- [X] Version number is correct (`2.4.0`)
- [X] BSL license file has been updated with the new date and version
number

---------

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
2026-06-02 18:08:43 +00:00
Jason Larabie 60899b1aec Add new money exchange TS template (#5134)
# Description of Changes

Add a simple account based exchange demo, each account is created with
$100.00 and create a nickname. The data is tracked with a simple double
entry for both accounts, you select an account and send money.
- TypeScript module 
- Simple React front-end

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1 - Added a small template

# Testing

- [x] Ran through `spacetime init` against local
2026-06-02 09:04:40 +00:00
Jason Larabie b6b072e3a1 Add new hangman TS template (#5119)
# Description of Changes

Add a small Hangman demo as one of our templates. One single game for
all players at once to keep the design small.
- TypeScript module 
- Simple React front-end

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1 - Added a small template

# Testing

- [x] Ran through `spacetime init` against local
2026-06-02 01:16:24 +00:00
Jason Larabie d2c5902b87 Add new LLM chat template (#5150)
# Description of Changes

Adds a small template to show adding OpenRouter/OpenAI to do chat
completion. Designed as a small ChatGPT clone with each user setting up
their API key and having chat threads.
- TypeScript module 
- Simple React front-end

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1 - Adding another small TypeScript template

# Testing

- [x] Ran through `spacetime init` against local
2026-06-01 22:00:41 +00:00
joshua-spacetime 4cf01ed964 Add required ci check for keynote-2 benchmark (#5078)
# Description of Changes

Adds a new required ci check for keynote-2 benchmark regressions. The
test runs for 60s and fails if throughput < 300K TPS.

Note, this check will be flaky as long as it's running concurrently with
other CI jobs. It may need a dedicated runner/host machine. Although it
may be sufficient to only schedule one runner/VM to a single host
machine at a time. I'll need to sync with @jdetter to determine the best
way forward here.

UPDATE: We're using a dedicated runner. See the **Testing** section.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

2

Mainly copy-paste from the other CI workflows.

# Testing

This job now uses `spacetimedb-benchmark-runner` which is entirely
dedicated to this one CI job. I've tested this at different times of the
day when the CI runners are under load and not. The performance is
consistent and the test isn't flaky. It has passed every time.
2026-05-28 19:16:45 +00:00
Zeke Foppa 0305a24fdc Bump versions to 2.3.0 (#5120)
# Description of Changes

Bump version numbers to 2.3.0 in preparation for an upcoming release

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing
CI 🤷

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-05-26 18:25:12 +00:00
Zeke Foppa c39141de6a Undo .npmrc in templates (#5084)
# Description of Changes

See https://github.com/clockworklabs/SpacetimeDB/issues/5083 for
motivation.

# API and ABI breaking changes

None

# Expected complexity level and risk

2

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] `cargo run -pspcaetimedb-cli -- init --template nodejs-ts` no
longer creates a directory with `.npmrc`

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-05-21 16:21:22 +00:00
Zeke Foppa fead68c308 CI - enforce minimum pnpm package age (#5032)
# Description of Changes

Due to the relatively frequent supply chain attacks on especially npm
packages, we're instituting a minimum package age in the whole repo.

- Globally set a minimum npm package age in CI
- Best-effort set npm package age using `.npmrc` beside any
`package.json`
- Add CI checks that pnpm version and minimum package age values are the
same everywhere

# API and ABI breaking changes

None

# Expected complexity level and risk

2

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] CI passes
- [x] if I remove a `.npmrc` then `cargo ci lint` fails
- [x] if I change a value in `.npmrc` then `cargo ci lint` fails

---------

Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-05-16 12:47:56 -07:00
bradleyshep 16a2a8fd39 Keynote 2 benchmark updates & refinements (#4997)
### This PR builds on #4978

# Description of Changes

Methodology refresh of `templates/keynote-2/` with retry-policy cleanup
and updated benchmark reporting.

- **Retry policy normalized for benchmark fairness.**
- Removed outer benchmark retry loop in
`src/scenario_recipes/rpc_single_call.ts` (single-attempt call path).
- Removed Cockroach connector-side retry wrapper in
`src/connectors/rpc/cockroach_rpc.ts`.
- Kept transaction-boundary retry in RPC servers (`withTxnRetry` in
pg/crdb/supabase RPC servers).
- **Per-second time-series sampling** added to `core/runner.ts`. Each
run emits a `timeSeries` array for warmup/decay/collapse analysis.
- **Bench CLI** supports `--alpha 0,1.5` (CSV), `--runs N`, and
`--prep-between-alphas`, writing one JSON per `(connector, alpha, run)`
tuple.
- **Helper scripts added:** `start-bench.sh`, `stop-bench.sh`,
`check-bench.sh`, `bench-stats.py`, `plot-bench.py`.
- **README** updated with refreshed measurements and methodology notes.
- Removed unused Docker files.

# Methodology Notes

- Benchmark client/scenario path is now single-attempt (no stacked outer
retries).
- Retry handling is kept at the server transaction layer for retryable
SQL transaction errors.
- Comparisons should be interpreted per topology profile (default local
vs 5-node+HAProxy), and with identical runtime knobs per run (`clients`,
`pipelining`, `max_pool`).

# Updated Results

## Alpha = 0

| System | clients | pipelining | max_pool | TPS | TPS Stddev | p50 lat
ms | p99 lat ms |
|---|---:|---:|---:|---:|---:|---:|---:|
| SpacetimeDB | 64 | 40 | N/A | 279,024 | 4,763 | 8 | 12 |
| Node.js + SQLite | 64 | off | N/A | 3,121 | 80 | 19 | 40 |
| Node.js + Supabase | 64 | off | 64 | 7,362 | 1,179 | 6 | 18 |
| Bun + Postgres | 64 | off | 64 | 10,729 | 146 | 5 | 11 |
| Node.js + Postgres | 64 | off | 64 | 9,904 | 223 | 6 | 11 |
| Node.js + PlanetScale (SN) | 64 | off | 64 | 4,535 | 117 | 14 | 20 |
| Node.js + PlanetScale (HA) | 384 | off | 384 | 4,275 | 135 | 89 | 110
|
| Convex | 64 | off | N/A | 1,140 | 118 | 53 | 62 |
| Node.js + CockroachDB (5 node) | 320 | off | 320 | 4,253 | 561 | 71 |
120 |
| HAProxy - Node.js + CockroachDB (5 node) | 320 | off | 320 | 5,481 |
566 | 57 | 95 |

## Alpha = 1.5

| System | clients | pipelining | max_pool | TPS | TPS Stddev | p50 lat
ms | p99 lat ms |
|---|---:|---:|---:|---:|---:|---:|---:|
| SpacetimeDB | 64 | 40 | N/A | 303,919 | 4,712 | 7 | 11 |
| Node.js + SQLite | 64 | off | N/A | 3,188 | 73 | 18 | 39 |
| Node.js + Supabase | 64 | off | 64 | 2,534 | 57 | 2 | 197 |
| Bun + Postgres | 64 | off | 64 | 2,772 | 61 | 7 | 13 |
| Node.js + Postgres | 64 | off | 64 | 961 | 25 | 10 | 16 |
| Node.js + PlanetScale (SN) | 64 | off | 64 | 235 | 12 | 20 | 2,504 |
| Node.js + PlanetScale (HA) | 384 | off | 384 | 248 | 13 | 416 | 10,121
|
| Convex | 64 | off | N/A | 126 | 52 | 20 | 1,081 |
| Node.js + CockroachDB (5 node) | 320 | off | 320 | 0.03 | 0.18 | 698 |
9,695 |
| HAProxy - Node.js + CockroachDB (5 node) | 64 | off | 64 | 6.87 | 9.12
| 5,943 | 9,880 |

## Alpha = 0 PIPELINED TEST

| System | clients | pipelining | max_pool | TPS | TPS Stddev | p50 lat
ms | p99 lat ms |
|---|---:|---:|---:|---:|---:|---:|---:|
| Node.js + SQLite | 64 | 40 | N/A | 2,977 | 84 | 722 | 747 |
| Node.js + Supabase | 64 | 40 | 64 | 8,874 | 308 | 284 | 303 |
| Bun + Postgres | 64 | 40 | 64 | 10,184 | 120 | 250.1 | 260.5 |
| Node.js + Postgres | 64 | 40 | 64 | 9,165 | 145 | 276 | 290 |
| Node.js + PlanetScale (SN) | 64 | 40 | 64 | 4,325 | 85 | 590 | 604 |
| Node.js + PlanetScale (HA) | 384 | 40 | 384 | 3,355 | 327 | 4,354 |
4,438 |
| Convex | 64 | 40 | N/A | 1,154 | 134 | 2,119 | 2,150 |
| Node.js + CockroachDB (5 node) | 320 | 40 | 320 | 4,250 | 766 | 3,030
| 3,161 |
| HAProxy - Node.js + CockroachDB (5 node) | 320 | 40 | 320 | 5,992 |
1,765 | 2,431 | 2,562 |

# API and ABI Breaking Changes

No engine API/ABI changes. Internal keynote-template shape changes only.

- `BenchOptions.alpha: number` → `alphas: number[]`.
- New fields: `runs`, `prepBetweenAlphas`.
- `RunResult` now includes required `timeSeries`.
- Output JSON filename format is
`test-1-<connector>-a<alpha>-<timestamp>.json`.

# Expected Complexity Level and Risk

**3.** Contained to `templates/keynote-2/`, but cross-cutting across
runner/CLI/connectors/RPC servers/scripts/docs.

# Testing

Reviewer smoke test:

```bash
pnpm run prep
pnpm run bench --alpha 0,1.5 --connectors postgres_rpc --seconds 30 --runs 1
ls runs/test-1-postgres_rpc-a*.json   # expect 2 files

---------

Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
2026-05-14 11:07:12 -04:00
joshua-spacetime 9abc04bb28 Rollback prepared statements (#4979)
This reverts commit c98bdf90e3.
2026-05-08 23:33:11 +00:00
Zeke Foppa 6108b791e9 CI - Fix gen-quickstart check (#4977)
# Description of Changes

CI was running `gen-quickstart.sh` and then checking for a diff.. but it
was checking in the wrong directory.

I have also regenerated the files because the fixed check was failing.

# API and ABI breaking changes

None.

# Expected complexity level and risk

1

# Testing

- [x] CI passes
- [x] updated CI failed without the changes to the other files

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-05-08 12:51:05 +00:00
joshua-spacetime eddb19bf48 Update keynote readme with updated benchmark figures (#4975)
# Description of Changes

Update keynote readme with updated benchmark figures

# API and ABI breaking changes

N/A

# Expected complexity level and risk

0

# Testing

N/A
2026-05-07 08:00:35 +00:00
joshua-spacetime eb0645cedb Remove distributed benchmark harness (#4967)
# Description of Changes

Never used

# API and ABI breaking changes

N/A

# Expected complexity level and risk

0

# Testing

N/A
2026-05-06 22:15:52 -04:00
John Detter eb11e2f5c4 Version bump 2.2.0 (#4916)
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

- Bumps version to 2.2.0

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

None

# Expected complexity level and risk

- 1 - this is just a version bump

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] Version number is correct (`2.2.0`)
- [x] BSL license file has been updated with the new date and version
number

---------

Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-04-30 19:24:41 +00:00
Zeke Foppa 70db721c3a Revert breaking PRs (#4881)
# Description of Changes

Revert the following PRs that have caused some breakage:
```
a32cffa76 Finish refactoring out replay (#4850)
d639be0af Replay: some code motion & reuse `ReplayCommittedState` (#4849)
78d6b6f7d Update NativeAOT-LLVM infrastructure to current ABI (#4515)
d5c1738c1 Better module backtraces for panics and whatnot (#577)
6f23b19f3 Wait for database update to become durable (#4846)
81c9eab86 Add `spacetime lock/unlock` to prevent accidental database deletion (#4502)
809aebd7c Move field `replay_table_updated` to `ReplayCommittedState` (#4807)
21b58ef99 Update axum (#2713)
b5cadff7a Extract replay stuff out of `CommittedState`, part 1 (#4804)
```

I also updated the Python smoketests for breakage introduced in
https://github.com/clockworklabs/SpacetimeDB/pull/4502. Reverting that
PR caused conflicts, so this fix is more straightforward.

# API and ABI breaking changes

Maybe kind of, but we haven't released any of these.

# Expected complexity level and risk

1

# Testing

Ask @bfops about testing

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-04-23 14:54:23 -07:00
DexterKoelson 59ac77970f Add useProcedure React hook and export procedures from codegen (#4752)
Closes #4751

# Description of Changes
- Add `useProcedure` React hook that mirrors `useReducer` which returns
a stable, typed callback that queues calls until the connection is ready
- Add `ProcedureParamsType` and `ProcedureReturnType` utility types to
`type_utils.ts`
- Update TypeScript codegen to emit `export const procedures =
__convertToAccessorMap(proceduresSchema.procedures)` in generated module
bindings, matching the existing pattern for reducers

```ts
import { procedures } from './module_bindings';
import { useProcedure } from 'spacetimedb/react';

const doSomeThing = useProcedure(procedures.doSomeThing);
const result = await doSomeThing({ foo: "..." });
```

# API and ABI breaking changes
None. Additive only — new hook export and new codegen line.

# Expected complexity level and risk
Low. The hook is a near-copy of `useReducer` adapted for procedure
signatures. The codegen change adds one line following the identical
pattern used for reducers.

# Testing
- [x] TypeScript SDK compiles with no new errors (`tsc --noEmit`)
- [x] All 170 existing tests pass (17 test files)
- [x] Tested end-to-end in a React app calling a procedure

---------

Co-authored-by: Jason Larabie <jason@clockworklabs.io>
2026-04-21 21:06:06 +00:00
Noa d5c1738c15 Better module backtraces for panics and whatnot (#577)
# Description of Changes


![image](https://github.com/clockworklabs/SpacetimeDB/assets/33094578/9c6356af-9b34-462a-8441-8bd859a73b86)

If these symbols aren't in the stack, it does no processing

# Expected complexity level and risk

1 - it's pretty self-contained, and backwards-compatible with the
existing logs data format

---------

Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
2026-04-21 13:08:57 +00:00
Leo Voon dea509bfc1 Add Astro TypeScript template (#4688)
## Summary
- add a new `templates/astro-ts` template based on the existing Astro +
SpacetimeDB integration work
- include Astro SSR, a React client island for realtime updates, and a
small `server:defer` example
- add Astro quickstart documentation under
`docs/docs/00100-intro/00200-quickstarts/00152-astro.md`

## Validation
- `pnpm install`
- `pnpm -F spacetimedb build`
- `pnpm -F ./templates/astro-ts build`

## Notes
- I did not add `astro-ts` to the built-in template listing in
`spacetime dev` docs, to match the current `nextjs-ts` pattern.

---------

Co-authored-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
2026-04-17 21:01:58 +00:00
bradleyshep 7143ed9722 Clean up keynote-2 template README & DEVELOP (#4624)
# Description of Changes

Refresh this stale PR against current `master`. Several original items
were already applied upstream or conflict with recent keynote-2 work
(#4616, #4647, #4678, #4682, #4698, #4703, #4743, #4745, #4753, #4757),
so those are dropped. What remains is the still-relevant subset, rebased
onto the current file structures.

**README.md:**
- Use `pnpm run demo` in Quick Demo (consistency with pnpm workspace)
- Add `--concurrency` and `--alpha` to demo options
- Add `--` separator to `docker compose run` bench example
- Fix hardware config punctuation (add comma before "OS:")
- Remove redundant Quick Start section; replace with link to DEVELOP.md
for prerequisites and CLI reference
- Add symlink for license

**DEVELOP.md:**
- Use `pnpm run` throughout (demo, prep, bench) instead of `npm run`
- Drop the `-- ` pass-through after `pnpm run bench` (not needed with
pnpm; matches the `#4703` testing examples)
- Add Rust to Prerequisites
- Add explicit list of valid connector names (`convex`, `spacetimedb`,
`bun`, `postgres_rpc`, `cockroach_rpc`, `sqlite_rpc`, `supabase_rpc`,
`planetscale_pg_rpc`)
- Update CLI reference defaults to match methodology (seconds: 1→10,
concurrency: 10→50)
- Condense `docker compose run` bench example to a single line with `--`
separator; fix `npm prep` → `pnpm run prep`

**src/opts.ts:** (CLI parsing moved here in `#4703`; original PR
targeted the now-gone inline parsers in `cli.ts`/`demo.ts`)
- `parseBenchOptions`: bench `--seconds` default `1` → `10`
- `parseDemoOptions`: demo `--concurrency` default `10` → `50`

**.env.example:**
- Comment out `USE_DOCKER=1` and `SKIP_CONVEX=1` so demo defaults
(convex, spacetimedb) work out of the box
- Comment out `CONVEX_USE_SHARDED_COUNTER=1` (still a supported knob,
just off by default)

# Dropped as superseded by master

- Rust Client README section tweaks (heading capitalization,
`bottlnecked`/`then` typo fixes) — section was removed by `#4753`
- Rename `SPACETIME_METRICS_ENDPOINT` → `USE_SPACETIME_METRICS_ENDPOINT`
— master's `src/config.ts` still reads the original name
- Connector-name fixes in examples (`sqlite` → `sqlite_rpc`, `postgres`
→ `postgres_rpc`) — already corrected on master

# API and ABI breaking changes

None.

# Expected complexity level and risk

**1** – Documentation and default-value changes. No functional changes
to core logic.

# Testing

- [x] `pnpm install` in `templates/keynote-2/` succeeds
- [x] `pnpm run bench --help` / `pnpm run demo --help` render with
valid-connec
2026-04-17 13:03:36 +00:00
Micha Huhn ddba47f76a fix: reorder Vue component (#4748)
Reorder the Vue component based on the Vue convention:
1. `<script>`
2. `<template>`

It's only a visual change of the code. It has no effect on the
functionality.
2026-04-15 21:14:08 +00:00
joshua-spacetime 0586258ece Remove warmup from distributed keynote bench (#4757)
# Description of Changes

Replaces the warmup period in the distributed version of the `keynote-2`
benchmark with an explicit start barrier.

1. Removes `--warmup-seconds` from the distributed benchmark flow
2. Adds an explicit `starting` phase where generators start their local
epoch and POST `/started`
3. Makes the coordinator wait for all participant start acknowledgements
before beginning the measured window
4. Adds `--start-ack-timeout-seconds` as the timeout for that start
barrier
5. Removes `warmupSeconds` from the distributed benchmark
protocol/result types

# API and ABI breaking changes

N/A

# Expected complexity level and risk

1.5

# Testing

N/A
2026-04-08 00:44:46 +00:00
joshua-spacetime 29a9d063a3 Remove rust client from keynote bench (#4753)
# Description of Changes

We only benchmark the typescript module and sdk now.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

N/A
2026-04-07 18:57:59 +00:00
joshua-spacetime b2fb04a8dc Update client defaults in keynote bench (#4745)
# Description of Changes

Updates ts client defaults for keynote-2 bench to optimize throughput.
These numbers were derived from runs on an apple m2, but I'd be
surprised if this configuration was sub-optimal on other platforms.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

Manual
2026-04-04 13:06:49 +00:00
joshua-spacetime 2043bc9140 Configure compression for keynote benchmark (#4743)
# Description of Changes

Message compression is now configurable for both the rust and typescript
keynote benchmark clients with the default being no compression. Before
this patch the rust client was using no compression and the typescript
client was using gzip by default.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

Manual
2026-04-03 22:19:14 +00:00
clockwork-labs-bot cd65a0785d Add .gitignore files to quickstart templates (#4609)
Fixes #4582

Templates created via `spacetime init` were missing `.gitignore` files,
causing build artifacts like `node_modules`, `target/`, `bin/`, `obj/`,
`dist/`, `.next/`, etc. to show up in git diffs when users initialize a
project inside a git repository.

Adds `.gitignore` files to the 15 templates that were missing one:

| Template | Ignores |
|----------|---------|
| Node/TS (`basic-ts`, `browser-ts`, `bun-ts`, `chat-react-ts`,
`deno-ts`, `nodejs-ts`, `react-ts`, `vue-ts`) | `node_modules`, `dist`,
`*.log` |
| Next.js (`nextjs-ts`) | `node_modules`, `.next`, `out`, `dist`,
`*.log` |
| Svelte (`svelte-ts`) | `node_modules`, `dist`, `.svelte-kit`, `*.log`
|
| Rust (`basic-rs`, `chat-console-rs`) | `target` |
| C# (`basic-cs`, `chat-console-cs`) | `bin`, `obj` |
| C++ (`basic-cpp`) | `target`, `build` |

All files also include `spacetime.local.json` and `.DS_Store`.

The 5 templates that already had `.gitignore` files (`angular-ts`,
`keynote-2`, `nuxt-ts`, `remix-ts`, `tanstack-ts`) are left unchanged.

Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
2026-03-30 21:02:56 +00:00
joshua-spacetime b98c68cef9 fix(keynote-2): split demo and bench CLI parsing (#4703)
# Description of Changes

This cleans up the `keynote-2` benchmark CLI and fixes the `bench` path
that was broken after `demo` and `bench` started sharing a single
parser. `demo` and `bench` now parse their own command grammars instead
of sharing one import-time parser. This avoids `bench` inheriting
`demo`-style validation and breaking on `test-1 --connectors ...`. in
addition, `bench` also uses the same `cac`-builder as `demo` now, and
I've deleted the unused `runner_1.ts`.

# API and ABI breaking changes

None

# Expected complexity level and risk

3

# Testing

Manually tested the following from `templates/keynote-2`:

- `pnpm bench test-1 --seconds 10 --concurrency 50 --alpha 1.5
--connectors spacetimedb`
- `pnpm demo --seconds 1 --concurrency 5 --alpha 1.5 --systems
spacetimedb --skip-prep --no-animation`
- `deno run --sloppy-imports -A src/demo.ts --help`
- `deno run --sloppy-imports -A src/cli.ts --help`
- `deno run --sloppy-imports -A src/cli.ts test-1 --seconds 10
--concurrency 50 --alpha 1.5 --connectors spacetimedb`
2026-03-26 01:18:12 +00:00
joshua-spacetime 5c6f308b74 Add distributed typescript benchmark harness (#4698)
# Description of Changes

The current keynote-2 benchmarks pipelines operations via
`MAX_INFLIGHT_PER_WORKER` in order to simulate a large number of client
connections while running the benchmark locally or on a single machine.

This patch adds a distributed benchmark mode for `templates/keynote-2`
so explicit SpacetimeDB client connections can be spread across multiple
machines without changing the existing single-process benchmark flow.

This is a pure extension. `npm run bench` and the current
`src/core/runner.ts` path remain intact. The new distributed path adds a
small coordinator/generator/control-plane harness specifically for
multi-machine ts client runs.

- New CLI entry points `bench-dist-coordinator`, `bench-dist-generator`,
and `bench-dist-control` were added
- The coordinator defines the benchmark window
- Generators begin submitting requests during warmup, but warmup
transactions are excluded from TPS
- Throughput is measured from the server-side committed transfer
counter, not client-local TPS
- Each connection runs closed-loop with one request at a time in this
distributed mode
- Connection startup is bounded-parallel (`--open-parallelism`) to avoid
a connection storm
- Verification is run by the coordinator after the epoch
- Late generators can be registered after a run to increase load on the
server incrementally
- If a participating generator dies and never sends `/stopped`, the
epoch result is flagged with an error so the run can be retried cleanly

See `DEVELOP.md` for instructions on how to run.

# API and ABI breaking changes

N/A

# Expected complexity level and risk

3

# Testing

Manual
2026-03-25 06:29:45 +00:00
Noa 7d0a0b97d0 Improve benchmark cli, make compatible with deno (#4647)
# Description of Changes

Now we get a `--help` for the benchmark, which is nicer. Also now can
run under deno, with `deno --sloppy-imports -A src/demo.ts` (might be
useful, deno's websocket is implemented in native code while node's is
implemented in JS). I removed the
[BOM](https://en.wikipedia.org/wiki/Byte_order_mark) because it seems
unintentional (only found in `templates/keynote-2`) and was causing a
little bit of weirdness.

Also, fix the rust benchmark client as a follow-up to #4616 

# Expected complexity level and risk

1

# Testing

- [x] Works under deno and has usage
2026-03-24 02:25:24 +00:00
bradleyshep cfa619f6af Keynote-2 sqlite fixes (#4678)
# Description of Changes

Fix the SQLite RPC benchmark so transfers actually persist and
verification produces useful output.

**`sqlite-rpc-server.ts`:**
- Add `.run()` to both Drizzle `tx.update()` chains in `rpcTransfer`.
Without this, the UPDATE statements were never executed — the benchmark
was only measuring SELECT + HTTP overhead, not real transactional
writes. This inflated SQLite TPS numbers significantly.
- Replace the generic `"internal error"` catch-all in `handleRpc` with
`rpcErr()`, which returns the actual error message to the client. Also
wrap the outer HTTP handler in a try/catch so errors outside `handleRpc`
are surfaced too.

**`sqlite_rpc.ts` (connector):**
- Detect `{ skipped: true }` from the server's verify endpoint and throw
a clear error telling the user to set `SEED_INITIAL_BALANCE` on the RPC
server process. Previously this was silently treated as success.

**`runner.ts` / `runner_1.ts`:**
- Log "Verification passed" on success and "Verification failed:
\<reason\>" (as a string, not a raw Error object) on failure, so the
outcome is always visible in bench output.

# API and ABI breaking changes

None.

# Expected complexity level and risk

1 

# Testing

- [ ] Run `npx tsx src/rpc-servers/sqlite-rpc-server.ts` (with
`SEED_INITIAL_BALANCE` set), then `npm run test-1 -- --connectors
sqlite_rpc --seconds 5` with `VERIFY=1`. Confirm TPS drops significantly
vs the old (broken) numbers and "Verification passed" appears.
- [ ] Run the same without `SEED_INITIAL_BALANCE` on the server process.
Confirm "Verification failed" with a message about the missing env var
(not "internal error").
2026-03-21 21:01:04 +00:00
joshua-spacetime 30cc7e0758 Make confirmed reads the default for the ts connector (#4682)
# Description of Changes

Make confirmed reads the default for the typescript benchmark client.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

N/A
2026-03-20 22:14:36 +00:00
Kim Altintop e22807cb98 core: Keep a reordering window in durability worker (#4677)
`RelationalDB::commit_tx` and `RelationalDB::commit_tx_downgrade`
release the exclusive transaction lock before calling
`DurabilityWorker::request_durability`. This can lead to transactions
appearing out-of-order on the worker queue, if transactions on the same
database instance are committed from multiple threads.

To mitigate this, the worker now keeps a small min-heap to re-order
transactions in that case. We expect the ordering issue to happen very
rarely, so the overhead of this should be negligible.

Supersedes #4661, which addressed the issue by holding the transaction
lock until durability request submission is completed.


# Expected complexity level and risk

2

# Testing

- [ ] Unit tests for the min-heap implementation
- [ ] Ran the keynote benchmark with various parameters 
      that would trigger the error without this patch
2026-03-20 22:10:45 +00:00
Zeke Foppa 480fd5841a Bump versions to 2.1.0 (#4681)
# Description of Changes

Bump versions to 2.1.0

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing
CI

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-03-20 21:53:51 +00:00
bradleyshep 7454329a08 Template README + template.json generation tool (#4570)
# Description of Changes

Adds `tools/templates/` scripts to derive template metadata from
manifests and generate READMEs from quickstart docs. Replaces slug-based
`builtWith` with manifest-derived data and hardcoded quickstart mappings
with discovery from docs.

**Manifest-based `builtWith`** (`update-template-jsons.ts`)

- Reads `package.json`, `Cargo.toml`, and `.csproj` to populate
`builtWith` in `.template.json`.
- Scoped npm packages normalize to scope (`@angular/core` → `angular`).
Excludes `@types/*`. Adds `nodejs` only for nodejs-ts when `@types/node`
is present.
- Root manifests processed before subdirs; primary framework first (e.g.
`react` before `spacetimedb` in react-ts). Dependencies reordered in
package.json where needed.

**Dynamic quickstart discovery** (`generate-template-readmes.ts`)

- Discovers template → quickstart by parsing `--template X` from files
in `docs/docs/00100-intro/00200-quickstarts/`.
- Optional `quickstart` override in `.template.json`; must resolve under
quickstarts dir.
- chat-react-ts has no quickstart; uses manual README.

**New:** `tools/templates/` (update-template-jsons.ts,
generate-template-readmes.ts, README, package.json, pnpm-lock).
**Modified:** all `templates/*/.template.json` (added `builtWith`),
new/generated `templates/*/README.md`.

# API and ABI breaking changes

None.

# Expected complexity level and risk

**1** – Dev tooling only. No runtime or API changes. Scripts are
isolated; failures only affect generated metadata and READMEs.

# Testing

- [ ] `cd tools/templates && pnpm run generate` completes without errors
- [ ] Spot-check `builtWith` and generated READMEs for a few templates
2026-03-20 13:01:58 +00:00
Noa 90b9e06ed2 Tidy up old code from the benchmark (#4616)
# Description of Changes

Fixes the `reducers.onTransfer is not a function` error - that was old
code left from the 1.x sdk. Also, regenerate the module_bindings and
make sure the rust and typescript modules are identical in that respect.

# Expected complexity level and risk

1

# Testing

- [x] Verified that `spacetime generate --lang typescript --module-path
spacetimedb --out-dir module_bindings` and `spacetime generate --lang
typescript --module-path rust_module --out-dir module_bindings` give the
same output.
- [x] Setting `USE_SPACETIME_METRICS_ENDPOINT` to `0` no longer causes
the error.
2026-03-13 13:01:20 +00:00
John Detter cf06f8ad3c Version bump to 2.0.5 (#4623)
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

- Bumps version to 2.0.5

# API and ABI breaking changes

None - this is just a version bump

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk

1 - just a version bump

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] License file has been properly updated, including the date change
- [x] CI is passing other than the internal check
2026-03-12 21:03:37 +00:00
Frederik 534fd306cf Fix Rust Chat App Tutorial not showing messages of other users live (#4588)
# Description of Changes

This fixes an Issue in the Rust [Chat App
Tutorial](https://spacetimedb.com/docs/tutorials/chat-app#creating-the-client),
that is caused by the [Event Type
Changes](https://spacetimedb.com/docs/upgrade/?client-language=rust&server-language=rust#event-type-changes)
in 2.0

This resulted in other clients not receiving new messages, since they
are now Event::Transaction and no longer included within the
Event::Reducer

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->
I don't think any? I only changed a tutorial, the real change was in 2.0

# Expected complexity level and risk

1

# Testing

- [x] I ran the Tutorial app with these changes and messages started to
appear as expected on the second client when writing something on the
first.



There might be more places where this is an Issue, I just noticed this
one while following the Tutorial.

---------

Signed-off-by: Frederik <39029799+OMGeeky@users.noreply.github.com>
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: Phoebe Goldman <phoebe@clockworklabs.io>
2026-03-11 21:54:32 +00:00
Phoebe Goldman 6882232108 Add a mode to the Rust SDK with additional logging to a file (#4566)
# Description of Changes

In the Rust client SDK, with this PR, doing
`.with_debug_to_file("path.txt")` on the connection builder will result
in the SDK logging additional verbose info to `path.txt`.

# API and ABI breaking changes

Adds a new user-facing-ish API to the Rust client SDK.

# Expected complexity level and risk

2? Some possibility of deadlock due to adding a new mutex, but this is
explicitly not for production use.

# Testing

- [x] Ran `chat-console-rs` locally with this enabled and got some debug
logs out of it.
2026-03-11 20:28:08 +00:00
John Detter 354dd45499 Version bump 2.0.4 (#4600)
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

- Bumps version to 2.0.4

# API and ABI breaking changes

None - this is just a version bump

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

# Expected complexity level and risk

1 - this is just a version bump

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

- [x] License file has been updated with correct version + time

---------

Signed-off-by: John Detter <4099508+jdetter@users.noreply.github.com>
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
2026-03-10 18:37:47 +00:00
Zeke Foppa 9f691f3cd8 Fix stale --project-path flag in templates (#4564)
# Description of Changes

We apparently have stale usage of `--project-path` in a ton of our
templates. This was renamed to `module-path` a while ago, but it looks
like that was only partially fixed in templates.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

I don't think it's _more_ broken 🤷

---------

Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
2026-03-05 21:23:36 +00:00