knitting 0.1.56 → 0.1.61

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 (101) hide show
  1. package/BROWSER.md +134 -0
  2. package/README.md +347 -46
  3. package/knitting.browser.d.ts +6 -0
  4. package/knitting.browser.js +1 -0
  5. package/knitting.d.ts +3 -2
  6. package/knitting.js +3 -3
  7. package/map.md +21 -9
  8. package/package.json +17 -2
  9. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
  12. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  13. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
  15. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  16. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  17. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  18. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  19. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  20. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  21. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  22. package/scripts/build-compiled-worker.ts +276 -0
  23. package/scripts/compiled-worker/porffor.d.ts +5 -0
  24. package/scripts/compiled-worker/runtime.ts +529 -0
  25. package/scripts/compiled-worker/task-shim.ts +32 -0
  26. package/src/api.d.ts +24 -1
  27. package/src/api.js +311 -27
  28. package/src/common/module-url.d.ts +1 -0
  29. package/src/common/module-url.js +30 -2
  30. package/src/common/node-compat.d.ts +2 -0
  31. package/src/common/path-canonical.js +20 -6
  32. package/src/common/runtime.d.ts +3 -1
  33. package/src/common/runtime.js +25 -4
  34. package/src/common/task-source.d.ts +2 -0
  35. package/src/common/task-source.js +17 -1
  36. package/src/common/worker-runtime.js +16 -7
  37. package/src/connections/buffer-reference-native.js +25 -1
  38. package/src/connections/buffer-reference.d.ts +23 -2
  39. package/src/connections/buffer-reference.js +143 -29
  40. package/src/connections/bun.d.ts +1 -1
  41. package/src/connections/bun.js +25 -14
  42. package/src/connections/deno.d.ts +3 -1
  43. package/src/connections/deno.js +35 -14
  44. package/src/connections/external-array-buffer.d.ts +2 -0
  45. package/src/connections/external-array-buffer.js +33 -0
  46. package/src/connections/node-addons.d.ts +11 -0
  47. package/src/connections/node-addons.js +49 -1
  48. package/src/connections/node-ffi-api.d.ts +19 -0
  49. package/src/connections/node-ffi-api.js +28 -0
  50. package/src/connections/node-ffi.d.ts +27 -0
  51. package/src/connections/node-ffi.js +262 -0
  52. package/src/connections/node.d.ts +1 -1
  53. package/src/connections/node.js +20 -20
  54. package/src/connections/package-assets.js +25 -15
  55. package/src/connections/posix.d.ts +42 -0
  56. package/src/connections/posix.js +101 -0
  57. package/src/connections/process-shared-buffer.d.ts +1 -0
  58. package/src/connections/process-shared-buffer.js +11 -45
  59. package/src/connections/types.d.ts +4 -0
  60. package/src/connections/windows.d.ts +19 -10
  61. package/src/connections/windows.js +87 -66
  62. package/src/error.d.ts +7 -6
  63. package/src/error.js +8 -7
  64. package/src/memory/lock.d.ts +112 -77
  65. package/src/memory/lock.js +314 -87
  66. package/src/memory/payloadCodec.d.ts +11 -2
  67. package/src/memory/payloadCodec.js +129 -37
  68. package/src/memory/regionRegistry.d.ts +1 -3
  69. package/src/memory/regionRegistry.js +16 -14
  70. package/src/permission/compatibility.d.ts +28 -0
  71. package/src/permission/compatibility.js +239 -0
  72. package/src/permission/index.d.ts +2 -0
  73. package/src/permission/index.js +1 -0
  74. package/src/permission/protocol.d.ts +1 -0
  75. package/src/permission/protocol.js +59 -15
  76. package/src/runtime/compiled-artifact.d.ts +21 -0
  77. package/src/runtime/compiled-artifact.js +260 -0
  78. package/src/runtime/compiled-builder.d.ts +8 -0
  79. package/src/runtime/compiled-builder.js +42 -0
  80. package/src/runtime/compiled-worker.d.ts +27 -0
  81. package/src/runtime/compiled-worker.js +562 -0
  82. package/src/runtime/dispatcher.js +9 -0
  83. package/src/runtime/inline-executor.js +27 -15
  84. package/src/runtime/pool.d.ts +121 -5
  85. package/src/runtime/pool.js +263 -49
  86. package/src/runtime/process-worker.d.ts +3 -13
  87. package/src/runtime/process-worker.js +97 -101
  88. package/src/runtime/tx-queue.d.ts +18 -2
  89. package/src/runtime/tx-queue.js +56 -16
  90. package/src/runtime/worker-common.d.ts +13 -0
  91. package/src/runtime/worker-common.js +102 -0
  92. package/src/types.d.ts +89 -9
  93. package/src/worker/composable-runners.js +3 -2
  94. package/src/worker/loop.d.ts +1 -0
  95. package/src/worker/loop.js +52 -17
  96. package/src/worker/rx-queue.d.ts +11 -1
  97. package/src/worker/rx-queue.js +4 -1
  98. package/src/worker/task-loader.d.ts +5 -4
  99. package/src/worker/task-loader.js +13 -7
  100. package/src/worker/timers.d.ts +8 -1
  101. package/src/worker/timers.js +28 -23
@@ -0,0 +1,2 @@
1
+ export declare const detachExternalArrayBuffer: (buffer: ArrayBuffer) => boolean;
2
+ export declare const requireDetachedExternalArrayBuffer: (buffer: ArrayBuffer) => void;
@@ -0,0 +1,33 @@
1
+ export const detachExternalArrayBuffer = (buffer) => {
2
+ if (buffer.byteLength === 0)
3
+ return true;
4
+ const detachable = buffer;
5
+ try {
6
+ if (typeof detachable.transferToFixedLength === "function") {
7
+ detachable.transferToFixedLength(0);
8
+ return buffer.byteLength === 0;
9
+ }
10
+ }
11
+ catch {
12
+ }
13
+ try {
14
+ if (typeof detachable.transfer === "function") {
15
+ detachable.transfer(0);
16
+ return buffer.byteLength === 0;
17
+ }
18
+ }
19
+ catch {
20
+ }
21
+ try {
22
+ structuredClone(buffer, { transfer: [buffer] });
23
+ return buffer.byteLength === 0;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ };
29
+ export const requireDetachedExternalArrayBuffer = (buffer) => {
30
+ if (detachExternalArrayBuffer(buffer))
31
+ return;
32
+ throw new Error("knitting: could not detach external ArrayBuffer; refusing to unmap memory still reachable from JavaScript");
33
+ };
@@ -1,5 +1,16 @@
1
1
  type NodeRequire = (specifier: string) => unknown;
2
2
  export type NodeNativeAddonName = "knitting_shared_memory" | "knitting_shm" | "knitting_buffer_pointer";
3
+ export type NodePlatformInfo = {
4
+ arch: string;
5
+ modules?: string;
6
+ platform: string;
7
+ version?: string;
8
+ };
9
+ export declare const SUPPORTED_NODE_NATIVE_ADDON_ABIS: {
10
+ readonly "127": "22";
11
+ readonly "137": "24";
12
+ };
13
+ export declare const formatNodeNativeAddonLoadError: (name: NodeNativeAddonName, platformInfo: NodePlatformInfo | undefined, errors: readonly string[]) => string;
3
14
  export declare const nodeNativeAddonSpecifiers: (name: NodeNativeAddonName) => readonly string[];
4
15
  export declare const loadNodeNativeAddon: <T>(require: NodeRequire, name: NodeNativeAddonName, specifier?: string) => T;
5
16
  export {};
@@ -1,4 +1,15 @@
1
1
  import { resolveKnittingPackageAsset } from "./package-assets.js";
2
+ export const SUPPORTED_NODE_NATIVE_ADDON_ABIS = {
3
+ "127": "22",
4
+ "137": "24",
5
+ };
6
+ const NODE_26_MODULE_ABI = "147";
7
+ const SUPPORTED_NODE_NATIVE_TARGETS = new Set([
8
+ "darwin-arm64",
9
+ "darwin-x64",
10
+ "linux-x64",
11
+ "win32-x64",
12
+ ]);
2
13
  const readNodePlatformInfo = () => {
3
14
  const processLike = globalThis.process;
4
15
  if (typeof processLike?.platform !== "string" ||
@@ -9,8 +20,45 @@ const readNodePlatformInfo = () => {
9
20
  arch: processLike.arch,
10
21
  modules: processLike.versions?.modules,
11
22
  platform: processLike.platform,
23
+ version: processLike.versions?.node,
12
24
  };
13
25
  };
26
+ const formatAttemptErrors = (errors) => errors.length > 0 ? `\nTried:\n${errors.join("\n")}` : "";
27
+ export const formatNodeNativeAddonLoadError = (name, platformInfo, errors) => {
28
+ const attempts = formatAttemptErrors(errors);
29
+ if (platformInfo === undefined) {
30
+ return `knitting: could not load native addon ${name}.${attempts}`;
31
+ }
32
+ const abi = platformInfo.modules;
33
+ const version = platformInfo.version === undefined
34
+ ? "an unknown version"
35
+ : `v${platformInfo.version}`;
36
+ const target = `${platformInfo.platform}-${platformInfo.arch}`;
37
+ if (abi === NODE_26_MODULE_ABI) {
38
+ return (`knitting: Node.js 26 uses node:ffi instead of ABI-specific addons. ` +
39
+ `Restart Node with the --experimental-ffi flag` +
40
+ ` (--allow-ffi is also required when using Node's Permission Model). ` +
41
+ `No Node ABI 147 addon is shipped.${attempts}`);
42
+ }
43
+ if (typeof abi === "string" &&
44
+ !(abi in SUPPORTED_NODE_NATIVE_ADDON_ABIS)) {
45
+ return (`knitting: no native addon is shipped for Node.js ${version} ` +
46
+ `(ABI ${abi}). Packaged native features support Node.js 22 ` +
47
+ `(ABI 127) and Node.js 24 (ABI 137) only. Plain thread workers that do ` +
48
+ `not use ProcessSharedBuffer or BufferReference do not need these ` +
49
+ `addons.${attempts}`);
50
+ }
51
+ if (!SUPPORTED_NODE_NATIVE_TARGETS.has(target)) {
52
+ return (`knitting: Node.js ${version} uses a supported ABI (${abi ?? "unknown"}), ` +
53
+ `but no packaged native addon target is shipped for ${target}. ` +
54
+ `Published targets are linux-x64, darwin-x64, darwin-arm64, and ` +
55
+ `win32-x64. You can build locally with "bun run build:native".${attempts}`);
56
+ }
57
+ return (`knitting: could not load native addon ${name} for Node.js ${version} ` +
58
+ `(ABI ${abi ?? "unknown"}, ${target}). The prebuild may be missing, ` +
59
+ `damaged, or incompatible; reinstall the package or build locally with ` +
60
+ `"bun run build:native".${attempts}`);
61
+ };
14
62
  export const nodeNativeAddonSpecifiers = (name) => {
15
63
  const platformInfo = readNodePlatformInfo();
16
64
  const fallback = resolveKnittingPackageAsset("build", "Release", `${name}.node`);
@@ -40,5 +88,5 @@ export const loadNodeNativeAddon = (require, name, specifier) => {
40
88
  errors.push(`${candidate}: ${String(error)}`);
41
89
  }
42
90
  }
43
- throw new Error(`Could not load native addon ${name}. Tried:\n${errors.join("\n")}`);
91
+ throw new Error(formatNodeNativeAddonLoadError(name, readNodePlatformInfo(), errors));
44
92
  };
@@ -0,0 +1,19 @@
1
+ export type NodeFfiFunctionSignature = {
2
+ arguments?: string[];
3
+ return?: string;
4
+ };
5
+ export type NodeFfiLibrary = {
6
+ close: () => void;
7
+ };
8
+ export type NodeFfiDlopenResult<Functions extends Record<string, (...args: never[]) => unknown>> = {
9
+ functions: Functions;
10
+ lib: NodeFfiLibrary;
11
+ };
12
+ export type NodeFfiApi = {
13
+ dlopen: <Functions extends Record<string, (...args: never[]) => unknown>>(path: string | null, definitions: Record<string, NodeFfiFunctionSignature>) => NodeFfiDlopenResult<Functions>;
14
+ getRawPointer: (source: ArrayBuffer | SharedArrayBuffer | ArrayBufferView) => bigint;
15
+ suffix: string;
16
+ toArrayBuffer: (pointer: bigint, length: number, copy?: boolean) => ArrayBuffer;
17
+ };
18
+ export declare const isNodeFfiTarget: () => boolean;
19
+ export declare const getNodeFfi: () => NodeFfiApi;
@@ -0,0 +1,28 @@
1
+ import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
2
+ const readNodeMajor = () => {
3
+ const version = getNodeProcess()?.versions?.node;
4
+ if (typeof version !== "string")
5
+ return undefined;
6
+ const major = Number.parseInt(version.split(".", 1)[0] ?? "", 10);
7
+ return Number.isInteger(major) ? major : undefined;
8
+ };
9
+ export const isNodeFfiTarget = () => {
10
+ const major = readNodeMajor() ?? 0;
11
+ return major >= 26 && major % 2 === 0;
12
+ };
13
+ let cachedNodeFfi;
14
+ export const getNodeFfi = () => {
15
+ if (cachedNodeFfi !== undefined)
16
+ return cachedNodeFfi;
17
+ const ffi = getNodeBuiltinModule("node:ffi");
18
+ if (ffi === undefined ||
19
+ typeof ffi.dlopen !== "function" ||
20
+ typeof ffi.getRawPointer !== "function" ||
21
+ typeof ffi.toArrayBuffer !== "function") {
22
+ throw new Error("knitting: Node.js 26 native features require node:ffi. " +
23
+ "Restart Node with --experimental-ffi" +
24
+ " (--allow-ffi is also required when using Node's Permission Model).");
25
+ }
26
+ cachedNodeFfi = ffi;
27
+ return ffi;
28
+ };
@@ -0,0 +1,27 @@
1
+ import { type NodeFfiLibrary } from "./node-ffi-api.js";
2
+ import { type CreateSharedMemoryOptions, type MapSharedMemoryOptions, type SharedMemoryConnectionPrimitives, type SharedMemoryMapping } from "./types.js";
3
+ export { getNodeFfi, isNodeFfiTarget } from "./node-ffi-api.js";
4
+ export type { NodeFfiApi, NodeFfiDlopenResult, NodeFfiFunctionSignature, NodeFfiLibrary, } from "./node-ffi-api.js";
5
+ type NodePosixFunctions = {
6
+ memfd_create?: (name: Uint8Array, flags: number) => number;
7
+ shm_open?: (name: Uint8Array, flags: number, ...rest: number[]) => number;
8
+ shm_unlink?: (name: Uint8Array) => number;
9
+ __error?: () => bigint;
10
+ __errno_location?: () => bigint;
11
+ ftruncate: (fd: number, length: bigint) => number;
12
+ dup: (fd: number) => number;
13
+ fcntl: (fd: number, cmd: number, arg: number) => number;
14
+ mmap: (address: null, length: bigint, protection: number, flags: number, fd: number, offset: bigint) => bigint;
15
+ munmap: (address: bigint, length: bigint) => number;
16
+ close: (fd: number) => number;
17
+ };
18
+ type NodePosixLibrary = {
19
+ functions: NodePosixFunctions;
20
+ lib: NodeFfiLibrary;
21
+ };
22
+ export declare const openNodePosixLibrary: () => NodePosixLibrary;
23
+ export declare const mapNodeFfiSharedMemory: (options: MapSharedMemoryOptions, library?: NodePosixLibrary) => SharedMemoryMapping<ArrayBuffer>;
24
+ export declare const createNodeFfiSharedMemory: (options: number | CreateSharedMemoryOptions, library?: NodePosixLibrary) => SharedMemoryMapping<ArrayBuffer>;
25
+ export declare const unlinkNodeFfiSharedMemory: (name: string, library?: NodePosixLibrary) => boolean;
26
+ export declare const createNodeFfiPosixConnectionPrimitives: (library?: NodePosixLibrary) => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
27
+ export declare const createNodeFfiConnectionPrimitives: () => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
@@ -0,0 +1,262 @@
1
+ import { assertNamedSharedMemoryIsReopenable, checkPosixResult, DARWIN_O_CREAT, DARWIN_O_EXCL, DARWIN_SHM_MODE, detectPosixPlatform, encodeCString, getErrnoSymbolName, getPosixLibcPath, LINUX_O_CREAT, LINUX_O_EXCL, makeDarwinSharedMemoryName, makeErrnoReader, MAP_SHARED, O_RDWR, POSIX_SHM_MODE, posixError, PROT_READ, PROT_WRITE, setCloseOnExec, SHM_OPEN_STACK_PAD, shmOpenModeIsStackPassed, toPosixSharedMemoryName, } from "./posix.js";
2
+ import { createNodeWindowsConnectionPrimitives, isWindowsRuntime, } from "./windows.js";
3
+ import { requireDetachedExternalArrayBuffer } from "./external-array-buffer.js";
4
+ import { getNodeFfi, } from "./node-ffi-api.js";
5
+ import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readCreateSize, readRequiredCreateName, } from "./types.js";
6
+ export { getNodeFfi, isNodeFfiTarget } from "./node-ffi-api.js";
7
+ let cachedNodePosixLibrary;
8
+ const getNodeCreateSymbols = (platform = detectPosixPlatform()) => {
9
+ if (platform === "darwin") {
10
+ return {
11
+ shm_open: {
12
+ arguments: shmOpenModeIsStackPassed(platform)
13
+ ? ["pointer", "i32", ...SHM_OPEN_STACK_PAD.map(() => "i32"), "u32"]
14
+ : ["pointer", "i32", "u32"],
15
+ return: "i32",
16
+ },
17
+ shm_unlink: {
18
+ arguments: ["pointer"],
19
+ return: "i32",
20
+ },
21
+ };
22
+ }
23
+ return {
24
+ memfd_create: {
25
+ arguments: ["pointer", "u32"],
26
+ return: "i32",
27
+ },
28
+ shm_open: {
29
+ arguments: ["pointer", "i32", "u32"],
30
+ return: "i32",
31
+ },
32
+ shm_unlink: {
33
+ arguments: ["pointer"],
34
+ return: "i32",
35
+ },
36
+ };
37
+ };
38
+ const getNodeErrnoSymbols = (platform = detectPosixPlatform()) => ({
39
+ [getErrnoSymbolName(platform)]: {
40
+ arguments: [],
41
+ return: "pointer",
42
+ },
43
+ });
44
+ const getNodePosixSymbols = () => ({
45
+ ...getNodeCreateSymbols(),
46
+ ftruncate: {
47
+ arguments: ["i32", "i64"],
48
+ return: "i32",
49
+ },
50
+ dup: {
51
+ arguments: ["i32"],
52
+ return: "i32",
53
+ },
54
+ fcntl: {
55
+ arguments: ["i32", "i32", "i32"],
56
+ return: "i32",
57
+ },
58
+ mmap: {
59
+ arguments: ["pointer", "u64", "i32", "i32", "i32", "i64"],
60
+ return: "pointer",
61
+ },
62
+ munmap: {
63
+ arguments: ["pointer", "u64"],
64
+ return: "i32",
65
+ },
66
+ close: {
67
+ arguments: ["i32"],
68
+ return: "i32",
69
+ },
70
+ });
71
+ export const openNodePosixLibrary = () => {
72
+ if (cachedNodePosixLibrary !== undefined)
73
+ return cachedNodePosixLibrary;
74
+ const symbols = getNodePosixSymbols();
75
+ const ffi = getNodeFfi();
76
+ // errno is diagnostics only: if this libc or FFI build will not bind the
77
+ // nullary errno accessor, open without it rather than lose shared memory.
78
+ let opened;
79
+ try {
80
+ opened = ffi.dlopen(getPosixLibcPath(), {
81
+ ...symbols,
82
+ ...getNodeErrnoSymbols(),
83
+ });
84
+ }
85
+ catch {
86
+ opened = ffi.dlopen(getPosixLibcPath(), symbols);
87
+ }
88
+ cachedNodePosixLibrary = opened;
89
+ return opened;
90
+ };
91
+ const callShmOpen = (shmOpen, name, flags, mode, platform) => shmOpenModeIsStackPassed(platform)
92
+ ? shmOpen(name, flags, ...SHM_OPEN_STACK_PAD, mode)
93
+ : shmOpen(name, flags, mode);
94
+ const errnoOf = (libc, platform = detectPosixPlatform()) => makeErrnoReader(platform === "darwin" ? libc.__error : libc.__errno_location, (pointer) => new Int32Array(getNodeFfi().toArrayBuffer(pointer, 4, true))[0]);
95
+ const isMapFailed = (pointer) => pointer === 0n ||
96
+ pointer === -1n ||
97
+ pointer === BigInt.asUintN(64, -1n);
98
+ const createAnonymousFd = (name, platform, libc) => {
99
+ if (platform === "darwin") {
100
+ const shmOpen = libc.shm_open;
101
+ const shmUnlink = libc.shm_unlink;
102
+ if (shmOpen === undefined || shmUnlink === undefined) {
103
+ throw new Error("shm_open symbols are not available");
104
+ }
105
+ const shmName = encodeCString(makeDarwinSharedMemoryName(name, "node"));
106
+ const fd = checkPosixResult(callShmOpen(shmOpen, shmName, O_RDWR | DARWIN_O_CREAT | DARWIN_O_EXCL, DARWIN_SHM_MODE, platform), "shm_open failed", errnoOf(libc, platform), platform);
107
+ shmUnlink(shmName);
108
+ try {
109
+ return setCloseOnExec({ symbols: libc }, fd);
110
+ }
111
+ catch (error) {
112
+ libc.close(fd);
113
+ throw error;
114
+ }
115
+ }
116
+ const memfdCreate = libc.memfd_create;
117
+ if (memfdCreate === undefined) {
118
+ throw new Error("memfd_create symbol is not available");
119
+ }
120
+ const fd = checkPosixResult(memfdCreate(encodeCString(name), 0), "memfd_create failed", errnoOf(libc, platform), platform);
121
+ try {
122
+ return setCloseOnExec({ symbols: libc }, fd);
123
+ }
124
+ catch (error) {
125
+ libc.close(fd);
126
+ throw error;
127
+ }
128
+ };
129
+ const createNamedFd = (name, mode, platform, libc) => {
130
+ const shmOpen = libc.shm_open;
131
+ if (shmOpen === undefined) {
132
+ throw new Error("shm_open symbol is not available");
133
+ }
134
+ const createFlag = platform === "darwin" ? DARWIN_O_CREAT : LINUX_O_CREAT;
135
+ const exclusiveFlag = platform === "darwin" ? DARWIN_O_EXCL : LINUX_O_EXCL;
136
+ const flags = mode === "create"
137
+ ? O_RDWR | createFlag | exclusiveFlag
138
+ : O_RDWR;
139
+ const shmMode = platform === "darwin" ? DARWIN_SHM_MODE : POSIX_SHM_MODE;
140
+ const shmName = encodeCString(toPosixSharedMemoryName(name));
141
+ const fd = checkPosixResult(callShmOpen(shmOpen, shmName, flags, shmMode, platform), "shm_open failed", errnoOf(libc, platform), platform);
142
+ if (mode === "create") {
143
+ assertNamedSharedMemoryIsReopenable(platform, fd, () => callShmOpen(shmOpen, shmName, O_RDWR, shmMode, platform), libc.close, errnoOf(libc, platform));
144
+ }
145
+ try {
146
+ return setCloseOnExec({ symbols: libc }, fd);
147
+ }
148
+ catch (error) {
149
+ libc.close(fd);
150
+ throw error;
151
+ }
152
+ };
153
+ const mapFd = (fd, size, name, libc, closeFdOnFailure) => {
154
+ const pointer = libc.mmap(null, BigInt(size), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0n);
155
+ if (isMapFailed(pointer)) {
156
+ // errno first: fcntl below overwrites it.
157
+ const error = posixError("mmap failed", errnoOf(libc));
158
+ const descriptorFlags = libc.fcntl(fd, 1, 0);
159
+ if (closeFdOnFailure)
160
+ libc.close(fd);
161
+ error.message +=
162
+ ` (fd ${fd}, size ${size}, descriptor flags ${descriptorFlags})`;
163
+ throw error;
164
+ }
165
+ let arrayBuffer;
166
+ try {
167
+ arrayBuffer = getNodeFfi().toArrayBuffer(pointer, size, false);
168
+ }
169
+ catch (error) {
170
+ libc.munmap(pointer, BigInt(size));
171
+ if (closeFdOnFailure)
172
+ libc.close(fd);
173
+ throw error;
174
+ }
175
+ let closed = false;
176
+ return {
177
+ runtime: "node",
178
+ fd,
179
+ name,
180
+ size,
181
+ byteLength: arrayBuffer.byteLength,
182
+ buffer: arrayBuffer,
183
+ kind: "external-array-buffer",
184
+ arrayBuffer,
185
+ unsafePointer: pointer,
186
+ baseAddressMod64: Number(pointer % 64n),
187
+ close: () => {
188
+ if (closed)
189
+ return;
190
+ requireDetachedExternalArrayBuffer(arrayBuffer);
191
+ closed = true;
192
+ try {
193
+ libc.munmap(pointer, BigInt(size));
194
+ }
195
+ finally {
196
+ libc.close(fd);
197
+ }
198
+ },
199
+ };
200
+ };
201
+ export const mapNodeFfiSharedMemory = (options, library = openNodePosixLibrary()) => {
202
+ const libc = library.functions;
203
+ const size = expectPositiveSize(options.size);
204
+ let fd;
205
+ let closeFdOnFailure = true;
206
+ if (options.name !== undefined) {
207
+ fd = createNamedFd(options.name, "open", detectPosixPlatform(), libc);
208
+ }
209
+ else {
210
+ const sourceFd = expectFd(options.fd);
211
+ closeFdOnFailure = options.duplicateFd !== false;
212
+ fd = closeFdOnFailure
213
+ ? checkPosixResult(libc.dup(sourceFd), "dup(fd) failed", errnoOf(libc))
214
+ : sourceFd;
215
+ if (closeFdOnFailure) {
216
+ try {
217
+ setCloseOnExec({ symbols: libc }, fd);
218
+ }
219
+ catch (error) {
220
+ libc.close(fd);
221
+ throw error;
222
+ }
223
+ }
224
+ }
225
+ // A non-duplicated descriptor remains caller-owned until mmap and the
226
+ // JavaScript wrapper both succeed. The returned mapping owns it thereafter.
227
+ return mapFd(fd, size, options.name, libc, closeFdOnFailure);
228
+ };
229
+ export const createNodeFfiSharedMemory = (options, library = openNodePosixLibrary()) => {
230
+ const libc = library.functions;
231
+ const size = expectPositiveSize(readCreateSize(options));
232
+ const mode = readCreateMode(options);
233
+ const name = mode === "anonymous"
234
+ ? readCreateName(options, "knitting_shared_memory")
235
+ : readRequiredCreateName(options);
236
+ const platform = detectPosixPlatform();
237
+ const fd = mode === "anonymous"
238
+ ? createAnonymousFd(name, platform, libc)
239
+ : createNamedFd(name, mode, platform, libc);
240
+ if (mode !== "open" && libc.ftruncate(fd, BigInt(size)) < 0) {
241
+ const error = posixError("ftruncate failed", errnoOf(libc, platform), platform);
242
+ libc.close(fd);
243
+ throw error;
244
+ }
245
+ return mapFd(fd, size, mode === "anonymous" ? undefined : name, libc, true);
246
+ };
247
+ export const unlinkNodeFfiSharedMemory = (name, library = openNodePosixLibrary()) => {
248
+ const shmUnlink = library.functions.shm_unlink;
249
+ if (shmUnlink === undefined) {
250
+ throw new Error("shm_unlink symbol is not available");
251
+ }
252
+ return shmUnlink(encodeCString(toPosixSharedMemoryName(name))) === 0;
253
+ };
254
+ export const createNodeFfiPosixConnectionPrimitives = (library = openNodePosixLibrary()) => ({
255
+ runtime: "node",
256
+ createSharedMemory: (options) => createNodeFfiSharedMemory(options, library),
257
+ mapSharedMemory: (options) => mapNodeFfiSharedMemory(options, library),
258
+ unlinkSharedMemory: (name) => unlinkNodeFfiSharedMemory(name, library),
259
+ });
260
+ export const createNodeFfiConnectionPrimitives = () => isWindowsRuntime()
261
+ ? createNodeWindowsConnectionPrimitives()
262
+ : createNodeFfiPosixConnectionPrimitives();
@@ -26,5 +26,5 @@ export declare const loadNodeFutexAddon: (specifier?: string) => NodeFutexAddon;
26
26
  export declare const fromNodeNativeMapping: (mapped: NodeSharedMemoryNativeMapping) => SharedMemoryMapping<SharedArrayBuffer>;
27
27
  export declare const createNodeSharedMemory: (options: number | CreateSharedMemoryOptions, addon?: NodeSharedMemoryAddon) => SharedMemoryMapping<SharedArrayBuffer>;
28
28
  export declare const mapNodeSharedMemory: (options: MapSharedMemoryOptions, addon?: NodeSharedMemoryAddon) => SharedMemoryMapping<SharedArrayBuffer>;
29
- export declare const createNodeConnectionPrimitives: (addon?: NodeSharedMemoryAddon) => SharedMemoryConnectionPrimitives<SharedMemoryMapping<SharedArrayBuffer>>;
29
+ export declare const createNodeConnectionPrimitives: (addon?: NodeSharedMemoryAddon) => SharedMemoryConnectionPrimitives;
30
30
  export declare const unlinkNodeSharedMemory: (name: string, addon?: NodeSharedMemoryAddon) => boolean;
@@ -1,24 +1,18 @@
1
1
  import { getNodeBuiltinModule } from "../common/node-compat.js";
2
- import { loadNodeNativeAddon } from "./node-addons.js";
3
- import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readRequiredCreateName, readCreateSize, } from "./types.js";
2
+ import { loadNodeNativeAddon, } from "./node-addons.js";
3
+ import { createNodeFfiConnectionPrimitives, isNodeFfiTarget, } from "./node-ffi.js";
4
+ import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readCreateSize, readRequiredCreateName, } from "./types.js";
4
5
  export const DEFAULT_NODE_SHARED_MEMORY_ADDON = "../../build/Release/knitting_shared_memory.node";
5
6
  export const DEFAULT_NODE_FUTEX_ADDON = "../../build/Release/knitting_shm.node";
6
- export const loadNodeSharedMemoryAddon = (specifier) => {
7
+ const loadNodeAddon = (label, name, specifier) => {
7
8
  const nodeModule = getNodeBuiltinModule("node:module");
8
9
  if (nodeModule === undefined) {
9
- throw new Error("Node shared memory addon can only be loaded in Node");
10
+ throw new Error(`Node ${label} addon can only be loaded in Node`);
10
11
  }
11
- const require = nodeModule.createRequire(import.meta.url);
12
- return loadNodeNativeAddon(require, "knitting_shared_memory", specifier);
13
- };
14
- export const loadNodeFutexAddon = (specifier) => {
15
- const nodeModule = getNodeBuiltinModule("node:module");
16
- if (nodeModule === undefined) {
17
- throw new Error("Node futex addon can only be loaded in Node");
18
- }
19
- const require = nodeModule.createRequire(import.meta.url);
20
- return loadNodeNativeAddon(require, "knitting_shm", specifier);
12
+ return loadNodeNativeAddon(nodeModule.createRequire(import.meta.url), name, specifier);
21
13
  };
14
+ export const loadNodeSharedMemoryAddon = (specifier) => loadNodeAddon("shared memory", "knitting_shared_memory", specifier);
15
+ export const loadNodeFutexAddon = (specifier) => loadNodeAddon("futex", "knitting_shm", specifier);
22
16
  export const fromNodeNativeMapping = (mapped) => {
23
17
  const mapping = {
24
18
  runtime: "node",
@@ -48,12 +42,18 @@ export const mapNodeSharedMemory = (options, addon = loadNodeSharedMemoryAddon()
48
42
  const size = expectPositiveSize(options.size);
49
43
  return fromNodeNativeMapping(addon.mapSharedMemory(fd, size, options.name));
50
44
  };
51
- export const createNodeConnectionPrimitives = (addon = loadNodeSharedMemoryAddon()) => ({
52
- runtime: "node",
53
- createSharedMemory: (options) => createNodeSharedMemory(options, addon),
54
- mapSharedMemory: (options) => mapNodeSharedMemory(options, addon),
55
- unlinkSharedMemory: (name) => unlinkNodeSharedMemory(name, addon),
56
- });
45
+ export const createNodeConnectionPrimitives = (addon) => {
46
+ if (addon === undefined && isNodeFfiTarget()) {
47
+ return createNodeFfiConnectionPrimitives();
48
+ }
49
+ const resolvedAddon = addon ?? loadNodeSharedMemoryAddon();
50
+ return {
51
+ runtime: "node",
52
+ createSharedMemory: (options) => createNodeSharedMemory(options, resolvedAddon),
53
+ mapSharedMemory: (options) => mapNodeSharedMemory(options, resolvedAddon),
54
+ unlinkSharedMemory: (name) => unlinkNodeSharedMemory(name, resolvedAddon),
55
+ };
56
+ };
57
57
  export const unlinkNodeSharedMemory = (name, addon = loadNodeSharedMemoryAddon()) => {
58
58
  if (typeof addon.unlinkSharedMemory !== "function") {
59
59
  throw new Error("Node shared memory addon cannot unlink named mappings");
@@ -1,30 +1,40 @@
1
- import { existsSync, statSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- const safeExists = (path) => {
1
+ import { getNodeBuiltinModule } from "../common/node-compat.js";
2
+ let fsModule;
3
+ let pathModule;
4
+ let urlModule;
5
+ const requireNode = (value, specifier) => {
6
+ if (value === undefined) {
7
+ throw new Error(`${specifier} is not available in this runtime`);
8
+ }
9
+ return value;
10
+ };
11
+ const fs = () => requireNode(fsModule ??= getNodeBuiltinModule("node:fs"), "node:fs");
12
+ const path = () => requireNode(pathModule ??= getNodeBuiltinModule("node:path"), "node:path");
13
+ const url = () => requireNode(urlModule ??= getNodeBuiltinModule("node:url"), "node:url");
14
+ const safeExists = (target) => {
5
15
  try {
6
- return existsSync(path);
16
+ return fs().existsSync(target);
7
17
  }
8
18
  catch {
9
19
  return false;
10
20
  }
11
21
  };
12
- const safeIsDirectory = (path) => {
22
+ const safeIsDirectory = (target) => {
13
23
  try {
14
- return statSync(path).isDirectory();
24
+ return fs().statSync(target).isDirectory();
15
25
  }
16
26
  catch {
17
27
  return false;
18
28
  }
19
29
  };
20
30
  const moduleDirectory = (moduleUrl) => {
21
- const path = fileURLToPath(moduleUrl);
22
- return safeIsDirectory(path) ? path : dirname(path);
31
+ const target = url().fileURLToPath(moduleUrl);
32
+ return safeIsDirectory(target) ? target : path().dirname(target);
23
33
  };
24
- const isKnittingPackageRoot = (dir) => safeExists(join(dir, "package.json")) &&
25
- (safeExists(join(dir, "prebuilds")) ||
26
- safeExists(join(dir, "src")) ||
27
- safeExists(join(dir, "build")));
34
+ const isKnittingPackageRoot = (dir) => safeExists(path().join(dir, "package.json")) &&
35
+ (safeExists(path().join(dir, "prebuilds")) ||
36
+ safeExists(path().join(dir, "src")) ||
37
+ safeExists(path().join(dir, "build")));
28
38
  let cachedPackageRoot;
29
39
  export const findKnittingPackageRoot = (moduleUrl = import.meta.url) => {
30
40
  if (cachedPackageRoot !== undefined)
@@ -35,11 +45,11 @@ export const findKnittingPackageRoot = (moduleUrl = import.meta.url) => {
35
45
  cachedPackageRoot = current;
36
46
  return current;
37
47
  }
38
- const parent = dirname(current);
48
+ const parent = path().dirname(current);
39
49
  if (parent === current) {
40
50
  throw new Error(`Could not locate knitting package root from ${moduleUrl}`);
41
51
  }
42
52
  current = parent;
43
53
  }
44
54
  };
45
- export const resolveKnittingPackageAsset = (...segments) => join(findKnittingPackageRoot(), ...segments);
55
+ export const resolveKnittingPackageAsset = (...segments) => path().join(findKnittingPackageRoot(), ...segments);
@@ -15,11 +15,53 @@ export declare const F_GETFD = 1;
15
15
  export declare const F_SETFD = 2;
16
16
  export declare const FD_CLOEXEC = 1;
17
17
  export declare const encodeCString: (value: string) => Uint8Array;
18
+ export declare const getErrnoSymbolName: (platform: PosixPlatform) => string;
19
+ /** Best effort: the runtime can issue its own libc call before errno is read. */
20
+ export type ErrnoReader = () => number | undefined;
21
+ /**
22
+ * Wraps a bound `__error`/`__errno_location` into a reader that never throws.
23
+ * Runtimes differ only in how they turn the returned address into an int.
24
+ */
25
+ export declare const makeErrnoReader: <Pointer>(locate: (() => Pointer) | undefined, readInt32: (pointer: Pointer) => number) => ErrnoReader;
26
+ /**
27
+ * Builds `<message>: EACCES (13)`, reading errno as late as possible, or the
28
+ * bare message when the runtime has no errno reader.
29
+ */
30
+ export declare const posixError: (message: string, errno?: ErrnoReader, platform?: PosixPlatform) => Error;
31
+ export declare const checkPosixResult: (result: number, message: string, errno?: ErrnoReader, platform?: PosixPlatform) => number;
18
32
  type FcntlLibc = {
19
33
  symbols: {
20
34
  fcntl: (fd: number, cmd: number, arg: number) => number;
21
35
  };
22
36
  };
37
+ /**
38
+ * Whether `shm_open`'s mode argument has to be delivered on the stack.
39
+ *
40
+ * macOS declares `shm_open(const char *, int, ...)` variadic and its libc
41
+ * wrapper reads the mode with `va_arg`. Apple's arm64 ABI passes variadic
42
+ * arguments on the stack, not in registers, so the mode a fixed-arity FFI call
43
+ * leaves in a register is never read: the segment is created with whatever the
44
+ * stack happened to hold. The creator's own descriptor still works, so the
45
+ * damage only surfaces when the name is opened again and fails with EACCES —
46
+ * and because the value comes from uninitialized stack, unrelated changes to
47
+ * the calling code can flip it between working and not.
48
+ *
49
+ * Declaring six extra register arguments moves the mode into the first stack
50
+ * slot, which is exactly where `va_arg` looks. Linux (fixed prototype) and
51
+ * x86-64 macOS (variadic arguments in registers) both want the plain form.
52
+ */
53
+ export declare const shmOpenModeIsStackPassed: (platform?: PosixPlatform) => boolean;
54
+ /** Fills x2–x7 so the mode becomes the first stack argument. */
55
+ export declare const SHM_OPEN_STACK_PAD: readonly number[];
56
+ /**
57
+ * Proves a freshly created macOS segment can be reopened by name, closing the
58
+ * owner descriptor and throwing if it cannot.
59
+ *
60
+ * Without this, a mode that failed to apply produces a confusing EACCES in
61
+ * whichever process later opens the name, far from the call that caused it.
62
+ * No-ops off macOS, where the mode always applies.
63
+ */
64
+ export declare const assertNamedSharedMemoryIsReopenable: (platform: PosixPlatform, ownerFd: number, reopen: () => number, close: (fd: number) => number, errno?: ErrnoReader) => void;
23
65
  export declare const setCloseOnExec: <T extends FcntlLibc>(libc: T, fd: number) => number;
24
66
  export declare const shmOpenCreateFlag: (platform: PosixPlatform) => number;
25
67
  export declare const shmOpenExclusiveFlag: (platform: PosixPlatform) => number;