castrum 0.9.4 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/Cargo.lock +25 -42
  2. package/Cargo.toml +21 -4
  3. package/README.md +8 -6
  4. package/castrum.darwin-arm64.node +0 -0
  5. package/castrum.darwin-x64.node +0 -0
  6. package/castrum.linux-arm64-gnu.node +0 -0
  7. package/castrum.linux-arm64-musl.node +0 -0
  8. package/castrum.linux-x64-gnu.node +0 -0
  9. package/castrum.linux-x64-musl.node +0 -0
  10. package/castrum.linux-x64-v3-gnu.node +0 -0
  11. package/castrum.win32-x64-msvc.node +0 -0
  12. package/dist/index.d.ts +164 -0
  13. package/dist/index.js +4432 -3641
  14. package/docs/API.md +1 -0
  15. package/docs/ARCHITECTURE.md +1 -1
  16. package/docs/CASE_STUDY.md +4 -2
  17. package/docs/ENVIRONMENT.md +9 -2
  18. package/docs/FFI_BUN_GUIDE.md +154 -9
  19. package/docs/HOW_TO_ADD_AN_OP.md +10 -0
  20. package/docs/REPO_MAP.md +16 -5
  21. package/docs/RND-CONCURRENCY.md +772 -0
  22. package/docs/bun-builtins-decision-matrix.md +4 -1
  23. package/index.ts +5 -0
  24. package/package.json +172 -163
  25. package/rust/crypto/pbkdf2.rs +9 -0
  26. package/rust/ffi/mod.rs +1 -0
  27. package/rust/ffi/task.rs +172 -0
  28. package/rust/http/form.rs +6 -2
  29. package/rust/http/query_parser.rs +53 -22
  30. package/rust/ingress/native_route.rs +131 -140
  31. package/rust/ingress/pipeline.rs +3 -3
  32. package/rust/ingress/tests.rs +5 -4
  33. package/rust/json/json_ser.rs +13 -11
  34. package/rust/lib.rs +1 -0
  35. package/rust/task/completion.rs +119 -0
  36. package/rust/task/mod.rs +65 -0
  37. package/rust/task/ops.rs +503 -0
  38. package/rust/task/runtime.rs +220 -0
  39. package/rust/task/tests.rs +231 -0
  40. package/rust/util/bytes.rs +238 -51
  41. package/src/ingress/health.ts +1 -2
  42. package/src/native/ffi/build/codecs.ts +29 -7
  43. package/src/native/ffi/build/compress.ts +8 -0
  44. package/src/native/ffi/build/instances.ts +7 -2
  45. package/src/native/ffi/build/metrics.ts +23 -0
  46. package/src/native/ffi/build/task.ts +79 -0
  47. package/src/native/ffi/build/util.ts +18 -0
  48. package/src/native/ffi/build.ts +2 -0
  49. package/src/native/ffi/selftest.ts +3 -1
  50. package/src/native/ffi/types.ts +40 -0
  51. package/src/native/ffi.ts +73 -14
  52. package/src/native/loader.ts +53 -3
  53. package/src/rust-ffi/scalar/http.ts +54 -16
  54. package/src/rust-ffi/text.ts +25 -3
  55. package/src/shared/bytes.ts +19 -0
  56. package/src/task/index.ts +24 -0
  57. package/src/task/op.ts +171 -0
  58. package/src/task/runtime.ts +568 -0
@@ -7,7 +7,10 @@
7
7
 
8
8
  ## Methodology
9
9
 
10
- - **Runtime**: Bun **1.4.0** (the primary target).
10
+ - **Runtime**: Bun **1.4.0** (the measurement anchor for the rows below; the
11
+ local toolchain has since moved to **1.4.2**, and these TIMING rows have not
12
+ been re-measured — only re-verified for parity, which
13
+ `test/unit/contract/delegation.test.ts` does on every run).
11
14
  - **Addon**: castrum **release** build (`bun run build`, baseline CPU — the
12
15
  shipped profile).
13
16
  - **Driver**: `bun run check` (CPU benchmark → `bench/results/cpu/latest.json`).
package/index.ts CHANGED
@@ -20,6 +20,11 @@ export * from './src/integration'
20
20
  // dispatch single items, packed batches, and load() coalescing. See README.
21
21
  export * from './src/loader'
22
22
  export * from './src/rust-ffi'
23
+ // Off-thread native task runtime ("castrum Tasks"): submit a CPU-bound native
24
+ // op to the Rust pool and `await` it without ever blocking the JS thread.
25
+ // Bun-only offload today; Node runs the op synchronously. See
26
+ // docs/RND-CONCURRENCY.md.
27
+ export * from './src/task'
23
28
  export type { OpDecision, OpImpl } from './src/selection'
24
29
  // Native-vs-JS selection (OWNED HERE, not by consumers): `opImpl(op)` is the
25
30
  // benchmark-driven recommendation for which operations should use the Rust
package/package.json CHANGED
@@ -1,168 +1,177 @@
1
1
  {
2
- "name": "castrum",
3
- "version": "0.9.4",
4
- "private": false,
5
- "description": "Enterprise-grade Bun + Rust runtime: Rust-accelerated FFI primitives, a production-grade HTTP ingress pipeline, validation, hashing, and JSON processing with benchmark-grade performance.",
6
- "type": "module",
7
- "main": "dist/index.js",
8
- "module": "dist/index.js",
9
- "types": "dist/index.d.ts",
10
- "license": "MIT",
11
- "keywords": [
12
- "bun",
13
- "rust",
14
- "napi",
15
- "ffi",
16
- "ingress",
17
- "http",
18
- "validation",
19
- "hashing",
20
- "json",
21
- "performance",
22
- "benchmark"
23
- ],
24
- "repository": {
25
- "type": "git",
26
- "url": "git+https://github.com/Axylabs/flux-rs.git"
2
+ "name": "castrum",
3
+ "version": "0.9.6",
4
+ "private": false,
5
+ "description": "Enterprise-grade Bun + Rust runtime: Rust-accelerated FFI primitives, a production-grade HTTP ingress pipeline, validation, hashing, and JSON processing with benchmark-grade performance.",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "bun",
13
+ "rust",
14
+ "napi",
15
+ "ffi",
16
+ "ingress",
17
+ "http",
18
+ "validation",
19
+ "hashing",
20
+ "json",
21
+ "performance",
22
+ "benchmark"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/Axylabs/castrum.git"
27
+ },
28
+ "homepage": "https://github.com/Axylabs/castrum#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/Axylabs/castrum/issues"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "bun": "./index.ts",
39
+ "node": "./dist/index.js",
40
+ "default": "./dist/index.js"
27
41
  },
28
- "homepage": "https://github.com/Axylabs/flux-rs#readme",
29
- "bugs": {
30
- "url": "https://github.com/Axylabs/flux-rs/issues"
31
- },
32
- "publishConfig": {
33
- "access": "public"
34
- },
35
- "exports": {
36
- ".": {
37
- "types": "./dist/index.d.ts",
38
- "bun": "./index.ts",
39
- "node": "./dist/index.js",
40
- "default": "./dist/index.js"
41
- },
42
- "./package.json": "./package.json"
43
- },
44
- "files": [
45
- "index.ts",
46
- "dist",
47
- "src",
48
- "docs",
49
- "README.md",
50
- "*.node",
51
- "scripts/postinstall.mjs",
52
- "rust",
53
- "Cargo.toml",
54
- "Cargo.lock",
55
- "build.rs",
56
- "rust-toolchain.toml",
57
- "!src/bench",
58
- "!src/baseline",
59
- "!src/data"
60
- ],
61
- "scripts": {
62
- "postinstall": "node scripts/postinstall.mjs",
63
- "build": "napi build --release --platform",
64
- "build:perf": "bash scripts/build-perf.sh",
65
- "build:v3": "bash scripts/build-v3.sh",
66
- "build:all": "bun run build && bun run build:v3",
67
- "build:js": "bun run build:js:bundle && bun run build:js:types",
68
- "build:js:bundle": "bun build ./index.ts --target=node --format=esm --outdir=dist",
69
- "build:js:types": "dts-bundle-generator -o dist/index.d.ts index.ts",
70
- "bench": "bun run build && bun bench.ts",
71
- "check": "bun bench.ts",
72
- "test": "bun test",
73
- "test:all": "bun test && bun run typecheck && bun run typecheck:test && bun run lint:ci && bun run check:clean && cargo test --lib",
74
- "test:coverage": "node scripts/check-coverage.mjs",
75
- "setup": "bun install && bun run build",
76
- "test:node": "node --test test/integration/node-smoke.test.mjs test/integration/node-enterprise.test.mjs",
77
- "verify:install": "node scripts/verify-install.mjs",
78
- "build:debug": "napi build --platform",
79
- "lint": "bunx biome check .",
80
- "lint:fix": "bunx biome check --write .",
81
- "lint:ci": "bunx biome check . --formatter-enabled=false --assist-enabled=false --error-on-warnings",
82
- "format": "bunx biome format --write .",
83
- "typecheck": "bunx tsc --noEmit",
84
- "typecheck:test": "bunx tsc --noEmit -p tsconfig.test.json",
85
- "test:rust": "cargo test",
86
- "deny": "cargo deny check",
87
- "check:version": "bun scripts/check-version.mjs",
88
- "check:jsdoc": "bun scripts/check-jsdoc.ts",
89
- "check:selection": "bun scripts/select-native.ts --check",
90
- "check:clean": "bun scripts/check-clean.ts",
91
- "gen:ai-map": "bun scripts/gen-ai-map.ts",
92
- "audit": "bun audit",
93
- "publish:manual": "node scripts/publish-manual.mjs",
94
- "publish:manual:dry": "node scripts/publish-manual.mjs --dry-run",
95
- "prepublishOnly": "bun run build:js && node scripts/prepublish.mjs",
96
- "bench:http": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts",
97
- "bench:http:smoke": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts smoke",
98
- "bench:http:stress": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts stress",
99
- "bench:http:heavy": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts heavy-json",
100
- "bench:http:crud": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts crud",
101
- "bench:http:spike": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts spike",
102
- "bench:http:soak": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts soak",
103
- "bench:http:storm": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts storm",
104
- "bench:http:boundary": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts boundary",
105
- "bench:http:elysia": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' elysia",
106
- "bench:http:ingress": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' ingress",
107
- "bench:http:bun-only": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' bun",
108
- "bench:http:all-heavy": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts 1",
109
- "bench:http:ac": "bun bench/autocannon-stress.mjs",
110
- "bench:http:stress:ac": "SCENARIO=03-stress bun bench/autocannon-stress.mjs",
111
- "bench:startup": "bun run bench/startup.ts",
112
- "bench:load": "bun run bench/http/load-phase.ts",
113
- "bench:ffi": "bun run bench/ffi/ffi-all.ts",
42
+ "./package.json": "./package.json"
43
+ },
44
+ "files": [
45
+ "index.ts",
46
+ "dist",
47
+ "src",
48
+ "docs",
49
+ "README.md",
50
+ "*.node",
51
+ "scripts/postinstall.mjs",
52
+ "rust",
53
+ "Cargo.toml",
54
+ "Cargo.lock",
55
+ "build.rs",
56
+ "rust-toolchain.toml",
57
+ "!src/bench",
58
+ "!src/baseline",
59
+ "!src/data"
60
+ ],
61
+ "scripts": {
62
+ "postinstall": "node scripts/postinstall.mjs",
63
+ "build": "napi build --release --platform",
64
+ "build:perf": "bash scripts/build-perf.sh",
65
+ "build:v3": "bash scripts/build-v3.sh",
66
+ "build:all": "bun run build && bun run build:v3",
67
+ "build:js": "bun run build:js:bundle && bun run build:js:types",
68
+ "build:js:bundle": "bun build ./index.ts --target=node --format=esm --outdir=dist",
69
+ "build:js:types": "dts-bundle-generator -o dist/index.d.ts index.ts",
70
+ "bench": "bun run build && bun bench.ts",
71
+ "check": "bun bench.ts",
72
+ "test": "bun test test/unit test/property test/compat",
73
+ "test:all": "bun test && bun run typecheck && bun run typecheck:test && bun run lint:ci && bun run check:clean && cargo test --lib",
74
+ "test:coverage": "node scripts/check-coverage.mjs",
75
+ "setup": "bun install && bun run build",
76
+ "test:node": "node --test test/integration/node-smoke.test.mjs test/integration/node-enterprise.test.mjs",
77
+ "verify:install": "node scripts/verify-install.mjs",
78
+ "build:debug": "napi build --platform",
79
+ "lint": "bunx biome check .",
80
+ "lint:fix": "bunx biome check --write .",
81
+ "lint:ci": "bunx biome check . --formatter-enabled=false --assist-enabled=false --error-on-warnings",
82
+ "format": "bunx biome format --write .",
83
+ "typecheck": "bunx tsc --noEmit",
84
+ "typecheck:test": "bunx tsc --noEmit -p tsconfig.test.json",
85
+ "test:rust": "cargo test",
86
+ "deny": "cargo deny check",
87
+ "check:version": "bun scripts/check-version.mjs",
88
+ "check:jsdoc": "bun scripts/check-jsdoc.ts",
89
+ "check:selection": "bun scripts/select-native.ts --check",
90
+ "check:clean": "bun scripts/check-clean.ts",
91
+ "gen:ai-map": "bun scripts/gen-ai-map.ts",
92
+ "audit": "bun audit",
93
+ "release": "bun scripts/release.ts",
94
+ "release:dry": "bun scripts/release.ts --dry-run",
95
+ "release:manual": "bun scripts/release.ts --publish",
96
+ "prepublishOnly": "bun run build:js && node scripts/prepublish.mjs",
97
+ "bench:http": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts",
98
+ "bench:http:smoke": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts smoke",
99
+ "bench:http:stress": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts stress",
100
+ "bench:http:heavy": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts heavy-json",
101
+ "bench:http:crud": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts crud",
102
+ "bench:http:spike": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts spike",
103
+ "bench:http:soak": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts soak",
104
+ "bench:http:storm": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts storm",
105
+ "bench:http:boundary": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts boundary",
106
+ "bench:http:elysia": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' elysia",
107
+ "bench:http:ingress": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' ingress",
108
+ "bench:http:bun-only": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts '' bun",
109
+ "bench:http:all-heavy": "BUN_CONFIG_MAX_HTTP_REQUESTS=65535 bun run bench/http/run-bench.ts 1",
110
+ "bench:http:ac": "bun bench/autocannon-stress.mjs",
111
+ "bench:http:stress:ac": "SCENARIO=03-stress bun bench/autocannon-stress.mjs",
112
+ "bench:startup": "bun run bench/startup.ts",
113
+ "bench:load": "bun run bench/http/load-phase.ts",
114
+ "bench:ffi": "bun run bench/ffi/ffi-all.ts",
114
115
  "bench:newops": "bun bench/cost/rust-vs-js-new-ops.ts",
115
- "bench:ffi:load": "bun run bench/ffi/ffi-load.ts",
116
- "bench:ffi:public": "bun run bench/ffi/ffi-public.ts",
117
- "bench:ffi:workers": "bun run bench/ffi/ffi-workers.ts",
118
- "bench:margin": "bun run bench/ffi/ffi-margin.ts",
119
- "bench:router": "bun run bench/cost/router-cost.ts",
120
- "bench:ingress-cost": "bun run bench/cost/ingress-cost.ts",
121
- "bench:ingress-cost:post": "bun run bench/cost/ingress-cost-post.ts",
122
- "verify:native:batch": "bun run scripts/verify-native-batch.ts"
123
- },
124
- "dependencies": {},
125
- "peerDependencies": {
126
- "@types/node": "^26.1.2"
127
- },
128
- "peerDependenciesMeta": {
129
- "@types/node": {
130
- "optional": true
131
- }
132
- },
133
- "devDependencies": {
134
- "@elysia/cors": "^1.4.2",
135
- "@napi-rs/cli": "^3.8.6",
136
- "@types/bun": "^1.3.14",
137
- "@types/mime-types": "^3.0.1",
138
- "autocannon": "^8.0.0",
139
- "@types/validator": "^13.15.10",
140
- "ajv": "^8.20.0",
141
- "crc-32": "^1.2.2",
142
- "dts-bundle-generator": "^9.5.1",
143
- "elysia": "^1.4.29",
144
- "fast-json-patch": "^3.1.1",
145
- "mime-types": "^3.0.2",
146
- "typescript": "5.9.3",
147
- "validator": "^13.15.35"
148
- },
149
- "overrides": {
150
- "typescript": "5.9.3"
116
+ "bench:ffi:load": "bun run bench/ffi/ffi-load.ts",
117
+ "bench:ffi:public": "bun run bench/ffi/ffi-public.ts",
118
+ "bench:ffi:workers": "bun run bench/ffi/ffi-workers.ts",
119
+ "bench:margin": "bun run bench/ffi/ffi-margin.ts",
120
+ "bench:router": "bun run bench/cost/router-cost.ts",
121
+ "bench:task": "bun run bench/cost/task-offload.ts",
122
+ "bench:ingress-cost": "bun run bench/cost/ingress-cost.ts",
123
+ "bench:ingress-cost:post": "bun run bench/cost/ingress-cost-post.ts",
124
+ "verify:native:batch": "bun run scripts/verify-native-batch.ts"
125
+ },
126
+ "dependencies": {},
127
+ "peerDependencies": {
128
+ "@types/node": "^26.1.2"
129
+ },
130
+ "peerDependenciesMeta": {
131
+ "@types/node": {
132
+ "optional": true
133
+ }
134
+ },
135
+ "devDependencies": {
136
+ "@elysia/cors": "^1.4.2",
137
+ "@napi-rs/cli": "^3.8.6",
138
+ "@types/bun": "^1.3.14",
139
+ "@types/mime-types": "^3.0.1",
140
+ "autocannon": "^8.0.0",
141
+ "@types/validator": "^13.15.10",
142
+ "ajv": "^8.20.0",
143
+ "crc-32": "^1.2.2",
144
+ "dts-bundle-generator": "^9.5.1",
145
+ "elysia": "^1.4.29",
146
+ "fast-json-patch": "^3.1.1",
147
+ "mime-types": "^3.0.2",
148
+ "typescript": "7.0.2",
149
+ "validator": "^13.15.35"
150
+ },
151
+ "overrides": {
152
+ "typescript": "7.0.2",
153
+ "dts-bundle-generator": {
154
+ "typescript": "5.9.3"
151
155
  },
152
- "engines": {
153
- "bun": ">=1.1.0",
154
- "node": ">=20.3"
156
+ "@napi-rs/cli": {
157
+ "typescript": "6.0.3"
155
158
  },
156
- "napi": {
157
- "binaryName": "castrum",
158
- "targets": [
159
- "aarch64-apple-darwin",
160
- "aarch64-unknown-linux-gnu",
161
- "aarch64-unknown-linux-musl",
162
- "x86_64-apple-darwin",
163
- "x86_64-pc-windows-msvc",
164
- "x86_64-unknown-linux-gnu",
165
- "x86_64-unknown-linux-musl"
166
- ]
167
- }
168
- }
159
+ "hyperid": "^4.0.0"
160
+ },
161
+ "engines": {
162
+ "bun": ">=1.1.0",
163
+ "node": ">=20.3"
164
+ },
165
+ "napi": {
166
+ "binaryName": "castrum",
167
+ "targets": [
168
+ "aarch64-apple-darwin",
169
+ "aarch64-unknown-linux-gnu",
170
+ "aarch64-unknown-linux-musl",
171
+ "x86_64-apple-darwin",
172
+ "x86_64-pc-windows-msvc",
173
+ "x86_64-unknown-linux-gnu",
174
+ "x86_64-unknown-linux-musl"
175
+ ]
176
+ }
177
+ }
@@ -17,6 +17,15 @@ pub const PBKDF2_MIN_LEN: u32 = 1;
17
17
  /// Cap derived-key length so a hostile caller can't request a huge allocation.
18
18
  pub const PBKDF2_MAX_LEN: u32 = 1024 * 1024;
19
19
 
20
+ /// Pure core (no napi types) for [`pbkdf2_sha256`] and the off-thread task
21
+ /// runtime: derive `dk_len` bytes, clamping the length to the documented range.
22
+ pub fn pbkdf2_sha256_into(password: &[u8], salt: &[u8], rounds: u32, dk_len: u32) -> Vec<u8> {
23
+ let dk_len = dk_len.clamp(PBKDF2_MIN_LEN, PBKDF2_MAX_LEN) as usize;
24
+ let mut out = vec![0u8; dk_len];
25
+ pbkdf2_hmac::<Sha256>(password, salt, rounds.max(1), &mut out);
26
+ out
27
+ }
28
+
20
29
  /// Derive a PBKDF2-HMAC-SHA256 key: `out = PBKDF2(password, salt, rounds, dkLen)`.
21
30
  #[napi]
22
31
  pub fn pbkdf2_sha256(
package/rust/ffi/mod.rs CHANGED
@@ -52,6 +52,7 @@ mod payload;
52
52
  mod probe;
53
53
  mod rate_limit;
54
54
  mod route;
55
+ mod task;
55
56
  mod util;
56
57
  mod validators;
57
58
 
@@ -0,0 +1,172 @@
1
+ // rust/ffi/task.rs — C-ABI exports for the off-thread task runtime.
2
+ //
3
+ // Eight symbols, all thin: the pool/ring/ops live in `rust/task/`. `submit`
4
+ // COPIES the packed args onto the worker (the JS caller's pooled buffer is
5
+ // released as soon as submit returns), and `drain` follows the repo-wide
6
+ // needed-size convention so JS grows once and retries once.
7
+
8
+ use std::slice;
9
+
10
+ use super::util::panic_guard;
11
+ use crate::task;
12
+
13
+ /// Start the pool with `threads` workers (0 → runtime default).
14
+ /// Returns `0` started, `1` already running, `2` internal error.
15
+ #[no_mangle]
16
+ pub extern "C" fn castrum_task_init(threads: u32) -> u32 {
17
+ panic_guard(|| task::init(Some(threads)), 2)
18
+ }
19
+
20
+ /// Submit `op` with `args_len` packed arg bytes, completing under `task_id`.
21
+ /// Returns `1` accepted, `0` rejected (null args with a non-zero length).
22
+ ///
23
+ /// # Safety
24
+ /// `args` must be valid for reads of `args_len` bytes.
25
+ #[no_mangle]
26
+ pub unsafe extern "C" fn castrum_task_submit(
27
+ op: u32,
28
+ args: *const u8,
29
+ args_len: usize,
30
+ task_id: usize,
31
+ ) -> u32 {
32
+ if args.is_null() && args_len != 0 {
33
+ return 0;
34
+ }
35
+ let owned: Vec<u8> = if args_len == 0 {
36
+ Vec::new()
37
+ } else {
38
+ slice::from_raw_parts(args, args_len).to_vec()
39
+ };
40
+ u32::from(task::submit_op(op, owned, task_id as u64))
41
+ }
42
+
43
+ /// Drain finished tasks into `out` (packed `[u32 count][entry…]`).
44
+ /// Returns bytes written, `0` when nothing is pending, or the EXACT required
45
+ /// size when `out` is too small (nothing is consumed in that case).
46
+ ///
47
+ /// # Safety
48
+ /// `out` must be valid for writes of `out_cap` bytes.
49
+ #[no_mangle]
50
+ pub unsafe extern "C" fn castrum_task_drain(out: *mut u8, out_cap: usize) -> usize {
51
+ if out.is_null() {
52
+ return 0;
53
+ }
54
+ panic_guard(|| task::drain(slice::from_raw_parts_mut(out, out_cap)), 0)
55
+ }
56
+
57
+ /// Completions waiting to be drained.
58
+ #[no_mangle]
59
+ pub extern "C" fn castrum_task_pending() -> u32 {
60
+ task::pending()
61
+ }
62
+
63
+ /// Submit an op that writes its result DIRECTLY into `out` (zero-copy output):
64
+ /// the completion body is the 8-byte LE written length, not the data, so JS
65
+ /// resolves a view of the buffer it already owns. A too-small `out` completes
66
+ /// with status 3 (TOO_SMALL) and the exact required size as the body; status 4
67
+ /// (UNSUPPORTED) means the op has no `_into` form and the caller should fall
68
+ /// back to `castrum_task_submit`.
69
+ ///
70
+ /// Returns `1` accepted, `0` rejected.
71
+ ///
72
+ /// # Safety
73
+ /// `args` must be valid for reads of `args_len` bytes. `out` must be valid for
74
+ /// writes of `out_cap` bytes and must stay alive (and untouched by JS) until the
75
+ /// completion for `task_id` is drained.
76
+ #[no_mangle]
77
+ pub unsafe extern "C" fn castrum_task_submit_out(
78
+ op: u32,
79
+ args: *const u8,
80
+ args_len: usize,
81
+ task_id: usize,
82
+ out: *mut u8,
83
+ out_cap: usize,
84
+ ) -> u32 {
85
+ if (args.is_null() && args_len != 0) || out.is_null() || out_cap == 0 {
86
+ return 0;
87
+ }
88
+ let owned: Vec<u8> = if args_len == 0 {
89
+ Vec::new()
90
+ } else {
91
+ slice::from_raw_parts(args, args_len).to_vec()
92
+ };
93
+ u32::from(task::submit_op_into(
94
+ op,
95
+ owned,
96
+ task_id as u64,
97
+ out as usize,
98
+ out_cap,
99
+ ))
100
+ }
101
+
102
+ /// Mark `task_id` cancelled. Returns `1` when newly marked, `0` otherwise.
103
+ #[no_mangle]
104
+ pub extern "C" fn castrum_task_cancel(task_id: usize) -> u32 {
105
+ u32::from(task::mark_cancelled(task_id as u64))
106
+ }
107
+
108
+ /// Submit an op whose PAYLOAD is read IN PLACE from the caller's buffer
109
+ /// (zero-copy INPUT). Only the `hdr_len` header bytes are copied, so a large
110
+ /// argument never crosses the boundary — without this, offloading a big
111
+ /// compression costs a full-size copy on the JS thread and measured WORSE than
112
+ /// the synchronous built-in (docs/RND-CONCURRENCY.md §7b).
113
+ ///
114
+ /// Returns `1` accepted, `0` rejected.
115
+ ///
116
+ /// # Safety
117
+ /// `hdr` must be valid for reads of `hdr_len` bytes. `data` must be valid for
118
+ /// reads of `data_len` bytes and must stay alive (and unmodified by JS) until
119
+ /// the completion for `task_id` is drained.
120
+ #[no_mangle]
121
+ pub unsafe extern "C" fn castrum_task_submit_slice(
122
+ op: u32,
123
+ hdr: *const u8,
124
+ hdr_len: usize,
125
+ data: *const u8,
126
+ data_len: usize,
127
+ task_id: usize,
128
+ ) -> u32 {
129
+ if (hdr.is_null() && hdr_len != 0) || (data.is_null() && data_len != 0) {
130
+ return 0;
131
+ }
132
+ // The header is small, so copying it keeps the caller free to reuse a
133
+ // scratch buffer; the payload is never copied.
134
+ let owned = if hdr_len == 0 {
135
+ Vec::new()
136
+ } else {
137
+ slice::from_raw_parts(hdr, hdr_len).to_vec()
138
+ };
139
+ u32::from(task::submit_slice_op(
140
+ op,
141
+ owned,
142
+ data as usize,
143
+ data_len,
144
+ task_id as u64,
145
+ ))
146
+ }
147
+
148
+ /// Register the thread-safe doorbell trampoline address (`0` disables it).
149
+ /// Returns `1` on success.
150
+ ///
151
+ /// # Safety
152
+ /// `cb` must be a live `void()` C trampoline that is safe to call from any
153
+ /// thread (a `bun:ffi` `JSCallback({ threadsafe: true }).ptr`).
154
+ #[no_mangle]
155
+ pub extern "C" fn castrum_task_set_doorbell(cb: *const std::os::raw::c_void) -> u32 {
156
+ task::set_doorbell(cb as usize);
157
+ 1
158
+ }
159
+
160
+ /// Stop the pool and drop pending completions. Returns `0`.
161
+ #[no_mangle]
162
+ pub extern "C" fn castrum_task_shutdown() -> u32 {
163
+ task::shutdown();
164
+ task::completion::clear();
165
+ 0
166
+ }
167
+
168
+ /// Configured pool worker count (0 before the pool starts).
169
+ #[no_mangle]
170
+ pub extern "C" fn castrum_task_threads() -> u32 {
171
+ task::threads()
172
+ }
package/rust/http/form.rs CHANGED
@@ -108,8 +108,12 @@ mod tests {
108
108
  }
109
109
 
110
110
  #[test]
111
- fn form_parse_rejects_malformed_percent() {
112
- assert!(form_parse_packed_vec(b"a=%ZZ").is_err());
111
+ fn form_parse_malformed_percent_falls_back_to_raw() {
112
+ let packed = form_parse_packed_vec(b"a=%ZZ").unwrap();
113
+ assert_eq!(
114
+ crate::test_support::decode_packed_pairs(&packed),
115
+ vec![(b"a".to_vec(), b"%ZZ".to_vec())]
116
+ );
113
117
  }
114
118
 
115
119
  #[test]