@actualwave/deferred-data-access 2.0.1 → 2.1.1
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/README.md +400 -27
- package/SKILL.md +106 -0
- package/command/index.d.ts.map +1 -0
- package/command/index.es.js +98 -0
- package/command/index.es.js.map +1 -0
- package/command/index.js +102 -4
- package/command/index.js.map +1 -1
- package/command/package.json +4 -1
- package/command/src/command-chain.d.ts +8 -1
- package/command/src/command-chain.d.ts.map +1 -0
- package/command/src/command-chain.js +9 -0
- package/command/src/command-chain.js.map +1 -1
- package/command/src/command-handler.d.ts.map +1 -0
- package/command/src/command.d.ts +2 -2
- package/command/src/command.d.ts.map +1 -0
- package/command/src/command.js +5 -6
- package/command/src/command.js.map +1 -1
- package/core/core.d.ts.map +1 -0
- package/core/core.js +23 -25
- package/core/core.js.map +1 -1
- package/core/index.d.ts.map +1 -0
- package/deferred-data-access.umd.js +2 -0
- package/deferred-data-access.umd.js.map +1 -0
- package/index.d.ts.map +1 -0
- package/{dist/deferred-data-access.js → index.es.js} +199 -138
- package/index.es.js.map +1 -0
- package/index.js +627 -2
- package/index.js.map +1 -1
- package/interface/index.d.ts.map +1 -0
- package/interface/index.es.js +380 -0
- package/interface/index.es.js.map +1 -0
- package/interface/index.js +396 -7
- package/interface/index.js.map +1 -1
- package/interface/package.json +4 -1
- package/interface/src/handshake.d.ts.map +1 -0
- package/interface/src/handshake.js +6 -4
- package/interface/src/handshake.js.map +1 -1
- package/interface/src/helpers.d.ts.map +1 -0
- package/interface/src/intialize.d.ts +5 -2
- package/interface/src/intialize.d.ts.map +1 -0
- package/interface/src/intialize.js +37 -57
- package/interface/src/intialize.js.map +1 -1
- package/interface/src/request.d.ts +3 -2
- package/interface/src/request.d.ts.map +1 -0
- package/interface/src/request.js +39 -29
- package/interface/src/request.js.map +1 -1
- package/interface/src/types.d.ts.map +1 -0
- package/interface/src/utils.d.ts +2 -2
- package/interface/src/utils.d.ts.map +1 -0
- package/interface/src/utils.js +44 -29
- package/interface/src/utils.js.map +1 -1
- package/package.json +8 -6
- package/proxy/index.d.ts.map +1 -0
- package/proxy/index.es.js +144 -0
- package/proxy/index.es.js.map +1 -0
- package/proxy/index.js +155 -5
- package/proxy/index.js.map +1 -1
- package/proxy/package.json +4 -1
- package/proxy/src/command.d.ts.map +1 -0
- package/proxy/src/proxy.d.ts +2 -2
- package/proxy/src/proxy.d.ts.map +1 -0
- package/proxy/src/proxy.js +13 -3
- package/proxy/src/proxy.js.map +1 -1
- package/proxy/src/traps.d.ts +1 -1
- package/proxy/src/traps.d.ts.map +1 -0
- package/proxy/src/traps.js +4 -14
- package/proxy/src/traps.js.map +1 -1
- package/proxy/src/types.d.ts.map +1 -0
- package/proxy/src/utils.d.ts +6 -0
- package/proxy/src/utils.d.ts.map +1 -0
- package/proxy/src/utils.js +11 -5
- package/proxy/src/utils.js.map +1 -1
- package/record/index.d.ts.map +1 -0
- package/record/index.es.js +26 -0
- package/record/index.es.js.map +1 -0
- package/record/index.js +31 -2
- package/record/index.js.map +1 -1
- package/record/package.json +4 -1
- package/record/record.d.ts +2 -2
- package/record/record.d.ts.map +1 -0
- package/record/record.js +9 -3
- package/record/record.js.map +1 -1
- package/resource/index.d.ts +1 -0
- package/resource/index.d.ts.map +1 -0
- package/resource/index.es.js +191 -0
- package/resource/index.es.js.map +1 -0
- package/resource/index.js +206 -6
- package/resource/index.js.map +1 -1
- package/resource/package.json +4 -1
- package/resource/src/default-resource-pool.d.ts +1 -0
- package/resource/src/default-resource-pool.d.ts.map +1 -0
- package/resource/src/default-resource-pool.js +8 -5
- package/resource/src/default-resource-pool.js.map +1 -1
- package/resource/src/finalization-registry.d.ts +13 -0
- package/resource/src/finalization-registry.d.ts.map +1 -0
- package/resource/src/finalization-registry.js +18 -0
- package/resource/src/finalization-registry.js.map +1 -0
- package/resource/src/resource-pool-registry.d.ts +1 -0
- package/resource/src/resource-pool-registry.d.ts.map +1 -0
- package/resource/src/resource-pool-registry.js +10 -8
- package/resource/src/resource-pool-registry.js.map +1 -1
- package/resource/src/resource-pool.d.ts +8 -1
- package/resource/src/resource-pool.d.ts.map +1 -0
- package/resource/src/resource-pool.js +29 -17
- package/resource/src/resource-pool.js.map +1 -1
- package/resource/src/resource.d.ts +1 -1
- package/resource/src/resource.d.ts.map +1 -0
- package/resource/src/resource.js +3 -2
- package/resource/src/resource.js.map +1 -1
- package/resource/src/utils.d.ts +1 -1
- package/resource/src/utils.d.ts.map +1 -0
- package/resource/src/utils.js +9 -1
- package/resource/src/utils.js.map +1 -1
- package/utils/index.d.ts.map +1 -0
- package/utils/index.es.js +48 -0
- package/utils/index.es.js.map +1 -0
- package/utils/index.js +54 -3
- package/utils/index.js.map +1 -1
- package/utils/package.json +4 -1
- package/utils/src/types.d.ts +3 -3
- package/utils/src/types.d.ts.map +1 -0
- package/utils/src/utils.d.ts +18 -2
- package/utils/src/utils.d.ts.map +1 -0
- package/utils/src/utils.js +28 -4
- package/utils/src/utils.js.map +1 -1
- package/dist/deferred-data-access.js.map +0 -1
- package/dist/deferred-data-access.umd.js +0 -2
- package/dist/deferred-data-access.umd.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,39 +1,412 @@
|
|
|
1
1
|
# Deferred Data Access
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
Deferred Data Access (DDA) is a TypeScript library that wraps any object or Promise in a `Proxy` and records every property access, assignment, deletion, and function call as a typed **command**. A developer-supplied handler function decides what each command means — fetching from a REST API, forwarding to a Web Worker, replaying in a test, or anything else.
|
|
4
|
+
|
|
5
|
+
## Table of contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Quick start](#quick-start)
|
|
9
|
+
- [How it works](#how-it-works)
|
|
10
|
+
- [Lazy vs reactive mode](#lazy-vs-reactive-mode)
|
|
11
|
+
- [Command types](#command-types)
|
|
12
|
+
- [Command handler](#command-handler)
|
|
13
|
+
- [createCommandHandler](#createcommandhandler)
|
|
14
|
+
- [CommandChain API](#commandchain-api)
|
|
15
|
+
- [Resource system](#resource-system)
|
|
16
|
+
- [Cross-context interface](#cross-context-interface)
|
|
17
|
+
- [Sub-package reference](#sub-package-reference)
|
|
18
|
+
- [Projects built on DDA](#projects-built-on-dda)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @actualwave/deferred-data-access
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { handle } from '@actualwave/deferred-data-access';
|
|
34
|
+
import { ProxyCommand } from '@actualwave/deferred-data-access/proxy';
|
|
35
|
+
|
|
36
|
+
// 1. Define a handler that interprets commands
|
|
37
|
+
const handler = async (command, context, wrap) => {
|
|
38
|
+
if (command.type === ProxyCommand.GET) {
|
|
39
|
+
const target = await context;
|
|
40
|
+
return target[command.name];
|
|
41
|
+
}
|
|
42
|
+
// ...
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// 2. Create a wrap factory for a given handler
|
|
46
|
+
const wrap = handle(handler);
|
|
47
|
+
|
|
48
|
+
// 3. Wrap an object (or a Promise of one) — returns a Proxy
|
|
49
|
+
const proxy = wrap({ user: { name: 'Alice' } });
|
|
50
|
+
|
|
51
|
+
// 4. Access properties — in lazy mode handler is called once on .then()
|
|
52
|
+
const name = await proxy.user.name; // → 'Alice'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
3
56
|
|
|
4
57
|
## How it works
|
|
5
|
-
1. Framework accepts an async handler function and returns an object.
|
|
6
|
-
```javascript
|
|
7
|
-
const obj = handle((command, context) => {
|
|
8
|
-
let result;
|
|
9
58
|
|
|
10
|
-
|
|
59
|
+
1. `handle(handler)` returns a `wrap` factory.
|
|
60
|
+
2. `wrap(target)` wraps `target` in a `Proxy`. Every property access, assignment, deletion, or call is intercepted.
|
|
61
|
+
3. Intercepted operations are chained into a `CommandChain` — a linked list of `ICommand` nodes, head to tail.
|
|
62
|
+
4. When the result is awaited (`.then()` / `await`), the handler receives the full chain plus a context Promise and must return a Promise with the resolved value.
|
|
63
|
+
5. The handler can call `wrap(result, command)` to return a new proxy that continues the same chain, enabling deeply nested lazy access.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Lazy vs reactive mode
|
|
68
|
+
|
|
69
|
+
`handle(handler, lazy?)` — second argument controls the mode (default: `true`).
|
|
70
|
+
|
|
71
|
+
### Lazy mode (default)
|
|
72
|
+
|
|
73
|
+
The handler is called **once** per `.then()` / `.catch()` invocation. Intermediate GET/APPLY operations build up a `CommandChain` but do not invoke the handler until the result is awaited.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const wrap = handle(handler); // lazy = true
|
|
77
|
+
|
|
78
|
+
const proxy = wrap(rootObject);
|
|
79
|
+
proxy.a.b.c; // no handler calls yet
|
|
80
|
+
await proxy.a.b.c; // handler called once with chain: GET(c) → GET(b) → GET(a)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The command delivered to the handler is the **head** of the chain. Walk `command.prev` to reach earlier operations.
|
|
84
|
+
|
|
85
|
+
### Reactive mode
|
|
86
|
+
|
|
87
|
+
The handler is called on **every** intercepted operation.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const wrap = handle(handler, false); // lazy = false
|
|
91
|
+
|
|
92
|
+
const proxy = wrap(rootObject);
|
|
93
|
+
proxy.a; // handler called: GET('a')
|
|
94
|
+
proxy.a.b; // handler called: GET('a'), then GET('b') on its result
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Command types
|
|
100
|
+
|
|
101
|
+
Imported from `@actualwave/deferred-data-access/proxy`:
|
|
102
|
+
|
|
103
|
+
| Constant | Value | Triggered by |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `ProxyCommand.GET` | `'P:get'` | `proxy.prop` |
|
|
106
|
+
| `ProxyCommand.SET` | `'P:set'` | `proxy.prop = value` |
|
|
107
|
+
| `ProxyCommand.DELETE_PROPERTY` | `'P:del'` | `delete proxy.prop` |
|
|
108
|
+
| `ProxyCommand.APPLY` | `'P:apply'` | `proxy(args)` |
|
|
109
|
+
| `ProxyCommand.METHOD_CALL` | `'P:call'` | `proxy.method(args)` *(lazy mode only — collapses GET + APPLY)* |
|
|
11
110
|
|
|
12
|
-
|
|
13
|
-
|
|
111
|
+
`METHOD_CALL` is only generated in lazy mode when a `GET` is immediately followed by an `APPLY`. It carries the method name in `command.name` and the arguments in `command.value`.
|
|
112
|
+
|
|
113
|
+
### Command shape
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
interface ICommand {
|
|
117
|
+
type: string; // ProxyCommand value
|
|
118
|
+
name?: PropertyName; // string | symbol — property name
|
|
119
|
+
value?: unknown; // SET value, APPLY args, or METHOD_CALL args
|
|
120
|
+
context?: Promise<unknown>; // Promise resolving to the target object
|
|
121
|
+
}
|
|
14
122
|
```
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Command handler
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
type CommandHandler = (
|
|
130
|
+
command: ICommandList,
|
|
131
|
+
context: CommandContext | undefined,
|
|
132
|
+
wrap: (context: CommandContext, command?: ICommandChain) => unknown
|
|
133
|
+
) => Promise<unknown>;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Argument | Description |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `command` | Head of the `CommandChain` — inspect `command.type`, `command.name`, `command.value`. Walk `command.prev` for earlier operations. |
|
|
139
|
+
| `context` | A `Promise` resolving to the target object the command was issued against. `undefined` for the root call. |
|
|
140
|
+
| `wrap` | Re-wraps a new Promise with the same handler, enabling chained lazy access on sub-objects. |
|
|
141
|
+
|
|
142
|
+
### Example — object property access
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const handler = async (command, context, wrap) => {
|
|
146
|
+
const target = await context;
|
|
147
|
+
|
|
148
|
+
switch (command.type) {
|
|
149
|
+
case ProxyCommand.GET:
|
|
150
|
+
return target[command.name];
|
|
151
|
+
|
|
152
|
+
case ProxyCommand.SET:
|
|
153
|
+
target[command.name] = command.value;
|
|
154
|
+
return;
|
|
155
|
+
|
|
156
|
+
case ProxyCommand.METHOD_CALL:
|
|
157
|
+
return target[command.name](...command.value);
|
|
158
|
+
|
|
159
|
+
case ProxyCommand.APPLY:
|
|
160
|
+
return (target as Function)(...command.value);
|
|
161
|
+
|
|
162
|
+
case ProxyCommand.DELETE_PROPERTY:
|
|
163
|
+
return delete target[command.name];
|
|
164
|
+
}
|
|
165
|
+
};
|
|
18
166
|
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## createCommandHandler
|
|
171
|
+
|
|
172
|
+
A utility that dispatches to per-type handler functions:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { createCommandHandler } from '@actualwave/deferred-data-access/command';
|
|
176
|
+
import { ProxyCommand } from '@actualwave/deferred-data-access/proxy';
|
|
177
|
+
|
|
178
|
+
const handler = createCommandHandler({
|
|
179
|
+
handlers: {
|
|
180
|
+
[ProxyCommand.GET]: async (command, context) => {
|
|
181
|
+
const target = await context;
|
|
182
|
+
return target[command.name];
|
|
183
|
+
},
|
|
184
|
+
[ProxyCommand.SET]: async (command, context) => {
|
|
185
|
+
const target = await context;
|
|
186
|
+
target[command.name] = command.value;
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
defaultHandler: async (command) => {
|
|
190
|
+
throw new Error(`Unhandled command: ${command.type}`);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const wrap = handle(handler, false);
|
|
22
195
|
```
|
|
23
|
-
4. Whatever function returns is returned as promise value.
|
|
24
196
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
197
|
+
If no matching handler and no `defaultHandler`, the call resolves to `undefined`.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## CommandChain API
|
|
202
|
+
|
|
203
|
+
`CommandChain` extends `Command` and is iterable from head to tail:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { CommandChain } from '@actualwave/deferred-data-access/command';
|
|
207
|
+
|
|
208
|
+
// Iterate head → tail
|
|
209
|
+
for (const node of command) {
|
|
210
|
+
console.log(node.type, node.name);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Functional traversal
|
|
214
|
+
const types = command.map(node => node.type);
|
|
215
|
+
const path = command.reduce((acc, node) => [node.name, ...acc], []);
|
|
216
|
+
|
|
217
|
+
command.forEach(node => { /* head → tail */ });
|
|
218
|
+
command.isTail(); // true if this node has no prev
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### withoutPrev()
|
|
222
|
+
|
|
223
|
+
Creates an immutable copy of the node with the `prev` link removed. Use this instead of mutating `prev` directly to avoid corrupting shared chain references:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const severed = command.withoutPrev();
|
|
227
|
+
// severed.prev === undefined
|
|
228
|
+
// command.prev is unchanged
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Resource system
|
|
234
|
+
|
|
235
|
+
Resources allow objects to be referenced by ID across serialisation boundaries (e.g. `postMessage`). Each `Resource` gets a stable string ID and lives in a `ResourcePool`.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import {
|
|
239
|
+
ResourcePool,
|
|
240
|
+
getDefaultResourcePool,
|
|
241
|
+
isResourceObject,
|
|
242
|
+
} from '@actualwave/deferred-data-access/resource';
|
|
243
|
+
|
|
244
|
+
const pool = getDefaultResourcePool(); // lazily-created singleton
|
|
245
|
+
|
|
246
|
+
// Register an object
|
|
247
|
+
const resource = pool.set(myObject);
|
|
248
|
+
console.log(resource.id); // stable string ID
|
|
249
|
+
console.log(resource.poolId); // pool's own ID
|
|
250
|
+
|
|
251
|
+
// Serialise for postMessage
|
|
252
|
+
const descriptor = resource.toObject();
|
|
253
|
+
// { id: '...', poolId: '...', type: 'object' }
|
|
254
|
+
|
|
255
|
+
// Reconstruct on the other side
|
|
256
|
+
const retrieved = pool.getById(descriptor.id);
|
|
257
|
+
|
|
258
|
+
// Type guard
|
|
259
|
+
if (isResourceObject(value)) {
|
|
260
|
+
const live = pool.getById(value.id);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Multiple pools can be managed through `ResourcePoolRegistry`:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { getRegistry } from '@actualwave/deferred-data-access/resource';
|
|
268
|
+
|
|
269
|
+
const registry = getRegistry(); // lazily-created singleton
|
|
270
|
+
const pool = registry.createPool();
|
|
271
|
+
registry.get(pool.id); // → pool
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Custom FinalizationRegistry
|
|
275
|
+
|
|
276
|
+
In environments where `globalThis.FinalizationRegistry` is absent or needs to be replaced (e.g. React Native / Hermes), set a custom implementation before any pools are created:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { setCustomFinalizationRegistryClass } from '@actualwave/deferred-data-access/resource';
|
|
280
|
+
|
|
281
|
+
setCustomFinalizationRegistryClass(MyFinalizationRegistryPolyfill);
|
|
282
|
+
// All ResourcePools created after this call will use MyFinalizationRegistryPolyfill.
|
|
283
|
+
// Pass null to explicitly disable GC-based cleanup.
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The constructor of `ResourcePool` also accepts a `FinalizationRegistry` directly, which takes precedence over the module-level setting:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const pool = new ResourcePool(MyFinalizationRegistryPolyfill);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Replacing singletons
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import {
|
|
296
|
+
setDefaultResourcePool,
|
|
297
|
+
setRegistry,
|
|
298
|
+
} from '@actualwave/deferred-data-access/resource';
|
|
299
|
+
|
|
300
|
+
setDefaultResourcePool(myPool); // replace the default pool singleton
|
|
301
|
+
setRegistry(myRegistry); // replace the default registry singleton
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Cross-context interface
|
|
307
|
+
|
|
308
|
+
`initialize()` sets up a **bidirectional proxy channel** between two contexts (main thread ↔ Worker, two iframes, WebSocket peers, etc.). Each side runs `initialize()` and they perform a handshake before exposing proxies to each other's `root` object.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { initialize, InterfaceType } from '@actualwave/deferred-data-access/interface';
|
|
312
|
+
|
|
313
|
+
// --- Main thread (HOST) ---
|
|
314
|
+
const worker = new Worker('./worker.js');
|
|
315
|
+
|
|
316
|
+
const { root, stop } = await initialize({
|
|
317
|
+
type: InterfaceType.HOST,
|
|
318
|
+
root: { greet: (name: string) => `Hello, ${name}!` }, // expose to worker
|
|
319
|
+
subscribe: (fn) => worker.addEventListener('message', fn),
|
|
320
|
+
unsubscribe: (fn) => worker.removeEventListener('message', fn),
|
|
321
|
+
sendMessage: (data) => worker.postMessage(data),
|
|
322
|
+
handshakeTimeout: 5000,
|
|
323
|
+
responseTimeout: 10000,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// `root` is a proxy to the worker's exported API
|
|
327
|
+
const result = await root.remoteMethod('arg');
|
|
328
|
+
stop(); // detach message listener
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// --- Worker (GUEST) ---
|
|
333
|
+
import { initialize, InterfaceType } from '@actualwave/deferred-data-access/interface';
|
|
334
|
+
|
|
335
|
+
await initialize({
|
|
336
|
+
type: InterfaceType.GUEST,
|
|
337
|
+
root: { remoteMethod: (arg: string) => arg.toUpperCase() },
|
|
338
|
+
subscribe: (fn) => self.addEventListener('message', fn),
|
|
339
|
+
unsubscribe: (fn) => self.removeEventListener('message', fn),
|
|
340
|
+
sendMessage: (data) => self.postMessage(data),
|
|
341
|
+
handshakeTimeout: 5000,
|
|
342
|
+
responseTimeout: 10000,
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### InitConfig options
|
|
347
|
+
|
|
348
|
+
| Option | Type | Default | Description |
|
|
349
|
+
|---|---|---|---|
|
|
350
|
+
| `type` | `InterfaceType` | required | `HOST` waits for the guest; `GUEST` initiates |
|
|
351
|
+
| `root` | `unknown` | — | Object to expose to the remote side |
|
|
352
|
+
| `subscribe` | `(fn) => void` | required | Attach a message listener |
|
|
353
|
+
| `unsubscribe` | `(fn) => void` | required | Detach a message listener |
|
|
354
|
+
| `sendMessage` | `(data) => void` | required | Send a message to the remote side |
|
|
355
|
+
| `id` | `string` | auto | Stable ID for this interface endpoint |
|
|
356
|
+
| `remoteId` | `string` | — | Expected remote ID (skips handshake if both sides know each other's ID) |
|
|
357
|
+
| `handshakeTimeout` | `number` | — | ms before handshake times out |
|
|
358
|
+
| `handshakeInterval` | `number` | — | ms between handshake retry attempts (GUEST only) |
|
|
359
|
+
| `responseTimeout` | `number` | `0` (none) | ms before a remote call times out |
|
|
360
|
+
| `preprocessResponse` | `(data) => unknown` | identity | Transform raw message data before parsing |
|
|
361
|
+
|
|
362
|
+
### initialize() return value
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
{
|
|
366
|
+
stop: () => void; // detach the message listener
|
|
367
|
+
pool: ResourcePool; // local resource pool
|
|
368
|
+
root: unknown | null; // proxy to the remote root (null if remote has no root)
|
|
369
|
+
wrap: Function; // wrap factory with the same handler (for advanced use)
|
|
370
|
+
pendingRequests: Map<…>; // in-flight request map (for advanced use)
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Sub-package reference
|
|
377
|
+
|
|
378
|
+
| Import path | Key exports |
|
|
379
|
+
|---|---|
|
|
380
|
+
| `@actualwave/deferred-data-access` | `handle` |
|
|
381
|
+
| `@actualwave/deferred-data-access/command` | `Command`, `CommandChain`, `createCommandHandler` |
|
|
382
|
+
| `@actualwave/deferred-data-access/proxy` | `ProxyCommand`, `wrapWithProxy`, `isWrappedWithProxy`, `unwrapProxy`, `generateProxyCommand` |
|
|
383
|
+
| `@actualwave/deferred-data-access/resource` | `Resource`, `ResourcePool`, `ResourcePoolRegistry`, `getDefaultResourcePool`, `setDefaultResourcePool`, `getRegistry`, `setRegistry`, `getCustomFinalizationRegistryClass`, `setCustomFinalizationRegistryClass`, `isResourceObject`, `createResource` |
|
|
384
|
+
| `@actualwave/deferred-data-access/record` | `recordHandlerCalls`, `latestCall`, `latestCallFor`, `clearLatestCalls` |
|
|
385
|
+
| `@actualwave/deferred-data-access/utils` | `IdOwner`, `generateId`, `createUIDGenerator`, `isReservedPropertyName`, `ReservedPropertyNames`, `reject` |
|
|
386
|
+
| `@actualwave/deferred-data-access/interface` | `initialize`, `InterfaceType`, `MessageType`, `createSubscriberFns`, `findEventEmitter`, `findMessagePort` |
|
|
387
|
+
|
|
388
|
+
### recordHandlerCalls
|
|
389
|
+
|
|
390
|
+
Wraps a `CommandHandler` to track the latest in-flight call Promise, useful for implementing loading indicators or sequential request logic:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { recordHandlerCalls, latestCall, latestCallFor } from '@actualwave/deferred-data-access/record';
|
|
394
|
+
|
|
395
|
+
const trackedHandler = recordHandlerCalls(myHandler);
|
|
396
|
+
const wrap = handle(trackedHandler);
|
|
397
|
+
|
|
398
|
+
const proxy = wrap(rootObject);
|
|
399
|
+
proxy.doWork();
|
|
400
|
+
|
|
401
|
+
// latestCall() returns the Promise of the most recent handler invocation
|
|
402
|
+
await latestCall();
|
|
403
|
+
|
|
404
|
+
// latestCallFor(context) returns the Promise for a specific context Promise
|
|
405
|
+
```
|
|
28
406
|
|
|
29
|
-
|
|
30
|
-
* ProxyCommand.**GET** - when property accessed
|
|
31
|
-
* ProxyCommand.**SET** - when property is set, new value is recorded as `value` in command object
|
|
32
|
-
* ProxyCommand.**DELETE_PROPERTY** - when property is being deleted
|
|
33
|
-
* ProxyCommand.**APPLY** - when function is called, call arguments recorded as `value` of command object
|
|
34
|
-
* ProxyCommand.**METHOD_CALL** - only generated in lazy mode, it combines GET and APPLY commands.
|
|
407
|
+
---
|
|
35
408
|
|
|
409
|
+
## Projects built on DDA
|
|
36
410
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* [WorkerInterface](https://github.com/burdiuz/js-deferred-data-access/tree/master/packages/worker-interface)
|
|
411
|
+
- [RESTObject](https://github.com/burdiuz/js-deferred-data-access/tree/master/packages/rest-object) — REST API access using dot notation
|
|
412
|
+
- [WorkerInterface](https://github.com/burdiuz/js-deferred-data-access/tree/master/packages/worker-interface) — transparent proxy to a Web Worker's API
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "deferred-data-access"
|
|
3
|
+
description: "TypeScript library that wraps objects in an ES Proxy and converts every property access, assignment, deletion, and call into a typed command delivered to a developer-supplied async handler. Use when working on or extending this package; building handlers that depend on it (REST object, worker proxy, etc.); reading or writing command chains; setting up cross-context communication via initialize(); working with ResourcePool and Resource; or writing and debugging tests for DDA-based code."
|
|
4
|
+
license: "MIT"
|
|
5
|
+
compatibility: "Node.js 18+. TypeScript 5+. Tests use Jest 30 + ts-jest. Run `npm test` from packages/deferred-data-access."
|
|
6
|
+
metadata:
|
|
7
|
+
author: "Oleg Galaburda <burdiuz@gmail.com>"
|
|
8
|
+
version: "2.0.0"
|
|
9
|
+
package: "@actualwave/deferred-data-access"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Deferred Data Access — Agent Skill
|
|
13
|
+
|
|
14
|
+
## Sub-packages
|
|
15
|
+
|
|
16
|
+
| Import path | Key exports |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `@actualwave/deferred-data-access` | `handle` |
|
|
19
|
+
| `.../command` | `Command`, `CommandChain`, `createCommandHandler` |
|
|
20
|
+
| `.../proxy` | `ProxyCommand`, `wrapWithProxy`, `isWrappedWithProxy`, `unwrapProxy`, `generateProxyCommand` |
|
|
21
|
+
| `.../resource` | `Resource`, `ResourcePool`, `ResourcePoolRegistry`, `getDefaultResourcePool`, `setDefaultResourcePool`, `getRegistry`, `setRegistry`, `getCustomFinalizationRegistryClass`, `setCustomFinalizationRegistryClass`, `isResourceObject`, `createResource` |
|
|
22
|
+
| `.../record` | `recordHandlerCalls`, `latestCall`, `latestCallFor`, `clearLatestCalls` |
|
|
23
|
+
| `.../utils` | `IdOwner`, `generateId`, `createUIDGenerator`, `isReservedPropertyName`, `ReservedPropertyNames`, `reject` |
|
|
24
|
+
| `.../interface` | `initialize`, `InterfaceType`, `MessageType`, `createSubscriberFns`, `findEventEmitter`, `findMessagePort` |
|
|
25
|
+
|
|
26
|
+
## Core workflow
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
handle(handler, lazy?) → wrap(target) → Proxy
|
|
30
|
+
→ property access / call / set
|
|
31
|
+
→ CommandChain (head → prev → … → tail)
|
|
32
|
+
→ .then() / await → handler(command, context, wrap)
|
|
33
|
+
→ Promise<result>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Command types (`ProxyCommand`)
|
|
37
|
+
|
|
38
|
+
| Constant | Triggered by |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `GET` `'P:get'` | `proxy.prop` |
|
|
41
|
+
| `SET` `'P:set'` | `proxy.prop = v` |
|
|
42
|
+
| `DELETE_PROPERTY` `'P:del'` | `delete proxy.prop` |
|
|
43
|
+
| `APPLY` `'P:apply'` | `proxy(args)` |
|
|
44
|
+
| `METHOD_CALL` `'P:call'` | `proxy.method(args)` *(lazy only)* |
|
|
45
|
+
|
|
46
|
+
## Lazy vs reactive
|
|
47
|
+
|
|
48
|
+
- **Lazy** (default): handler fires once on `await`. Consecutive GET + APPLY collapses to `METHOD_CALL`. Walk `command.prev` for the full chain.
|
|
49
|
+
- **Reactive** (`handle(h, false)`): fires on every operation. `METHOD_CALL` never emitted; `APPLY` has no `name` — derive it from the preceding GET's path segment.
|
|
50
|
+
|
|
51
|
+
## CommandChain
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
command.forEach(n => path.push(String(n.name))); // deepest-first
|
|
55
|
+
const nodes = [...command]; // head … tail via Symbol.iterator
|
|
56
|
+
command.withoutPrev(); // immutable copy, prev severed — never mutate prev directly
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Resource system
|
|
60
|
+
|
|
61
|
+
All singletons are lazily created — no pool or registry is instantiated at module load time.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import {
|
|
65
|
+
getDefaultResourcePool, setDefaultResourcePool,
|
|
66
|
+
getRegistry, setRegistry,
|
|
67
|
+
getCustomFinalizationRegistryClass, setCustomFinalizationRegistryClass,
|
|
68
|
+
} from '@actualwave/deferred-data-access/resource';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `getDefaultResourcePool()` / `setDefaultResourcePool(pool)` — module-level default `ResourcePool` singleton.
|
|
72
|
+
- `getRegistry()` / `setRegistry(registry)` — module-level `ResourcePoolRegistry` singleton. The registry auto-registers the default pool on first creation.
|
|
73
|
+
- `getCustomFinalizationRegistryClass()` / `setCustomFinalizationRegistryClass(cls)` — module-level fallback `FinalizationRegistry` class passed to every new `ResourcePool`. Set this before any pool is created in environments where `globalThis.FinalizationRegistry` is absent (e.g. Hermes/React Native). Pass `null` to explicitly disable GC cleanup.
|
|
74
|
+
- `ResourcePool` constructor: `new ResourcePool(FinalizationRegistry?)` — explicit argument takes precedence over the module-level setting.
|
|
75
|
+
- `ResourcePoolRegistry.createPool()` — creates and registers a new pool; uses the module-level `FinalizationRegistry` class via `ResourcePool`'s constructor default.
|
|
76
|
+
|
|
77
|
+
## `initialize()` (cross-context)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { initialize, InterfaceType } from '@actualwave/deferred-data-access/interface';
|
|
81
|
+
const { root, stop } = await initialize({
|
|
82
|
+
type: InterfaceType.HOST, // or GUEST
|
|
83
|
+
root: myApi,
|
|
84
|
+
subscribe: fn => emitter.on('message', fn),
|
|
85
|
+
unsubscribe: fn => emitter.off('message', fn),
|
|
86
|
+
sendMessage: data => transport.send(data),
|
|
87
|
+
preprocessResponse: e => e.data,
|
|
88
|
+
handshakeTimeout: 5_000,
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Testing
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
cd packages/deferred-data-access && npm test
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Jest 30, ts-jest. `moduleNameMapper` in `jest.config.js` + `paths` in `tsconfig.spec.json`.
|
|
99
|
+
|
|
100
|
+
## Key invariants
|
|
101
|
+
|
|
102
|
+
- `typeof null === 'object'` — always guard `value != null && typeof value === 'object'`.
|
|
103
|
+
- DDA always wraps root context in `Promise.resolve()`. Avoid unconditional `await`; check `isPromiseLike` first.
|
|
104
|
+
- `APPLY` in reactive mode has no `name` — derive from preceding GET's path.
|
|
105
|
+
- `Command.type`, `Command.name`, `Resource.pool`, `IdOwner.id` are runtime non-writable.
|
|
106
|
+
- `isReservedPropertyName('then')` → `true`; symbol keys always → `false`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../packages/deferred-data-access/command/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
class Command {
|
|
2
|
+
constructor(type, name, value, context) {
|
|
3
|
+
this.type = type;
|
|
4
|
+
this.name = name;
|
|
5
|
+
this.value = value;
|
|
6
|
+
this.context = context;
|
|
7
|
+
Object.defineProperty(this, 'type', { value: type, writable: false, configurable: false });
|
|
8
|
+
Object.defineProperty(this, 'name', { value: name, writable: false, configurable: false });
|
|
9
|
+
}
|
|
10
|
+
toObject(includeContext = false) {
|
|
11
|
+
const { type, name, value, context } = this;
|
|
12
|
+
return {
|
|
13
|
+
type,
|
|
14
|
+
name,
|
|
15
|
+
value,
|
|
16
|
+
context: includeContext ? context : undefined,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
toJSON(includeContext = false) {
|
|
20
|
+
const { type, name, value, context } = this;
|
|
21
|
+
return includeContext
|
|
22
|
+
? JSON.stringify([type, name, value, context])
|
|
23
|
+
: JSON.stringify([type, name, value]);
|
|
24
|
+
}
|
|
25
|
+
static fromJSON(jsonString) {
|
|
26
|
+
const [type, name, value, context] = JSON.parse(jsonString);
|
|
27
|
+
return new Command(type, name, value, context);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class CommandChain extends Command {
|
|
32
|
+
constructor(prev, type, name, value, context) {
|
|
33
|
+
super(type, name, value, context);
|
|
34
|
+
this.prev = prev;
|
|
35
|
+
}
|
|
36
|
+
*[Symbol.iterator]() {
|
|
37
|
+
let item = this;
|
|
38
|
+
while (item) {
|
|
39
|
+
yield item;
|
|
40
|
+
item = item.prev;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
isTail() {
|
|
44
|
+
return !this.prev;
|
|
45
|
+
}
|
|
46
|
+
forEach(callback) {
|
|
47
|
+
let node = this;
|
|
48
|
+
do {
|
|
49
|
+
callback(node);
|
|
50
|
+
node = node.prev;
|
|
51
|
+
} while (node);
|
|
52
|
+
}
|
|
53
|
+
map(callback) {
|
|
54
|
+
let node = this;
|
|
55
|
+
const list = [];
|
|
56
|
+
do {
|
|
57
|
+
list.push(callback(node));
|
|
58
|
+
node = node.prev;
|
|
59
|
+
} while (node);
|
|
60
|
+
return list;
|
|
61
|
+
}
|
|
62
|
+
reduce(callback, base) {
|
|
63
|
+
let node = this;
|
|
64
|
+
let result = base;
|
|
65
|
+
do {
|
|
66
|
+
result = callback(result, node);
|
|
67
|
+
node = node.prev;
|
|
68
|
+
} while (node);
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns a new CommandChain that is identical to this one but with the
|
|
73
|
+
* `prev` link severed, rather than mutating the existing instance in-place.
|
|
74
|
+
* Use this instead of `delete command.prev` to avoid corrupting shared chain
|
|
75
|
+
* references held by other code.
|
|
76
|
+
*/
|
|
77
|
+
withoutPrev() {
|
|
78
|
+
return new CommandChain(undefined, this.type, this.name, this.value, this.context);
|
|
79
|
+
}
|
|
80
|
+
static fromCommand({ type, name, value, context }, prev) {
|
|
81
|
+
return new CommandChain(prev, type, name, value, context);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
Creates a function that calls handler depending on command type
|
|
87
|
+
*/
|
|
88
|
+
const createCommandHandler = ({ handlers, defaultHandler, }) => (command, ...args) => {
|
|
89
|
+
const { type } = command;
|
|
90
|
+
const handler = (handlers && handlers[type]) || defaultHandler;
|
|
91
|
+
if (handler) {
|
|
92
|
+
return handler(command, ...args);
|
|
93
|
+
}
|
|
94
|
+
return Promise.resolve(undefined);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { Command, CommandChain, createCommandHandler };
|
|
98
|
+
//# sourceMappingURL=index.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../../../../../../packages/deferred-data-access/command/src/command.ts","../../../../../../packages/deferred-data-access/command/src/command-chain.ts","../../../../../../packages/deferred-data-access/command/src/command-handler.ts"],"sourcesContent":[null,null,null],"names":[],"mappings":"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"}
|