@nice-code/action 0.1.0 → 0.1.2
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 +410 -0
- package/build/index.js +70 -7
- package/build/types/ActionDomain/NiceActionDomain.types.d.ts +0 -1
- package/build/types/ActionRuntimeEnvironment/ActionConnect/Transport/Transport.d.ts +3 -0
- package/build/types/ActionRuntimeEnvironment/ActionConnect/Transport/Transport.types.d.ts +24 -4
- package/build/types/ActionRuntimeEnvironment/ActionConnect/Transport/TransportCustom.d.ts +12 -0
- package/build/types/ActionRuntimeEnvironment/ActionConnect/Transport/TransportWebSocket.d.ts +3 -1
- package/build/types/ActionRuntimeEnvironment/ActionConnect/Transport/err_nice_transport.d.ts +4 -0
- package/build/types/ActionRuntimeEnvironment/ActionHandler/ActionHandler.d.ts +1 -1
- package/build/types/ActionRuntimeEnvironment/ActionHandler/ActionHandler.types.d.ts +2 -1
- package/build/types/NiceAction/MatchAction/MatchAction.d.ts +2 -1
- package/build/types/NiceAction/NiceAction.d.ts +1 -1
- package/build/types/NiceAction/NiceAction.types.d.ts +1 -1
- package/build/types/index.d.ts +9 -2
- package/build/types/react-query/index.d.ts +2 -2
- package/build/types/utils/maybePromise.d.ts +1 -0
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# @nice-code/action
|
|
2
|
+
|
|
3
|
+
A fully-typed action-based RPC framework for TypeScript. Define actions once, execute them locally or over HTTP/WebSocket — same code, same types, everywhere.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
Modern apps split logic across server, client, and workers. Typed RPC frameworks help, but most still require separate client/server type definitions, custom serialization glue, and ad-hoc error handling. `@nice-code/action` solves this with:
|
|
8
|
+
|
|
9
|
+
- **One definition, every environment** — same action works locally, over HTTP, or WebSocket
|
|
10
|
+
- **Custom serialization baked in** — `Date`, `Map`, `Buffer` — define it once, it works across the wire automatically
|
|
11
|
+
- **Typed error unions** — declare which errors an action throws; TypeScript enforces handling them
|
|
12
|
+
- **Observable** — attach listeners for logging, analytics, or tracing with zero boilerplate
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add @nice-code/action @nice-code/error valibot
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Core Concepts
|
|
25
|
+
|
|
26
|
+
Actions flow through three states:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
NiceAction (definition) → NiceActionPrimed (input attached) → NiceActionResponse (result)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You mostly work with `NiceAction` and let the framework handle the rest.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### 1. Define a domain and its actions
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { createActionRootDomain, action } from "@nice-code/action";
|
|
42
|
+
import * as v from "valibot";
|
|
43
|
+
|
|
44
|
+
const root = createActionRootDomain({ domain: "app" });
|
|
45
|
+
|
|
46
|
+
const orderDomain = root.createChildDomain({
|
|
47
|
+
domain: "order",
|
|
48
|
+
actions: {
|
|
49
|
+
placeOrder: action()
|
|
50
|
+
.input({ schema: v.object({ items: v.array(v.string()), total: v.number() }) })
|
|
51
|
+
.output({ schema: v.object({ orderId: v.string(), estimatedDelivery: v.string() }) }),
|
|
52
|
+
|
|
53
|
+
cancelOrder: action()
|
|
54
|
+
.input({ schema: v.object({ orderId: v.string(), reason: v.string() }) })
|
|
55
|
+
.output({ schema: v.object({ refundAmount: v.number() }) }),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Register handlers
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { ActionHandler, createActionRuntime } from "@nice-code/action";
|
|
64
|
+
|
|
65
|
+
const handler = new ActionHandler()
|
|
66
|
+
.forAction(orderDomain, "placeOrder", {
|
|
67
|
+
execution: async (primed) => {
|
|
68
|
+
const { items, total } = primed.input; // fully typed
|
|
69
|
+
const order = await db.orders.create({ items, total });
|
|
70
|
+
return primed.setResponse({
|
|
71
|
+
orderId: order.id,
|
|
72
|
+
estimatedDelivery: "2-3 business days",
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
.forAction(orderDomain, "cancelOrder", {
|
|
77
|
+
execution: async (primed) => {
|
|
78
|
+
const refund = await payments.refund(primed.input.orderId);
|
|
79
|
+
return primed.setResponse({ refundAmount: refund.amount });
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
root.setRuntimeEnvironment(
|
|
84
|
+
createActionRuntime({ envId: "server" }).addHandlers([handler])
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Execute
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const result = await orderDomain.action("placeOrder").execute({
|
|
92
|
+
items: ["SKU-001", "SKU-002"],
|
|
93
|
+
total: 49.99,
|
|
94
|
+
});
|
|
95
|
+
// result: { orderId: "ord_abc123", estimatedDelivery: "2-3 business days" }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Typed Error Handling
|
|
101
|
+
|
|
102
|
+
Declare errors your action can throw — TypeScript tracks them through `executeSafe()`.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { err_auth, err_payment } from "@nice-code/common-errors";
|
|
106
|
+
|
|
107
|
+
const checkoutDomain = root.createChildDomain({
|
|
108
|
+
domain: "checkout",
|
|
109
|
+
actions: {
|
|
110
|
+
pay: action()
|
|
111
|
+
.input({ schema: v.object({ cartId: v.string(), cardToken: v.string() }) })
|
|
112
|
+
.output({ schema: v.object({ receiptId: v.string() }) })
|
|
113
|
+
.throws(err_auth, ["unauthenticated"]) // only this error from auth domain
|
|
114
|
+
.throws(err_payment), // all payment errors
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const result = await checkoutDomain.action("pay").executeSafe({
|
|
121
|
+
cartId: "cart_xyz",
|
|
122
|
+
cardToken: "tok_...",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
// result.error is a fully typed NiceError union
|
|
127
|
+
result.error.handleWithSync([
|
|
128
|
+
forId(err_auth, "unauthenticated", () => res.status(401).json({ error: "Login required" })),
|
|
129
|
+
forId(err_payment, "card_declined", (err) => {
|
|
130
|
+
const { last4 } = err.getContext();
|
|
131
|
+
res.status(402).json({ error: `Card ending in ${last4} was declined` });
|
|
132
|
+
}),
|
|
133
|
+
forDomain(err_payment, () => res.status(402).json({ error: "Payment failed" })),
|
|
134
|
+
]);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// result.output: { receiptId: string }
|
|
139
|
+
res.json({ receiptId: result.output.receiptId });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Custom Serialization
|
|
145
|
+
|
|
146
|
+
Non-JSON types — `Date`, `Map`, binary — need serialization for transport. Define it once on the schema; the framework handles it on both ends.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const eventDomain = root.createChildDomain({
|
|
150
|
+
domain: "event",
|
|
151
|
+
actions: {
|
|
152
|
+
schedule: action()
|
|
153
|
+
.input(
|
|
154
|
+
{ schema: v.object({ name: v.string(), scheduledAt: v.date() }) },
|
|
155
|
+
// serialize: Date → ISO string for the wire
|
|
156
|
+
({ name, scheduledAt }) => ({ name, iso: scheduledAt.toISOString() }),
|
|
157
|
+
// deserialize: ISO string → Date on the other end
|
|
158
|
+
({ name, iso }) => ({ name, scheduledAt: new Date(iso) })
|
|
159
|
+
)
|
|
160
|
+
.output({ schema: v.object({ eventId: v.string() }) }),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Client sends this:
|
|
167
|
+
await eventDomain.action("schedule").execute({
|
|
168
|
+
name: "Team meeting",
|
|
169
|
+
scheduledAt: new Date("2025-03-15T14:00:00Z"), // ← native Date
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Wire format (what actually travels over HTTP):
|
|
173
|
+
// { name: "Team meeting", iso: "2025-03-15T14:00:00.000Z" }
|
|
174
|
+
|
|
175
|
+
// Server receives native Date automatically — no manual parsing
|
|
176
|
+
handler.forAction(eventDomain, "schedule", {
|
|
177
|
+
execution: async (primed) => {
|
|
178
|
+
console.log(primed.input.scheduledAt instanceof Date); // true
|
|
179
|
+
await calendar.create(primed.input.scheduledAt);
|
|
180
|
+
return primed.setResponse({ eventId: "evt_..." });
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Same Actions, Different Environments
|
|
188
|
+
|
|
189
|
+
The same domain definition works on both server (with handlers) and client (with remote transport). No code duplication.
|
|
190
|
+
|
|
191
|
+
### Server
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// server.ts
|
|
195
|
+
const handler = new ActionHandler().forDomain(orderDomain, {
|
|
196
|
+
execution: async (primed) => {
|
|
197
|
+
/* ... real DB logic ... */
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
root.setRuntimeEnvironment(
|
|
202
|
+
createActionRuntime({ envId: "server" }).addHandlers([handler])
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// One endpoint handles all actions
|
|
206
|
+
app.post("/actions", async (req, res) => {
|
|
207
|
+
const result = await handler.handleWire(req.body);
|
|
208
|
+
if (!result.handled) return res.status(404).end();
|
|
209
|
+
res.json(result.response.toJsonObject());
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Client
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// client.ts — exact same domain, different runtime
|
|
217
|
+
import { ActionConnect, ConnectionConfig, createActionRuntime } from "@nice-code/action";
|
|
218
|
+
|
|
219
|
+
const connect = new ActionConnect(
|
|
220
|
+
[new ConnectionConfig({ transports: [{ type: "http", url: "/actions" }] })],
|
|
221
|
+
{ requestTimeout: 30_000 }
|
|
222
|
+
).routeDomain(orderDomain);
|
|
223
|
+
|
|
224
|
+
root.setRuntimeEnvironment(
|
|
225
|
+
createActionRuntime({ envId: "browser" }).addHandlers([connect])
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Works exactly like local execution — goes over HTTP transparently
|
|
229
|
+
const result = await orderDomain.action("placeOrder").execute({
|
|
230
|
+
items: ["SKU-001"],
|
|
231
|
+
total: 29.99,
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### WebSocket for real-time
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
new ConnectionConfig({
|
|
239
|
+
transports: [
|
|
240
|
+
{ type: "ws", url: "wss://api.example.com/ws" }, // persistent connection, no reconnect overhead
|
|
241
|
+
{ type: "http", url: "/actions" }, // fallback
|
|
242
|
+
],
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Observability
|
|
249
|
+
|
|
250
|
+
Attach listeners to any domain — fire for every action without modifying handlers.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const unsubscribe = orderDomain.addActionListener({
|
|
254
|
+
execution: (primed, { runtime }) => {
|
|
255
|
+
logger.info(`[${runtime.name}] → ${primed.domain}::${primed.id}`, primed.input);
|
|
256
|
+
},
|
|
257
|
+
response: (response, { runtime }) => {
|
|
258
|
+
const { result } = response;
|
|
259
|
+
if (result.ok) {
|
|
260
|
+
metrics.increment(`action.success`, { action: response.id });
|
|
261
|
+
} else {
|
|
262
|
+
metrics.increment(`action.error`, { action: response.id, error: result.error.id });
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Clean up when done
|
|
268
|
+
unsubscribe();
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Pattern Matching
|
|
274
|
+
|
|
275
|
+
When you receive an action and need to branch on its identity:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { matchAction } from "@nice-code/action";
|
|
279
|
+
|
|
280
|
+
await matchAction(incomingAction)
|
|
281
|
+
.with({
|
|
282
|
+
domain: orderDomain,
|
|
283
|
+
id: "placeOrder",
|
|
284
|
+
handler: async (action) => {
|
|
285
|
+
// action narrowed to NiceAction<OrderDomain, "placeOrder">
|
|
286
|
+
await notifyWarehouse(action.input);
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
.with({
|
|
290
|
+
domain: orderDomain,
|
|
291
|
+
id: "cancelOrder",
|
|
292
|
+
handler: async (action) => {
|
|
293
|
+
await notifyCustomer(action.input.orderId);
|
|
294
|
+
},
|
|
295
|
+
})
|
|
296
|
+
.otherwise(async (action) => {
|
|
297
|
+
logger.warn(`Unhandled action: ${action.domain}::${action.id}`);
|
|
298
|
+
})
|
|
299
|
+
.runAsync();
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Wire Format
|
|
305
|
+
|
|
306
|
+
Every action state serializes to JSON — useful for logging, queuing, or replay:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
const primed = orderDomain.action("placeOrder").prime({ items: ["SKU-001"], total: 29.99 });
|
|
310
|
+
|
|
311
|
+
const wire = primed.toJsonObject();
|
|
312
|
+
// {
|
|
313
|
+
// type: "primed",
|
|
314
|
+
// domain: "order",
|
|
315
|
+
// allDomains: ["order", "app"],
|
|
316
|
+
// id: "placeOrder",
|
|
317
|
+
// cuid: "abc123...",
|
|
318
|
+
// timeCreated: 1704067200000,
|
|
319
|
+
// timePrimed: 1704067201000,
|
|
320
|
+
// input: { items: ["SKU-001"], total: 29.99 }
|
|
321
|
+
// }
|
|
322
|
+
|
|
323
|
+
// Reconstruct anywhere
|
|
324
|
+
const rehydrated = orderDomain.hydratePrimed(wire);
|
|
325
|
+
await rehydrated.execute(); // executes with original input
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Store actions in a queue, ship them to a worker, execute on the other side — full round-trip with validation.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Named Handlers (Tags)
|
|
333
|
+
|
|
334
|
+
Route different action types to different handlers — useful for auth tiers, feature flags, or test overrides.
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
const adminHandler = new ActionHandler({ tag: "admin" })
|
|
338
|
+
.forAction(userDomain, "deleteUser", {
|
|
339
|
+
execution: async (primed) => { /* admin-only logic */ },
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const publicHandler = new ActionHandler()
|
|
343
|
+
.forDomain(userDomain, { execution: handlePublicActions });
|
|
344
|
+
|
|
345
|
+
root.setRuntimeEnvironment(
|
|
346
|
+
createActionRuntime({ envId: "server" })
|
|
347
|
+
.addHandlers([adminHandler, publicHandler])
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Use the tagged handler explicitly
|
|
351
|
+
await userDomain.action("deleteUser").execute(input, { tag: "admin" });
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## API Reference
|
|
357
|
+
|
|
358
|
+
### Domain
|
|
359
|
+
|
|
360
|
+
| Method | Description |
|
|
361
|
+
|---|---|
|
|
362
|
+
| `createActionRootDomain({ domain })` | Create root domain |
|
|
363
|
+
| `root.createChildDomain({ domain, actions })` | Create child domain |
|
|
364
|
+
| `domain.action(id)` | Get action definition |
|
|
365
|
+
| `domain.addActionListener({ execution?, response? })` | Observe all actions; returns unsubscribe fn |
|
|
366
|
+
| `domain.hydratePrimed(wire)` | Reconstruct primed action from JSON |
|
|
367
|
+
| `domain.hydrateResponse(wire)` | Reconstruct response from JSON |
|
|
368
|
+
|
|
369
|
+
### Action
|
|
370
|
+
|
|
371
|
+
| Method | Description |
|
|
372
|
+
|---|---|
|
|
373
|
+
| `action.execute(input, meta?)` | Execute; throws on error |
|
|
374
|
+
| `action.executeSafe(input, meta?)` | Execute; returns `{ ok, output }` or `{ ok, error }` |
|
|
375
|
+
| `action.prime(input)` | Attach and validate input |
|
|
376
|
+
| `action.is(other)` | Type guard — check if action matches this definition |
|
|
377
|
+
|
|
378
|
+
### Schema Builder (`action()`)
|
|
379
|
+
|
|
380
|
+
| Method | Description |
|
|
381
|
+
|---|---|
|
|
382
|
+
| `.input({ schema }, serialize?, deserialize?)` | Declare input schema + optional serde |
|
|
383
|
+
| `.output({ schema }, serialize?, deserialize?)` | Declare output schema + optional serde |
|
|
384
|
+
| `.throws(errorDomain, [ids]?)` | Declare throwable errors (all or subset) |
|
|
385
|
+
|
|
386
|
+
### Handlers
|
|
387
|
+
|
|
388
|
+
| Class | Use case |
|
|
389
|
+
|---|---|
|
|
390
|
+
| `ActionHandler` | Local/same-process execution |
|
|
391
|
+
| `ActionConnect` | Remote execution over HTTP or WebSocket |
|
|
392
|
+
|
|
393
|
+
### ActionHandler
|
|
394
|
+
|
|
395
|
+
| Method | Description |
|
|
396
|
+
|---|---|
|
|
397
|
+
| `.forAction(domain, id, { execution, response? })` | Handle one action |
|
|
398
|
+
| `.forActionIds(domain, ids, { execution, response? })` | Handle a subset |
|
|
399
|
+
| `.forDomain(domain, { execution, response? })` | Handle all actions in domain |
|
|
400
|
+
| `.forDomainActionCases(domain, cases)` | Handle actions via case map |
|
|
401
|
+
| `.handleWire(body)` | Parse and dispatch raw JSON (for HTTP endpoints) |
|
|
402
|
+
|
|
403
|
+
### ActionConnect
|
|
404
|
+
|
|
405
|
+
| Method | Description |
|
|
406
|
+
|---|---|
|
|
407
|
+
| `.routeDomain(domain, route?)` | Route all domain actions remotely |
|
|
408
|
+
| `.routeAction(domain, id, route?)` | Route one action remotely |
|
|
409
|
+
| `.routeActionIds(domain, ids, route?)` | Route a subset remotely |
|
|
410
|
+
| `.disconnect()` | Close connections |
|
package/build/index.js
CHANGED
|
@@ -3393,6 +3393,7 @@ var EErrId_NiceTransport;
|
|
|
3393
3393
|
((EErrId_NiceTransport2) => {
|
|
3394
3394
|
EErrId_NiceTransport2["timeout"] = "timeout";
|
|
3395
3395
|
EErrId_NiceTransport2["not_found"] = "not_found";
|
|
3396
|
+
EErrId_NiceTransport2["not_available"] = "not_available";
|
|
3396
3397
|
EErrId_NiceTransport2["initialization_failed"] = "initialization_failed";
|
|
3397
3398
|
EErrId_NiceTransport2["send_failed"] = "send_failed";
|
|
3398
3399
|
EErrId_NiceTransport2["invalid_action_response"] = "invalid_action_response";
|
|
@@ -3406,6 +3407,9 @@ var err_nice_transport = err_nice_connect.createChildDomain({
|
|
|
3406
3407
|
["not_found" /* not_found */]: err({
|
|
3407
3408
|
message: ({ actionId, routeKey, tag }) => `No connected transport found for action "${actionId}"${routeKey ? ` with route key "${routeKey}"` : ``}${tag ? ` and action tag "${tag}"` : ""}.`
|
|
3408
3409
|
}),
|
|
3410
|
+
["not_available" /* not_available */]: err({
|
|
3411
|
+
message: ({ transportCount }) => `${transportCount} Transport(s) found but were filtered out via filterUsage().`
|
|
3412
|
+
}),
|
|
3409
3413
|
["initialization_failed" /* initialization_failed */]: err({
|
|
3410
3414
|
message: ({ actionId, routeKey, tag }) => `Transports found for action "${actionId}"${routeKey ? ` with route key "${routeKey}"` : ""}${tag ? ` and action tag "${tag}"` : ""}, but none are ready.`
|
|
3411
3415
|
}),
|
|
@@ -3487,13 +3491,21 @@ class Transport {
|
|
|
3487
3491
|
def;
|
|
3488
3492
|
type;
|
|
3489
3493
|
requestResolvers = new Map;
|
|
3494
|
+
_filterUsage;
|
|
3490
3495
|
constructor(def) {
|
|
3491
3496
|
this.def = def;
|
|
3492
3497
|
this.type = def.type;
|
|
3498
|
+
this._filterUsage = def.filterUsage;
|
|
3493
3499
|
}
|
|
3494
3500
|
get status() {
|
|
3495
3501
|
return this._status;
|
|
3496
3502
|
}
|
|
3503
|
+
filterUsage(primed) {
|
|
3504
|
+
if (this._filterUsage == null) {
|
|
3505
|
+
return true;
|
|
3506
|
+
}
|
|
3507
|
+
return this._filterUsage(primed);
|
|
3508
|
+
}
|
|
3497
3509
|
checkAndPrepare() {
|
|
3498
3510
|
return this._status;
|
|
3499
3511
|
}
|
|
@@ -3528,6 +3540,21 @@ class Transport {
|
|
|
3528
3540
|
}
|
|
3529
3541
|
}
|
|
3530
3542
|
|
|
3543
|
+
// src/ActionRuntimeEnvironment/ActionConnect/Transport/Transport.types.ts
|
|
3544
|
+
var ETransportType;
|
|
3545
|
+
((ETransportType2) => {
|
|
3546
|
+
ETransportType2["ws"] = "ws";
|
|
3547
|
+
ETransportType2["http"] = "http";
|
|
3548
|
+
ETransportType2["custom"] = "custom";
|
|
3549
|
+
})(ETransportType ||= {});
|
|
3550
|
+
var ETransportStatus;
|
|
3551
|
+
((ETransportStatus2) => {
|
|
3552
|
+
ETransportStatus2["uninitialized"] = "uninitialized";
|
|
3553
|
+
ETransportStatus2["initializing"] = "initializing";
|
|
3554
|
+
ETransportStatus2["ready"] = "ready";
|
|
3555
|
+
ETransportStatus2["failed"] = "failed";
|
|
3556
|
+
})(ETransportStatus ||= {});
|
|
3557
|
+
|
|
3531
3558
|
// src/ActionRuntimeEnvironment/ActionConnect/Transport/TransportHttp.ts
|
|
3532
3559
|
class TransportHttp extends Transport {
|
|
3533
3560
|
abortControllers = new Map;
|
|
@@ -3607,6 +3634,7 @@ var err_nice_transport_ws = err_nice_transport.createChildDomain({
|
|
|
3607
3634
|
class TransportWebSocket extends Transport {
|
|
3608
3635
|
websocket;
|
|
3609
3636
|
_status = { status: "uninitialized" /* uninitialized */ };
|
|
3637
|
+
_customMessageSerde;
|
|
3610
3638
|
constructor(def) {
|
|
3611
3639
|
super(def);
|
|
3612
3640
|
}
|
|
@@ -3628,21 +3656,24 @@ class TransportWebSocket extends Transport {
|
|
|
3628
3656
|
waitForInitialization
|
|
3629
3657
|
};
|
|
3630
3658
|
}
|
|
3631
|
-
|
|
3659
|
+
handlePureActionResponseMessage(message) {
|
|
3632
3660
|
let json;
|
|
3633
3661
|
try {
|
|
3634
|
-
json = JSON.parse(
|
|
3662
|
+
json = JSON.parse(message);
|
|
3635
3663
|
} catch {
|
|
3636
3664
|
return;
|
|
3637
3665
|
}
|
|
3638
3666
|
if (!isActionResponseJsonObject(json)) {
|
|
3639
3667
|
return;
|
|
3640
3668
|
}
|
|
3641
|
-
|
|
3669
|
+
return json;
|
|
3670
|
+
}
|
|
3671
|
+
handleResponse(response) {
|
|
3672
|
+
const pending = this.requestResolvers.get(response.cuid);
|
|
3642
3673
|
if (pending == null) {
|
|
3643
3674
|
return;
|
|
3644
3675
|
}
|
|
3645
|
-
this.respond(pending.primed.coreAction.actionDomain.hydrateResponse(
|
|
3676
|
+
this.respond(pending.primed.coreAction.actionDomain.hydrateResponse(response));
|
|
3646
3677
|
}
|
|
3647
3678
|
rejectPendingWebSocketRequests(error) {
|
|
3648
3679
|
for (const [, pending] of this.requestResolvers) {
|
|
@@ -3662,7 +3693,9 @@ class TransportWebSocket extends Transport {
|
|
|
3662
3693
|
}
|
|
3663
3694
|
};
|
|
3664
3695
|
try {
|
|
3665
|
-
|
|
3696
|
+
const { ws, customMessageSerde } = await this.def.createWebSocket();
|
|
3697
|
+
this.websocket = ws;
|
|
3698
|
+
this._customMessageSerde = customMessageSerde;
|
|
3666
3699
|
} catch (e2) {
|
|
3667
3700
|
console.error("Failed to create WebSocket:", e2);
|
|
3668
3701
|
const error = err_nice_transport_ws.fromId("ws_create_failed" /* ws_create_failed */, {
|
|
@@ -3687,7 +3720,10 @@ class TransportWebSocket extends Transport {
|
|
|
3687
3720
|
});
|
|
3688
3721
|
this.websocket.addEventListener("message", (event) => {
|
|
3689
3722
|
if (typeof event.data === "string") {
|
|
3690
|
-
this.
|
|
3723
|
+
const response = this._customMessageSerde?.deserialize?.(event.data) ?? this.handlePureActionResponseMessage(event.data);
|
|
3724
|
+
if (response) {
|
|
3725
|
+
this.handleResponse(response);
|
|
3726
|
+
}
|
|
3691
3727
|
}
|
|
3692
3728
|
});
|
|
3693
3729
|
this.websocket.addEventListener("close", (event) => {
|
|
@@ -3759,7 +3795,23 @@ class ConnectionConfig {
|
|
|
3759
3795
|
async dispatch(primed, defaultTimeout) {
|
|
3760
3796
|
const timeout = this.config.defaultTimeout ?? defaultTimeout;
|
|
3761
3797
|
const initializingWaiters = [];
|
|
3798
|
+
const unavailableTransports = [];
|
|
3762
3799
|
for (const transport of this._transports) {
|
|
3800
|
+
const isAvailable = transport.filterUsage(primed);
|
|
3801
|
+
if (isAvailable instanceof Promise) {
|
|
3802
|
+
try {
|
|
3803
|
+
if (!await isAvailable) {
|
|
3804
|
+
unavailableTransports.push(transport);
|
|
3805
|
+
continue;
|
|
3806
|
+
}
|
|
3807
|
+
} catch {
|
|
3808
|
+
unavailableTransports.push(transport);
|
|
3809
|
+
continue;
|
|
3810
|
+
}
|
|
3811
|
+
} else if (!isAvailable) {
|
|
3812
|
+
unavailableTransports.push(transport);
|
|
3813
|
+
continue;
|
|
3814
|
+
}
|
|
3763
3815
|
const statusInfo = transport.checkAndPrepare();
|
|
3764
3816
|
if (statusInfo.status === "ready" /* ready */) {
|
|
3765
3817
|
return transport.makeRequest(primed, timeout);
|
|
@@ -3774,6 +3826,11 @@ class ConnectionConfig {
|
|
|
3774
3826
|
}
|
|
3775
3827
|
}
|
|
3776
3828
|
if (initializingWaiters.length === 0) {
|
|
3829
|
+
if (unavailableTransports.length > 0) {
|
|
3830
|
+
throw err_nice_transport.fromId("not_available" /* not_available */, {
|
|
3831
|
+
transportCount: unavailableTransports.length
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3777
3834
|
throw err_nice_transport.fromId("not_found" /* not_found */, {
|
|
3778
3835
|
actionId: primed.id
|
|
3779
3836
|
});
|
|
@@ -4200,16 +4257,22 @@ export {
|
|
|
4200
4257
|
createActionRuntime,
|
|
4201
4258
|
createActionRootDomain,
|
|
4202
4259
|
action,
|
|
4203
|
-
|
|
4260
|
+
TransportWebSocket,
|
|
4261
|
+
TransportHttp,
|
|
4262
|
+
Transport,
|
|
4204
4263
|
NiceActionSchema,
|
|
4264
|
+
NiceActionRootDomain,
|
|
4205
4265
|
NiceActionResponse,
|
|
4206
4266
|
NiceActionPrimed,
|
|
4207
4267
|
NiceActionDomain,
|
|
4208
4268
|
NiceAction,
|
|
4269
|
+
ETransportType,
|
|
4270
|
+
ETransportStatus,
|
|
4209
4271
|
EErrId_NiceTransport_WebSocket,
|
|
4210
4272
|
EErrId_NiceTransport,
|
|
4211
4273
|
EErrId_NiceAction,
|
|
4212
4274
|
EActionState,
|
|
4275
|
+
ConnectionConfig,
|
|
4213
4276
|
ActionRuntimeEnvironment,
|
|
4214
4277
|
ActionHandler,
|
|
4215
4278
|
ActionConnect
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { NiceActionSchema } from "../ActionSchema/NiceActionSchema";
|
|
2
2
|
import type { INiceActionErrorDeclaration, TTransportedValue } from "../ActionSchema/NiceActionSchema.types";
|
|
3
|
-
export type MaybePromise<T> = T | Promise<T>;
|
|
4
3
|
export type TPossibleDomainId = string;
|
|
5
4
|
export type TPossibleDomainIdList = [TPossibleDomainId, ...TPossibleDomainId[]];
|
|
6
5
|
export type TNiceActionDomainSchema = Record<string, NiceActionSchema<TTransportedValue<any, any>, TTransportedValue<any, any>, readonly INiceActionErrorDeclaration<any, any>[]>>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MaybePromise } from "../../..";
|
|
1
2
|
import type { NiceActionPrimed } from "../../../NiceAction/NiceActionPrimed";
|
|
2
3
|
import type { NiceActionResponse } from "../../../NiceAction/NiceActionResponse";
|
|
3
4
|
import { type ITransportPendingRequest, type TActionTransportDef, type TTransportStatusInfo } from "./Transport.types";
|
|
@@ -6,8 +7,10 @@ export declare abstract class Transport<DEF extends TActionTransportDef> {
|
|
|
6
7
|
readonly type: DEF["type"];
|
|
7
8
|
readonly requestResolvers: Map<string, ITransportPendingRequest>;
|
|
8
9
|
protected abstract _status: TTransportStatusInfo;
|
|
10
|
+
protected _filterUsage?: (primed: NiceActionPrimed<any>) => MaybePromise<boolean>;
|
|
9
11
|
constructor(def: DEF);
|
|
10
12
|
get status(): TTransportStatusInfo;
|
|
13
|
+
filterUsage(primed: NiceActionPrimed<any>): MaybePromise<boolean>;
|
|
11
14
|
checkAndPrepare(): TTransportStatusInfo;
|
|
12
15
|
protected abstract send(primed: NiceActionPrimed<any>): Promise<void>;
|
|
13
16
|
abstract disconnect(): void;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { NiceError } from "@nice-code/error";
|
|
1
|
+
import type { MaybePromise, NiceError } from "@nice-code/error";
|
|
2
|
+
import type { INiceActionPrimed_JsonObject, TNiceActionResponse_JsonObject } from "../../../NiceAction/NiceAction.types";
|
|
2
3
|
import type { NiceActionPrimed } from "../../../NiceAction/NiceActionPrimed";
|
|
3
4
|
import type { NiceActionResponse } from "../../../NiceAction/NiceActionResponse";
|
|
4
5
|
import type { Transport } from "./Transport";
|
|
5
6
|
export declare enum ETransportType {
|
|
6
7
|
ws = "ws",
|
|
7
|
-
http = "http"
|
|
8
|
+
http = "http",
|
|
9
|
+
custom = "custom"
|
|
8
10
|
}
|
|
9
11
|
export declare enum ETransportStatus {
|
|
10
12
|
uninitialized = "uninitialized",
|
|
@@ -31,16 +33,34 @@ export type TTransportStatusInfo = ITransportStatusInfo_Base<ETransportStatus.un
|
|
|
31
33
|
export interface IActionTransport_Base {
|
|
32
34
|
/** Per-transport timeout override (ms) */
|
|
33
35
|
timeout?: number;
|
|
36
|
+
filterUsage?: (primed: NiceActionPrimed<any>) => MaybePromise<boolean>;
|
|
37
|
+
}
|
|
38
|
+
export interface ICustomWebsocketMessageSerde {
|
|
39
|
+
serialize?: (primedJson: INiceActionPrimed_JsonObject<any>) => string;
|
|
40
|
+
deserialize?: (message: string) => TNiceActionResponse_JsonObject<any>;
|
|
34
41
|
}
|
|
35
42
|
export interface IActionTransportDef_Ws extends IActionTransport_Base {
|
|
36
43
|
type: ETransportType.ws;
|
|
37
|
-
createWebSocket: () => Promise<
|
|
44
|
+
createWebSocket: () => Promise<{
|
|
45
|
+
ws: WebSocket;
|
|
46
|
+
customMessageSerde?: ICustomWebsocketMessageSerde;
|
|
47
|
+
}>;
|
|
38
48
|
}
|
|
39
49
|
export interface IActionTransportDef_Http extends IActionTransport_Base {
|
|
40
50
|
type: ETransportType.http;
|
|
41
51
|
url: string;
|
|
42
52
|
}
|
|
43
|
-
export
|
|
53
|
+
export interface ICustomActionTransport {
|
|
54
|
+
checkAndPrepare: () => TTransportStatusInfo;
|
|
55
|
+
handleAction: (primed: NiceActionPrimed<any>, onResponse: (response: NiceActionResponse<any>) => void) => Promise<void>;
|
|
56
|
+
onDisconnect: () => void;
|
|
57
|
+
}
|
|
58
|
+
export interface IActionTransportDef_Custom extends IActionTransport_Base {
|
|
59
|
+
type: ETransportType.custom;
|
|
60
|
+
initialStatus: TTransportStatusInfo;
|
|
61
|
+
createTransport: () => ICustomActionTransport;
|
|
62
|
+
}
|
|
63
|
+
export type TActionTransportDef = IActionTransportDef_Ws | IActionTransportDef_Http | IActionTransportDef_Custom;
|
|
44
64
|
export interface ITransportPendingRequest {
|
|
45
65
|
type: ETransportType;
|
|
46
66
|
resolve: (response: NiceActionResponse<any>) => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NiceActionPrimed } from "../../../NiceAction/NiceActionPrimed";
|
|
2
|
+
import { Transport } from "./Transport";
|
|
3
|
+
import { type IActionTransportDef_Custom, type TTransportStatusInfo } from "./Transport.types";
|
|
4
|
+
export declare class TransportCustom extends Transport<IActionTransportDef_Custom> {
|
|
5
|
+
readonly abortControllers: Map<string, AbortController>;
|
|
6
|
+
protected _status: TTransportStatusInfo;
|
|
7
|
+
private _customTransport;
|
|
8
|
+
constructor(def: IActionTransportDef_Custom);
|
|
9
|
+
checkAndPrepare(): TTransportStatusInfo;
|
|
10
|
+
send(primed: NiceActionPrimed<any>): Promise<void>;
|
|
11
|
+
disconnect(): void;
|
|
12
|
+
}
|
package/build/types/ActionRuntimeEnvironment/ActionConnect/Transport/TransportWebSocket.d.ts
CHANGED
|
@@ -4,10 +4,12 @@ import { type IActionTransportDef_Ws, type TTransportStatusInfo } from "./Transp
|
|
|
4
4
|
export declare class TransportWebSocket extends Transport<IActionTransportDef_Ws> {
|
|
5
5
|
websocket?: WebSocket;
|
|
6
6
|
protected _status: TTransportStatusInfo;
|
|
7
|
+
private _customMessageSerde?;
|
|
7
8
|
constructor(def: IActionTransportDef_Ws);
|
|
8
9
|
checkAndPrepare(): TTransportStatusInfo;
|
|
9
10
|
private startInitializing;
|
|
10
|
-
private
|
|
11
|
+
private handlePureActionResponseMessage;
|
|
12
|
+
private handleResponse;
|
|
11
13
|
private rejectPendingWebSocketRequests;
|
|
12
14
|
private _connect;
|
|
13
15
|
protected send(primed: NiceActionPrimed<any>): Promise<void>;
|
package/build/types/ActionRuntimeEnvironment/ActionConnect/Transport/err_nice_transport.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare enum EErrId_NiceTransport {
|
|
2
2
|
timeout = "timeout",
|
|
3
3
|
not_found = "not_found",
|
|
4
|
+
not_available = "not_available",
|
|
4
5
|
initialization_failed = "initialization_failed",
|
|
5
6
|
send_failed = "send_failed",
|
|
6
7
|
invalid_action_response = "invalid_action_response"
|
|
@@ -17,6 +18,9 @@ export declare const err_nice_transport: import("@nice-code/error").NiceErrorDom
|
|
|
17
18
|
routeKey?: string;
|
|
18
19
|
tag?: string;
|
|
19
20
|
}, import("@nice-code/error").JSONSerializableValue>;
|
|
21
|
+
not_available: import("@nice-code/error").INiceErrorIdMetadata<{
|
|
22
|
+
transportCount: number;
|
|
23
|
+
}, import("@nice-code/error").JSONSerializableValue>;
|
|
20
24
|
initialization_failed: import("@nice-code/error").INiceErrorIdMetadata<{
|
|
21
25
|
actionId: string;
|
|
22
26
|
routeKey?: string;
|
|
@@ -15,7 +15,7 @@ export declare class ActionHandler implements IActionHandler {
|
|
|
15
15
|
private getHandlersForAction;
|
|
16
16
|
/**
|
|
17
17
|
* Register a handler for all actions in a domain.
|
|
18
|
-
* Receives the full primed action — use `
|
|
18
|
+
* Receives the full primed action — use `matchAction()` to narrow to a specific action id.
|
|
19
19
|
* Useful for forwarding all domain actions to a remote endpoint.
|
|
20
20
|
* Lower priority than `forAction`.
|
|
21
21
|
*/
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { INiceActionDomain,
|
|
1
|
+
import type { INiceActionDomain, TInferOutputFromSchema } from "../../ActionDomain/NiceActionDomain.types";
|
|
2
2
|
import type { INiceAction, TNiceActionResponse_JsonObject } from "../../NiceAction/NiceAction.types";
|
|
3
3
|
import type { TDistributedDomainActions } from "../../NiceAction/NiceActionCombined.types";
|
|
4
4
|
import type { NiceActionPrimed } from "../../NiceAction/NiceActionPrimed";
|
|
5
5
|
import type { NiceActionResponse } from "../../NiceAction/NiceActionResponse";
|
|
6
|
+
import type { MaybePromise } from "../../utils/maybePromise";
|
|
6
7
|
import type { IRuntimeEnvironmentMeta } from "../ActionRuntimeEnvironment.types";
|
|
7
8
|
export type TAtLeastOne<T extends object> = {
|
|
8
9
|
[K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { INiceActionDomain
|
|
1
|
+
import type { INiceActionDomain } from "../../ActionDomain/NiceActionDomain.types";
|
|
2
|
+
import type { MaybePromise } from "../../utils/maybePromise";
|
|
2
3
|
import type { INiceAction } from "../NiceAction.types";
|
|
3
4
|
import type { TNarrowActionType } from "../NiceActionCombined.types";
|
|
4
5
|
type TMatchHandler<A extends INiceAction<any>> = (action: A) => MaybePromise<void>;
|
|
@@ -39,7 +39,7 @@ export declare class NiceAction<DOM extends INiceActionDomain, ID extends keyof
|
|
|
39
39
|
* ```ts
|
|
40
40
|
* const result = await domain.action("getUser").executeSafe({ userId: "123" });
|
|
41
41
|
* if (!result.ok) {
|
|
42
|
-
* result.error.
|
|
42
|
+
* result.error.handleWithSync([
|
|
43
43
|
* forDomain(err_auth, (h) => res.status(401).end()),
|
|
44
44
|
* ]);
|
|
45
45
|
* return;
|
|
@@ -39,7 +39,7 @@ export type INiceActionPrimed_JsonObject<DOM extends INiceActionDomain = INiceAc
|
|
|
39
39
|
* ```ts
|
|
40
40
|
* const result = await domain.action("getUser").executeSafe({ userId: "123" });
|
|
41
41
|
* if (!result.ok) {
|
|
42
|
-
* result.error.
|
|
42
|
+
* result.error.handleWithSync([
|
|
43
43
|
* forDomain(err_auth, (h) => res.status(401).end()),
|
|
44
44
|
* ]);
|
|
45
45
|
* return;
|
package/build/types/index.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
export { createActionRootDomain } from "./ActionDomain/helpers/createRootActionDomain";
|
|
2
2
|
export { NiceActionDomain } from "./ActionDomain/NiceActionDomain";
|
|
3
|
-
export type { INiceActionDomain, INiceActionDomainChildOptions,
|
|
3
|
+
export type { INiceActionDomain, INiceActionDomainChildOptions, INiceActionRootDomain, TDomainActionId, TInferInputFromSchema, TInferOutputFromSchema, TNiceActionDomainChildDef, TNiceActionDomainSchema, TPossibleDomainId, TPossibleDomainIdList, } from "./ActionDomain/NiceActionDomain.types";
|
|
4
|
+
export { NiceActionRootDomain } from "./ActionDomain/RootDomain/NiceActionRootDomain";
|
|
4
5
|
export { ActionConnect } from "./ActionRuntimeEnvironment/ActionConnect/ActionConnect";
|
|
5
6
|
export * from "./ActionRuntimeEnvironment/ActionConnect/ActionConnect.types";
|
|
6
|
-
export { ConnectionConfig
|
|
7
|
+
export { ConnectionConfig } from "./ActionRuntimeEnvironment/ActionConnect/ConnectionConfig/ConnectionConfig";
|
|
8
|
+
export * from "./ActionRuntimeEnvironment/ActionConnect/ConnectionConfig/ConnectionConfig.types";
|
|
7
9
|
export * from "./ActionRuntimeEnvironment/ActionConnect/err_nice_connect";
|
|
8
10
|
export * from "./ActionRuntimeEnvironment/ActionConnect/Transport/err_nice_transport";
|
|
9
11
|
export * from "./ActionRuntimeEnvironment/ActionConnect/Transport/err_nice_transport_ws";
|
|
12
|
+
export * from "./ActionRuntimeEnvironment/ActionConnect/Transport/Transport";
|
|
13
|
+
export * from "./ActionRuntimeEnvironment/ActionConnect/Transport/Transport.types";
|
|
14
|
+
export * from "./ActionRuntimeEnvironment/ActionConnect/Transport/TransportHttp";
|
|
15
|
+
export * from "./ActionRuntimeEnvironment/ActionConnect/Transport/TransportWebSocket";
|
|
10
16
|
export { ActionHandler, createHandler, } from "./ActionRuntimeEnvironment/ActionHandler/ActionHandler";
|
|
11
17
|
export type { IActionHandlerInputs, TAtLeastOne, TExecutionAndResponseHandlers, THandleActionExecutionFn, THandleActionResponseFn, THandleActionResult, } from "./ActionRuntimeEnvironment/ActionHandler/ActionHandler.types";
|
|
12
18
|
export { ActionRuntimeEnvironment, createActionRuntime, } from "./ActionRuntimeEnvironment/ActionRuntimeEnvironment";
|
|
@@ -24,3 +30,4 @@ export { NiceActionPrimed } from "./NiceAction/NiceActionPrimed";
|
|
|
24
30
|
export { NiceActionResponse } from "./NiceAction/NiceActionResponse";
|
|
25
31
|
export * from "./utils/isActionResponseJsonObject";
|
|
26
32
|
export * from "./utils/isPrimedActionJsonObject";
|
|
33
|
+
export * from "./utils/maybePromise";
|
|
@@ -37,7 +37,7 @@ export type TUseNiceMutationOptions<DOM extends INiceActionDomain, ID extends ke
|
|
|
37
37
|
* Passing `null` or `undefined` as `input` disables the query (sets `enabled: false`),
|
|
38
38
|
* which allows conditional execution while respecting React's rules of hooks.
|
|
39
39
|
*
|
|
40
|
-
* The `
|
|
40
|
+
* The `tag` option targets a specific named handler registered on the runtime environment.
|
|
41
41
|
*
|
|
42
42
|
* Supports TanStack Query's `select` option with full type inference — if you pass a
|
|
43
43
|
* `select` transformer, `data` will be typed as the transformer's return type.
|
|
@@ -59,7 +59,7 @@ export declare function useNiceQuery<DOM extends INiceActionDomain, ID extends k
|
|
|
59
59
|
* Ideal for actions that change server state — form submissions, updates, deletes, etc.
|
|
60
60
|
* The input is provided at call time via `mutation.mutate(input)` or `mutation.mutateAsync(input)`.
|
|
61
61
|
*
|
|
62
|
-
* The `
|
|
62
|
+
* The `tag` option targets a specific named handler registered on the runtime environment.
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
65
65
|
* const mutation = useNiceMutation(domain.action("createUser"));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type MaybePromise<T> = T | Promise<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nice-code/action",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"build-types": "tsc --project tsconfig.build.json"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@nice-code/error": "0.1.
|
|
36
|
-
"@nice-code/common-errors": "0.1.
|
|
35
|
+
"@nice-code/error": "0.1.2",
|
|
36
|
+
"@nice-code/common-errors": "0.1.2",
|
|
37
37
|
"@standard-schema/spec": "^1.1.0",
|
|
38
38
|
"http-status-codes": "^2.3.0",
|
|
39
39
|
"nanoid": "^5.1.9",
|