@procedures/store 0.44.0 → 0.46.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 (52) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +63 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/indexeddb/idb.d.ts +2 -1
  5. package/dist/indexeddb/idb.d.ts.map +1 -1
  6. package/dist/indexeddb/idb.js +2 -1
  7. package/dist/indexeddb/idb.js.map +1 -1
  8. package/dist/indexeddb/program-store.d.ts +7 -3
  9. package/dist/indexeddb/program-store.d.ts.map +1 -1
  10. package/dist/indexeddb/program-store.js +28 -6
  11. package/dist/indexeddb/program-store.js.map +1 -1
  12. package/dist/indexeddb.d.ts +1 -1
  13. package/dist/indexeddb.d.ts.map +1 -1
  14. package/dist/program-store/collection.d.ts +13 -4
  15. package/dist/program-store/collection.d.ts.map +1 -1
  16. package/dist/program-store/collection.js +29 -6
  17. package/dist/program-store/collection.js.map +1 -1
  18. package/dist/program-store/file.d.ts +7 -3
  19. package/dist/program-store/file.d.ts.map +1 -1
  20. package/dist/program-store/file.js +18 -5
  21. package/dist/program-store/file.js.map +1 -1
  22. package/dist/program-store/index.d.ts +4 -2
  23. package/dist/program-store/index.d.ts.map +1 -1
  24. package/dist/program-store/index.js +2 -1
  25. package/dist/program-store/index.js.map +1 -1
  26. package/dist/program-store/memory.d.ts +7 -4
  27. package/dist/program-store/memory.d.ts.map +1 -1
  28. package/dist/program-store/memory.js +12 -5
  29. package/dist/program-store/memory.js.map +1 -1
  30. package/dist/program-store/procedures.d.ts +6 -1
  31. package/dist/program-store/procedures.d.ts.map +1 -1
  32. package/dist/program-store/procedures.js +4 -2
  33. package/dist/program-store/procedures.js.map +1 -1
  34. package/dist/program-store/programs.d.ts +110 -0
  35. package/dist/program-store/programs.d.ts.map +1 -0
  36. package/dist/program-store/programs.js +202 -0
  37. package/dist/program-store/programs.js.map +1 -0
  38. package/dist/program-store/types.d.ts +53 -4
  39. package/dist/program-store/types.d.ts.map +1 -1
  40. package/dist/program-store/types.js +51 -0
  41. package/dist/program-store/types.js.map +1 -1
  42. package/package.json +4 -4
  43. package/src/indexeddb/idb.ts +3 -2
  44. package/src/indexeddb/program-store.ts +36 -6
  45. package/src/indexeddb.ts +1 -1
  46. package/src/program-store/collection.ts +39 -6
  47. package/src/program-store/file.ts +30 -5
  48. package/src/program-store/index.ts +12 -2
  49. package/src/program-store/memory.ts +20 -5
  50. package/src/program-store/procedures.ts +4 -2
  51. package/src/program-store/programs.ts +307 -0
  52. package/src/program-store/types.ts +85 -4
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { AggregationDefinition, ProcedurePath } from "@procedures/client";
11
+ import { versionConflict, VersionConflictError, type CurrentItem } from "@procedures/client/query";
11
12
 
12
13
  /** One stored program. */
13
14
  export interface StoredProgram {
@@ -21,11 +22,34 @@ export interface StoredProgram {
21
22
  metadata?: Record<string, unknown> | undefined;
22
23
  /** When the store got this version, in milliseconds since 1970. */
23
24
  storedAt: number;
25
+ /** The name of the program, for a view or a tool that lists the programs. */
26
+ name?: string | undefined;
27
+ /** The description of the program, in words. */
28
+ description?: string | undefined;
29
+ /**
30
+ * The version of the record. The store sets it on each put, and ignores the version of the
31
+ * input. The value is opaque: a caller compares it, and gives it back as `expectedVersion`.
32
+ */
33
+ version?: string | number | undefined;
34
+ }
35
+
36
+ /** The options of a write of a program store (decision D11). */
37
+ export interface ProgramWriteOptions {
38
+ /**
39
+ * The version that the write expects for the current record. `null` expects no record. When the
40
+ * current record has another version, the write changes nothing and rejects with
41
+ * `VersionConflictError`. Absent: no precondition, and the last write wins.
42
+ */
43
+ readonly expectedVersion?: string | number | null | undefined;
24
44
  }
25
45
 
26
46
  /**
27
47
  * A store of programs. Each method gives a Promise: a caller cannot tell a local store from a
28
48
  * remote one.
49
+ *
50
+ * The four backends of this package set the `version` of each record and test `expectedVersion`.
51
+ * A store of another package that ignores the options of `put` and `delete` keeps working, but it
52
+ * gives no conflict check: its writes always win.
29
53
  */
30
54
  export interface ProgramStore {
31
55
  /** The kind of the backend: "memory", "file", "collection" or another name. */
@@ -34,14 +58,62 @@ export interface ProgramStore {
34
58
  get(key: string): Promise<StoredProgram | undefined>;
35
59
  /** The programs, sorted by key. With a prefix, only the key and the keys under it (`prefix.x`). */
36
60
  list(prefix?: string): Promise<StoredProgram[]>;
37
- /** Write a program. A program of the same key is replaced. */
38
- put(program: StoredProgram): Promise<void>;
39
- /** Delete the program of a key. True when it existed. */
40
- delete(key: string): Promise<boolean>;
61
+ /**
62
+ * Write a program. A program of the same key is replaced. The store sets a new `version`. With
63
+ * `expectedVersion`, a conflict rejects with `VersionConflictError` and changes nothing.
64
+ */
65
+ put(program: StoredProgram, options?: ProgramWriteOptions): Promise<void>;
66
+ /**
67
+ * Delete the program of a key. True when it existed. With `expectedVersion`, a conflict rejects
68
+ * with `VersionConflictError` and changes nothing.
69
+ */
70
+ delete(key: string, options?: ProgramWriteOptions): Promise<boolean>;
41
71
  /** Release the resources of the store. */
42
72
  close?(): Promise<void>;
43
73
  }
44
74
 
75
+ /**
76
+ * This function gives the version of a record as a string, or undefined for a record without a
77
+ * version. Give it as the `versionOf` of the collection storage of a `CollectionProgramStore`.
78
+ */
79
+ export function storedProgramVersion(program: StoredProgram): string | undefined {
80
+ return program.version === undefined ? undefined : String(program.version);
81
+ }
82
+
83
+ /**
84
+ * This function gives an expected version in the form of the collection storages: a string, or
85
+ * `null` for no record. A number becomes its string, so `3` and `"3"` are the same version.
86
+ */
87
+ export function expectedVersionOf(options: ProgramWriteOptions | undefined): string | null | undefined {
88
+ const expected = options?.expectedVersion;
89
+ return expected === undefined || expected === null ? expected : String(expected);
90
+ }
91
+
92
+ /**
93
+ * This function tests the precondition of a write against the current record. It throws
94
+ * `VersionConflictError` with a copy of the current record when the version is not the expected
95
+ * version. Without `expectedVersion`, it does nothing.
96
+ */
97
+ export function checkProgramVersion(key: string, current: StoredProgram | undefined, options: ProgramWriteOptions | undefined): void {
98
+ const expected = expectedVersionOf(options);
99
+ if (expected === undefined) return;
100
+ const state: CurrentItem<StoredProgram> = current === undefined ? { found: false } : { found: true, value: structuredClone(current) };
101
+ const conflict = versionConflict(key, expected, state, storedProgramVersion);
102
+ if (conflict) throw new VersionConflictError([conflict]);
103
+ }
104
+
105
+ /**
106
+ * This function gives the next version of a counter: one more than the counter and than each
107
+ * numeric version of the records. A record of an older release has no version: it counts as 0.
108
+ */
109
+ export function nextCounterVersion(counter: number, programs: Iterable<StoredProgram>): number {
110
+ let highest = Number.isSafeInteger(counter) && counter > 0 ? counter : 0;
111
+ for (const program of programs) {
112
+ if (typeof program.version === "number" && Number.isSafeInteger(program.version) && program.version > highest) highest = program.version;
113
+ }
114
+ return highest + 1;
115
+ }
116
+
45
117
  /** True when a key is the prefix or under it (`a.b` for the prefix `a`). */
46
118
  export function keyMatches(key: string, prefix: string | undefined): boolean {
47
119
  return prefix === undefined || prefix === "" || key === prefix || key.startsWith(`${prefix}.`);
@@ -75,5 +147,14 @@ export function checkStoredProgram(value: unknown): StoredProgram {
75
147
  if (typeof record.storedAt !== "number" || !Number.isFinite(record.storedAt)) {
76
148
  throw new TypeError(`The program of ${record.key} needs storedAt, a time in milliseconds`);
77
149
  }
150
+ for (const field of ["name", "description"] as const) {
151
+ if (record[field] !== undefined && typeof record[field] !== "string") {
152
+ throw new TypeError(`The ${field} of the program ${record.key} must be a string`);
153
+ }
154
+ }
155
+ const version = record.version;
156
+ if (version !== undefined && typeof version !== "string" && !(typeof version === "number" && Number.isFinite(version))) {
157
+ throw new TypeError(`The version of the program ${record.key} must be a string or a finite number`);
158
+ }
78
159
  return record as StoredProgram;
79
160
  }