@larc-iu/plaid-client 0.2.0-alpha.22 → 0.2.0-alpha.23

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 ADDED
@@ -0,0 +1,35 @@
1
+ # @larc-iu/plaid-client
2
+
3
+ The JavaScript client for the [Plaid](https://larc-iu.github.io/plaid/) annotation API.
4
+ It has no dependencies and runs in browsers and in Node.
5
+
6
+ ## From npm
7
+
8
+ ```sh
9
+ npm install @larc-iu/plaid-client@alpha
10
+ ```
11
+
12
+ ```js
13
+ import { PlaidClient } from "@larc-iu/plaid-client";
14
+
15
+ const client = await PlaidClient.login("http://localhost:8080", "user@example.com", "password");
16
+ console.log(await client.projects.list());
17
+ ```
18
+
19
+ ## From a Plaid server
20
+
21
+ Every Plaid server serves the client that matches its own version at `/client/plaid-client.js`, with its type declarations at `/client/plaid-client.d.ts`.
22
+ A page served by Plaid, for example from the folder set as `static_resources_path` in its `config.toml`, can import it without bundling a copy:
23
+
24
+ ```html
25
+ <script type="module">
26
+ import { PlaidClient } from "/client/plaid-client.js";
27
+ const client = await PlaidClient.login(location.origin, "user@example.com", "password");
28
+ console.log(await client.projects.list());
29
+ </script>
30
+ ```
31
+
32
+ ## Documentation
33
+
34
+ - [Manual](https://larc-iu.github.io/plaid/manual.html), which covers the API and its concepts.
35
+ - [API reference](https://larc-iu.github.io/plaid/api-js.html) for this client.
package/index.d.ts CHANGED
@@ -422,6 +422,8 @@ interface UsersBundle {
422
422
  interface UserDataEntry {
423
423
  key: string;
424
424
  updatedAt: string;
425
+ /** Raised by one on every write of the entry. A new entry is 1. */
426
+ version: number;
425
427
  value?: any;
426
428
  }
427
429
 
@@ -446,11 +448,18 @@ interface UserDataBundle {
446
448
  opts?: UserDataOptions & { pageSize?: number },
447
449
  ): AsyncGenerator<UserDataEntry[]>;
448
450
  get(userId: string, key: string): Promise<UserDataEntry & { value: any }>;
451
+ /**
452
+ * `version`: write only when the entry is still at this version (0: only
453
+ * when there is none). Refused with 409, `error: "version-mismatch"` and the
454
+ * stored `version` and `updatedAt` on the error's `responseData`, when
455
+ * another write landed since.
456
+ */
449
457
  put(
450
458
  userId: string,
451
459
  key: string,
452
460
  value: any,
453
- ): Promise<{ key: string; updatedAt: string }>;
461
+ opts?: { version?: number },
462
+ ): Promise<{ key: string; updatedAt: string; version: number }>;
454
463
  delete(userId: string, key: string): Promise<any>;
455
464
  }
456
465
 
@@ -1904,6 +1913,8 @@ export interface KeySeed {
1904
1913
  }
1905
1914
  /** A fresh UUIDv7, for the id of something a client creates. */
1906
1915
  export function uuidv7(): string;
1916
+ /** A fresh random UUID (version 4), from crypto.getRandomValues, so it works on any page. */
1917
+ export function uuidv4(): string;
1907
1918
  /** Whether a write's answer was replayed from its Idempotency-Key's first send (`Idempotent-Replayed: true`), so it wrote nothing new. Marked on an object or list answer as a non-enumerable `replayed: true`. */
1908
1919
  export function wasReplayed(answer: unknown): boolean;
1909
1920
  export function createdId(result: any): string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larc-iu/plaid-client",
3
- "version": "0.2.0-alpha.22",
3
+ "version": "0.2.0-alpha.23",
4
4
  "description": "JavaScript client for the Plaid annotation API",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,4 +1,5 @@
1
1
  import { isUnknownOutcome, retryUnknown } from "./http.js";
2
+ import { uuidv4 } from "./ids.js";
2
3
 
3
4
  /**
4
5
  * Keeping a document lock alive for as long as a `locked()` block runs.
@@ -216,7 +217,7 @@ export class DocumentLock {
216
217
  export const LOCK_ACQUIRE_RETRY_MS = 500;
217
218
 
218
219
  function mintLockId() {
219
- return globalThis.crypto.randomUUID();
220
+ return uuidv4();
220
221
  }
221
222
 
222
223
  /**
package/src/ids.js CHANGED
@@ -52,3 +52,23 @@ export function uuidv7() {
52
52
  }
53
53
  return s;
54
54
  }
55
+
56
+ /**
57
+ * A random UUIDv4, for ids that need no order (an operation group, a lock
58
+ * holder). Built on `crypto.getRandomValues`, since `crypto.randomUUID`
59
+ * exists only in a secure context and a page served over plain HTTP from
60
+ * another host than localhost is not one.
61
+ * @returns {string}
62
+ */
63
+ export function uuidv4() {
64
+ const b = new Uint8Array(16);
65
+ crypto.getRandomValues(b);
66
+ b[6] = (b[6] & 0x0f) | 0x40;
67
+ b[8] = (b[8] & 0x3f) | 0x80;
68
+ let s = "";
69
+ for (let i = 0; i < 16; i++) {
70
+ s += hex[b[i]];
71
+ if (i === 3 || i === 5 || i === 7 || i === 9) s += "-";
72
+ }
73
+ return s;
74
+ }
package/src/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  DEFAULT_BATCH_TIMEOUT_MS,
23
23
  queryTimeout,
24
24
  } from "./http.js";
25
- import { uuidv7 } from "./ids.js";
25
+ import { uuidv4, uuidv7 } from "./ids.js";
26
26
  import { constraintsBody } from "./constraints.js";
27
27
  import { listAll, listPage, iterPages } from "./pagination.js";
28
28
  import { recorderFor } from "./events.js";
@@ -1474,7 +1474,7 @@ class PlaidClient {
1474
1474
  query: { prefix, pattern, "include-values": includeValues },
1475
1475
  }),
1476
1476
  /**
1477
- * Read one private data entry ({key, updatedAt, value}); 404 if absent.
1477
+ * Read one private data entry ({key, updatedAt, version, value}); 404 if absent.
1478
1478
  * @param {string} userId
1479
1479
  * @param {string} key
1480
1480
  */
@@ -1492,17 +1492,28 @@ class PlaidClient {
1492
1492
  * value whose keys are camelCase round-trips unchanged while one keyed
1493
1493
  * by arbitrary strings does not. Put such a map under a `metadata` key,
1494
1494
  * which both clients pass through untouched.
1495
+ *
1496
+ * Every write raises the entry's `version` by one, and reads answer it.
1497
+ * With `version`, the write lands only when the entry is still at that
1498
+ * version (0: only when there is no entry), and is otherwise refused with
1499
+ * 409, `error: "version-mismatch"`, and the stored `version` and
1500
+ * `updatedAt` on the error's `responseData`: read it again and make the
1501
+ * change on what is there.
1495
1502
  * @param {string} userId
1496
1503
  * @param {string} key
1497
1504
  * @param {*} value
1505
+ * @param {object} [opts]
1506
+ * @param {number} [opts.version] - The version this write was made from
1507
+ * @returns {Promise<{key: string, updatedAt: string, version: number}>}
1498
1508
  */
1499
- put: (userId, key, value) =>
1509
+ put: (userId, key, value, { version } = {}) =>
1500
1510
  this._request(
1501
1511
  "PUT",
1502
1512
  `/api/v1/users/${userId}/data/${encodeURIComponent(key)}`,
1503
1513
  {
1504
1514
  body: value,
1505
1515
  noBatch: true,
1516
+ queryParams: { version },
1506
1517
  },
1507
1518
  ),
1508
1519
  /**
@@ -2148,9 +2159,9 @@ class PlaidClient {
2148
2159
  * Upload a media file for a document. Uses Apache Tika for content validation.
2149
2160
  * A document that already has a recording refuses it with a 409
2150
2161
  * (`media-exists`, and the current `media-url`): delete that one first.
2151
- * A WAV whose samples are not PCM or float (IMA or MS ADPCM, GSM, A-law
2152
- * and the rest), which browsers cannot play, is refused with a 415
2153
- * whose `error` names its coding.
2162
+ * A WAV whose samples are not PCM, 32-bit float, A-law or mu-law (IMA or
2163
+ * MS ADPCM, GSM, 64-bit float and the rest), which browsers cannot play,
2164
+ * is refused with a 415 whose `error` names its coding.
2154
2165
  * @param {string} documentId - The document ID
2155
2166
  * @param {File} file - The file to upload
2156
2167
  * @param {string} [auditMessage] - Custom audit-log message for this write
@@ -2216,7 +2227,10 @@ class PlaidClient {
2216
2227
  }),
2217
2228
  /**
2218
2229
  * Get audit log for a document. Transparently follows pagination cursors
2219
- * and returns the full flat array.
2230
+ * and returns the full flat array. Each entry, and each of its `ops`,
2231
+ * says what kind of credential made it as `credential`: `login`,
2232
+ * `named-token`, `service` or `delegated` (absent on older operations),
2233
+ * beside `apiToken` (the named token's id and name) when there was one.
2220
2234
  * @param {string} documentId - The document ID
2221
2235
  * @param {string} [startTime] - Start of time range
2222
2236
  * @param {string} [endTime] - End of time range
@@ -2452,7 +2466,10 @@ class PlaidClient {
2452
2466
  ),
2453
2467
  /**
2454
2468
  * Get audit log for a project. Transparently follows pagination cursors
2455
- * and returns the full flat array.
2469
+ * and returns the full flat array. Each entry, and each of its `ops`,
2470
+ * says what kind of credential made it as `credential`: `login`,
2471
+ * `named-token`, `service` or `delegated` (absent on older operations),
2472
+ * beside `apiToken` (the named token's id and name) when there was one.
2456
2473
  * @param {string} projectId - The project ID
2457
2474
  * @param {string} [startTime] - Start of time range
2458
2475
  * @param {string} [endTime] - End of time range
@@ -3809,7 +3826,7 @@ class PlaidClient {
3809
3826
  return open.id;
3810
3827
  }
3811
3828
  this.operationGroup = {
3812
- id: groupId ? String(groupId) : crypto.randomUUID(),
3829
+ id: groupId ? String(groupId) : uuidv4(),
3813
3830
  message: message == null ? null : String(message),
3814
3831
  kind: kind == null ? null : String(kind),
3815
3832
  ref: ref == null ? null : String(ref),
@@ -4412,7 +4429,8 @@ export {
4412
4429
  // result. See ./created.js.
4413
4430
  export { createdId, createdIds } from "./created.js";
4414
4431
  // UUIDv7 ids for what a client creates. See ./ids.js and the manual,
4415
- // "Retrying a write".
4416
- export { uuidv7 } from "./ids.js";
4432
+ // "Retrying a write". uuidv4 works where crypto.randomUUID does not (a page
4433
+ // served over plain HTTP from any host but localhost).
4434
+ export { uuidv4, uuidv7 } from "./ids.js";
4417
4435
  export { wasReplayed } from "./replayed.js";
4418
4436
  export { MAX_BATCH_OPS };