@php-wasm/universal 3.0.53 → 3.1.0

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.
package/lib/api.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { type NodeEndpoint, type Remote, type IsomorphicMessagePort } from './comlink-sync';
1
+ import { releaseProxy, type NodeEndpoint as NodeWorker, type Remote, type IsomorphicMessagePort, type ProxyMethods } from './comlink-sync';
2
+ import { type NodeProcess } from './comlink-node-process-adapter';
3
+ export declare const releaseApiProxy: typeof releaseProxy;
2
4
  export type WithAPIState = {
3
5
  /**
4
6
  * Resolves to true when the remote API is ready for
@@ -11,9 +13,9 @@ export type WithAPIState = {
11
13
  */
12
14
  isReady: () => Promise<void>;
13
15
  };
14
- export type RemoteAPI<T> = Remote<T> & WithAPIState;
16
+ export type RemoteAPI<T> = Remote<T> & ProxyMethods & WithAPIState;
15
17
  export declare function consumeAPISync<APIType>(remote: IsomorphicMessagePort): Promise<APIType>;
16
- export declare function consumeAPI<APIType>(remote: Worker | Window | NodeEndpoint, context?: undefined | EventTarget): RemoteAPI<APIType>;
18
+ export declare function consumeAPI<APIType>(remote: Worker | Window | NodeWorker | NodeProcess, context?: undefined | EventTarget): RemoteAPI<APIType>;
17
19
  export type PublicAPI<Methods, PipedAPI = unknown> = RemoteAPI<Methods & PipedAPI>;
18
- export declare function exposeAPI<Methods, PipedAPI>(apiMethods?: Methods, pipedApi?: PipedAPI, targetWorker?: NodeEndpoint): [() => void, (e: Error) => void, PublicAPI<Methods, PipedAPI>];
20
+ export declare function exposeAPI<Methods, PipedAPI>(apiMethods?: Methods, pipedApi?: PipedAPI, targetWorker?: MessagePort | NodeWorker | NodeProcess): [() => void, (e: Error) => void, PublicAPI<Methods, PipedAPI>];
19
21
  export declare function exposeSyncAPI<Methods>(apiMethods: Methods, port: IsomorphicMessagePort): Promise<[() => void, (e: Error) => void, Methods]>;
@@ -0,0 +1,7 @@
1
+ import type { Endpoint } from './comlink-sync';
2
+ export interface NodeProcess {
3
+ send: (...args: any[]) => unknown;
4
+ addListener: (type: string, listener: EventListenerOrEventListenerObject) => void;
5
+ removeListener: (type: string, listener: EventListenerOrEventListenerObject) => void;
6
+ }
7
+ export declare function nodeProcessEndpoint(worker?: NodeProcess): Endpoint;
@@ -116,7 +116,7 @@ export declare namespace Emscripten {
116
116
  }
117
117
  interface Mount {
118
118
  type: Emscripten.FileSystemType;
119
- opts: object;
119
+ opts: Record<string, any>;
120
120
  mountpoint: string;
121
121
  mounts: Mount[];
122
122
  root: FSNode;
@@ -145,6 +145,7 @@ export declare namespace Emscripten {
145
145
  write: boolean;
146
146
  readonly isFolder: boolean;
147
147
  readonly isDevice: boolean;
148
+ readonly isSharedFS?: boolean;
148
149
  }
149
150
  interface ErrnoError extends Error {
150
151
  name: 'ErronoError';
@@ -220,6 +221,7 @@ export declare namespace Emscripten {
220
221
  export const MEMFS: Emscripten.FileSystemType;
221
222
  export const NODEFS: Emscripten.FileSystemType;
222
223
  export const IDBFS: Emscripten.FileSystemType;
224
+ export const PROXYFS: Emscripten.FileSystemType;
223
225
  type StringToType<R> = R extends Emscripten.JSType ? {
224
226
  number: number;
225
227
  string: string;
@@ -0,0 +1,38 @@
1
+ import type { ByteRange, LockedRange, RequestedRangeLock } from './file-lock-manager';
2
+ export declare class FileLockIntervalTree {
3
+ private root;
4
+ isEmpty(): boolean;
5
+ /**
6
+ * Insert a new locked range into the tree
7
+ */
8
+ insert(range: LockedRange): void;
9
+ /**
10
+ * Find all ranges that overlap with the given range
11
+ */
12
+ findOverlapping(range: ByteRange): LockedRange[];
13
+ /**
14
+ * Remove a lock range from the tree
15
+ */
16
+ remove(range: RequestedRangeLock): void;
17
+ /**
18
+ * Find all ranges locked by the given process.
19
+ *
20
+ * @param pid The process ID to find locks for.
21
+ * @returns All locked ranges for the given process.
22
+ */
23
+ findLocksForProcess(pid: number): RequestedRangeLock[];
24
+ /**
25
+ * Find the strictest existing lock type in the range lock tree.
26
+ *
27
+ * @returns The strictest existing lock type, or 'unlocked' if no locks exist.
28
+ */
29
+ findStrictestExistingLockType(): RequestedRangeLock['type'];
30
+ private insertNode;
31
+ private bigintMax;
32
+ private findOverlappingRanges;
33
+ private doRangesOverlap;
34
+ private removeNode;
35
+ private findMin;
36
+ private areRangesEqual;
37
+ private findLocksForProcessInNode;
38
+ }
@@ -0,0 +1,14 @@
1
+ import { type Path, type RequestedRangeLock, type WholeFileLockOp, type FileLockManager } from './file-lock-manager';
2
+ export declare class FileLockManagerComposite implements FileLockManager {
3
+ nativeLockManager: FileLockManager;
4
+ wasmLockManager: FileLockManager;
5
+ constructor({ nativeLockManager, wasmLockManager, }: {
6
+ nativeLockManager: FileLockManager;
7
+ wasmLockManager: FileLockManager;
8
+ });
9
+ lockWholeFile(path: Path, op: WholeFileLockOp): boolean;
10
+ lockFileByteRange(path: Path, requestedLock: RequestedRangeLock, waitForLock: boolean): boolean;
11
+ findFirstConflictingByteRangeLock(path: Path, desiredLock: RequestedRangeLock): Omit<RequestedRangeLock, 'fd'> | undefined;
12
+ releaseLocksForProcess(pid: number): void;
13
+ releaseLocksOnFdClose(pid: number, fd: number, path: Path): void;
14
+ }
@@ -0,0 +1,169 @@
1
+ import type { FileLockManager, RequestedRangeLock, WholeFileLockOp, Pid, Fd } from './file-lock-manager';
2
+ import { type LockedRange } from './file-lock-manager';
3
+ /**
4
+ * This is the file lock manager for use within JS runtimes like Node.js.
5
+ *
6
+ * A FileLockManagerInMemory is a wrapper around a Map of FileLock instances.
7
+ * It provides methods for locking and unlocking files, as well as finding conflicting locks.
8
+ */
9
+ export declare class FileLockManagerInMemory implements FileLockManager {
10
+ locks: Map<string, FileLock>;
11
+ /**
12
+ * Create a new FileLockManagerInMemory instance.
13
+ *
14
+ * @param nativeFlockSync A synchronous flock() function to lock files via the host OS.
15
+ */
16
+ constructor();
17
+ /**
18
+ * Lock the whole file.
19
+ *
20
+ * @param path The path to the file to lock. This should be the path
21
+ * of the file in the native filesystem.
22
+ * @param op The whole file lock operation to perform.
23
+ * @returns True if the lock was granted, false otherwise.
24
+ */
25
+ lockWholeFile(path: string,
26
+ /**
27
+ * NOTE: FileLockManagerInMemory does not support waiting for a lock
28
+ * because it is intended to be used with a native file lock manager
29
+ * which does support waiting.
30
+ */
31
+ op: Omit<WholeFileLockOp, 'waitForLock'>): boolean;
32
+ /**
33
+ * Lock a byte range.
34
+ *
35
+ * @param path The path to the file to lock. This should be the path
36
+ * of the file in the native filesystem.
37
+ * @param requestedLock The byte range lock to perform.
38
+ * @returns True if the lock was granted, false otherwise.
39
+ */
40
+ lockFileByteRange(path: string,
41
+ /**
42
+ * NOTE: fcntl()-style F_SETLK/F_GETLK do not associate
43
+ * resulting locks with a file descrtiptor, so we ignore fd here.
44
+ */
45
+ requestedLock: Omit<RequestedRangeLock, 'fd'>
46
+ /**
47
+ * NOTE: FileLockManagerInMemory does not support waiting for a lock
48
+ * because it is intended to be used with a native file lock manager
49
+ * which does support waiting.
50
+ */
51
+ ): boolean;
52
+ /**
53
+ * Find the first conflicting byte range lock.
54
+ *
55
+ * @param path The path to the file to find the conflicting lock for.
56
+ * @param desiredLock The desired byte range lock.
57
+ * @returns The first conflicting byte range lock, or undefined if no conflicting lock exists.
58
+ */
59
+ findFirstConflictingByteRangeLock(path: string,
60
+ /**
61
+ * NOTE: fcntl()-style F_SETLK/F_GETLK do not associate
62
+ * resulting locks with a file descrtiptor, so we ignore fd here.
63
+ */
64
+ desiredLock: Omit<RequestedRangeLock, 'fd'>): Omit<RequestedRangeLock, 'fd'> | undefined;
65
+ /**
66
+ * Release all locks for the given process.
67
+ *
68
+ * @param pid The process ID to release locks for.
69
+ */
70
+ releaseLocksForProcess(pid: number): void;
71
+ /**
72
+ * Release all locks for the given process and file descriptor.
73
+ *
74
+ * @param pid The process ID to release locks for.
75
+ * @param fd The file descriptor to release locks for.
76
+ * @param path The path to the file to release locks for.
77
+ */
78
+ releaseLocksOnFdClose(pid: number, fd: number, nativePath: string): void;
79
+ /**
80
+ * Forget the path if it is unlocked.
81
+ *
82
+ * @param path The path to the file to forget.
83
+ */
84
+ private forgetPathIfUnlocked;
85
+ }
86
+ /**
87
+ * A FileLock instance encapsulates a native whole-file lock and file locking between
88
+ * php-wasm processes.
89
+ *
90
+ * A FileLock supports php-wasm whole-file locks and byte range locks.
91
+ * Before granting a php-wasm lock, a FileLock ensures that it first holds a compatible
92
+ * native lock. If a compatible native lock cannot be acquired, the php-wasm lock is
93
+ * not granted.
94
+ */
95
+ export declare class FileLock {
96
+ private wholeFileLock;
97
+ private rangeLocks;
98
+ constructor();
99
+ /**
100
+ * Lock the whole file.
101
+ *
102
+ * This method corresponds to the flock() function.
103
+ *
104
+ * @param op The whole file lock operation to perform.
105
+ * @returns True if the lock was granted, false otherwise.
106
+ */
107
+ lockWholeFile(op: Omit<WholeFileLockOp, 'waitForLock'>): boolean;
108
+ /**
109
+ * Lock a byte range.
110
+ *
111
+ * This method corresponds to the fcntl() F_SETLK command.
112
+ *
113
+ * @param requestedLock The byte range lock to perform.
114
+ * @returns True if the lock was granted, false otherwise.
115
+ */
116
+ lockFileByteRange(requestedLock: Omit<RequestedRangeLock, 'fd' | 'waitForLock'>): boolean;
117
+ /**
118
+ * Find the first conflicting byte range lock.
119
+ *
120
+ * This method corresponds to the fcntl() F_GETLK command.
121
+ *
122
+ * @param desiredLock The desired byte range lock.
123
+ * @returns The first conflicting byte range lock, or undefined if no conflicting lock exists.
124
+ */
125
+ findFirstConflictingByteRangeLock(
126
+ /**
127
+ * NOTE: fcntl()-style F_SETLK/F_GETLK do not associate
128
+ * resulting locks with a file descrtiptor, so we ignore fd here.
129
+ */
130
+ desiredLock: Omit<RequestedRangeLock, 'fd'>): LockedRange | {
131
+ type: "shared" | "exclusive";
132
+ start: bigint;
133
+ end: bigint;
134
+ pid: number;
135
+ } | undefined;
136
+ /**
137
+ * Release all locks for the given process.
138
+ *
139
+ * @param pid The process ID to release locks for.
140
+ */
141
+ releaseLocksForProcess(pid: Pid): void;
142
+ /**
143
+ * Release all locks for the given process and file descriptor.
144
+ *
145
+ * @param pid The process ID to release locks for.
146
+ * @param fd The file descriptor to release locks for.
147
+ */
148
+ releaseLocksOnFdClose(pid: Pid, fd: Fd): void;
149
+ /**
150
+ * Check if the file lock is unlocked.
151
+ *
152
+ * @returns True if the file lock is unlocked, false otherwise.
153
+ */
154
+ isUnlocked(): boolean;
155
+ /**
156
+ * Check if a lock exists that conflicts with the requested range lock.
157
+ *
158
+ * @param requestedLock The desired byte range lock.
159
+ * @returns True if a conflicting lock exists, false otherwise.
160
+ */
161
+ private isThereAConflictWithRequestedRangeLock;
162
+ /**
163
+ * Check if a lock exists that conflicts with the requested whole-file lock.
164
+ *
165
+ * @param requestedLock The desired whole-file lock.
166
+ * @returns True if a conflicting lock exists, false otherwise.
167
+ */
168
+ private isThereAConflictWithRequestedWholeFileLock;
169
+ }
@@ -0,0 +1,117 @@
1
+ export declare const MAX_ADDRESSABLE_FILE_OFFSET: bigint;
2
+ export type Path = string;
3
+ export type Pid = number;
4
+ export type Fd = number;
5
+ /**
6
+ * This is an interface used to abstract byte range locking like fcntl()
7
+ * and whole-file locking like flock().
8
+ */
9
+ export type FileLockManager = {
10
+ /**
11
+ * Update the lock on the whole file.
12
+ *
13
+ * This method is for updating the lock on the whole file with the F_SETLKW fcntl() command.
14
+ * https://sourceware.org/glibc/manual/2.41/html_node/File-Locks.html#index-F_005fSETLKW-1
15
+ *
16
+ * @param path - The path of the file to lock. This should be the path of the file in the
17
+ * underlying filesystem.
18
+ * @param op - The operation to perform, including 'shared', 'exclusive', or 'unlock'.
19
+ * @returns A promise for a boolean value.
20
+ */
21
+ lockWholeFile: (path: Path, op: WholeFileLockOp) => boolean;
22
+ /**
23
+ * Update the lock on a byte range of a file.
24
+ *
25
+ * This method is for locking with the F_SETLK fcntl() command.
26
+ * https://sourceware.org/glibc/manual/2.41/html_node/File-Locks.html#index-F_005fSETLK-1
27
+ *
28
+ * @param path - The path of the file to lock. This should be the path of the file in the
29
+ * underlying filesystem.
30
+ * @param requestedLock - The lock to request, including start, end, type, and pid.
31
+ * @param waitForLock - Whether to block until the lock is acquired.
32
+ * @returns A promise for a boolean value.
33
+ * When locking: True if the lock was acquired, false if it was not.
34
+ * When unlocking: Always true.
35
+ */
36
+ lockFileByteRange: (path: Path, requestedLock: RequestedRangeLock, waitForLock: boolean) => boolean;
37
+ /**
38
+ * Get the first lock that would conflict with the specified lock.
39
+ *
40
+ * This method is meant to satisfy the needs of the F_GETLK fcntl() command.
41
+ * https://sourceware.org/glibc/manual/2.41/html_node/File-Locks.html#index-F_005fGETLK-1
42
+ *
43
+ * @param path - The path of the file to check for conflicts. This should be the path
44
+ * of the file in the underlying filesystem.
45
+ * @param desiredLock - The lock to check for conflicts.
46
+ * @returns A promise for the first conflicting lock,
47
+ * or undefined if there is no conflict.
48
+ */
49
+ findFirstConflictingByteRangeLock: (path: Path, desiredLock: RequestedRangeLock) => Omit<RequestedRangeLock, 'fd'> | undefined;
50
+ /**
51
+ * Release all locks for a given process.
52
+ *
53
+ * Used when a process exits or is otherwise terminated.
54
+ *
55
+ * @param pid - The PID of the process that wants to release the locks.
56
+ */
57
+ releaseLocksForProcess: (pid: number) => void;
58
+ /**
59
+ * Release all locks for the given process and file descriptor.
60
+ *
61
+ * @param pid The process ID to release locks for.
62
+ * @param fd The file descriptor to release locks for.
63
+ * @param path The path to the file to release locks for. This should be the path
64
+ * of the file in the underlying filesystem.
65
+ */
66
+ releaseLocksOnFdClose: (pid: number, fd: number, path: Path) => void;
67
+ };
68
+ export type ByteRange = {
69
+ start: bigint;
70
+ end: bigint;
71
+ };
72
+ export type RequestedRangeLock = ByteRange & {
73
+ /**
74
+ * The type of lock request
75
+ */
76
+ type: 'shared' | 'exclusive' | 'unlocked';
77
+ /**
78
+ * The file descriptor to use. This should be the native file descriptor,
79
+ * not the Emscripten file descriptor because it may be used to lock the file
80
+ * using native OS file locking APIs.
81
+ */
82
+ fd: Fd;
83
+ /** The process ID that owns this lock */
84
+ pid: Pid;
85
+ };
86
+ export type LockedRange = RequestedRangeLock & {
87
+ type: Exclude<RequestedRangeLock['type'], 'unlocked'>;
88
+ };
89
+ export type ConflictingLockedRange = Omit<LockedRange, 'fd'>;
90
+ export type WholeFileLock = Readonly<WholeFileLock_Exclusive | WholeFileLock_Shared | WholeFileLock_Unlocked>;
91
+ export type WholeFileLock_Exclusive = {
92
+ type: 'exclusive';
93
+ pid: Pid;
94
+ fd: Fd;
95
+ };
96
+ export type WholeFileLock_Shared = {
97
+ type: 'shared';
98
+ /**
99
+ * NOTE: flock() locks are associated with open file descriptors and duplicated file descriptors.
100
+ * We do not currently recognize duplicate file descriptors.
101
+ */
102
+ pidFds: Map<Pid, Set<Fd>>;
103
+ };
104
+ export type WholeFileLock_Unlocked = {
105
+ type: 'unlocked';
106
+ };
107
+ export type WholeFileLockOp = {
108
+ pid: number;
109
+ fd: number;
110
+ type: 'shared' | 'exclusive';
111
+ /** Whether to block until the lock is acquired. */
112
+ waitForLock: boolean;
113
+ } | {
114
+ pid: number;
115
+ fd: number;
116
+ type: 'unlock';
117
+ };
package/lib/index.d.ts CHANGED
@@ -40,4 +40,11 @@ export { proxyFileSystem, isPathToSharedFS } from './proxy-file-system';
40
40
  export { sandboxedSpawnHandlerFactory } from './sandboxed-spawn-handler-factory';
41
41
  export * from './api';
42
42
  export type { WithAPIState as WithIsReady } from './api';
43
+ export type { NodeProcess } from './comlink-node-process-adapter';
44
+ export * from './file-lock-manager';
45
+ export * from './file-lock-manager-in-memory';
46
+ export * from './file-lock-manager-composite';
47
+ export * from './file-lock-interval-tree';
43
48
  export type { Remote } from './comlink-sync';
49
+ export { createObjectPoolProxy } from './object-pool-proxy';
50
+ export type { Pooled } from './object-pool-proxy';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Converts an object type to a promisified version where:
3
+ * - Methods return `Promise<Awaited<ReturnType>>` (no double-wrapping)
4
+ * - Properties become `Promise<Awaited<PropertyType>>`
5
+ */
6
+ type Promisified<T extends object> = {
7
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<Awaited<R>> : Promise<Awaited<T[K]>>;
8
+ };
9
+ /**
10
+ * The type returned by `createObjectPoolProxy`. All method calls and
11
+ * property accesses are wrapped in promises because acquiring a free
12
+ * pool instance is inherently async.
13
+ *
14
+ * Dispose/asyncDispose symbols are omitted because the pool proxy
15
+ * forwards calls to a single random instance — disposing one
16
+ * instance out of the pool is never the intended behavior. Pool
17
+ * lifecycle should be managed by the code that created the pool.
18
+ */
19
+ export type Pooled<T extends object> = Omit<Promisified<T>, typeof Symbol.dispose | typeof Symbol.asyncDispose>;
20
+ /**
21
+ * Creates a proxy that distributes method calls and property accesses
22
+ * across a pool of object instances. Only one ongoing access per
23
+ * instance is allowed at a time. If all instances are busy, accesses
24
+ * wait until one becomes free.
25
+ *
26
+ * The returned proxy provides a promisified version of the original
27
+ * interface: method calls and property accesses all return promises.
28
+ */
29
+ export declare function createObjectPoolProxy<T extends object>(instances: T[]): Pooled<T>;
30
+ export {};
@@ -1,4 +1,5 @@
1
1
  import type { Remote } from './comlink-sync';
2
+ import type { Pooled } from './object-pool-proxy';
2
3
  import type { LimitedPHPApi } from './php-worker';
3
4
  /**
4
5
  * Represents an event related to the PHP request.
@@ -43,7 +44,7 @@ export type PHPEvent = PHPRequestEndEvent | PHPRequestErrorEvent | PHPRuntimeIni
43
44
  * A callback function that handles PHP events.
44
45
  */
45
46
  export type PHPEventListener = (event: PHPEvent) => void;
46
- export type UniversalPHP = LimitedPHPApi | Remote<LimitedPHPApi>;
47
+ export type UniversalPHP = LimitedPHPApi | Remote<LimitedPHPApi> | Pooled<LimitedPHPApi>;
47
48
  export type MessageListener = (data: string) => Promise<string | Uint8Array | void> | string | void;
48
49
  export interface EventEmitter {
49
50
  on(event: string, listener: (...args: any[]) => void): this;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@php-wasm/universal",
3
- "version": "3.0.53",
3
+ "version": "3.1.0",
4
4
  "description": "PHP.wasm – emscripten bindings for PHP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,18 +37,18 @@
37
37
  "module": "./index.js",
38
38
  "types": "index.d.ts",
39
39
  "license": "GPL-2.0-or-later",
40
- "gitHead": "01ab4186302ca07b56ac54acd122d17b5350b3e2",
40
+ "gitHead": "be66d7e27ea9a8f09c2cc15e4470a6e28def5c58",
41
41
  "engines": {
42
42
  "node": ">=20.10.0",
43
43
  "npm": ">=10.2.3"
44
44
  },
45
45
  "dependencies": {
46
46
  "ini": "4.1.2",
47
- "@php-wasm/node-polyfills": "3.0.53",
48
- "@php-wasm/logger": "3.0.53",
49
- "@php-wasm/util": "3.0.53",
50
- "@php-wasm/stream-compression": "3.0.53",
51
- "@php-wasm/progress": "3.0.53"
47
+ "@php-wasm/node-polyfills": "3.1.0",
48
+ "@php-wasm/logger": "3.1.0",
49
+ "@php-wasm/util": "3.1.0",
50
+ "@php-wasm/stream-compression": "3.1.0",
51
+ "@php-wasm/progress": "3.1.0"
52
52
  },
53
53
  "packageManager": "npm@10.9.2",
54
54
  "overrides": {
@@ -62,8 +62,5 @@
62
62
  "form-data": "^4.0.4",
63
63
  "lodash": "^4.17.23",
64
64
  "glob": "^9.3.0"
65
- },
66
- "optionalDependencies": {
67
- "fs-ext": "2.1.1"
68
65
  }
69
66
  }