@tstdl/base 0.93.139 → 0.93.140

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 (133) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.js +2 -2
  28. package/audit/README.md +267 -0
  29. package/authentication/README.md +288 -0
  30. package/authentication/client/authentication.service.d.ts +12 -11
  31. package/authentication/client/authentication.service.js +21 -21
  32. package/authentication/client/http-client.middleware.js +2 -2
  33. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  34. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  35. package/browser/README.md +401 -0
  36. package/cancellation/README.md +156 -0
  37. package/cancellation/tests/coverage.test.d.ts +1 -0
  38. package/cancellation/tests/coverage.test.js +49 -0
  39. package/cancellation/tests/leak.test.js +24 -29
  40. package/cancellation/tests/token.test.d.ts +1 -0
  41. package/cancellation/tests/token.test.js +136 -0
  42. package/cancellation/token.d.ts +53 -177
  43. package/cancellation/token.js +132 -208
  44. package/context/README.md +174 -0
  45. package/cookie/README.md +161 -0
  46. package/css/README.md +157 -0
  47. package/data-structures/README.md +320 -0
  48. package/decorators/README.md +140 -0
  49. package/distributed-loop/README.md +231 -0
  50. package/distributed-loop/distributed-loop.js +1 -1
  51. package/document-management/README.md +403 -0
  52. package/document-management/server/services/document-management.service.js +9 -7
  53. package/document-management/tests/document-management-core.test.js +2 -7
  54. package/document-management/tests/document-management.api.test.js +6 -7
  55. package/document-management/tests/document-statistics.service.test.js +11 -12
  56. package/document-management/tests/document.service.test.js +3 -3
  57. package/document-management/tests/enum-helpers.test.js +2 -3
  58. package/dom/README.md +213 -0
  59. package/enumerable/README.md +259 -0
  60. package/enumeration/README.md +121 -0
  61. package/errors/README.md +267 -0
  62. package/file/README.md +191 -0
  63. package/formats/README.md +210 -0
  64. package/function/README.md +144 -0
  65. package/http/README.md +318 -0
  66. package/http/client/adapters/undici.adapter.js +1 -1
  67. package/http/client/http-client-request.d.ts +6 -5
  68. package/http/client/http-client-request.js +8 -9
  69. package/http/server/node/node-http-server.js +1 -2
  70. package/image-service/README.md +137 -0
  71. package/injector/README.md +491 -0
  72. package/intl/README.md +113 -0
  73. package/json-path/README.md +182 -0
  74. package/jsx/README.md +154 -0
  75. package/key-value-store/README.md +191 -0
  76. package/lock/README.md +249 -0
  77. package/lock/web/web-lock.js +119 -47
  78. package/logger/README.md +287 -0
  79. package/mail/README.md +256 -0
  80. package/memory/README.md +144 -0
  81. package/message-bus/README.md +244 -0
  82. package/message-bus/message-bus-base.js +1 -1
  83. package/module/README.md +182 -0
  84. package/module/module.d.ts +1 -1
  85. package/module/module.js +77 -17
  86. package/module/modules/web-server.module.js +1 -1
  87. package/notification/tests/notification-type.service.test.js +24 -15
  88. package/object-storage/README.md +300 -0
  89. package/openid-connect/README.md +274 -0
  90. package/orm/README.md +423 -0
  91. package/package.json +8 -6
  92. package/password/README.md +164 -0
  93. package/pdf/README.md +246 -0
  94. package/polyfills.js +1 -0
  95. package/pool/README.md +198 -0
  96. package/process/README.md +237 -0
  97. package/promise/README.md +252 -0
  98. package/promise/cancelable-promise.js +1 -1
  99. package/random/README.md +193 -0
  100. package/reflection/README.md +305 -0
  101. package/rpc/README.md +386 -0
  102. package/rxjs-utils/README.md +262 -0
  103. package/schema/README.md +342 -0
  104. package/serializer/README.md +342 -0
  105. package/signals/implementation/README.md +134 -0
  106. package/sse/README.md +278 -0
  107. package/task-queue/README.md +300 -0
  108. package/task-queue/postgres/task-queue.d.ts +2 -1
  109. package/task-queue/postgres/task-queue.js +32 -2
  110. package/task-queue/task-context.js +1 -1
  111. package/task-queue/task-queue.d.ts +17 -0
  112. package/task-queue/task-queue.js +103 -45
  113. package/task-queue/tests/complex.test.js +4 -4
  114. package/task-queue/tests/dependencies.test.js +4 -2
  115. package/task-queue/tests/queue.test.js +111 -0
  116. package/task-queue/tests/worker.test.js +21 -13
  117. package/templates/README.md +287 -0
  118. package/testing/README.md +157 -0
  119. package/text/README.md +346 -0
  120. package/threading/README.md +238 -0
  121. package/types/README.md +311 -0
  122. package/utils/README.md +322 -0
  123. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  124. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  125. package/utils/async-iterable-helpers/take-until.js +4 -4
  126. package/utils/backoff.js +89 -30
  127. package/utils/retry-with-backoff.js +1 -1
  128. package/utils/timer.d.ts +1 -1
  129. package/utils/timer.js +5 -7
  130. package/utils/timing.d.ts +1 -1
  131. package/utils/timing.js +2 -4
  132. package/utils/z-base32.d.ts +1 -0
  133. package/utils/z-base32.js +1 -0
@@ -0,0 +1,305 @@
1
+ # Reflection
2
+
3
+ A powerful and unified toolkit for creating and managing TypeScript decorators with a robust runtime reflection system. It simplifies decorator creation, manages metadata storage, and supports inheritance, enabling advanced runtime introspection.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [🚀 Basic Usage](#-basic-usage)
10
+ - [🔧 Advanced Topics](#-advanced-topics)
11
+ - [📚 API](#-api)
12
+
13
+ ## ✨ Features
14
+
15
+ - **Unified Decorator API**: Consistent factory functions (`Class`, `Property`, `Method`, etc.) to create type-safe decorators for specific targets.
16
+ - **Centralized Registry**: A global `reflectionRegistry` automatically collects and organizes metadata from all decorators.
17
+ - **Metadata Storage**: Attach arbitrary custom data to classes, properties, methods, and parameters via a `ContextDataMap`. Supports merging complex data structures.
18
+ - **Inheritance Support**: Metadata is automatically inherited from parent classes, allowing for polymorphic behavior in frameworks.
19
+ - **Static Member Support**: Reflect on both instance and static properties and methods.
20
+ - **Decorator Wrapping**: Utilities to wrap third-party decorators, integrating them into the reflection system without modifying their source.
21
+ - **Inclusive Decorators**: Apply multiple decorators simultaneously via a single wrapper, simplifying complex decoration logic.
22
+ - **Type Inspection**: Helper functions to visualize and inspect registered type information at runtime.
23
+
24
+ ## Core Concepts
25
+
26
+ ### Decorator Factories
27
+
28
+ Instead of writing raw decorator functions that handle `target`, `propertyKey`, and `descriptor` manually, this module provides factories. These factories accept an options object, often including a `handler` function where you define your logic.
29
+
30
+ - **`Class()`**: Creates a class decorator.
31
+ - **`Property()`**: Creates a property decorator.
32
+ - **`Method()`**: Creates a method decorator.
33
+ - **`Parameter()`**: Creates a parameter decorator (constructor or method).
34
+ - **`Decorate()`**: Creates a universal decorator applicable to multiple targets.
35
+
36
+ ### Reflection Registry
37
+
38
+ The `reflectionRegistry` is a singleton that stores `TypeMetadata` for every class decorated with this library. When a decorator is applied, it registers the target (class, method, property) and allows you to attach data to it.
39
+
40
+ You can later retrieve this metadata using `reflectionRegistry.getMetadata(Constructor)`. This metadata object contains a structured view of the class, including its properties, methods, parameters, and any custom data attached.
41
+
42
+ ### Context Data
43
+
44
+ Every metadata object (for a type, property, method, etc.) has a `data` property, which is a `ContextDataMap`. This acts as a key-value store for your custom metadata (e.g., serialization rules, validation constraints, dependency injection tokens).
45
+
46
+ ## 🚀 Basic Usage
47
+
48
+ ### Creating and Using a Class Decorator
49
+
50
+ This example demonstrates how to create a decorator to mark a class as a "Service" and retrieve that information at runtime.
51
+
52
+ ```typescript
53
+ import { Class, reflectionRegistry } from '@tstdl/base/reflection';
54
+
55
+ // 1. Define a key for the metadata
56
+ const SERVICE_NAME_KEY = Symbol('ServiceName');
57
+
58
+ // 2. Create the decorator factory
59
+ function Service(name: string) {
60
+ return Class({
61
+ handler: (data, metadata) => {
62
+ // 'metadata' is the TypeMetadata for the class
63
+ metadata.data.set(SERVICE_NAME_KEY, name);
64
+ },
65
+ });
66
+ }
67
+
68
+ // 3. Apply the decorator
69
+ @Service('UserManagement')
70
+ class UserService {
71
+ // ...
72
+ }
73
+
74
+ // 4. Inspect metadata at runtime
75
+ const metadata = reflectionRegistry.getMetadata(UserService);
76
+
77
+ if (metadata) {
78
+ const serviceName = metadata.data.get(SERVICE_NAME_KEY);
79
+ console.log(`Service Name: ${serviceName}`); // Output: Service Name: UserManagement
80
+ }
81
+ ```
82
+
83
+ ### Property and Parameter Decorators
84
+
85
+ Decorators are often used for defining schemas or dependency injection.
86
+
87
+ ```typescript
88
+ import { Property, Parameter, reflectionRegistry } from '@tstdl/base/reflection';
89
+
90
+ const SERIALIZE_KEY = Symbol('Serialize');
91
+ const INJECT_KEY = Symbol('Inject');
92
+
93
+ // Property decorator to mark fields for serialization
94
+ function Serializable() {
95
+ return Property({
96
+ handler: (data, metadata) => {
97
+ metadata.data.set(SERIALIZE_KEY, true);
98
+ },
99
+ });
100
+ }
101
+
102
+ // Parameter decorator for dependency injection
103
+ function Inject(token: string) {
104
+ return Parameter({
105
+ handler: (data, metadata) => {
106
+ metadata.data.set(INJECT_KEY, token);
107
+ },
108
+ });
109
+ }
110
+
111
+ class Database {}
112
+
113
+ class User {
114
+ @Serializable()
115
+ id: string;
116
+
117
+ @Serializable()
118
+ username: string;
119
+
120
+ passwordHash: string; // Not marked
121
+
122
+ constructor(@Inject('DbConnection') db: Database) {}
123
+ }
124
+
125
+ // Inspecting the class
126
+ const meta = reflectionRegistry.getMetadata(User);
127
+
128
+ // Find serializable properties
129
+ for (const [key, propMeta] of meta.properties) {
130
+ if (propMeta.data.get(SERIALIZE_KEY)) {
131
+ console.log(`Property '${String(key)}' is serializable.`);
132
+ }
133
+ }
134
+ // Output:
135
+ // Property 'id' is serializable.
136
+ // Property 'username' is serializable.
137
+
138
+ // Check constructor parameters
139
+ const ctorParams = meta.parameters; // Array of ConstructorParameterMetadata
140
+ if (ctorParams) {
141
+ const firstParamToken = ctorParams[0].data.get(INJECT_KEY);
142
+ console.log(`Constructor param 0 injects: ${firstParamToken}`);
143
+ // Output: Constructor param 0 injects: DbConnection
144
+ }
145
+ ```
146
+
147
+ ## 🔧 Advanced Topics
148
+
149
+ ### Method Interception
150
+
151
+ You can use `Method` decorators to wrap or intercept method calls. The handler receives the property descriptor, allowing you to modify the `value`.
152
+
153
+ ```typescript
154
+ import { Method } from '@tstdl/base/reflection';
155
+
156
+ function Log() {
157
+ return Method({
158
+ handler: (data, metadata, [target, propertyKey, descriptor]) => {
159
+ const originalMethod = descriptor.value;
160
+
161
+ descriptor.value = function (...args: any[]) {
162
+ console.log(`Calling ${String(data.methodKey)} with`, args);
163
+ return originalMethod.apply(this, args);
164
+ };
165
+ },
166
+ });
167
+ }
168
+
169
+ class Calculator {
170
+ @Log()
171
+ add(a: number, b: number) {
172
+ return a + b;
173
+ }
174
+ }
175
+
176
+ new Calculator().add(2, 3);
177
+ // Console: Calling add with [2, 3]
178
+ ```
179
+
180
+ ### Wrapping Third-Party Decorators
181
+
182
+ If you use decorators from other libraries (like an ORM or validation library), you can wrap them to ensure they also register metadata in the `reflectionRegistry`.
183
+
184
+ ```typescript
185
+ import { wrapDecorator } from '@tstdl/base/reflection';
186
+
187
+ // Assume this comes from an external library
188
+ function ExternalEntity(tableName: string) {
189
+ return (target: any) => {
190
+ /* ... external logic ... */
191
+ };
192
+ }
193
+
194
+ // Wrap it
195
+ const Entity = (tableName: string) =>
196
+ wrapDecorator(ExternalEntity(tableName), {
197
+ data: { tableName }, // Automatically attach this data to metadata
198
+ handler: (data, metadata) => {
199
+ console.log(`Registered entity: ${tableName}`);
200
+ },
201
+ });
202
+
203
+ @Entity('users')
204
+ class User {}
205
+ ```
206
+
207
+ ### Inheritance
208
+
209
+ Metadata is inherited. If you request metadata for a subclass, it includes properties and methods decorated in the parent class, marked with `inherited: true`.
210
+
211
+ ```typescript
212
+ import { Property, reflectionRegistry } from '@tstdl/base/reflection';
213
+
214
+ function Prop() {
215
+ return Property();
216
+ }
217
+
218
+ class Base {
219
+ @Prop() baseField: string;
220
+ }
221
+
222
+ class Child extends Base {
223
+ @Prop() childField: string;
224
+ }
225
+
226
+ const meta = reflectionRegistry.getMetadata(Child);
227
+ console.log(meta.properties.has('baseField')); // true
228
+ console.log(meta.properties.get('baseField').inherited); // true
229
+ ```
230
+
231
+ ### Inclusive Decorators
232
+
233
+ The `include` option allows you to trigger other decorators when your decorator is applied. This is useful for creating "composite" decorators.
234
+
235
+ ```typescript
236
+ import { Class, Property } from '@tstdl/base/reflection';
237
+
238
+ const MyClassDecorator = Class();
239
+ const MyPropertyDecorator = Property();
240
+
241
+ function Composite() {
242
+ return Class({
243
+ include: [MyClassDecorator, MyPropertyDecorator] // Error in this specific case as targets differ, but shows the concept
244
+ });
245
+ }
246
+ ```
247
+
248
+ *Note: The included decorators must be compatible with the target where the main decorator is applied.*
249
+
250
+ ### Data Merging
251
+
252
+ When setting data on metadata, you can choose to merge instead of overwrite by setting `mergeData: true`. This works for objects, arrays, maps, and sets.
253
+
254
+ ```typescript
255
+ import { Class } from '@tstdl/base/reflection';
256
+
257
+ const KEY = Symbol('Tags');
258
+
259
+ function Tag(tag: string) {
260
+ return Class({
261
+ data: { [KEY]: [tag] },
262
+ mergeData: true
263
+ });
264
+ }
265
+
266
+ @Tag('alpha')
267
+ @Tag('beta')
268
+ class MyClass {}
269
+
270
+ // Metadata for MyClass will have [KEY]: ['alpha', 'beta']
271
+ ```
272
+
273
+ ## 📚 API
274
+
275
+ | Function / Object | Description |
276
+ | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------- |
277
+ | `reflectionRegistry` | The global singleton instance of `ReflectionRegistry`. Use this to retrieve metadata (`getMetadata(Type)`), check for registration (`hasType(Type)`), or manually unregister types (`unregister(Type)`). |
278
+ | `Class(options?)` | Factory for creating a class decorator. |
279
+ | `Property(options?)` | Factory for creating a property decorator. |
280
+ | `Method(options?)` | Factory for creating a method decorator. |
281
+ | `Accessor(options?)` | Factory for creating an accessor (getter/setter) decorator. |
282
+ | `PropertyOrAccessor(options?)` | Factory for creating a decorator that can target both properties and accessors. |
283
+ | `Parameter(options?)` | Factory for creating a parameter decorator (works on constructor and method parameters). |
284
+ | `ConstructorParameter(options?)` | Factory for creating a decorator specifically for constructor parameters. |
285
+ | `MethodParameter(options?)` | Factory for creating a decorator specifically for method parameters. |
286
+ | `Decorate(options?)` | Factory for creating a universal decorator that can target multiple types (class, property, etc.). |
287
+ | `createDecorator(options, handler?)` | Low-level primitive for creating highly customized decorators. |
288
+ | `wrapDecorator(decorator, options?)` | Wraps an existing decorator instance to integrate it with the reflection system. |
289
+ | `wrapDecoratorFactory(factory, options?)` | Wraps a decorator factory function. |
290
+ | `getDecoratorData(...)` | Utility to parse standard decorator arguments into a structured `DecoratorData` object. |
291
+ | `getConstructor(target)` | Utility to get the constructor from either a constructor function or an instance prototype. |
292
+ | `getTypeInfoString(constructor)` | Debug utility that returns a string representation of the type structure. |
293
+ | `printType(constructor)` | Debug utility that prints the structure of a type based on registered metadata to the console. |
294
+
295
+ ### Types
296
+
297
+ | Type | Description |
298
+ | :------------------ | :---------------------------------------------------------------------------------------- |
299
+ | `TypeMetadata` | Metadata for a class, containing maps of properties, methods, and constructor parameters. |
300
+ | `PropertyMetadata` | Metadata for a specific property (instance or static). |
301
+ | `MethodMetadata` | Metadata for a method, including its parameters and return type (instance or static). |
302
+ | `ParameterMetadata` | Metadata for a parameter (index, type). |
303
+ | `DecoratorData` | Object passed to handlers containing context (e.g., `propertyKey`, `index`, `static`). |
304
+ | `DecoratorHandler` | Function signature for the logic inside a decorator factory. |
305
+ | `MetadataType` | Union of possible metadata types (`'type'`, `'property'`, `'method'`, etc.). |
package/rpc/README.md ADDED
@@ -0,0 +1,386 @@
1
+ # RPC Module
2
+
3
+ A powerful, type-safe Remote Procedure Call (RPC) framework for seamless communication between JavaScript environments, such as the main thread, Web Workers, SharedWorkers, and Node.js worker threads. It abstracts the complexity of message passing into intuitive local function calls and object interactions.
4
+
5
+ ## Table of Contents
6
+
7
+ - [✨ Features](#-features)
8
+ - [Core Concepts](#core-concepts)
9
+ - [🚀 Basic Usage](#-basic-usage)
10
+ - [🔧 Advanced Topics](#-advanced-topics)
11
+ - [Streaming Data](#streaming-data)
12
+ - [Transferring Data Efficiently](#transferring-data-efficiently)
13
+ - [Serialization vs. Proxying](#serialization-vs-proxying)
14
+ - [📚 API](#-api)
15
+
16
+ ## ✨ Features
17
+
18
+ - **Type-Safe Proxies**: Interact with remote objects and functions with full TypeScript support, as if they were local.
19
+ - **Environment Agnostic**: Works with `Worker`, `SharedWorker`, `MessagePort`, `Window`, and Node.js `worker_threads`.
20
+ - **Efficient Data Transfer**: Built-in support for `Transferable` objects (like `ArrayBuffer`) to avoid copying overhead.
21
+ - **Custom Adapters**: Extensible architecture to handle complex non-serializable types like `ReadableStream`.
22
+ - **Flexible Serialization**: Control whether objects are sent by reference (proxy) or by value (serialized).
23
+
24
+ ## Core Concepts
25
+
26
+ The RPC module simplifies cross-context communication by establishing a structured connection.
27
+
28
+ ### Endpoint
29
+
30
+ An **Endpoint** (`RpcEndpoint`) wraps the underlying transport mechanism (like a `Worker` or `MessagePort`). It manages the lifecycle of the connection and handles the routing of messages. The primary implementation provided is `MessagePortRpcEndpoint`.
31
+
32
+ ### Channel
33
+
34
+ A **Channel** (`RpcChannel`) is a logical subdivision of an endpoint. While the endpoint manages the physical connection, channels allow multiple independent conversations to happen simultaneously without interference. The system uses a control channel to negotiate connections and dynamic channels for specific object proxies.
35
+
36
+ ### Expose & Connect
37
+
38
+ The fundamental pattern is **Expose** and **Connect**:
39
+
40
+ - **Expose**: One side (e.g., a Worker) registers an object or function under a specific name using `Rpc.expose()`.
41
+ - **Connect**: The other side (e.g., the Main Thread) requests access to that named resource using `Rpc.connect()`. This returns a **Proxy**.
42
+
43
+ ### Proxies
44
+
45
+ When you connect to a remote object, you receive a **Proxy**. Accessing properties or calling methods on this proxy sends a message to the remote side, executes the operation on the original object, and returns the result. This makes remote interactions feel synchronous or promise-based, hiding the asynchronous nature of message passing.
46
+
47
+ The system also supports class constructors. If you expose a class (constructor), you can `new` it on the client side, and it will return a proxy to a remote instance.
48
+
49
+ ### Automatic Cleanup & Lifecycle
50
+
51
+ The RPC module uses `FinalizationRegistry` to manage the lifecycle of channels automatically. When a proxy is garbage collected on the receiving side, the corresponding channel on both sides is closed.
52
+
53
+ However, garbage collection is non-deterministic. For scenarios requiring explicit control (like unit tests), you can use:
54
+
55
+ - **`Rpc.isAlive(proxy)`**: Checks if the proxy's underlying channel is still open.
56
+ - **`Rpc.release(proxy)`**: Immediately closes the channel associated with the proxy. Subsequent calls to the proxy will throw an `RpcConnectionClosedError`.
57
+
58
+ ### Async Property Assignments, `has`, and `delete`
59
+
60
+ While you can assign properties directly (`proxy.foo = 42`), check for existence (`'foo' in proxy`), or delete properties (`delete proxy.foo`), standard JavaScript expects these operations to be synchronous. However, remote operations are inherently asynchronous.
61
+
62
+ When using these operators on a proxy:
63
+
64
+ - **Assignment**: Returns the value assigned immediately, while the remote operation happens in the background.
65
+ - **`in` operator**: Returns a `Promise` (which is truthy), not the actual boolean result.
66
+ - **`delete` operator**: Returns a `Promise` (which is truthy), not the actual boolean result.
67
+
68
+ **Recommendation**: Use the `Rpc` helpers for awaitable and correct results:
69
+
70
+ ```typescript
71
+ // Awaitable assignment
72
+ await Rpc.set(remoteService, 'status', 'active');
73
+
74
+ // Awaitable 'in' check
75
+ if (await Rpc.has(remoteService, 'status')) { ... }
76
+
77
+ // Awaitable 'delete'
78
+ const deleted = await Rpc.delete(remoteService, 'status');
79
+ ```
80
+
81
+ ### Enhanced Error Handling
82
+
83
+ When a connection is lost or manually released, pending and future requests throw an `RpcConnectionClosedError`.
84
+
85
+ Remote errors are wrapped in `RpcRemoteError`. This implementation is now enhanced to:
86
+
87
+ 1. Preserve the original error name, message, and stack.
88
+ 2. Attempt to reconstruct the original error prototype if the error type was registered with the `@tstdl/base/serializer` system.
89
+
90
+ ### Proxy Trap Support
91
+
92
+ The RPC proxies now support additional standard traps, allowing more natural interaction with remote objects:
93
+
94
+ - **`has`**: Works with the `in` operator (e.g., `'foo' in proxy`).
95
+ - **`deleteProperty`**: Works with the `delete` operator (e.g., `delete proxy.foo`).
96
+ - **`defineProperty`**: Supports remote definition of properties with full descriptor support.
97
+
98
+ ---
99
+
100
+ ## 🚀 Basic Usage
101
+
102
+ This example demonstrates how to expose a service in a Web Worker and consume it from the main thread.
103
+
104
+ ### 1. Define the Service (Shared)
105
+
106
+ ```typescript
107
+ // pet.service.ts
108
+ export type Pet = {
109
+ name: string;
110
+ species: 'dog' | 'cat';
111
+ };
112
+
113
+ export class PetService {
114
+ private pets: Pet[] = [{ name: 'Fido', species: 'dog' }];
115
+
116
+ async getPets(): Promise<Pet[]> {
117
+ return this.pets;
118
+ }
119
+
120
+ async addPet(pet: Pet): Promise<void> {
121
+ this.pets.push(pet);
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### 2. Expose in Worker
127
+
128
+ ```typescript
129
+ // worker.ts
130
+ import { Rpc } from '@tstdl/base/rpc';
131
+ import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
132
+ import { PetService } from './pet.service.js';
133
+
134
+ // Create an endpoint wrapping the worker's global scope
135
+ const endpoint = new MessagePortRpcEndpoint(self as any);
136
+
137
+ // Start listening for incoming connections
138
+ Rpc.listen(endpoint);
139
+
140
+ // Instantiate and expose the service
141
+ const petService = new PetService();
142
+ Rpc.expose(petService, 'pet-service');
143
+ ```
144
+
145
+ ### 3. Connect from Main Thread
146
+
147
+ ```typescript
148
+ // main.ts
149
+ import { Rpc } from '@tstdl/base/rpc';
150
+ import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
151
+ import type { PetService } from './pet.service.js';
152
+
153
+ async function main() {
154
+ const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
155
+ const endpoint = new MessagePortRpcEndpoint(worker);
156
+
157
+ // Connect to the remote service.
158
+ // The generic type argument ensures type safety for the returned proxy.
159
+ const remotePetService = await Rpc.connect<PetService>(endpoint, 'pet-service');
160
+
161
+ // Call methods as if they were local
162
+ const allPets = await remotePetService.getPets();
163
+ console.log('Initial pets:', allPets);
164
+
165
+ await remotePetService.addPet({ name: 'Whiskers', species: 'cat' });
166
+
167
+ const updatedPets = await remotePetService.getPets();
168
+ console.log('Updated pets:', updatedPets);
169
+ }
170
+
171
+ main();
172
+ ```
173
+
174
+ ## 🔧 Advanced Topics
175
+
176
+ ### Streaming Data
177
+
178
+ Standard serialization cannot handle streams. The `ReadableStreamRpcAdapter` allows you to stream data transparently between environments.
179
+
180
+ **Worker (Source):**
181
+
182
+ ```typescript
183
+ import { Rpc } from '@tstdl/base/rpc';
184
+ import { ReadableStreamRpcAdapter } from '@tstdl/base/rpc/adapters';
185
+ import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
186
+ import { timeout } from '@tstdl/base/utils';
187
+
188
+ // 1. Register the adapter
189
+ Rpc.registerAdapter(new ReadableStreamRpcAdapter());
190
+
191
+ const endpoint = new MessagePortRpcEndpoint(self as any);
192
+ Rpc.listen(endpoint);
193
+
194
+ function getLogStream(): ReadableStream<string> {
195
+ let count = 0;
196
+ return new ReadableStream({
197
+ async pull(controller) {
198
+ if (count++ >= 5) {
199
+ controller.close();
200
+ return;
201
+ }
202
+ await timeout(500);
203
+ controller.enqueue(`Log entry #${count}`);
204
+ },
205
+ });
206
+ }
207
+
208
+ // 2. Use Rpc.adapt to wrap the stream
209
+ const getAdaptedLogStream = () => Rpc.adapt(getLogStream(), new ReadableStreamRpcAdapter());
210
+
211
+ Rpc.expose(getAdaptedLogStream, 'log-service');
212
+ ```
213
+
214
+ **Main Thread (Target):**
215
+
216
+ ```typescript
217
+ import { Rpc } from '@tstdl/base/rpc';
218
+ import { ReadableStreamRpcAdapter } from '@tstdl/base/rpc/adapters';
219
+ import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
220
+
221
+ async function main() {
222
+ // 1. Register the adapter here as well
223
+ Rpc.registerAdapter(new ReadableStreamRpcAdapter());
224
+
225
+ const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
226
+ const endpoint = new MessagePortRpcEndpoint(worker);
227
+
228
+ const getRemoteLogStream = await Rpc.connect<() => ReadableStream<string>>(endpoint, 'log-service');
229
+
230
+ // 2. Receive the proxy stream
231
+ const stream = await getRemoteLogStream();
232
+ const reader = stream.getReader();
233
+
234
+ while (true) {
235
+ const { done, value } = await reader.read();
236
+ if (done) break;
237
+ console.log('Received:', value);
238
+ }
239
+ }
240
+ ```
241
+
242
+ ### Transferring Data Efficiently
243
+
244
+ To avoid cloning large objects (like `ArrayBuffer`, `MessagePort`, `ImageBitmap`), use `Rpc.transfer()`. This moves ownership of the object to the receiving context.
245
+
246
+ ```typescript
247
+ // In Main Thread
248
+ const buffer = new Uint8Array(1024 * 1024 * 10).buffer; // 10MB
249
+
250
+ // Mark 'buffer' to be transferred in the second argument
251
+ await remoteService.processData(Rpc.transfer(buffer, [buffer]));
252
+
253
+ console.log(buffer.byteLength); // 0 (ownership transferred)
254
+ ```
255
+
256
+ ### Serialization vs. Proxying
257
+
258
+ By default:
259
+
260
+ - **Primitives** are copied.
261
+ - **Objects** are proxied (a reference is kept, and a proxy is sent).
262
+
263
+ You can change this behavior:
264
+
265
+ - **`Rpc.serialize(obj)`**: Forces an object to be serialized (deep copied) instead of proxied. Useful for DTOs or configuration objects.
266
+ - **`Rpc.proxy(obj)`**: Explicitly marks an object to be proxied. Useful if the default heuristic doesn't catch it or if you are nesting objects.
267
+
268
+ ```typescript
269
+ const config = { mode: 'dark', settings: { ... } };
270
+
271
+ // Send a copy of 'config', not a proxy to it
272
+ await remoteService.updateConfig(Rpc.serialize(config));
273
+ ```
274
+
275
+ ### Error Handling
276
+
277
+ When an error occurs on the remote side, it is caught, serialized, and re-thrown on the calling side.
278
+
279
+ - **`RpcError`**: The base error thrown by the RPC system itself (e.g., connection lost, method not found).
280
+ - **`RpcRemoteError`**: Wraps an error that occurred on the remote implementation. It preserves the original error's message, stack, and name where possible.
281
+
282
+ ```typescript
283
+ try {
284
+ await remoteService.doSomethingRisky();
285
+ } catch (error) {
286
+ if (error instanceof RpcError) {
287
+ console.error('RPC communication failed:', error.message);
288
+ if (error.cause instanceof RpcRemoteError) {
289
+ console.error('Original remote error:', error.cause.message);
290
+ }
291
+ }
292
+ }
293
+ ```
294
+
295
+ ### Implementing Custom Adapters
296
+
297
+ You can handle types that aren't natively serializable by implementing the `RpcAdapter` interface.
298
+
299
+ ```typescript
300
+ import { RpcAdapter, RpcChannel, RpcEndpoint } from '@tstdl/base/rpc';
301
+
302
+ class MyCustomAdapter implements RpcAdapter<MyType, MyData> {
303
+ name = 'MyCustom';
304
+
305
+ adaptSource(value: MyType, channel: RpcChannel): { data: MyData } {
306
+ // Setup communication on the source side
307
+ return {
308
+ data: {
309
+ /* ... */
310
+ },
311
+ };
312
+ }
313
+
314
+ adaptTarget(data: MyData, channel: RpcChannel): MyType {
315
+ // Reconstruct the object on the target side using the channel
316
+ return new MyType(/* ... */);
317
+ }
318
+ }
319
+
320
+ // Don't forget to register it on both sides!
321
+ Rpc.registerAdapter(new MyCustomAdapter());
322
+ ```
323
+
324
+ ## 📚 API
325
+
326
+ ### Rpc
327
+
328
+ The main entry point for the library.
329
+
330
+ | Method | Arguments | Returns | Description |
331
+ | :---------------- | :----------------------- | :---------------------- | :----------------------------------------------------------------------------------------------------------- |
332
+ | `listen` | `endpoint: RpcEndpoint` | `void` | Starts listening for incoming connections on the provided endpoint. |
333
+ | `connect` | `endpoint, name?` | `Promise<RpcRemote<T>>` | Connects to a remote object exposed with the given name (default: `'default'`). |
334
+ | `expose` | `object, name?` | `void` | Makes an object or function available for remote connections (default: `'default'`). |
335
+ | `registerAdapter` | `adapter: RpcAdapter` | `void` | Registers a custom adapter for handling specific types (e.g., streams). |
336
+ | `proxy` | `object, root?` | `T` | Explicitly marks an object to be transmitted as a remote proxy. |
337
+ | `transfer` | `object, transfer` | `T` | Marks an object and specifies associated transferable data (e.g., ArrayBuffers). |
338
+ | `serialize` | `object, options?` | `T` | Forces an object to be transmitted by value (serialized) instead of by proxy using `@tstdl/base/serializer`. |
339
+ | `adapt` | `object, adapter, root?` | `T` | Marks an object to be handled by a specific adapter instance. |
340
+ | `isProxied` | `object: object` | `boolean` | Returns `true` if the object has been marked for proxying via `Rpc.proxy()`. |
341
+ | `release` | `proxy: object` | `void` | Manually closes the channel associated with the proxy (deterministic cleanup). |
342
+ | `isAlive` | `proxy: object` | `boolean` | Returns `true` if the proxy's underlying channel is still open. |
343
+ | `set` | `proxy, property, value` | `Promise<void>` | Sets a property on the remote object and awaits completion. |
344
+ | `has` | `proxy, property` | `Promise<boolean>` | Checks if a property exists on the remote object and awaits the result. |
345
+ | `delete` | `proxy, property` | `Promise<boolean>` | Deletes a property on the remote object and awaits the result. |
346
+ | `reset` | - | `void` | Clears all exposed objects. |
347
+
348
+ ### MessagePortRpcEndpoint
349
+
350
+ Implementation of `RpcEndpoint` for message-passing environments.
351
+
352
+ | Method | Arguments | Returns | Description |
353
+ | :------------ | :----------------------------------- | :----------------------- | :------------------------------------------------------------------------------------------------------------------------- |
354
+ | `constructor` | `source: MessagePortRpcTransport` | `MessagePortRpcEndpoint` | Creates an endpoint. `source` can be `Worker`, `MessagePort`, `Window`, `SharedWorker`, or Node.js `Worker`/`MessagePort`. |
355
+ | `static from` | `transport: MessagePortRpcTransport` | `MessagePortRpcEndpoint` | Static factory method to create an endpoint. |
356
+ | `close` | - | `void` | Closes the endpoint and the underlying transport. |
357
+
358
+ ### RpcChannel
359
+
360
+ Low-level communication channel.
361
+
362
+ | Property / Method | Returns | Description |
363
+ | :---------------- | :----------------------- | :---------------------------------------------- |
364
+ | `id` | `string` | The unique identifier for this channel. |
365
+ | `request$` | `Observable<RpcRequest>` | Emits incoming requests for this channel. |
366
+ | `message$` | `Observable<any>` | Emits incoming data messages for this channel. |
367
+ | `request(data)` | `Promise<any>` | Sends a request and waits for a response. |
368
+ | `respond(id, ok)` | `Promise<void>` | Sends a response to a specific request. |
369
+ | `send(data)` | `Promise<void>` | Sends a one-way data message. |
370
+ | `close()` | `void` | Closes the channel and stops all subscriptions. |
371
+
372
+ ### ReadableStreamRpcAdapter
373
+
374
+ Adapter for transmitting `ReadableStream` objects.
375
+
376
+ | Method | Arguments | Description |
377
+ | :------------ | :---------------------- | :----------------------------------------------------------------------------------------------- |
378
+ | `constructor` | `maxChunkSize?: number` | Creates a new adapter. `maxChunkSize` controls how many items are pulled at once (default 1000). |
379
+
380
+ ## 💡 Best Practices
381
+
382
+ - **Explicit Release**: While `FinalizationRegistry` provides automatic cleanup, it is non-deterministic. For resources that are frequently created and destroyed, use `Rpc.release(proxy)` to free up communication channels immediately.
383
+ - **Register Adapters on Both Sides**: Custom adapters (like `ReadableStreamRpcAdapter`) MUST be registered on both the exposing and the consuming side.
384
+ - **Prefer `Rpc.serialize` for DTOs**: If you are passing plain data objects that don't need to be interactive, use `Rpc.serialize(obj)`. This is more efficient as it avoids the overhead of creating and managing a proxy channel.
385
+ - **Error Handling**: Always wrap remote calls in `try...catch`. Be aware that `RpcRemoteError` preserves the original error's properties, which is useful for structured error reporting.
386
+ - **Type Safety**: Always provide the generic type argument to `Rpc.connect<T>(...)` to ensure full TypeScript support for the returned proxy.