Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e5e1c8a
feat(init): add guided and reviewable project setup
wangcch Sep 4, 2026
3309eb7
docs(init): document guided setup and dry-run output
wangcch Sep 4, 2026
26ae2c5
docs(brand): add GitHub organization avatar
wangcch Sep 4, 2026
4da5038
feat(dev): synchronize manifest types and expose source diagnostics
wangcch Sep 5, 2026
f613f0b
test: verify editor feedback and packaged example workflows
wangcch Sep 5, 2026
685df93
docs: record workflow acceptance and isolated deployment requirements
wangcch Sep 5, 2026
48081e8
feat(website): add reviewed Chinese localization and SEO validation
wangcch Sep 5, 2026
cb64df0
fix(runtime): preserve HTTP body bytes within isolate memory limits
wangcch Sep 6, 2026
42635fd
feat(runtime): add lazy Streams and P1 JavaScript APIs
wangcch Sep 7, 2026
8659aba
fix(runtime): preserve URL paths and decode malformed query bytes
wangcch Sep 7, 2026
3429825
docs(runtime): document the supported P1 JavaScript contract
wangcch Sep 7, 2026
aa8d599
fix(docs): synchronize generated URL reference and translation
wangcch Sep 7, 2026
8a8fb1d
feat(runtime): add bounded encodeInto and Response factories
wangcch Sep 7, 2026
1621307
perf(runtime): add equivalent-output encoding comparisons
wangcch Sep 7, 2026
45fe230
docs(runtime): synchronize encoding and Response API contracts
wangcch Sep 7, 2026
3f02b47
feat(runtime): add Body bytes and TextEncoderStream
wangcch Sep 7, 2026
25810a4
fix(streams): bound promise retention during pipe backpressure
wangcch Sep 7, 2026
28aa78f
docs(runtime): document byte helpers and text encoding streams
wangcch Sep 7, 2026
0bc0185
fix(http): enforce request deadlines and drain connections on shutdown
wangcch Sep 8, 2026
fdc224f
feat(durable): persist completion and supervise finalization recovery
wangcch Sep 8, 2026
e83a7ce
fix(durable): validate completion leases inside storage transactions
wangcch Sep 8, 2026
b61b46f
feat(durable): persist fenced execution and idempotent delivery ident…
wangcch Sep 8, 2026
526ed89
fix(runtime): recover active tasks and preserve durable control acros…
wangcch Sep 8, 2026
6af9bb0
test(durable): add deployment crash gates and document recovery limits
wangcch Sep 8, 2026
32482a2
perf(runtime): cache durable exports per source revision
wangcch Sep 9, 2026
f60d86d
perf(runtime): wake durable scheduler after signal commits
wangcch Sep 9, 2026
87476d0
test(perf): add resource comparisons and bounded failure checks
wangcch Sep 9, 2026
f03d402
test(durable): isolate PostgreSQL live test schemas
wangcch Sep 9, 2026
0f253f9
chore(release): prepare 0.3.0 versions and documentation
wangcch Sep 9, 2026
484b2a7
chore(release): maintain changelog and validate release notes
wangcch Sep 9, 2026
2aaffca
test(cli): wait for worker exit before asserting recovery
wangcch Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/scripts/check-version-sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ runtime_constant="$(extract_quoted_version runtime-js/bootstrap/index.ts \
'.*runtimeJsVersion = "([0-9]+\.[0-9]+\.[0-9]+)";.*')"
web_api_constant="$(extract_quoted_version runtime-js/web-api/index.ts \
'.*webApiVersion = "([0-9]+\.[0-9]+\.[0-9]+)";.*')"
website_version="$(extract_quoted_version 'website/app/(home)/page.tsx' \
website_version="$(extract_quoted_version 'website/components/home/home-page.tsx' \
'.*softwareVersion: "([0-9]+\.[0-9]+\.[0-9]+)",.*')"
for version_entry in \
"runtime-js/bootstrap/index.ts:${runtime_constant}" \
"runtime-js/web-api/index.ts:${web_api_constant}" \
"website/app/(home)/page.tsx:${website_version}"; do
"website/components/home/home-page.tsx:${website_version}"; do
version_file="${version_entry%%:*}"
declared_version="${version_entry#*:}"
if [[ "$declared_version" != "$version" ]]; then
Expand Down
78 changes: 78 additions & 0 deletions .github/scripts/release-notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Extract one version's release body from the repository changelog."""
import argparse
from datetime import date
import json
from pathlib import Path
import re
import sys

ROOT = Path(__file__).resolve().parents[2]


def version_headings(text):
headings = []
fence = None
offset = 0
for line in text.splitlines(keepends=True):
marker = re.fullmatch(r" {0,3}(`{3,}|~{3,})([^\n]*)\n?", line)
if fence:
if (marker and marker[1][0] == fence[0]
and len(marker[1]) >= len(fence) and not marker[2].strip()):
fence = None
elif marker and (marker[1][0] == "~" or "`" not in marker[2]):
fence = marker[1]
else:
heading = re.fullmatch(r"## (.+?)\s*", line)
if heading:
headings.append((heading[1], offset, offset + len(line)))
offset += len(line)
if fence:
raise ValueError("unclosed changelog code fence")
return headings


def extract(text, version, require_date=False):
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?", version):
raise ValueError("expected an unprefixed release version")
text = text.replace("\r\n", "\n")
headings = version_headings(text)
matches = [i for i, h in enumerate(headings)
if re.fullmatch(r"\[" + re.escape(version) + r"\](?: - .+)?", h[0])]
if len(matches) != 1:
raise ValueError(f"expected exactly one changelog section for {version}")
i = matches[0]
if require_date:
dated = re.fullmatch(r"\[" + re.escape(version) + r"\] - (\d{4}-\d{2}-\d{2})", headings[i][0])
if not dated:
raise ValueError(f"release {version} requires a YYYY-MM-DD date, not a draft status")
try:
date.fromisoformat(dated[1])
except ValueError:
raise ValueError(f"invalid release date for {version}: {dated[1]}") from None
end = headings[i + 1][1] if i + 1 < len(headings) else len(text)
body = text[headings[i][2]:end].strip()
if not any(line.strip() and not line.lstrip().startswith('#') for line in body.splitlines()):
raise ValueError(f"empty changelog section for {version}")
return body + "\n"


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("version", nargs="?")
parser.add_argument("--changelog", type=Path, default=ROOT / "CHANGELOG.md")
parser.add_argument("--release-tag", help="require the matching vVERSION tag and a valid release date")
args = parser.parse_args()
version = args.version or json.loads((ROOT / "packages/tysel/package.json").read_text())["version"]
try:
if args.release_tag is not None and args.release_tag != f"v{version}":
raise ValueError(f"release tag {args.release_tag} does not match version {version}")
notes = extract(args.changelog.read_text(encoding="utf-8"), version,
require_date=args.release_tag is not None)
except (OSError, ValueError) as error:
parser.exit(1, f"release notes: {error}\n")
sys.stdout.write(notes)


if __name__ == "__main__":
main()
81 changes: 81 additions & 0 deletions .github/scripts/test-release-notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import importlib.util
from pathlib import Path
import unittest
import subprocess
import sys
import tempfile

spec = importlib.util.spec_from_file_location("release_notes", Path(__file__).with_name("release-notes.py"))
notes = importlib.util.module_from_spec(spec)
spec.loader.exec_module(notes)


class ReleaseNotesTests(unittest.TestCase):
def test_exact_version_and_boundaries(self):
text = "# Changelog\n\n## [Unreleased]\nFuture\n\n## [0.3.0] - Unreleased\n\n### Fixed\n- Recovery.\n\n## [0.2.0]\nOld\n"
self.assertEqual(notes.extract(text, "0.3.0"), "### Fixed\n- Recovery.\n")
self.assertEqual(notes.extract(text, "0.2.0"), "Old\n")

def test_prerelease_is_not_stable(self):
text = "## [0.3.0-rc.1]\nPreview\n## [0.3.0]\nStable\n"
self.assertEqual(notes.extract(text, "0.3.0-rc.1"), "Preview\n")
self.assertEqual(notes.extract(text, "0.3.0"), "Stable\n")

def test_fenced_headings_preserve_migration_steps(self):
for opening, interior, closing in [
("```markdown", "## Example heading", "```"),
("~~~~markdown", "## [0.3.0]\n~~~\n## More examples", "~~~~"),
(" ````", "```\n## [0.2.0]", " `````"),
]:
body = f"### Migration\n{opening}\n{interior}\n{closing}\n- Required migration step.\n"
text = f"## [0.3.0] - 2026-09-09\n{body}\n## [0.2.0]\nOld\n"
with self.subTest(opening=opening):
self.assertEqual(notes.extract(text, "0.3.0", require_date=True), body)
self.assertEqual(notes.extract(text.replace("\n", "\r\n"), "0.3.0"), body)
with self.assertRaisesRegex(ValueError, "unclosed"):
notes.extract("## [0.3.0]\n```\n## Example\n", "0.3.0")

def test_release_dates(self):
for suffix in ["", " - Unreleased", " - Unpublished draft", " - 2026-02-29", " - 2026-13-01", " - 2026-9-09"]:
text = f"## [0.3.0]{suffix}\n- Changes.\n"
with self.subTest(suffix=suffix):
self.assertEqual(notes.extract(text, "0.3.0"), "- Changes.\n")
with self.assertRaises(ValueError):
notes.extract(text, "0.3.0", require_date=True)
for version in ["0.3.0", "0.3.0-rc.1"]:
self.assertEqual(notes.extract(f"## [{version}] - 2024-02-29\n- Changes.\n", version, require_date=True), "- Changes.\n")

def test_cli_release_mode(self):
script = str(Path(__file__).with_name("release-notes.py").resolve())
with tempfile.TemporaryDirectory() as folder:
changelog = Path(folder) / "CHANGELOG.md"
for status, tag, succeeds in [
("Unreleased", None, True),
("Unreleased", "v0.3.0", False),
("2026-09-09", "v0.3.0", True),
("2026-09-09", "v0.2.0", False),
("2026-02-30", "v0.3.0", False),
]:
changelog.write_text(f"## [0.3.0] - {status}\n- Changes.\n")
command = [sys.executable, script, "0.3.0", "--changelog", str(changelog)]
if tag:
command += ["--release-tag", tag]
result = subprocess.run(command, capture_output=True, text=True)
with self.subTest(status=status, tag=tag):
self.assertEqual(result.returncode == 0, succeeds, result.stderr)
self.assertEqual(result.stdout, "- Changes.\n" if succeeds else "")

def test_missing_duplicate_empty_and_invalid_fail(self):
for text, version in [
("## [0.2.0]\nOld", "0.3.0"),
("## [0.3.0]\nA\n## [0.3.0] - Today\nB", "0.3.0"),
("## [0.3.0]\n\n### Fixed\n", "0.3.0"),
("## [0.3.0]\nValid", "v0.3.0"),
]:
with self.subTest(text=text, version=version), self.assertRaises(ValueError):
notes.extract(text, version)


if __name__ == "__main__":
unittest.main()
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: Validate release notes
run: |
python3 .github/scripts/test-release-notes.py
python3 .github/scripts/release-notes.py > /dev/null
- name: Classify changed files
id: classify
shell: bash
Expand Down
24 changes: 23 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,22 @@ env:
TYSEL_TOOLCHAIN_IMAGE: ghcr.io/wangcch/tysel-toolchain

jobs:
release-notes:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Validate changelog before building release artifacts
run: |
python3 .github/scripts/test-release-notes.py
args=()
if [[ "$GITHUB_REF_TYPE" == tag ]]; then
args+=(--release-tag "$GITHUB_REF_NAME")
fi
python3 .github/scripts/release-notes.py "${args[@]}" > /dev/null

policy:
needs: release-notes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand Down Expand Up @@ -100,6 +115,7 @@ jobs:
if-no-files-found: error

package-npm:
needs: release-notes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand All @@ -124,6 +140,7 @@ jobs:
if-no-files-found: error

package-rust-sdk:
needs: release-notes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand All @@ -139,6 +156,7 @@ jobs:
if-no-files-found: error

package-component-starters:
needs: release-notes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand Down Expand Up @@ -186,6 +204,7 @@ jobs:
if-no-files-found: error

build-linux:
needs: release-notes
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -260,6 +279,7 @@ jobs:
if-no-files-found: error

build-darwin:
needs: release-notes
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -543,13 +563,15 @@ jobs:
version="$(bash .github/scripts/check-version-sync.sh)"
channel="$(bash .github/scripts/release-channel.sh "$version")"
bash .github/scripts/verify-release-assets.sh target/publish "$version"
python3 .github/scripts/release-notes.py "$version" --release-tag "$GITHUB_REF_NAME" > target/release-notes.md
if gh release view "$GITHUB_REF_NAME" --json isDraft > target/release-state.json 2>/dev/null; then
jq -e '.isDraft == true' target/release-state.json > /dev/null || {
echo "release is already public and immutable" >&2
exit 1
}
gh release edit "$GITHUB_REF_NAME" --notes-file target/release-notes.md
else
release_flags=(--draft --verify-tag --generate-notes)
release_flags=(--draft --verify-tag --notes-file target/release-notes.md)
[[ "$channel" == stable ]] || release_flags+=(--prerelease)
gh release create "$GITHUB_REF_NAME" "${release_flags[@]}"
fi
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,18 @@ jobs:
- name: Check benchmark evidence loader
run: pnpm test:benchmarks
working-directory: website
- name: Check localization contracts
run: pnpm test:i18n && pnpm i18n:check
working-directory: website
- name: Build static website
run: pnpm build
working-directory: website
- name: Verify localized HTML and section links
run: python3 scripts/check-localized-html.py
working-directory: website
- name: Verify sitemap and multilingual SEO
run: python3 scripts/check-seo.py
working-directory: website
- name: Verify generated documentation is committed
run: |
changes="$(git status --short --untracked-files=all -- website/content)"
Expand Down
84 changes: 84 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Changelog

User-visible changes are recorded here. Release notes reuse the matching version
section; detailed contracts and procedures remain in the linked documentation.
Use an exact version heading (including any prerelease suffix), and replace
`Unreleased` with the release date before tagging. Keep entries focused on user
impact rather than individual commits. PR checks allow `Unreleased`; tag releases
require a matching version and a valid date before artifact builds start.

## [0.3.0] - 2026-09-09

### Upgrade notes

- Durable stores migrate from log v1/v2 to v3 on open. Stop old writers and back
up the complete store first; rollback requires the pre-upgrade snapshot and
matching runtime. Legacy tasks without wakeup or execution records are not
automatically recovered. See the [upgrade procedure](https://tysel.dev/docs/operations/production#durable-log-v3-upgrade).
- External writes still need provider idempotency or reconciliation. Applications
own outbox delivery: admission retries require the original bundle, and completed
tasks must be retained beyond the retry horizon because pruning removes deduplication
records. See the [durable contract](https://tysel.dev/reference/runtime/durable).

### Added and fixed

- Recover active durable tasks after process death and preserve recovery across
hot reload, with execution fencing and retries for temporary completion-storage failures.
- Add stable idempotency keys for durable admission and signals, plus retained
results and cleanup through `tysel durable result` and `tysel durable prune`.
- Add Streams, `TextEncoder.encodeInto`, `TextEncoderStream`, body `bytes()` helpers,
`Response.error()` and `Response.redirect()`; fix HTTP body byte preservation,
URL path handling and malformed query decoding.
- Add guided project setup, automatic manifest-type synchronization during
development, and structured diagnostics for manifest and import errors.
- Include queue time and body consumption in HTTP deadlines, and drain connections
during shutdown.
- Add reviewed Chinese documentation and localized website navigation.
- Fix promise retention during stream backpressure.

### Performance

- Cache durable export metadata per source revision and wake the local scheduler
after signal commits, while retaining periodic polling for recovery and other instances.

## [0.2.0] - 2026-09-04

- Added verified cross-target packaging for Linux and macOS on x64 and arm64,
with authenticated runtime downloads and offline reuse of verified cached
runtimes. Cross-target builds require a managed installation and trust policy.
- Published Linux toolchain images and hardened `tysel image` handling of
existing binaries, release sidecars and generated container metadata.
- Added `tysel test --filter` and `--list`, including machine-readable discovery
and exact test-ID selection.
- Added source-mapped development diagnostic events and improved installer
feedback and the development server's displayed URL.
- Upgraded QuickJS-NG to 0.16.2.

[Release](https://github.com/wangcch/tysel/releases/tag/v0.2.0) ·
[Changes since 0.1.1](https://github.com/wangcch/tysel/compare/v0.1.1...v0.2.0)

## [0.1.1] - 2026-09-02

- Corrected TypeScript SDK publication to the scoped `@tysel/sdk` package.
- Made registry publication recoverable after partial release failures.

[Release](https://github.com/wangcch/tysel/releases/tag/v0.1.1) ·
[Changes since 0.1.0](https://github.com/wangcch/tysel/compare/v0.1.0...v0.1.1)

## [0.1.0] - Unpublished draft

The tag exists, but its GitHub Release remains a draft with no publication date.
This section records the tagged baseline, not a completed public release.

- Introduced native packaging of TypeScript applications as a single executable,
with host-platform builds and a Web-API-first runtime.
- Provided HTTP services, cron and queue workers, MCP tools and explicit host
capability configuration.
- Added durable steps, effects, sleeps, retries and signal waits with persisted
boundary history. Active-execution crash recovery was not guaranteed; the
recovery and admission fixes are recorded under 0.3.0.
- Included experimental Rust and Go Wasm Component tasks with bounded execution.
- Provided project setup, development, testing, compatibility checks and signed
release verification tooling.

[Tagged source](https://github.com/wangcch/tysel/tree/v0.1.0)
Loading