sendscript 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,21 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v2.5.0](https://github.com/bas080/sendscript/compare/v2.4.3...v2.5.0)
8
+
9
+ - Clarify native await behavior in README [`a26f1b3`](https://github.com/bas080/sendscript/commit/a26f1b3c204390690eda7950c967d31ee55aa353)
10
+ - Allow passing onAwait callback to references make function [`8fdb1c4`](https://github.com/bas080/sendscript/commit/8fdb1c4bfa0682fabdd9d27ca075cd1e0cfaedf7)
11
+
12
+ #### [v2.4.3](https://github.com/bas080/sendscript/compare/v2.4.2...v2.4.3)
13
+
14
+ > 17 August 2026
15
+
16
+ - Improve TypeScript section with comprehensive examples and explanations [`a3c5867`](https://github.com/bas080/sendscript/commit/a3c5867f835bbcde1e802e75c80ca6f02dd4f0e8)
17
+
7
18
  #### [v2.4.2](https://github.com/bas080/sendscript/compare/v2.4.1...v2.4.2)
8
19
 
20
+ > 17 August 2026
21
+
9
22
  - Update tap and typedoc [`46421de`](https://github.com/bas080/sendscript/commit/46421de16533066ee13c91ea75505b1ecdd548f0)
10
23
  - Remove the unused curry helper [`9dcbb48`](https://github.com/bas080/sendscript/commit/9dcbb485b455937070c65359ecde8236c128bee8)
11
24
  - Remove duplicate Promises section with outdated async/await information [`5f43ddb`](https://github.com/bas080/sendscript/commit/5f43ddb91ddb2d77227ddd6a570807f874230ea1)
package/README.md CHANGED
@@ -36,6 +36,10 @@ Serialize and execute composable JavaScript function calls with JSON.
36
36
  * [.then / .catch](#then--catch)
37
37
  * [await](#await)
38
38
  - [TypeScript](#typescript)
39
+ * [Server-Side Module](#server-side-module)
40
+ * [Client-Side Type Stub](#client-side-type-stub)
41
+ * [Using Typed References](#using-typed-references)
42
+ * [Generating API Documentation](#generating-api-documentation)
39
43
  - [Schema and Nested Modules](#schema-and-nested-modules)
40
44
  * [Defining a Nested Module](#defining-a-nested-module)
41
45
  - [Validation (using Zod)](#validation-using-zod)
@@ -302,61 +306,121 @@ behavior for the sendscript DSL and parser.
302
306
 
303
307
  ### await
304
308
 
305
- SendScript supports async/await seamlessly within a single request. This avoids
306
- the performance pitfalls of waterfall-style messaging, which can be especially
307
- slow on high-latency networks.
309
+ By default, `await api.someMethod(...)` still creates a SendScript await stub
310
+ and keeps the result serializable with `stringify()`. It does not automatically
311
+ send anything over the network.
308
312
 
309
- While it's possible to chain promises manually or use utility functions, native
310
- async/await support makes your code more readable, modern, and easier to reason
311
- about aligning SendScript with today’s JavaScript best practices.
313
+ ```js
314
+ const api = references(['add'])
315
+ const program = api.add(1, 2)
316
+
317
+ Stringify()(program)
318
+ // => "[\"call\",[\"ref\",\"add\"],[1,2]]"
319
+ ```
320
+
321
+ If you want native `await` to cross the transport boundary, pass an `onAwait`
322
+ handler when creating the references:
312
323
 
313
324
  ```js
314
- const userId = 'user-123'
315
- const program = {
316
- unread: await fetchUnreadMessages(userId),
317
- emptyTrash: await emptyTrash(userId),
318
- archived: await archiveMessages(selectMessages({ old: true })),
319
- }
325
+ import Stringify from 'sendscript/stringify.mjs'
326
+ import references from 'sendscript/references.mjs'
320
327
 
321
- const result = await send(program)
328
+ const stringify = Stringify()
329
+
330
+ const api = references(['add'], (program) => {
331
+ return fetch('/api', {
332
+ method: 'POST',
333
+ headers: { 'content-type': 'application/json' },
334
+ body: stringify(program),
335
+ }).then((response) => response.json())
336
+ })
337
+
338
+ const result = await api.add(1, 2)
322
339
  ```
323
340
 
324
- This operation is done in a single round-trip. The result is an object with the
325
- defined properties and returned values.
341
+ The callback receives the generated SendScript program, and you decide how it is
342
+ transported or executed.
326
343
 
327
344
  ## TypeScript
328
345
 
329
- There is a good use-case to write a module in TypeScript.
346
+ Using SendScript with TypeScript enables **type-safe client-side code**. Your
347
+ client can have full IDE autocomplete and compile-time type checking when
348
+ calling server functions.
330
349
 
331
- 1. Obviously the module would have the benefits that TypeScript offers when
332
- coding.
333
- 2. You can use tools like [typedoc][typedoc] to generate docs from your types to
334
- share with consumers of your API.
335
- 3. You can use the types of the module to coerce your client to adopt the
336
- module's type.
350
+ ### Server-Side Module
337
351
 
338
- Let's say we have this module which we use on the server.
352
+ Define your API as a TypeScript module on the server:
339
353
 
340
354
  ```bash
341
355
  cat ./example/typescript/math.ts
342
356
  ```
343
357
  ```ts
344
- export const add = (a: number, b: number) => a + b
345
- export const square = (a: number) => a * a
358
+ /**
359
+ * Server-side math module with typed functions
360
+ * These functions will be called from the client through SendScript
361
+ */
362
+
363
+ export const add = (a: number, b: number): number => a + b
364
+
365
+ export const square = (a: number): number => a * a
366
+
346
367
  ```
347
368
 
348
- We can then coerce the types of the instrumented stubs.
369
+ ### Client-Side Type Stub
370
+
371
+ Create a client-side file that mirrors your server types using the `as typeof`
372
+ casting pattern:
373
+
374
+ ```bash
375
+ cat ./example/typescript/math.client.ts
376
+ ```
377
+ ```ts
378
+ /**
379
+ * Client-side type-safe stubs for the math API
380
+ *
381
+ * This file creates typed references that mirror the server's functions.
382
+ * The 'as typeof mathTypes' cast gives us full TypeScript support and IDE autocomplete.
383
+ */
384
+
385
+ import type * as mathTypes from './math.ts'
386
+ import references from 'sendscript/references.mjs'
387
+
388
+ // Create type-safe stubs - this tells TypeScript that 'add' and 'square'
389
+ // have the same signatures as the server functions
390
+ export default references(['add', 'square']) as typeof mathTypes
391
+
392
+ ```
393
+
394
+ The `as typeof mathTypes` type assertion gives your client-side references the
395
+ exact same types as your server module. This means:
396
+
397
+ - Full IDE autocomplete for function names and parameters
398
+ - Compile-time type checking - catch errors before runtime
399
+ - Your client code looks identical to regular JavaScript calls
400
+
401
+ ### Using Typed References
402
+
403
+ Now on your client, you have complete type safety:
349
404
 
350
405
  ```bash
351
406
  cat ./example/typescript/client.ts
352
407
  ```
353
408
  ```ts
354
- import math from './math.client.ts'
409
+ /**
410
+ * Client-side usage with type-safe SendScript
411
+ */
412
+
413
+ import math from './math.client.ts'
355
414
  import Stringify from 'sendscript/stringify.mjs'
356
415
 
357
416
  const stringify = Stringify()
358
417
 
359
- // The return type of this function matches the type passed as the return of the program.
418
+ /**
419
+ * Send a SendScript program to the server
420
+ *
421
+ * TypeScript knows that the return type matches the program's return type.
422
+ * In this case, square(add(1, 2)) returns a number, so T is number.
423
+ */
360
424
  async function send<T>(program: T): Promise<T> {
361
425
  return (await fetch('/api', {
362
426
  method: 'POST',
@@ -364,20 +428,41 @@ async function send<T>(program: T): Promise<T> {
364
428
  })).json()
365
429
  }
366
430
 
367
- send(square(add(1, 2)))
431
+ // TypeScript provides full autocomplete for math.add and math.square
432
+ // It knows they take numbers and return numbers
433
+ const result = await send(math.square(math.add(1, 2)))
434
+ console.log(result) // 9
435
+
368
436
  ```
369
437
 
370
- We'll also generate the docs for this module.
438
+ TypeScript knows the exact parameter types and return types for every function
439
+ call.
440
+
441
+ ### Generating API Documentation
442
+
443
+ You can use [typedoc][typedoc] to automatically generate documentation from your
444
+ TypeScript types:
371
445
 
372
446
  ```bash
373
- npx typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./example/typescript/math.ts
447
+ npx typedoc --plugin typedoc-plugin-markdown --out ./docs ./example/typescript/math.ts
448
+ ```
449
+ ```
450
+ [info] Loaded plugin typedoc-plugin-markdown
451
+ [info] markdown generated at ./docs
374
452
  ```
375
453
 
376
- You can see the docs [here](./example/typescript/docs/globals.md)
454
+ This generates markdown docs that can be shared with API consumers. See the
455
+ [generated docs](./example/typescript/docs/globals.md).
377
456
 
378
- > [!NOTE] Although type coercion on the client side can improve the development
379
- > experience, it does not represent the actual type. Values are subject to
380
- > serialization and deserialization.
457
+ > [!IMPORTANT] **Type vs. Runtime Values**
458
+ >
459
+ > Type casting on the client side improves the development experience, but
460
+ > remember:
461
+ >
462
+ > - The actual serialized JSON may differ from the static types
463
+ > - Runtime values depend on serialization/deserialization
464
+ > - Always validate user input on the server using schema validation (e.g., Zod)
465
+ > - Use the types as a contract, not a guarantee
381
466
 
382
467
  ## Schema and Nested Modules
383
468
 
@@ -529,36 +614,21 @@ code to create the AST.
529
614
 
530
615
  ### Callbacks
531
616
 
532
- Although it is possible to mix client and server functions, it works very
533
- different to ordinary functions. Client functions can be used but should be seen
534
- as a templating tool to make sendscript programs; just like one would use
535
- JavaScript with react templates. `items.map(deleteItem)` would return an array
536
- of sendscript function calls which can be given to sendscript's parse.
537
-
538
- Client functions cannot be called by sendscript functions (as of yet) since we
539
- cannot serialize client functions. No work has been done to have the server send
540
- back intermediate values to perform client function calls or by performing
541
- smaller sendscript program payloads that are passed to the client. Very
542
- interesting stuff to look into. You can achieve this now but it looks less clean
543
- because you have to do `send` calls which is a bit manual.
617
+ SendScript supports basic callback-style usage, such as passing a function as an
618
+ argument or using a callback to flip arguments into the generated program.
544
619
 
545
620
  ```js
546
- await send(updateUser(id, merge(await send(getUser(id)), { ...newValues })))
547
- ```
548
-
549
- It might be interesting to allow configuration to create references that will
550
- trigger a send whenever await is called. That would remove the ability to create
551
- a single payload whenever using await. You can then write the above in the
552
- following manner.
621
+ const api = references(['map', 'add'])
553
622
 
554
- ```js
555
- await updateUser(id, merge(await getUser(id), { ...newValues }))
623
+ const result = api.map((value) => api.add(value, 1))([1, 2, 3])
556
624
  ```
557
625
 
558
- Possible footgun is that updateUser is only performed when awaited (.then is
559
- called). This lazy behavior can trip users. This footgun could be resolved by
560
- checking if any outstanding work exists at the end of a step or the beginning of
561
- a new step.
626
+ This is meant for composing SendScript programs, not for executing arbitrary
627
+ client-side logic at runtime.
628
+
629
+ > [!WARNING] Mixing client and server functions can be confusing, because the
630
+ > callback may be used to build a program rather than execute immediately. Keep
631
+ > callback logic simple and deterministic.
562
632
 
563
633
  ### Error handling
564
634
 
@@ -579,10 +649,10 @@ npm t -- report text-summary
579
649
  ```
580
650
 
581
651
  =============================== Coverage summary ===============================
582
- Statements : 100% ( 512/512 )
583
- Branches : 100% ( 147/147 )
652
+ Statements : 100% ( 516/516 )
653
+ Branches : 100% ( 157/157 )
584
654
  Functions : 100% ( 23/23 )
585
- Lines : 100% ( 512/512 )
655
+ Lines : 100% ( 516/516 )
586
656
  ================================================================================
587
657
  ```
588
658
 
package/README.mz CHANGED
@@ -261,62 +261,103 @@ behavior for the sendscript DSL and parser.
261
261
 
262
262
  ### await
263
263
 
264
- SendScript supports async/await seamlessly within a single request. This avoids
265
- the performance pitfalls of waterfall-style messaging, which can be especially
266
- slow on high-latency networks.
264
+ By default, `await api.someMethod(...)` still creates a SendScript await stub
265
+ and keeps the result serializable with `stringify()`. It does not automatically
266
+ send anything over the network.
267
267
 
268
- While it's possible to chain promises manually or use utility functions, native
269
- async/await support makes your code more readable, modern, and easier to reason
270
- about aligning SendScript with today’s JavaScript best practices.
268
+ ```js
269
+ const api = references(['add'])
270
+ const program = api.add(1, 2)
271
+
272
+ Stringify()(program)
273
+ // => "[\"call\",[\"ref\",\"add\"],[1,2]]"
274
+ ```
275
+
276
+ If you want native `await` to cross the transport boundary, pass an `onAwait`
277
+ handler when creating the references:
271
278
 
272
279
  ```js
273
- const userId = 'user-123'
274
- const program = {
275
- unread: await fetchUnreadMessages(userId),
276
- emptyTrash: await emptyTrash(userId),
277
- archived: await archiveMessages(selectMessages({ old: true })),
278
- }
280
+ import Stringify from 'sendscript/stringify.mjs'
281
+ import references from 'sendscript/references.mjs'
279
282
 
280
- const result = await send(program)
283
+ const stringify = Stringify()
284
+
285
+ const api = references(['add'], (program) => {
286
+ return fetch('/api', {
287
+ method: 'POST',
288
+ headers: { 'content-type': 'application/json' },
289
+ body: stringify(program),
290
+ }).then((response) => response.json())
291
+ })
292
+
293
+ const result = await api.add(1, 2)
281
294
  ```
282
295
 
283
- This operation is done in a single round-trip. The result is an object with the
284
- defined properties and returned values.
296
+ The callback receives the generated SendScript program, and you decide how it is
297
+ transported or executed.
285
298
 
286
299
  ## TypeScript
287
300
 
288
- There is a good use-case to write a module in TypeScript.
301
+ Using SendScript with TypeScript enables **type-safe client-side code**. Your
302
+ client can have full IDE autocomplete and compile-time type checking when
303
+ calling server functions.
289
304
 
290
- 1. Obviously the module would have the benefits that TypeScript offers when
291
- coding.
292
- 2. You can use tools like [typedoc][typedoc] to generate docs from your types to
293
- share with consumers of your API.
294
- 3. You can use the types of the module to coerce your client to adopt the
295
- module's type.
305
+ ### Server-Side Module
296
306
 
297
- Let's say we have this module which we use on the server.
307
+ Define your API as a TypeScript module on the server:
298
308
 
299
309
  ```bash|ts bash
300
310
  cat ./example/typescript/math.ts
301
311
  ```
302
312
 
303
- We can then coerce the types of the instrumented stubs.
313
+ ### Client-Side Type Stub
314
+
315
+ Create a client-side file that mirrors your server types using the `as typeof`
316
+ casting pattern:
317
+
318
+ ```bash|ts bash
319
+ cat ./example/typescript/math.client.ts
320
+ ```
321
+
322
+ The `as typeof mathTypes` type assertion gives your client-side references the
323
+ exact same types as your server module. This means:
324
+
325
+ - Full IDE autocomplete for function names and parameters
326
+ - Compile-time type checking - catch errors before runtime
327
+ - Your client code looks identical to regular JavaScript calls
328
+
329
+ ### Using Typed References
330
+
331
+ Now on your client, you have complete type safety:
304
332
 
305
333
  ```bash|ts bash
306
334
  cat ./example/typescript/client.ts
307
335
  ```
308
336
 
309
- We'll also generate the docs for this module.
337
+ TypeScript knows the exact parameter types and return types for every function
338
+ call.
339
+
340
+ ### Generating API Documentation
310
341
 
311
- ```bash bash 1>&2
312
- npx typedoc --plugin typedoc-plugin-markdown --out ./example/typescript/docs ./example/typescript/math.ts
342
+ You can use [typedoc][typedoc] to automatically generate documentation from your
343
+ TypeScript types:
344
+
345
+ ```bash bash
346
+ npx typedoc --plugin typedoc-plugin-markdown --out ./docs ./example/typescript/math.ts
313
347
  ```
314
348
 
315
- You can see the docs [here](./example/typescript/docs/globals.md)
349
+ This generates markdown docs that can be shared with API consumers. See the
350
+ [generated docs](./example/typescript/docs/globals.md).
316
351
 
317
- > [!NOTE] Although type coercion on the client side can improve the development
318
- > experience, it does not represent the actual type. Values are subject to
319
- > serialization and deserialization.
352
+ > [!IMPORTANT] **Type vs. Runtime Values**
353
+ >
354
+ > Type casting on the client side improves the development experience, but
355
+ > remember:
356
+ >
357
+ > - The actual serialized JSON may differ from the static types
358
+ > - Runtime values depend on serialization/deserialization
359
+ > - Always validate user input on the server using schema validation (e.g., Zod)
360
+ > - Use the types as a contract, not a guarantee
320
361
 
321
362
  ## Schema and Nested Modules
322
363
 
@@ -468,36 +509,21 @@ code to create the AST.
468
509
 
469
510
  ### Callbacks
470
511
 
471
- Although it is possible to mix client and server functions, it works very
472
- different to ordinary functions. Client functions can be used but should be seen
473
- as a templating tool to make sendscript programs; just like one would use
474
- JavaScript with react templates. `items.map(deleteItem)` would return an array
475
- of sendscript function calls which can be given to sendscript's parse.
476
-
477
- Client functions cannot be called by sendscript functions (as of yet) since we
478
- cannot serialize client functions. No work has been done to have the server send
479
- back intermediate values to perform client function calls or by performing
480
- smaller sendscript program payloads that are passed to the client. Very
481
- interesting stuff to look into. You can achieve this now but it looks less clean
482
- because you have to do `send` calls which is a bit manual.
512
+ SendScript supports basic callback-style usage, such as passing a function as an
513
+ argument or using a callback to flip arguments into the generated program.
483
514
 
484
515
  ```js
485
- await send(updateUser(id, merge(await send(getUser(id)), { ...newValues })))
486
- ```
516
+ const api = references(['map', 'add'])
487
517
 
488
- It might be interesting to allow configuration to create references that will
489
- trigger a send whenever await is called. That would remove the ability to create
490
- a single payload whenever using await. You can then write the above in the
491
- following manner.
492
-
493
- ```js
494
- await updateUser(id, merge(await getUser(id), { ...newValues }))
518
+ const result = api.map((value) => api.add(value, 1))([1, 2, 3])
495
519
  ```
496
520
 
497
- Possible footgun is that updateUser is only performed when awaited (.then is
498
- called). This lazy behavior can trip users. This footgun could be resolved by
499
- checking if any outstanding work exists at the end of a step or the beginning of
500
- a new step.
521
+ This is meant for composing SendScript programs, not for executing arbitrary
522
+ client-side logic at runtime.
523
+
524
+ > [!WARNING] Mixing client and server functions can be confusing, because the
525
+ > callback may be used to build a program rather than execute immediately. Keep
526
+ > callback logic simple and deterministic.
501
527
 
502
528
  ### Error handling
503
529
 
@@ -1,9 +1,18 @@
1
- import math from './math.client.ts'
1
+ /**
2
+ * Client-side usage with type-safe SendScript
3
+ */
4
+
5
+ import math from './math.client.ts'
2
6
  import Stringify from 'sendscript/stringify.mjs'
3
7
 
4
8
  const stringify = Stringify()
5
9
 
6
- // The return type of this function matches the type passed as the return of the program.
10
+ /**
11
+ * Send a SendScript program to the server
12
+ *
13
+ * TypeScript knows that the return type matches the program's return type.
14
+ * In this case, square(add(1, 2)) returns a number, so T is number.
15
+ */
7
16
  async function send<T>(program: T): Promise<T> {
8
17
  return (await fetch('/api', {
9
18
  method: 'POST',
@@ -11,4 +20,8 @@ async function send<T>(program: T): Promise<T> {
11
20
  })).json()
12
21
  }
13
22
 
14
- send(square(add(1, 2)))
23
+ // TypeScript provides full autocomplete for math.add and math.square
24
+ // It knows they take numbers and return numbers
25
+ const result = await send(math.square(math.add(1, 2)))
26
+ console.log(result) // 9
27
+
@@ -1,5 +1,14 @@
1
+ /**
2
+ * Client-side type-safe stubs for the math API
3
+ *
4
+ * This file creates typed references that mirror the server's functions.
5
+ * The 'as typeof mathTypes' cast gives us full TypeScript support and IDE autocomplete.
6
+ */
7
+
1
8
  import type * as mathTypes from './math.ts'
2
- import Stringify from 'sendscript/stringify.mjs'
3
9
  import references from 'sendscript/references.mjs'
4
10
 
11
+ // Create type-safe stubs - this tells TypeScript that 'add' and 'square'
12
+ // have the same signatures as the server functions
5
13
  export default references(['add', 'square']) as typeof mathTypes
14
+
@@ -1,2 +1,9 @@
1
- export const add = (a: number, b: number) => a + b
2
- export const square = (a: number) => a * a
1
+ /**
2
+ * Server-side math module with typed functions
3
+ * These functions will be called from the client through SendScript
4
+ */
5
+
6
+ export const add = (a: number, b: number): number => a + b
7
+
8
+ export const square = (a: number): number => a * a
9
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sendscript",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "Blur the line between server and client code.",
5
5
  "module": true,
6
6
  "main": "index.mjs",
package/references.mjs CHANGED
@@ -7,7 +7,7 @@ import { awaitSymbol, call, ref, then, referenceSymbol } from './symbol.mjs'
7
7
  * @param {Array<string>} path - Path representing the function location in schema.
8
8
  * @returns {Function} Reference function with attached control methods (.then, .catch, toJSON).
9
9
  */
10
- function instrument (path) {
10
+ function instrument (path, onAwait = null) {
11
11
  /**
12
12
  * Creates a callable reference invocation.
13
13
  *
@@ -15,7 +15,7 @@ function instrument (path) {
15
15
  * @returns {Function} New instrumented reference node.
16
16
  */
17
17
  function reference (...args) {
18
- const called = instrument(path)
18
+ const called = instrument(path, onAwait)
19
19
 
20
20
  called.toJSON = () => ({
21
21
  [call]: call,
@@ -71,7 +71,7 @@ function instrument (path) {
71
71
  return dotThen(resolve, reject)
72
72
  }
73
73
 
74
- const awaited = instrument(path)
74
+ const awaited = instrument(path, onAwait)
75
75
  delete awaited.then
76
76
 
77
77
  awaited.toJSON = () => ({
@@ -79,6 +79,10 @@ function instrument (path) {
79
79
  ref: reference
80
80
  })
81
81
 
82
+ if (typeof onAwait === 'function') {
83
+ return resolve(onAwait(awaited))
84
+ }
85
+
82
86
  return resolve(awaited)
83
87
  }
84
88
 
@@ -106,15 +110,15 @@ function instrument (path) {
106
110
  * @throws {Error} If schema format is invalid
107
111
  * @public
108
112
  */
109
- export default function References (schema, parentPath = []) {
113
+ export default function References (schema, onAwait = null, parentPath = []) {
110
114
  return schema.reduce((acc, item) => {
111
115
  if (typeof item === 'string') {
112
- acc[item] = instrument([...parentPath, item])
116
+ acc[item] = instrument([...parentPath, item], onAwait)
113
117
  } else if (Array.isArray(item)) {
114
118
  const [name, children] = item
115
119
 
116
120
  if (Array.isArray(children)) {
117
- acc[name] = References(children, [...parentPath, name])
121
+ acc[name] = References(children, onAwait, [...parentPath, name])
118
122
  } else {
119
123
  throw new Error(`Expected children array for namespace "${name}"`)
120
124
  }
@@ -1,8 +1,62 @@
1
1
  import { test } from 'tap'
2
2
  import references from './references.mjs'
3
+ import Stringify from './stringify.mjs'
3
4
 
4
5
  test('invalid uses of references', t => {
5
6
  t.throws(() => references([['a']]))
6
7
  t.throws(() => references([{}]))
7
8
  t.end()
8
9
  })
10
+
11
+ test('native await hook is optional and backwards compatible', async t => {
12
+ const schema = ['add', 'square', 'identity', ['nested', ['value']]]
13
+ const stringify = Stringify()
14
+
15
+ const defaultApi = references(schema)
16
+ const defaultProgram = defaultApi.add(1, 2)
17
+
18
+ t.doesNotThrow(() => stringify(defaultProgram))
19
+ t.same(JSON.parse(stringify(defaultProgram))[0], 'call')
20
+
21
+ const calls = []
22
+ const api = references(schema, (program) => {
23
+ calls.push(program)
24
+ return 42
25
+ })
26
+
27
+ t.equal(await api.add(1, 2), 42)
28
+ t.equal(calls.length, 1)
29
+ t.equal(typeof calls[0], 'function')
30
+
31
+ const asyncApi = references(schema, async () => {
32
+ return await Promise.resolve(17)
33
+ })
34
+
35
+ t.equal(await asyncApi.identity(9), 17)
36
+
37
+ const rejectedApi = references(schema, async () => {
38
+ throw new Error('boom')
39
+ })
40
+
41
+ await t.rejects((async () => {
42
+ await rejectedApi.identity(9)
43
+ })(), { message: 'boom' })
44
+
45
+ const thenApi = references(schema)
46
+ const thenProgram = thenApi.add(1, 2).then(thenApi.square)
47
+ t.same(JSON.parse(stringify(thenProgram))[0], 'then')
48
+
49
+ const nestedApi = references([['outer', ['inner']]], (program) => program)
50
+ t.equal(typeof nestedApi.outer.inner, 'function')
51
+ t.same(JSON.parse(stringify(nestedApi.outer.inner(1)))[0], 'call')
52
+
53
+ const onAwaitApi = references(['add'], (program) => {
54
+ const serialized = stringify(program)
55
+ t.same(JSON.parse(serialized), ['await', ['call', ['ref', 'add'], [['leaf', '1'], ['leaf', '2']]]])
56
+ return 99
57
+ })
58
+
59
+ t.equal(await onAwaitApi.add(1, 2), 99)
60
+
61
+ t.end()
62
+ })