Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b0e71356f |
@@ -637,6 +637,10 @@ pub fn build(b: *std.Build) void {
|
||||
.{ .path = "build/app.zig", .pattern = "cannot resolve its TypeScript toolchain" },
|
||||
.{ .path = "build/app.zig", .pattern = "std.process.exit(1);" },
|
||||
});
|
||||
addFileContainsCheckStep(b, file_contains_checker, test_step, "test-scriptc-pin-cache-inputs", "Verify both TypeScript service build graphs invalidate frontend contracts when the exact scriptc pin changes", &.{
|
||||
.{ .path = "build.zig", .pattern = "if (spec.emit_services) check.addFileInput(b.path(\"packages/core/package.json\"));" },
|
||||
.{ .path = "build/app.zig", .pattern = "if (has_services) check.addFileInput(dep.path(\"packages/core/package.json\"));" },
|
||||
});
|
||||
addFileContainsCheckStep(b, file_contains_checker, test_step, "test-app-test-entry-analysis", "Verify the managed app test step force-analyzes the entry point (UiApp.create's Model-defaults rule must teach at `native test`, not ambush at `native build`)", &.{
|
||||
.{ .path = "build/app.zig", .pattern = "app_analysis.zig" },
|
||||
.{ .path = "build/app.zig", .pattern = "if (@hasDecl(app, \"main\")) _ = &app.main;" },
|
||||
@@ -3395,6 +3399,10 @@ fn externalCoreFixtureModule(
|
||||
for (frontend_sources) |source| {
|
||||
check.addFileInput(b.path(b.fmt("packages/core/src/{s}", .{source})));
|
||||
}
|
||||
// Service contracts echo the exact scriptc pin read from this manifest.
|
||||
// Declare that runtime read explicitly so a compiler bump invalidates a
|
||||
// warm build cache before the compile lane checks the echoed version.
|
||||
if (spec.emit_services) check.addFileInput(b.path("packages/core/package.json"));
|
||||
|
||||
// corewire projects the generated compile entry and its profile in
|
||||
// one invocation, so the profile's entry spelling and the facade
|
||||
|
||||
+5
-1
@@ -418,6 +418,10 @@ fn tsCoreStage(
|
||||
for (frontend_sources) |source| {
|
||||
check.addFileInput(dep.path(b.fmt("packages/core/src/{s}", .{source})));
|
||||
}
|
||||
// Service contracts echo the exact scriptc pin read from this manifest.
|
||||
// Declare that runtime read explicitly so a compiler bump invalidates a
|
||||
// warm build cache before the compile lane checks the echoed version.
|
||||
if (has_services) check.addFileInput(dep.path("packages/core/package.json"));
|
||||
|
||||
// corewire, compiled from the SDK dependency for the build host: one
|
||||
// invocation projects the generated compile entry and its profile,
|
||||
@@ -450,7 +454,7 @@ fn tsCoreStage(
|
||||
|
||||
// Ordinary service TypeScript is staged without core-subset rewrites.
|
||||
// The one service-boundary lowering turns NS1067's `{ kind, message }`
|
||||
// throw into the tagged Error shape scriptc 0.0.22 can catch from an
|
||||
// throw into the tagged Error shape scriptc 0.0.26 can catch from an
|
||||
// imported op; no deterministic profile fences participate here.
|
||||
const service_stage_run = b.addSystemCommand(&.{node});
|
||||
service_stage_run.addFileArg(dep.path("packages/core/scripts/stage_external_services.mjs"));
|
||||
|
||||
@@ -47,7 +47,7 @@ The question behind "can I use npm?" is almost always one of these five:
|
||||
|
||||
Put ordinary static-tier TypeScript under `src/services/` when the work needs Node built-ins, regexes, JSON, `Map`/`Set`, `Date`, classes, or ambient process authority. Each directly exported, non-default named synchronous function becomes an operation named `<module-basename>.<export>`. Its request and result may be shared, contract-encodable records; the core calls the generated constructor from `@native-sdk/services`, so success and failure still arrive as Msgs and record/replay remains offline.
|
||||
|
||||
This is compiled native code with no JavaScript engine. `native vendor . package@X.Y.Z` resolves an exact package graph once, with lifecycle scripts disabled, into checked-in `src/services/vendor/` sources and hash facts in app.zon. Builds are offline: every byte is verified and scriptc receives only the explicit `--npm-static` package list—never automatic or dynamic fallback. `native check` preserves scriptc's coverage note and refuses anything below 100% static coverage. A five-package 0.0.22 calibration passed three small source-shipping utilities and refused two (`nanoid` and `micromark`), so package support is intentionally selective. The host is a lazily spawned sibling executable on desktop today; [TypeScript services](/docs/typescript#typescript-services) covers typed calls, streaming, cancellation, authority, crash recovery, and the mobile/in-process limitation.
|
||||
This is compiled native code with no JavaScript engine. `native vendor . package@X.Y.Z` resolves an exact package graph once, with lifecycle scripts disabled, into checked-in `src/services/vendor/` sources and hash facts in app.zon. Builds are offline: every byte is verified and scriptc receives only the explicit `--npm-static` package list—never automatic or dynamic fallback. `native check` preserves scriptc's coverage note and refuses anything below 100% static coverage. A five-package 0.0.26 calibration passed three small source-shipping utilities and refused two (`nanoid` and `micromark`), so package support is intentionally selective. The host is a lazily spawned sibling executable on desktop today; [TypeScript services](/docs/typescript#typescript-services) covers typed calls, streaming, cancellation, authority, crash recovery, and the mobile/in-process limitation.
|
||||
|
||||
## Calling APIs, AI endpoints included
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@ case "parse":
|
||||
|
||||
The core never receives a synchronous handle. Its update returns a command, the typed result crosses back as the named Msg arm, and the runtime journals that result like every other effect. Replay parks the request and feeds the recorded result without launching the service executable.
|
||||
|
||||
Service modules are ordinary TypeScript on scriptc's static tier: Node built-ins such as `fs`, `path`, `process`, `os`, and `child_process`, plus `fetch`, regexes, JSON, `Map`/`Set`, `Date`, and classes are available when the pinned compiler supports them. They may import local service files, shared core-class data declarations, and exact vendored packages. Run `native vendor . escape-string-regexp@5.0.0` (always an exact `X.Y.Z`): it resolves with package scripts disabled, copies the flattened graph and license files into `src/services/vendor/`, and writes app.zon's exact name/version/tree-hash facts. Commit both. Every build re-hashes those offline bytes and gives scriptc an explicit `--npm-static` allowlist; anything below 100% static coverage fails `native check` with scriptc's coverage note preserved verbatim and a remediation. `auto` and `--dynamic` are never used. The pinned 0.0.22 calibration checked five small exact-version candidates: `escape-string-regexp` and the comma/space token parsers cleared 100%; `nanoid` (76%) and `micromark` (92%) were refused. npm support is selective—`native check` is the verdict for the package bytes you vendor.
|
||||
Service modules are ordinary TypeScript on scriptc's static tier: Node built-ins such as `fs`, `path`, `process`, `os`, and `child_process`, plus `fetch`, regexes, JSON, `Map`/`Set`, `Date`, and classes are available when the pinned compiler supports them. They may import local service files, shared core-class data declarations, and exact vendored packages. Run `native vendor . escape-string-regexp@5.0.0` (always an exact `X.Y.Z`): it resolves with package scripts disabled, copies the flattened graph and license files into `src/services/vendor/`, and writes app.zon's exact name/version/tree-hash facts. Commit both. Every build re-hashes those offline bytes and gives scriptc an explicit `--npm-static` allowlist; anything below 100% static coverage fails `native check` with scriptc's coverage note preserved verbatim and a remediation. `auto` and `--dynamic` are never used. The pinned 0.0.26 calibration checked five small exact-version candidates: `escape-string-regexp` and the comma/space token parsers cleared 100%; `nanoid` (76%) and `micromark` (92%) were refused. npm support is selective—`native check` is the verdict for the package bytes you vendor.
|
||||
|
||||
For incremental work, declare a final typed emit capability. The generated client opens an external channel, each `emit(chunk)` arrives through its channel-event Msg arm, and the function's return remains the one typed terminal result:
|
||||
|
||||
@@ -614,7 +614,7 @@ export function parseLarge(
|
||||
|
||||
The generated route adds `channelKey` and `event`. A terminal closes the channel after all accepted chunks. A final `ServiceCancellation` parameter opts the operation into cooperative cancellation; poll `cancelled()` or call `throwIfCancelled()` at bounded intervals. `Cmd.cancel(key)` is loud for a service stream: it flips that token, closes the channel, routes `cancelled` to `err`, and drops every later chunk. A declared deadline flips the same token but routes JSON with `kind: "timeout"`. The subprocess gets a short grace period to unwind and remains alive when it cooperates; an operation that ignores its token is hard-killed and the next request starts a clean host. `@streamBuffer` is a 1–64 in-flight contract cap.
|
||||
|
||||
The authority difference is intentional. A service runs with the app's privileges, its working directory is the app data directory, and it receives only an explicit environment allowlist: `PATH`, home/user/temp variables, locale/time-zone variables, certificate locations, and proxy variables. Windows matches environment names case-insensitively and carries its corresponding user/profile, system-root, command-shell, and executable-extension names. `NATIVE_SDK_*` internals are removed. Standard output carries the framed transport, so service diagnostics belong on standard error. An explicit throw must be exactly an inline kind-tagged shape such as `throw { kind: "parse", message: "bad feed" }`, with a string-valued message, and it must escape through the operation boundary rather than be caught locally; its encoded kind and message arrive through the error Msg. The pinned compiler's scratch stage mechanically lowers that boundary value to a tagged native `Error` because scriptc 0.0.22 cannot inspect an imported record throw in a catch; your checked-in source—and its behavior under Node—does not change.
|
||||
The authority difference is intentional. A service runs with the app's privileges, its working directory is the app data directory, and it receives only an explicit environment allowlist: `PATH`, home/user/temp variables, locale/time-zone variables, certificate locations, and proxy variables. Windows matches environment names case-insensitively and carries its corresponding user/profile, system-root, command-shell, and executable-extension names. `NATIVE_SDK_*` internals are removed. Standard output carries the framed transport, so service diagnostics belong on standard error. An explicit throw must be exactly an inline kind-tagged shape such as `throw { kind: "parse", message: "bad feed" }`, with a string-valued message, and it must escape through the operation boundary rather than be caught locally; its encoded kind and message arrive through the error Msg. The pinned compiler's scratch stage mechanically lowers that boundary value to a tagged native `Error` because scriptc 0.0.26 cannot inspect an imported record throw in a catch; your checked-in source—and its behavior under Node—does not change.
|
||||
|
||||
Today the carrier is a lazily started second executable placed next to the app executable. Startup verifies both the protocol version and a fingerprint of the generated operation/type/package registry, so a stale or mismatched sibling is rejected before an operation index is dispatched. It restarts after a crash or after a cancellation/deadline that ignores the cooperative grace period. This makes TypeScript services desktop-only and adds a process hop plus copies at the encoded boundary. The current compiler emits the service executable only for the build host, so cross-target service builds fail with a teaching instead of packaging an executable for the wrong OS or architecture. The future in-process carrier keeps the same contract and app API; no JavaScript engine is used in either form. Async service entry points wait for that later compiler/runtime phase.
|
||||
|
||||
|
||||
Generated
+9
-9
@@ -8,25 +8,25 @@
|
||||
"name": "@native-sdk/core",
|
||||
"version": "0.8.4",
|
||||
"dependencies": {
|
||||
"scriptc": "0.0.22"
|
||||
"scriptc": "0.0.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript/old": "npm:typescript@6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@scriptc/compiler": {
|
||||
"version": "0.0.22",
|
||||
"integrity": "sha512-cZil7ZgK64yRpSk1X3BSq1Q2DBru53KfC4X0i/qa0vHNk/rvk3teDzykSP5l3nb5wY2ckh0Es3fojfD0kUp/gA==",
|
||||
"version": "0.0.26",
|
||||
"integrity": "sha512-JCLQbW5ZLTDOwlSbUwfA8ZYcsHUb1SJzzpAwNFNPOlf+mw8kvTMKFnQaDzCD+bK/eOAzLhn2U6jO95fiYsNJxQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scriptc/runtime": "0.0.22",
|
||||
"@scriptc/runtime": "0.0.26",
|
||||
"typescript": "7.0.2",
|
||||
"typescript5": "npm:typescript@5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@scriptc/runtime": {
|
||||
"version": "0.0.22",
|
||||
"integrity": "sha512-UytlS/6vxWXYIpIO2lQYVh4xTzMoybX0Uz1wKA41ezq62sMeuhHT6JVappUG55DgVNAWGgMI/WAHun2TeRutNg==",
|
||||
"version": "0.0.26",
|
||||
"integrity": "sha512-M035p4yYbnxTkgIt+hKVO4ekWc0iAicMXpsBEgxmpr2fAUHgM7UI/r8V9nHtzAXb8rB2kQEe/l6nbzSeUmIgSg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@typescript/old": {
|
||||
@@ -344,11 +344,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scriptc": {
|
||||
"version": "0.0.22",
|
||||
"integrity": "sha512-gJQ5Johx477Al1EqZOTeXOPsPgwzSOvVljIc9l1S0BqhaffyZtxXa1Mm3YFKZaCV3u4YNAEMsbO9EBgvW1awDg==",
|
||||
"version": "0.0.26",
|
||||
"integrity": "sha512-UqqmtrdYV0cS7lA/hOfx+DLhWwglHjxSwKXUqUez6l2TnFo8D12dSickGp0oo7dPxBhVcvrLHIG8aOZ0PunmqQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scriptc/compiler": "0.0.22"
|
||||
"@scriptc/compiler": "0.0.26"
|
||||
},
|
||||
"bin": {
|
||||
"scriptc": "dist/main.js"
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"@typescript/old": "npm:typescript@6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"scriptc": "0.0.22"
|
||||
"scriptc": "0.0.26"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+13
-13
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
scriptc:
|
||||
specifier: 0.0.22
|
||||
version: 0.0.22
|
||||
specifier: 0.0.26
|
||||
version: 0.0.26
|
||||
devDependencies:
|
||||
'@typescript/old':
|
||||
specifier: npm:typescript@6.0.3
|
||||
@@ -18,11 +18,11 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@scriptc/compiler@0.0.22':
|
||||
resolution: {integrity: sha512-cZil7ZgK64yRpSk1X3BSq1Q2DBru53KfC4X0i/qa0vHNk/rvk3teDzykSP5l3nb5wY2ckh0Es3fojfD0kUp/gA==}
|
||||
'@scriptc/compiler@0.0.26':
|
||||
resolution: {integrity: sha512-JCLQbW5ZLTDOwlSbUwfA8ZYcsHUb1SJzzpAwNFNPOlf+mw8kvTMKFnQaDzCD+bK/eOAzLhn2U6jO95fiYsNJxQ==}
|
||||
|
||||
'@scriptc/runtime@0.0.22':
|
||||
resolution: {integrity: sha512-UytlS/6vxWXYIpIO2lQYVh4xTzMoybX0Uz1wKA41ezq62sMeuhHT6JVappUG55DgVNAWGgMI/WAHun2TeRutNg==}
|
||||
'@scriptc/runtime@0.0.26':
|
||||
resolution: {integrity: sha512-M035p4yYbnxTkgIt+hKVO4ekWc0iAicMXpsBEgxmpr2fAUHgM7UI/r8V9nHtzAXb8rB2kQEe/l6nbzSeUmIgSg==}
|
||||
|
||||
'@typescript/typescript-aix-ppc64@7.0.2':
|
||||
resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==}
|
||||
@@ -144,8 +144,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
scriptc@0.0.22:
|
||||
resolution: {integrity: sha512-gJQ5Johx477Al1EqZOTeXOPsPgwzSOvVljIc9l1S0BqhaffyZtxXa1Mm3YFKZaCV3u4YNAEMsbO9EBgvW1awDg==}
|
||||
scriptc@0.0.26:
|
||||
resolution: {integrity: sha512-UqqmtrdYV0cS7lA/hOfx+DLhWwglHjxSwKXUqUez6l2TnFo8D12dSickGp0oo7dPxBhVcvrLHIG8aOZ0PunmqQ==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
@@ -166,13 +166,13 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@scriptc/compiler@0.0.22':
|
||||
'@scriptc/compiler@0.0.26':
|
||||
dependencies:
|
||||
'@scriptc/runtime': 0.0.22
|
||||
'@scriptc/runtime': 0.0.26
|
||||
typescript: 7.0.2
|
||||
typescript5: typescript@5.9.3
|
||||
|
||||
'@scriptc/runtime@0.0.22': {}
|
||||
'@scriptc/runtime@0.0.26': {}
|
||||
|
||||
'@typescript/typescript-aix-ppc64@7.0.2':
|
||||
optional: true
|
||||
@@ -234,9 +234,9 @@ snapshots:
|
||||
'@typescript/typescript-win32-x64@7.0.2':
|
||||
optional: true
|
||||
|
||||
scriptc@0.0.22:
|
||||
scriptc@0.0.26:
|
||||
dependencies:
|
||||
'@scriptc/compiler': 0.0.22
|
||||
'@scriptc/compiler': 0.0.26
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
// Stage the typed service executable: ordinary service TypeScript plus
|
||||
// corewire's generated service_host_main.ts. One narrow mechanical lowering
|
||||
// is required by scriptc 0.0.22: an escaping `throw { kind, message }` becomes
|
||||
// is required by scriptc 0.0.26: an escaping `throw { kind, message }` becomes
|
||||
// an Error subclass carrying kind in `.name`, the object form its reachable
|
||||
// static catch tier cannot inspect yet. NS1067 rejects locally caught tagged
|
||||
// records; shared core-class modules receive the same transform in this
|
||||
|
||||
@@ -53,7 +53,7 @@ test("an npm package outside the static tier fails check with the compiler note
|
||||
name: "dynamic-only", version: "1.0.0", type: "module", types: "index.d.ts", exports: "./index.js",
|
||||
}));
|
||||
fs.writeFileSync(path.join(vendor, "index.d.ts"), "export default function probe(value: string): string;\n");
|
||||
// randomFillSync is typed but has no static lowering in scriptc 0.0.22.
|
||||
// randomFillSync is typed but has no static lowering in scriptc 0.0.26.
|
||||
// The package itself remains marked "static" in coverage, so this pins
|
||||
// Native SDK's stricter requirement that the total be 100%.
|
||||
fs.writeFileSync(path.join(vendor, "index.js"), [
|
||||
|
||||
@@ -44,7 +44,7 @@ export function parse(payload: Uint8Array): Uint8Array {
|
||||
const contract = JSON.parse(result.servicesContract!);
|
||||
assert.equal(contract.format, 3);
|
||||
assert.equal(contract.protocol_version, 3);
|
||||
assert.equal(contract.compiler_version, "0.0.22");
|
||||
assert.equal(contract.compiler_version, "0.0.26");
|
||||
assert.equal(contract.deterministic, false);
|
||||
assert.deepEqual(contract.packages, []);
|
||||
assert.deepEqual(contract.types, { records: [], enums: [], unions: [] });
|
||||
@@ -576,7 +576,7 @@ test("the service compile lane refuses a contract whose compiler echo skews from
|
||||
try {
|
||||
const stage = path.join(root, "stage");
|
||||
fs.mkdirSync(stage);
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.22" } }));
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.26" } }));
|
||||
fs.writeFileSync(path.join(root, "services.contract.json"), JSON.stringify({ compiler_version: "0.0.21" }));
|
||||
const script = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "scripts", "run_external_service_compiler.mjs");
|
||||
const result = spawnSync(process.execPath, [
|
||||
@@ -590,7 +590,7 @@ test("the service compile lane refuses a contract whose compiler echo skews from
|
||||
"--compiler", process.execPath,
|
||||
], { encoding: "utf8" });
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /contract echoes scriptc 0\.0\.21, but packages\/core pins 0\.0\.22/);
|
||||
assert.match(result.stderr, /contract echoes scriptc 0\.0\.21, but packages\/core pins 0\.0\.26/);
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
@@ -601,8 +601,8 @@ test("the service compile lane refuses a target that differs from the build host
|
||||
try {
|
||||
const stage = path.join(root, "stage");
|
||||
fs.mkdirSync(stage);
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.22" } }));
|
||||
fs.writeFileSync(path.join(root, "services.contract.json"), JSON.stringify({ compiler_version: "0.0.22" }));
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.26" } }));
|
||||
fs.writeFileSync(path.join(root, "services.contract.json"), JSON.stringify({ compiler_version: "0.0.26" }));
|
||||
const script = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "scripts", "run_external_service_compiler.mjs");
|
||||
const result = spawnSync(process.execPath, [
|
||||
script,
|
||||
@@ -627,14 +627,14 @@ test("the service compile lane passes an explicit npm-static allowlist and prese
|
||||
try {
|
||||
const stage = path.join(root, "stage");
|
||||
fs.mkdirSync(stage);
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.22" } }));
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.26" } }));
|
||||
fs.writeFileSync(path.join(root, "services.contract.json"), JSON.stringify({
|
||||
compiler_version: "0.0.22",
|
||||
compiler_version: "0.0.26",
|
||||
packages: [{ name: "dynamic-only", version: "1.0.0", content_hash: "a".repeat(64) }],
|
||||
}));
|
||||
const compiler = path.join(root, "compiler.mjs");
|
||||
fs.writeFileSync(compiler, `
|
||||
if (process.argv.includes("-v")) { console.log("0.0.22"); process.exit(0); }
|
||||
if (process.argv.includes("-v")) { console.log("0.0.26"); process.exit(0); }
|
||||
const at = process.argv.indexOf("--npm-static");
|
||||
if (at < 0 || process.argv[at + 1] !== "dynamic-only" || process.argv.includes("auto") || process.argv.includes("--dynamic")) {
|
||||
console.error("wrong npm policy: " + process.argv.slice(2).join(" ")); process.exit(9);
|
||||
@@ -667,15 +667,15 @@ test("the service compile lane refuses a package whose successful coverage verdi
|
||||
try {
|
||||
const stage = path.join(root, "stage");
|
||||
fs.mkdirSync(stage);
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.22" } }));
|
||||
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ dependencies: { scriptc: "0.0.26" } }));
|
||||
fs.writeFileSync(path.join(root, "services.contract.json"), JSON.stringify({
|
||||
compiler_version: "0.0.22",
|
||||
compiler_version: "0.0.26",
|
||||
packages: [{ name: "partial-static", version: "1.0.0", content_hash: "b".repeat(64) }],
|
||||
}));
|
||||
const compiler = path.join(root, "compiler.mjs");
|
||||
fs.writeFileSync(compiler, `
|
||||
import fs from "node:fs";
|
||||
if (process.argv.includes("-v")) { console.log("0.0.22"); process.exit(0); }
|
||||
if (process.argv.includes("-v")) { console.log("0.0.26"); process.exit(0); }
|
||||
if (process.argv[2] === "coverage") {
|
||||
console.log("scriptc coverage service_host_main.ts\\n\\n statements analyzed 10\\n compile statically 8 (80%)\\n\\n deferred to runtime 2 sites\\n ×2 unsupported package calls SC2020");
|
||||
process.exit(0);
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@typescript/old": "npm:typescript@6.0.3",
|
||||
"scriptc": "0.0.22"
|
||||
"scriptc": "0.0.26"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@native-sdk/cli-darwin-arm64": "0.8.4",
|
||||
|
||||
@@ -79,7 +79,7 @@ native vendor . escape-string-regexp@5.0.0
|
||||
|
||||
The command installs with lifecycle scripts disabled in a temporary directory, copies the flattened package graph into `src/services/vendor/`, retains package/license files, computes canonical tree hashes, and rewrites app.zon's `service_packages` with exact names, `X.Y.Z` versions, and hashes. Check those bytes and manifest facts into source control.
|
||||
|
||||
Builds never run npm or use the network. They verify package identity and every vendored byte, stage only declared packages, and invoke scriptc with `--npm-static <explicit-list>`. `auto`, dynamic-island fallback, and `--dynamic` are refused. Anything below 100% static coverage fails: `native check` preserves the coverage note verbatim and names the three options—choose another exact package, port/vendor a suitable implementation, or wait for compiler support. The checked-in 0.0.22 spike (`tests/ts-services/npm-static-spike.json`) passed three of five deliberately small candidates and refused `nanoid` and `micromark`; npm support is selective.
|
||||
Builds never run npm or use the network. They verify package identity and every vendored byte, stage only declared packages, and invoke scriptc with `--npm-static <explicit-list>`. `auto`, dynamic-island fallback, and `--dynamic` are refused. Anything below 100% static coverage fails: `native check` preserves the coverage note verbatim and names the three options—choose another exact package, port/vendor a suitable implementation, or wait for compiler support. The checked-in 0.0.26 spike (`tests/ts-services/npm-static-spike.json`) passed three of five deliberately small candidates and refused `nanoid` and `micromark`; npm support is selective.
|
||||
|
||||
## Streaming, cancellation, and deadlines
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"date": "2026-08-11",
|
||||
"compiler": "scriptc 0.0.22",
|
||||
"compiler": "scriptc 0.0.26",
|
||||
"policy": "explicit --npm-static; no auto or dynamic fallback",
|
||||
"method": "packages/core/scripts/npm_static_spike.mjs against exact package bytes in the locked docs workspace",
|
||||
"summary": "3 of 5 deliberately small candidates cleared 100% static coverage; nanoid and micromark were refused, so package support is selective",
|
||||
|
||||
@@ -688,7 +688,7 @@ test "service projection derives host dispatch and registry from one contract" {
|
||||
const contract: service.Contract = .{
|
||||
.format = 3,
|
||||
.protocol_version = 3,
|
||||
.compiler_version = "0.0.22",
|
||||
.compiler_version = "0.0.26",
|
||||
.deterministic = false,
|
||||
.packages = &.{},
|
||||
.types = .{ .records = &.{}, .enums = &.{}, .unions = &.{} },
|
||||
@@ -709,7 +709,7 @@ test "service projection derives host dispatch and registry from one contract" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, registry, ".name = \"feeds.parse\", .index = 0") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, registry, "0 => true") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, registry, "copyServiceBytes(bytes)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, registry, "pub const compiler_version = \"0.0.22\"") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, registry, "pub const compiler_version = \"0.0.26\"") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, registry, "pub const contract_fingerprint = [_]u8{") != null);
|
||||
|
||||
const client = try emitClient(std.testing.allocator, contract);
|
||||
|
||||
@@ -216,7 +216,7 @@ test "service contract validates typed operations and authority attestation" {
|
||||
defer arena_state.deinit();
|
||||
const arena = arena_state.allocator();
|
||||
const good =
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.22","deterministic":false,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"feeds.parse","client":"feedsParse","module":"src/services/feeds.ts","export":"parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":false,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.26","deterministic":false,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"feeds.parse","client":"feedsParse","module":"src/services/feeds.ts","export":"parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":false,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
;
|
||||
var diagnostics: [512]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&diagnostics);
|
||||
@@ -225,7 +225,7 @@ test "service contract validates typed operations and authority attestation" {
|
||||
try std.testing.expectEqualStrings("feeds.parse", contract.operations[0].name);
|
||||
|
||||
const dollar =
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.22","deterministic":false,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"feeds.$parse","client":"feeds$parse","module":"src/services/feeds.ts","export":"$parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":true,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.26","deterministic":false,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"feeds.$parse","client":"feeds$parse","module":"src/services/feeds.ts","export":"$parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":true,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
;
|
||||
var dollar_diagnostics: [512]u8 = undefined;
|
||||
var dollar_writer = std.Io.Writer.fixed(&dollar_diagnostics);
|
||||
@@ -233,7 +233,7 @@ test "service contract validates typed operations and authority attestation" {
|
||||
try std.testing.expectEqualStrings("$parse", dollar_contract.operations[0].@"export");
|
||||
|
||||
const bad =
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.22","deterministic":true,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"feeds.parse","client":"feedsParse","module":"src/services/feeds.ts","export":"parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":false,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.26","deterministic":true,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"feeds.parse","client":"feedsParse","module":"src/services/feeds.ts","export":"parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":false,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
;
|
||||
var bad_diagnostics: [512]u8 = undefined;
|
||||
var bad_writer = std.Io.Writer.fixed(&bad_diagnostics);
|
||||
@@ -241,7 +241,7 @@ test "service contract validates typed operations and authority attestation" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, bad_writer.buffered(), "deterministic=true") != null);
|
||||
|
||||
const skewed =
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.22","deterministic":false,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"other.parse","client":"otherParse","module":"src/services/feeds.ts","export":"parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":false,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
\\{"format":3,"protocol_version":3,"compiler_version":"0.0.26","deterministic":false,"packages":[],"types":{"records":[],"enums":[],"unions":[]},"operations":[{"name":"other.parse","client":"otherParse","module":"src/services/feeds.ts","export":"parse","request":{"kind":"bytes"},"result":{"kind":"bytes"},"deadline_ms":null,"cancellable":false,"stream":null,"source_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}
|
||||
;
|
||||
var skewed_diagnostics: [512]u8 = undefined;
|
||||
var skewed_writer = std.Io.Writer.fixed(&skewed_diagnostics);
|
||||
|
||||
Reference in New Issue
Block a user