@php-wasm/universal 0.7.0 → 0.7.3

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/base-php.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { PHPRequestHandler, PHPRequestHandlerConfiguration } from './php-request-handler';
2
1
  import { PHPResponse } from './php-response';
3
2
  import type { PHPRuntimeId } from './load-php-runtime';
4
3
  import { IsomorphicLocalPHP, MessageListener, PHPRequest, PHPRequestHeaders, PHPRunOptions, RmDirOptions, ListFilesOptions, SpawnHandler, PHPEventListener, PHPEvent } from './universal-php';
5
4
  import { Semaphore } from '../../../util/src/index.ts';
5
+ import { PHPRequestHandler } from './php-request-handler';
6
6
  export declare const __private__dont__use: unique symbol;
7
7
  export declare class PHPExecutionFailureError extends Error {
8
8
  response: PHPResponse;
@@ -17,10 +17,10 @@ export declare class PHPExecutionFailureError extends Error {
17
17
  * It exposes a minimal set of methods to run PHP scripts and to
18
18
  * interact with the PHP filesystem.
19
19
  */
20
- export declare abstract class BasePHP implements IsomorphicLocalPHP {
20
+ export declare abstract class BasePHP implements IsomorphicLocalPHP, Disposable {
21
21
  #private;
22
22
  protected [__private__dont__use]: any;
23
- requestHandler?: PHPRequestHandler;
23
+ requestHandler?: PHPRequestHandler<any>;
24
24
  /**
25
25
  * An exclusive lock that prevent multiple requests from running at
26
26
  * the same time.
@@ -31,9 +31,9 @@ export declare abstract class BasePHP implements IsomorphicLocalPHP {
31
31
  *
32
32
  * @internal
33
33
  * @param PHPRuntime - Optional. PHP Runtime ID as initialized by loadPHPRuntime.
34
- * @param serverOptions - Optional. Options for the PHPRequestHandler. If undefined, no request handler will be initialized.
34
+ * @param requestHandlerOptions - Optional. Options for the PHPRequestHandler. If undefined, no request handler will be initialized.
35
35
  */
36
- constructor(PHPRuntimeId?: PHPRuntimeId, serverOptions?: PHPRequestHandlerConfiguration);
36
+ constructor(PHPRuntimeId?: PHPRuntimeId);
37
37
  addEventListener(eventType: PHPEvent['type'], listener: PHPEventListener): void;
38
38
  removeEventListener(eventType: PHPEvent['type'], listener: PHPEventListener): void;
39
39
  dispatchEvent<Event extends PHPEvent>(event: Event): void;
@@ -58,7 +58,10 @@ export declare abstract class BasePHP implements IsomorphicLocalPHP {
58
58
  setPhpIniEntry(key: string, value: string): void;
59
59
  /** @inheritDoc */
60
60
  chdir(path: string): void;
61
- /** @inheritDoc */
61
+ /**
62
+ * Do not use. Use new PHPRequestHandler() instead.
63
+ * @deprecated
64
+ */
62
65
  request(request: PHPRequest): Promise<PHPResponse>;
63
66
  /** @inheritDoc */
64
67
  run(request: PHPRunOptions): Promise<PHPResponse>;
@@ -97,6 +100,7 @@ export declare abstract class BasePHP implements IsomorphicLocalPHP {
97
100
  */
98
101
  hotSwapPHPRuntime(runtime: number, cwd?: string): void;
99
102
  exit(code?: number): void;
103
+ [Symbol.dispose](): void;
100
104
  }
101
105
  export declare function normalizeHeaders(headers: PHPRequestHeaders): PHPRequestHeaders;
102
106
  type EmscriptenFS = any;
package/lib/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  export type { IsomorphicLocalPHP, IsomorphicRemotePHP, MessageListener, PHPOutput, PHPRunOptions, UniversalPHP, ListFilesOptions, RmDirOptions, PHPEvent, PHPEventListener, HTTPMethod, PHPRequest, PHPRequestHeaders, SpawnHandler, } from './universal-php';
2
2
  export { UnhandledRejectionsTarget } from './wasm-error-reporting';
3
+ export { HttpCookieStore } from './http-cookie-store';
3
4
  export type { IteratePhpFilesOptions as IterateFilesOptions } from './iterate-files';
4
5
  export { iteratePhpFiles as iterateFiles } from './iterate-files';
5
6
  export { writeFilesStreamToPhp } from './write-files-stream-to-php';
7
+ export { PHPProcessManager } from './php-process-manager';
8
+ export type { MaxPhpInstancesError, PHPFactory, PHPFactoryOptions, ProcessManagerOptions, SpawnedPHP, } from './php-process-manager';
6
9
  export { PHPResponse } from './php-response';
7
10
  export type { PHPResponseData } from './php-response';
8
11
  export type { ErrnoError } from './rethrow-file-system-error';
@@ -0,0 +1,104 @@
1
+ import { BasePHP } from './base-php';
2
+ export type PHPFactoryOptions = {
3
+ isPrimary: boolean;
4
+ };
5
+ export type PHPFactory<PHP extends BasePHP> = (options: PHPFactoryOptions) => Promise<PHP>;
6
+ export interface ProcessManagerOptions<PHP extends BasePHP> {
7
+ /**
8
+ * The maximum number of PHP instances that can exist at
9
+ * the same time.
10
+ */
11
+ maxPhpInstances?: number;
12
+ /**
13
+ * The number of milliseconds to wait for a PHP instance when
14
+ * we have reached the maximum number of PHP instances and
15
+ * cannot spawn a new one. If the timeout is reached, we assume
16
+ * all the PHP instances are deadlocked and a throw MaxPhpInstancesError.
17
+ *
18
+ * Default: 5000
19
+ */
20
+ timeout?: number;
21
+ /**
22
+ * The primary PHP instance that's never killed. This instance
23
+ * contains the reference filesystem used by all other PHP instances.
24
+ */
25
+ primaryPhp?: PHP;
26
+ /**
27
+ * A factory function used for spawning new PHP instances.
28
+ */
29
+ phpFactory?: PHPFactory<PHP>;
30
+ }
31
+ export interface SpawnedPHP<PHP extends BasePHP> {
32
+ php: PHP;
33
+ reap: () => void;
34
+ }
35
+ export declare class MaxPhpInstancesError extends Error {
36
+ constructor(limit: number);
37
+ }
38
+ /**
39
+ * A PHP Process manager.
40
+ *
41
+ * Maintains:
42
+ * * A single "primary" PHP instance that's never killed – it contains the
43
+ * reference filesystem used by all other PHP instances.
44
+ * * A pool of disposable PHP instances that are spawned to handle a single
45
+ * request and reaped immediately after.
46
+ *
47
+ * When a new request comes in, PHPProcessManager yields the idle instance to handle it,
48
+ * and immediately starts initializing a new idle instance. In other words, for n concurrent
49
+ * requests, there are at most n+1 PHP instances running at the same time.
50
+ *
51
+ * A slight nuance is that the first idle instance is not initialized until the first
52
+ * concurrent request comes in. This is because many use-cases won't involve parallel
53
+ * requests and, for those, we can avoid eagerly spinning up a second PHP instance.
54
+ *
55
+ * This strategy is inspired by Cowboy, an Erlang HTTP server. Handling a single extra
56
+ * request can happen immediately, while handling multiple extra requests requires
57
+ * extra time to spin up a few PHP instances. This is a more resource-friendly tradeoff
58
+ * than keeping 5 idle instances at all times.
59
+ */
60
+ export declare class PHPProcessManager<PHP extends BasePHP> implements AsyncDisposable {
61
+ private primaryPhp?;
62
+ private primaryIdle;
63
+ private nextInstance;
64
+ /**
65
+ * All spawned PHP instances, including the primary PHP instance.
66
+ * Used for bookkeeping and reaping all instances on dispose.
67
+ */
68
+ private allInstances;
69
+ private phpFactory?;
70
+ private maxPhpInstances;
71
+ private semaphore;
72
+ constructor(options?: ProcessManagerOptions<PHP>);
73
+ /**
74
+ * Get the primary PHP instance.
75
+ *
76
+ * If the primary PHP instance is not set, it will be spawned
77
+ * using the provided phpFactory.
78
+ *
79
+ * @throws {Error} when called twice before the first call is resolved.
80
+ */
81
+ getPrimaryPhp(): Promise<PHP>;
82
+ /**
83
+ * Get a PHP instance.
84
+ *
85
+ * It could be either the primary PHP instance, an idle disposable PHP instance,
86
+ * or a newly spawned PHP instance – depending on the resource availability.
87
+ *
88
+ * @throws {MaxPhpInstancesError} when the maximum number of PHP instances is reached
89
+ * and the waiting timeout is exceeded.
90
+ */
91
+ acquirePHPInstance(): Promise<SpawnedPHP<PHP>>;
92
+ /**
93
+ * Initiated spawning of a new PHP instance.
94
+ * This function is synchronous on purpose – it needs to synchronously
95
+ * add the spawn promise to the allInstances array without waiting
96
+ * for PHP to spawn.
97
+ */
98
+ private spawn;
99
+ /**
100
+ * Actually acquires the lock and spawns a new PHP instance.
101
+ */
102
+ private doSpawn;
103
+ [Symbol.asyncDispose](): Promise<void>;
104
+ }
@@ -1,11 +1,12 @@
1
1
  import { BasePHP } from './base-php';
2
2
  import { PHPResponse } from './php-response';
3
3
  import { PHPRequest } from './universal-php';
4
+ import { PHPFactoryOptions, PHPProcessManager } from './php-process-manager';
4
5
  export type RewriteRule = {
5
6
  match: RegExp;
6
7
  replacement: string;
7
8
  };
8
- export interface PHPRequestHandlerConfiguration {
9
+ interface BaseConfiguration {
9
10
  /**
10
11
  * The directory in the PHP filesystem where the server will look
11
12
  * for the files to serve. Default: `/var/www`.
@@ -20,6 +21,30 @@ export interface PHPRequestHandlerConfiguration {
20
21
  */
21
22
  rewriteRules?: RewriteRule[];
22
23
  }
24
+ export type PHPRequestHandlerFactoryArgs<PHP extends BasePHP> = PHPFactoryOptions & {
25
+ requestHandler: PHPRequestHandler<PHP>;
26
+ };
27
+ export type PHPRequestHandlerConfiguration<PHP extends BasePHP> = BaseConfiguration & ({
28
+ /**
29
+ * PHPProcessManager is required because the request handler needs
30
+ * to make a decision for each request.
31
+ *
32
+ * Static assets are served using the primary PHP's filesystem, even
33
+ * when serving 100 static files concurrently. No new PHP interpreter
34
+ * is ever created as there's no need for it.
35
+ *
36
+ * Dynamic PHP requests, however, require grabbing an available PHP
37
+ * interpreter, and that's where the PHPProcessManager comes in.
38
+ */
39
+ processManager: PHPProcessManager<PHP>;
40
+ } | {
41
+ phpFactory: (requestHandler: PHPRequestHandlerFactoryArgs<PHP>) => Promise<PHP>;
42
+ /**
43
+ * The maximum number of PHP instances that can exist at
44
+ * the same time.
45
+ */
46
+ maxPhpInstances?: number;
47
+ });
23
48
  /**
24
49
  * Handles HTTP requests using PHP runtime as a backend.
25
50
  *
@@ -74,18 +99,23 @@ export interface PHPRequestHandlerConfiguration {
74
99
  * // "Hi from PHP!"
75
100
  * ```
76
101
  */
77
- export declare class PHPRequestHandler {
102
+ export declare class PHPRequestHandler<PHP extends BasePHP> {
78
103
  #private;
79
104
  rewriteRules: RewriteRule[];
105
+ processManager: PHPProcessManager<PHP>;
80
106
  /**
81
- * The PHP instance
82
- */
83
- php: BasePHP;
84
- /**
107
+ * The request handler needs to decide whether to serve a static asset or
108
+ * run the PHP interpreter. For static assets it should just reuse the primary
109
+ * PHP even if there's 50 concurrent requests to serve. However, for
110
+ * dynamic PHP requests, it needs to grab an available interpreter.
111
+ * Therefore, it cannot just accept PHP as an argument as serving requests
112
+ * requires access to ProcessManager.
113
+ *
85
114
  * @param php - The PHP instance.
86
115
  * @param config - Request Handler configuration.
87
116
  */
88
- constructor(php: BasePHP, config?: PHPRequestHandlerConfiguration);
117
+ constructor(config: PHPRequestHandlerConfiguration<PHP>);
118
+ getPrimaryPhp(): Promise<PHP>;
89
119
  /**
90
120
  * Converts a path to an absolute URL based at the PHPRequestHandler
91
121
  * root.
@@ -102,7 +132,6 @@ export declare class PHPRequestHandler {
102
132
  * @returns The relative path.
103
133
  */
104
134
  internalUrlToPath(internalUrl: string): string;
105
- get isRequestRunning(): boolean;
106
135
  /**
107
136
  * The absolute URL of this PHPRequestHandler instance.
108
137
  */
@@ -187,3 +216,4 @@ export declare function seemsLikeAPHPRequestHandlerPath(path: string): boolean;
187
216
  * @returns The path with the rules applied.
188
217
  */
189
218
  export declare function applyRewriteRules(path: string, rules: RewriteRule[]): string;
219
+ export {};
@@ -41,6 +41,7 @@ export declare class PHPResponse implements PHPResponseData {
41
41
  /** @inheritDoc */
42
42
  readonly httpStatusCode: number;
43
43
  constructor(httpStatusCode: number, headers: Record<string, string[]>, body: ArrayBuffer, errors?: string, exitCode?: number);
44
+ static forHttpCode(httpStatusCode: number, text?: string): PHPResponse;
44
45
  static fromRawData(data: PHPResponseData): PHPResponse;
45
46
  toRawData(): PHPResponseData;
46
47
  /**
@@ -293,7 +293,13 @@ type ChildProcess = EventEmitter & {
293
293
  stderr: EventEmitter;
294
294
  };
295
295
  export type SpawnHandler = (command: string, args: string[]) => ChildProcess;
296
- export type IsomorphicRemotePHP = Remote<IsomorphicLocalPHP>;
296
+ /**
297
+ * The omited methods must either be called synchronously before
298
+ * the PHP internal state is initialized, or with a complex argument
299
+ * that can't be serialized over a remote connection. Therefeore,
300
+ * they don't make sense in a remote PHP instance.
301
+ */
302
+ export type IsomorphicRemotePHP = Remote<Omit<IsomorphicLocalPHP, 'setSapiName' | 'setPhpIniEntry' | 'setPhpIniPath'>>;
297
303
  export type UniversalPHP = IsomorphicLocalPHP | IsomorphicRemotePHP;
298
304
  export type HTTPMethod = 'GET' | 'POST' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'PUT' | 'DELETE';
299
305
  export type PHPRequestHeaders = Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@php-wasm/universal",
3
- "version": "0.7.0",
3
+ "version": "0.7.3",
4
4
  "description": "PHP.wasm – emscripten bindings for PHP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,7 +36,7 @@
36
36
  "main": "./index.cjs",
37
37
  "module": "./index.js",
38
38
  "license": "GPL-2.0-or-later",
39
- "gitHead": "c5eba3d709f2821c4303521e8c81b962e3bcca23",
39
+ "gitHead": "e8fedf92bbead5cec963c8d6976e7143d8e68721",
40
40
  "engines": {
41
41
  "node": ">=18.18.0",
42
42
  "npm": ">=8.11.0"