@procedures/client 0.14.0 → 0.15.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.
Files changed (136) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +12 -3
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/client/transport.d.ts.map +1 -1
  5. package/dist/adapters/http/client/transport.js +4 -0
  6. package/dist/adapters/http/client/transport.js.map +1 -1
  7. package/dist/adapters/http/server/body.d.ts +17 -0
  8. package/dist/adapters/http/server/body.d.ts.map +1 -0
  9. package/dist/adapters/http/server/body.js +44 -0
  10. package/dist/adapters/http/server/body.js.map +1 -0
  11. package/dist/adapters/http/server/cors.d.ts +24 -1
  12. package/dist/adapters/http/server/cors.d.ts.map +1 -1
  13. package/dist/adapters/http/server/cors.js +33 -1
  14. package/dist/adapters/http/server/cors.js.map +1 -1
  15. package/dist/adapters/http/server/handler.d.ts +3 -1
  16. package/dist/adapters/http/server/handler.d.ts.map +1 -1
  17. package/dist/adapters/http/server/handler.js +7 -34
  18. package/dist/adapters/http/server/handler.js.map +1 -1
  19. package/dist/adapters/local/client/transport.d.ts.map +1 -1
  20. package/dist/adapters/local/client/transport.js +5 -0
  21. package/dist/adapters/local/client/transport.js.map +1 -1
  22. package/dist/browser.d.ts +4 -3
  23. package/dist/browser.d.ts.map +1 -1
  24. package/dist/browser.js +6 -1
  25. package/dist/browser.js.map +1 -1
  26. package/dist/client/client.js +1 -1
  27. package/dist/client/client.js.map +1 -1
  28. package/dist/client/errors/registry.d.ts.map +1 -1
  29. package/dist/client/errors/registry.js +1 -0
  30. package/dist/client/errors/registry.js.map +1 -1
  31. package/dist/client/types.d.ts +6 -1
  32. package/dist/client/types.d.ts.map +1 -1
  33. package/dist/client/types.js +7 -1
  34. package/dist/client/types.js.map +1 -1
  35. package/dist/events/publisher.d.ts.map +1 -1
  36. package/dist/events/publisher.js +10 -5
  37. package/dist/events/publisher.js.map +1 -1
  38. package/dist/events/rows.d.ts +12 -4
  39. package/dist/events/rows.d.ts.map +1 -1
  40. package/dist/events/rows.js +13 -16
  41. package/dist/events/rows.js.map +1 -1
  42. package/dist/index.d.ts +5 -2
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +8 -2
  45. package/dist/index.js.map +1 -1
  46. package/dist/node.d.ts +10 -1
  47. package/dist/node.d.ts.map +1 -1
  48. package/dist/node.js +11 -0
  49. package/dist/node.js.map +1 -1
  50. package/dist/procedures/collection/admission.d.ts +77 -0
  51. package/dist/procedures/collection/admission.d.ts.map +1 -0
  52. package/dist/procedures/collection/admission.js +204 -0
  53. package/dist/procedures/collection/admission.js.map +1 -0
  54. package/dist/procedures/collection/changes.d.ts +3 -1
  55. package/dist/procedures/collection/changes.d.ts.map +1 -1
  56. package/dist/procedures/collection/changes.js +10 -6
  57. package/dist/procedures/collection/changes.js.map +1 -1
  58. package/dist/procedures/collection/index.d.ts +5 -1
  59. package/dist/procedures/collection/index.d.ts.map +1 -1
  60. package/dist/procedures/collection/index.js +4 -0
  61. package/dist/procedures/collection/index.js.map +1 -1
  62. package/dist/procedures/collection/patch-in-parts.d.ts +15 -0
  63. package/dist/procedures/collection/patch-in-parts.d.ts.map +1 -0
  64. package/dist/procedures/collection/patch-in-parts.js +47 -0
  65. package/dist/procedures/collection/patch-in-parts.js.map +1 -0
  66. package/dist/procedures/collection/precondition.d.ts +111 -0
  67. package/dist/procedures/collection/precondition.d.ts.map +1 -0
  68. package/dist/procedures/collection/precondition.js +153 -0
  69. package/dist/procedures/collection/precondition.js.map +1 -0
  70. package/dist/procedures/collection/procedures.d.ts +8 -0
  71. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  72. package/dist/procedures/collection/procedures.js +66 -76
  73. package/dist/procedures/collection/procedures.js.map +1 -1
  74. package/dist/procedures/collection/query.d.ts +2 -0
  75. package/dist/procedures/collection/query.d.ts.map +1 -1
  76. package/dist/procedures/collection/query.js +3 -0
  77. package/dist/procedures/collection/query.js.map +1 -1
  78. package/dist/procedures/collection/schemas.d.ts +16 -0
  79. package/dist/procedures/collection/schemas.d.ts.map +1 -1
  80. package/dist/procedures/collection/schemas.js +26 -5
  81. package/dist/procedures/collection/schemas.js.map +1 -1
  82. package/dist/procedures/collection/storage.d.ts +34 -6
  83. package/dist/procedures/collection/storage.d.ts.map +1 -1
  84. package/dist/procedures/collection/storage.js +3 -0
  85. package/dist/procedures/collection/storage.js.map +1 -1
  86. package/dist/procedures/index.d.ts +2 -2
  87. package/dist/procedures/index.d.ts.map +1 -1
  88. package/dist/procedures/index.js +3 -1
  89. package/dist/procedures/index.js.map +1 -1
  90. package/dist/procedures/invoke.d.ts +5 -2
  91. package/dist/procedures/invoke.d.ts.map +1 -1
  92. package/dist/procedures/invoke.js +5 -1
  93. package/dist/procedures/invoke.js.map +1 -1
  94. package/dist/procedures/types.d.ts +7 -0
  95. package/dist/procedures/types.d.ts.map +1 -1
  96. package/dist/procedures/types.js.map +1 -1
  97. package/dist/protocol.d.ts +1 -1
  98. package/dist/protocol.d.ts.map +1 -1
  99. package/dist/protocol.js +1 -0
  100. package/dist/protocol.js.map +1 -1
  101. package/dist/server/index.d.ts +70 -5
  102. package/dist/server/index.d.ts.map +1 -1
  103. package/dist/server/index.js +26 -3
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/server/procedure-server.d.ts.map +1 -1
  106. package/dist/server/procedure-server.js +5 -0
  107. package/dist/server/procedure-server.js.map +1 -1
  108. package/package.json +12 -1
  109. package/src/adapters/http/client/transport.ts +3 -0
  110. package/src/adapters/http/server/body.ts +43 -0
  111. package/src/adapters/http/server/cors.ts +42 -1
  112. package/src/adapters/http/server/handler.ts +10 -32
  113. package/src/adapters/local/client/transport.ts +4 -0
  114. package/src/browser.ts +25 -1
  115. package/src/client/client.ts +1 -1
  116. package/src/client/errors/registry.ts +1 -0
  117. package/src/client/types.ts +8 -0
  118. package/src/events/publisher.ts +8 -5
  119. package/src/events/rows.ts +19 -6
  120. package/src/index.ts +9 -6
  121. package/src/node.ts +13 -1
  122. package/src/procedures/collection/admission.ts +243 -0
  123. package/src/procedures/collection/changes.ts +11 -5
  124. package/src/procedures/collection/index.ts +21 -0
  125. package/src/procedures/collection/patch-in-parts.ts +56 -0
  126. package/src/procedures/collection/precondition.ts +180 -0
  127. package/src/procedures/collection/procedures.ts +69 -78
  128. package/src/procedures/collection/query.ts +16 -0
  129. package/src/procedures/collection/schemas.ts +43 -6
  130. package/src/procedures/collection/storage.ts +36 -6
  131. package/src/procedures/index.ts +22 -0
  132. package/src/procedures/invoke.ts +7 -2
  133. package/src/procedures/types.ts +8 -0
  134. package/src/protocol.ts +3 -1
  135. package/src/server/index.ts +94 -17
  136. package/src/server/procedure-server.ts +4 -0
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The patch of an item of a storage (roadmap CLI-14): `patch()` of the storage, or a read, a merge
3
+ * and a write for a storage without it.
4
+ */
5
+ import { applyPatch } from "./query.js";
6
+ /**
7
+ * The patches of a storage without `patch()` that run, for each id. A patch of an id waits for the
8
+ * patch before it, so two patches of one process keep each field. A write of another writer
9
+ * between the read and the write of a patch is lost: give the storage a `patch()` for one step.
10
+ */
11
+ const PATCHES = new WeakMap();
12
+ /**
13
+ * This function patches an item of a storage without `patch()`: a read, a merge and a write. The
14
+ * check gets the stored item and the merged item before the write. An error of the check stops
15
+ * the patch, and the storage does not change.
16
+ */
17
+ export function patchInParts(storage, id, fields, options, check) {
18
+ let running = PATCHES.get(storage);
19
+ if (!running) {
20
+ running = new Map();
21
+ PATCHES.set(storage, running);
22
+ }
23
+ const queue = running;
24
+ const before = queue.get(id) ?? Promise.resolve();
25
+ const patch = before.then(async () => {
26
+ const current = await storage.get(id);
27
+ if (current === undefined)
28
+ return undefined;
29
+ const next = applyPatch(current, fields);
30
+ if (check)
31
+ await check(current, next);
32
+ await storage.set(id, next, options);
33
+ return next;
34
+ });
35
+ const settled = patch.then(() => undefined, () => undefined);
36
+ queue.set(id, settled);
37
+ void settled.then(() => {
38
+ if (queue.get(id) === settled)
39
+ queue.delete(id);
40
+ });
41
+ return patch;
42
+ }
43
+ /** This function patches an item: with `patch()` of the storage, or in parts. It gives the new item, or undefined for a missing id. */
44
+ export function patchItem(storage, id, fields, options) {
45
+ return storage.patch ? storage.patch(id, fields, options) : patchInParts(storage, id, fields, options);
46
+ }
47
+ //# sourceMappingURL=patch-in-parts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-in-parts.js","sourceRoot":"","sources":["../../../src/procedures/collection/patch-in-parts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAG3D;;;;GAIG;AACH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAyC,CAAC;AAErE;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAmC,EACnC,EAAU,EACV,MAAoB,EACpB,OAA6B,EAC7B,KAAuD;IAEvD,IAAI,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC;IACtB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACzC,IAAI,KAAK;YAAE,MAAM,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACtC,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CACxB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;IACF,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACvB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;QACrB,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,OAAO;YAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,uIAAuI;AACvI,MAAM,UAAU,SAAS,CAAC,OAAmC,EAAE,EAAU,EAAE,MAAoB,EAAE,OAA6B;IAC5H,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACzG,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The precondition of a collection write (roadmap CLI-16, decision D11).
3
+ *
4
+ * - `set` and `delete` take `expectedVersion`. `setBatch` takes `expectedVersions`, with one
5
+ * expected version for each id. The value is the version of the current item that the write
6
+ * expects. `null` expects no item.
7
+ * - The version of an item is the value that the function `versionOf` of the storage gives for
8
+ * the item, for example a revision field. `HybridStorage` reads the same versions with its own
9
+ * `versionOf`, so its queue can send the base version of a write as the expected version.
10
+ * - The storage tests the precondition and writes in one step. When the version of the current
11
+ * item is not the expected version, the write changes nothing, and it rejects with
12
+ * `VersionConflictError`.
13
+ * - The error has the code `VERSION_CONFLICT` (HTTP 409). It has the current item and its version
14
+ * for each conflict. On the wire, the conflicts go in the metadata key `conflicts`.
15
+ * - A storage without `versionOf` cannot test a precondition. It rejects such a write with a
16
+ * `TypeError`, and changes nothing. It never ignores a precondition.
17
+ * - A write without a precondition is the same as before: the last write wins.
18
+ *
19
+ * This module has no imports: `@procedures/client/query` exports it for the storage packages.
20
+ */
21
+ /** One conflict of a write with a precondition (roadmap CLI-16). The data is JSON. */
22
+ export interface VersionConflict {
23
+ /** The id of the item. */
24
+ readonly id: string;
25
+ /** The version that the write expected. `null`: the write expected no item. */
26
+ readonly expected: string | null;
27
+ /** True when the id has an item now. */
28
+ readonly found: boolean;
29
+ /** The current item, when `found` is true. */
30
+ readonly value?: unknown;
31
+ /**
32
+ * The version of the current item: `null` when the id has no item. When the current item has no
33
+ * version, the field is absent.
34
+ */
35
+ readonly version?: string | null;
36
+ }
37
+ /** The function that gives the version of an item, or undefined for an item without a version. */
38
+ export type VersionOf<T> = (item: T) => string | undefined;
39
+ /** The current state of an id: no item, or an item. */
40
+ export type CurrentItem<T> = {
41
+ readonly found: false;
42
+ } | {
43
+ readonly found: true;
44
+ readonly value: T;
45
+ };
46
+ /** The metadata key of the conflicts of a `VERSION_CONFLICT` error on the wire. */
47
+ export declare const VERSION_CONFLICTS_METADATA_KEY = "conflicts";
48
+ /** The longest expected version, in characters. */
49
+ export declare const MAX_EXPECTED_VERSION_LENGTH = 1024;
50
+ /**
51
+ * A write with a precondition found an item with another version (roadmap CLI-16). The write
52
+ * changed nothing. `conflicts` has the current item and its version for each id of a conflict.
53
+ */
54
+ export declare class VersionConflictError extends Error {
55
+ readonly name = "VersionConflictError";
56
+ /** The error code of the protocol: HTTP 409, not retryable. */
57
+ readonly code = "VERSION_CONFLICT";
58
+ /** A retry of the same write fails again: the caller reads the current item first. */
59
+ readonly retryable = false;
60
+ /** The conflicts, one for each id whose current item does not have the expected version. */
61
+ readonly conflicts: readonly VersionConflict[];
62
+ constructor(conflicts: readonly VersionConflict[], message?: string);
63
+ }
64
+ /**
65
+ * This function gives the conflict of one id, or undefined when the current item has the expected
66
+ * version. `null` expects no item. An item without a version never has an expected string version.
67
+ *
68
+ * @param id - The id of the item
69
+ * @param expected - The expected version, or `null` for no item
70
+ * @param current - The current state of the id
71
+ * @param versionOf - The version of an item
72
+ */
73
+ export declare function versionConflict<T>(id: string, expected: string | null, current: CurrentItem<T>, versionOf: VersionOf<T>): VersionConflict | undefined;
74
+ /**
75
+ * This function tests the preconditions of a write against the current items, and gives the
76
+ * conflicts. The list is empty when each precondition holds.
77
+ *
78
+ * @param expected - The expected version of each id
79
+ * @param currentOf - The current state of an id, before the write
80
+ * @param versionOf - The version of an item
81
+ */
82
+ export declare function versionConflicts<T>(expected: Iterable<readonly [string, string | null]>, currentOf: (id: string) => CurrentItem<T>, versionOf: VersionOf<T>): VersionConflict[];
83
+ /** The code of the error of a write with a precondition on a storage that cannot test it. */
84
+ export declare const PRECONDITION_NOT_SUPPORTED = "PRECONDITION_NOT_SUPPORTED";
85
+ /**
86
+ * The error of a write with a precondition on a storage that cannot test it: a storage without
87
+ * `versionOf`. The storage changes nothing. The error is a `TypeError` with the code
88
+ * `PRECONDITION_NOT_SUPPORTED`, so a sync does not take it for a network failure.
89
+ *
90
+ * @param storage - The name of the storage, for the message
91
+ * @param remedy - What makes the test possible
92
+ */
93
+ export declare function preconditionNotSupported(storage: string, remedy?: string): TypeError & {
94
+ readonly code: string;
95
+ readonly retryable: false;
96
+ };
97
+ /** True for a value that a precondition can expect: a string of at most 1024 characters, or `null`. */
98
+ export declare function isExpectedVersion(value: unknown): value is string | null;
99
+ /**
100
+ * This function gives the conflicts of an error with the code `VERSION_CONFLICT`: the `conflicts`
101
+ * of a `VersionConflictError`, or the metadata key `conflicts` of an error from the wire (a
102
+ * `ClientError`). It gives undefined for another error. A `VERSION_CONFLICT` error without
103
+ * readable conflicts gives an empty list.
104
+ */
105
+ export declare function versionConflictsOf(error: unknown): readonly VersionConflict[] | undefined;
106
+ /**
107
+ * This function gives a `VersionConflictError` for an error with the code `VERSION_CONFLICT`, with
108
+ * its conflicts and its message. It gives undefined for another error.
109
+ */
110
+ export declare function asVersionConflictError(error: unknown): VersionConflictError | undefined;
111
+ //# sourceMappingURL=precondition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precondition.d.ts","sourceRoot":"","sources":["../../../src/procedures/collection/precondition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC9B,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,kGAAkG;AAClG,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;AAE3D,uDAAuD;AACvD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAErG,mFAAmF;AACnF,eAAO,MAAM,8BAA8B,cAAc,CAAC;AAE1D,mDAAmD;AACnD,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAShD;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAkB,IAAI,0BAA0B;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,sBAAsB;IACnC,sFAAsF;IACtF,QAAQ,CAAC,SAAS,SAAS;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAE/C,YAAY,SAAS,EAAE,SAAS,eAAe,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,EAGlE;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,eAAe,GAAG,SAAS,CAKrJ;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,EACpD,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,EACzC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,GACtB,eAAe,EAAE,CAOnB;AAED,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AAEvE;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAiC,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAA;CAAE,CAEnK;AAED,uGAAuG;AACvG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,GAAG,IAAI,CAExE;AAuBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,eAAe,EAAE,GAAG,SAAS,CAOzF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,GAAG,SAAS,CAMvF"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The precondition of a collection write (roadmap CLI-16, decision D11).
3
+ *
4
+ * - `set` and `delete` take `expectedVersion`. `setBatch` takes `expectedVersions`, with one
5
+ * expected version for each id. The value is the version of the current item that the write
6
+ * expects. `null` expects no item.
7
+ * - The version of an item is the value that the function `versionOf` of the storage gives for
8
+ * the item, for example a revision field. `HybridStorage` reads the same versions with its own
9
+ * `versionOf`, so its queue can send the base version of a write as the expected version.
10
+ * - The storage tests the precondition and writes in one step. When the version of the current
11
+ * item is not the expected version, the write changes nothing, and it rejects with
12
+ * `VersionConflictError`.
13
+ * - The error has the code `VERSION_CONFLICT` (HTTP 409). It has the current item and its version
14
+ * for each conflict. On the wire, the conflicts go in the metadata key `conflicts`.
15
+ * - A storage without `versionOf` cannot test a precondition. It rejects such a write with a
16
+ * `TypeError`, and changes nothing. It never ignores a precondition.
17
+ * - A write without a precondition is the same as before: the last write wins.
18
+ *
19
+ * This module has no imports: `@procedures/client/query` exports it for the storage packages.
20
+ */
21
+ /** The metadata key of the conflicts of a `VERSION_CONFLICT` error on the wire. */
22
+ export const VERSION_CONFLICTS_METADATA_KEY = "conflicts";
23
+ /** The longest expected version, in characters. */
24
+ export const MAX_EXPECTED_VERSION_LENGTH = 1024;
25
+ /** The message of an error with some conflicts. */
26
+ function conflictMessage(conflicts) {
27
+ const ids = conflicts.slice(0, 10).map((conflict) => JSON.stringify(conflict.id)).join(", ");
28
+ const more = conflicts.length > 10 ? ` and ${conflicts.length - 10} more` : "";
29
+ return `The version of the current item is not the expected version: ${ids}${more}`;
30
+ }
31
+ /**
32
+ * A write with a precondition found an item with another version (roadmap CLI-16). The write
33
+ * changed nothing. `conflicts` has the current item and its version for each id of a conflict.
34
+ */
35
+ export class VersionConflictError extends Error {
36
+ name = "VersionConflictError";
37
+ /** The error code of the protocol: HTTP 409, not retryable. */
38
+ code = "VERSION_CONFLICT";
39
+ /** A retry of the same write fails again: the caller reads the current item first. */
40
+ retryable = false;
41
+ /** The conflicts, one for each id whose current item does not have the expected version. */
42
+ conflicts;
43
+ constructor(conflicts, message) {
44
+ super(message ?? conflictMessage(conflicts));
45
+ this.conflicts = conflicts;
46
+ }
47
+ }
48
+ /**
49
+ * This function gives the conflict of one id, or undefined when the current item has the expected
50
+ * version. `null` expects no item. An item without a version never has an expected string version.
51
+ *
52
+ * @param id - The id of the item
53
+ * @param expected - The expected version, or `null` for no item
54
+ * @param current - The current state of the id
55
+ * @param versionOf - The version of an item
56
+ */
57
+ export function versionConflict(id, expected, current, versionOf) {
58
+ if (!current.found)
59
+ return expected === null ? undefined : { id, expected, found: false, version: null };
60
+ const version = versionOf(current.value);
61
+ if (expected !== null && version === expected)
62
+ return undefined;
63
+ return version === undefined ? { id, expected, found: true, value: current.value } : { id, expected, found: true, value: current.value, version };
64
+ }
65
+ /**
66
+ * This function tests the preconditions of a write against the current items, and gives the
67
+ * conflicts. The list is empty when each precondition holds.
68
+ *
69
+ * @param expected - The expected version of each id
70
+ * @param currentOf - The current state of an id, before the write
71
+ * @param versionOf - The version of an item
72
+ */
73
+ export function versionConflicts(expected, currentOf, versionOf) {
74
+ const conflicts = [];
75
+ for (const [id, version] of expected) {
76
+ const conflict = versionConflict(id, version, currentOf(id), versionOf);
77
+ if (conflict)
78
+ conflicts.push(conflict);
79
+ }
80
+ return conflicts;
81
+ }
82
+ /** The code of the error of a write with a precondition on a storage that cannot test it. */
83
+ export const PRECONDITION_NOT_SUPPORTED = "PRECONDITION_NOT_SUPPORTED";
84
+ /**
85
+ * The error of a write with a precondition on a storage that cannot test it: a storage without
86
+ * `versionOf`. The storage changes nothing. The error is a `TypeError` with the code
87
+ * `PRECONDITION_NOT_SUPPORTED`, so a sync does not take it for a network failure.
88
+ *
89
+ * @param storage - The name of the storage, for the message
90
+ * @param remedy - What makes the test possible
91
+ */
92
+ export function preconditionNotSupported(storage, remedy = "give it the option versionOf") {
93
+ return Object.assign(new TypeError(`${storage} cannot test expectedVersion: ${remedy}`), { code: PRECONDITION_NOT_SUPPORTED, retryable: false });
94
+ }
95
+ /** True for a value that a precondition can expect: a string of at most 1024 characters, or `null`. */
96
+ export function isExpectedVersion(value) {
97
+ return value === null || (typeof value === "string" && value.length <= MAX_EXPECTED_VERSION_LENGTH);
98
+ }
99
+ /** The conflicts of untrusted data from the wire, or undefined when the data is not a list of conflicts. */
100
+ function conflictsOfData(data) {
101
+ if (!Array.isArray(data))
102
+ return undefined;
103
+ const conflicts = [];
104
+ for (const entry of data) {
105
+ if (typeof entry !== "object" || entry === null)
106
+ return undefined;
107
+ const record = entry;
108
+ const { id, expected, found, version } = record;
109
+ if (typeof id !== "string" || typeof found !== "boolean" || !(expected === null || typeof expected === "string"))
110
+ return undefined;
111
+ if (!(version === undefined || version === null || typeof version === "string"))
112
+ return undefined;
113
+ conflicts.push({
114
+ id,
115
+ expected,
116
+ found,
117
+ ...(found && Object.prototype.hasOwnProperty.call(record, "value") && { value: record["value"] }),
118
+ ...(version !== undefined && { version }),
119
+ });
120
+ }
121
+ return conflicts;
122
+ }
123
+ /**
124
+ * This function gives the conflicts of an error with the code `VERSION_CONFLICT`: the `conflicts`
125
+ * of a `VersionConflictError`, or the metadata key `conflicts` of an error from the wire (a
126
+ * `ClientError`). It gives undefined for another error. A `VERSION_CONFLICT` error without
127
+ * readable conflicts gives an empty list.
128
+ */
129
+ export function versionConflictsOf(error) {
130
+ if (typeof error !== "object" || error === null)
131
+ return undefined;
132
+ if (error instanceof VersionConflictError)
133
+ return error.conflicts;
134
+ const { code, conflicts, metadata } = error;
135
+ if (code !== "VERSION_CONFLICT")
136
+ return undefined;
137
+ const fromMetadata = typeof metadata === "object" && metadata !== null ? metadata[VERSION_CONFLICTS_METADATA_KEY] : undefined;
138
+ return conflictsOfData(conflicts) ?? conflictsOfData(fromMetadata) ?? [];
139
+ }
140
+ /**
141
+ * This function gives a `VersionConflictError` for an error with the code `VERSION_CONFLICT`, with
142
+ * its conflicts and its message. It gives undefined for another error.
143
+ */
144
+ export function asVersionConflictError(error) {
145
+ if (error instanceof VersionConflictError)
146
+ return error;
147
+ const conflicts = versionConflictsOf(error);
148
+ if (conflicts === undefined)
149
+ return undefined;
150
+ const message = error instanceof Error ? error.message : undefined;
151
+ return new VersionConflictError(conflicts, message);
152
+ }
153
+ //# sourceMappingURL=precondition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precondition.js","sourceRoot":"","sources":["../../../src/procedures/collection/precondition.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { ProcedureContext, AnyProcedure } from "../types.js";
8
8
  import { type StorageQueryPolicy } from "./query.js";
9
+ import { type CollectionAdmission } from "./admission.js";
9
10
  /** The options of the change events of the collection procedures (roadmap CLI-15). */
10
11
  export interface CollectionChangeEventsOptions {
11
12
  /**
@@ -40,6 +41,13 @@ export interface CollectionProceduresOptions {
40
41
  * @default true, with the topic `collections.<name>` and no values
41
42
  */
42
43
  changeEvents?: boolean | CollectionChangeEventsOptions | undefined;
44
+ /**
45
+ * The rules of the rows (roadmap CLI-41). `admit` examines each row of each read and of each
46
+ * change event with values. `guard` examines each write before the storage changes, and a
47
+ * refused write gets `FORBIDDEN` (reason `write-not-allowed`). Without it, the procedures check
48
+ * only the partition.
49
+ */
50
+ admission?: CollectionAdmission | undefined;
43
51
  }
44
52
  /**
45
53
  * Create collection procedures for a specific collection name.
@@ -1 +1 @@
1
- {"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../../../src/procedures/collection/procedures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkDlE,OAAO,EAA6F,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA+BhJ,sFAAsF;AACtF,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IACvH;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC7C;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,6BAA6B,GAAG,SAAS,CAAC;CACpE;AA6CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,YAAY,EAAE,CAyT5H"}
1
+ {"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../../../src/procedures/collection/procedures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkDlE,OAAO,EAA8D,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIjH,OAAO,EAA8B,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AA6BtF,sFAAsF;AACtF,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IACvH;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC7C;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,6BAA6B,GAAG,SAAS,CAAC;IACnE;;;;;OAKG;IACH,SAAS,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;CAC7C;AAwBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,YAAY,EAAE,CA4T5H"}