@onlyworlds/sdk 4.27.0 → 4.27.1

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
@@ -5,6 +5,17 @@ earlier history lives in git log only.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.27.1] — 2026-10-16
9
+
10
+ ### Fixed
11
+ - **A retried `create` with the same `idempotencyKey` now replays instead of failing.** `create` minted a
12
+ fresh id on every call, so a retry of an id-less element sent a different body and keel answered 409
13
+ `idempotency_error`. With a key and no id, the id is now derived from the key (a UUIDv5, the same id the
14
+ Python package mints), so the retry sends the same body and gets the first answer back. Without a key,
15
+ a fresh UUIDv7 as before; an id you pass is kept.
16
+ - `create`'s doc said a keyed retry "rewrites the same element"; keel's replay writes nothing. Found by
17
+ Quillon's parity audit of the Unity SDK (h37 #5764).
18
+
8
19
  ## [4.27.0] — 2026-10-16
9
20
 
10
21
  ### Added
package/dist/index.d.ts CHANGED
@@ -2556,9 +2556,12 @@ declare class OwV2Client {
2556
2556
  /** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
2557
2557
  get(type: ElementType | string, id: string, opts?: Pick<ListParams, 'expand' | 'fields'>): Promise<OwElement>;
2558
2558
  /**
2559
- * POST /{type}/ -- create one element, minting a UUIDv7 id on the client when you leave id out.
2560
- * The client-side id makes a retry carrying the same Idempotency-Key safe: it rewrites
2561
- * the same element instead of creating a second one. You may still supply your own id.
2559
+ * POST /{type}/ -- create one element, minting an id on the client when you leave id out.
2560
+ * With an `idempotencyKey`, the minted id is derived from the key (a UUIDv5, the same one the
2561
+ * Python package mints), so a retry with the same key and element sends the same body and keel
2562
+ * replays its first answer: nothing is written twice, and the stored 201 comes back. Without a
2563
+ * key, a fresh UUIDv7. You may still supply your own id. A new create meant to write again
2564
+ * (an undo that re-creates) needs a new key, or none: within 24 h a reused key only replays.
2562
2565
  * `name` is the one required field, and required means the key is present: `''` and
2563
2566
  * `null` are accepted and stored as `''` (nameless Markers exist).
2564
2567
  */
package/dist/index.js CHANGED
@@ -1,3 +1,61 @@
1
+ // src/v2/keyed-id.ts
2
+ var KEYED_IDS = "0199b6a0-6f0e-7c2a-9d1e-3b5c7a8e4f21";
3
+ function sha1(msg) {
4
+ const ml = msg.length;
5
+ const total = (ml + 8 >> 6) + 1 << 6;
6
+ const buf = new Uint8Array(total);
7
+ buf.set(msg);
8
+ buf[ml] = 128;
9
+ const bits = ml * 8;
10
+ const view = new DataView(buf.buffer);
11
+ view.setUint32(total - 8, Math.floor(bits / 2 ** 32));
12
+ view.setUint32(total - 4, bits >>> 0);
13
+ let h0 = 1732584193, h1 = 4023233417, h2 = 2562383102, h3 = 271733878, h4 = 3285377520;
14
+ const w = new Uint32Array(80);
15
+ for (let off = 0; off < total; off += 64) {
16
+ for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + i * 4);
17
+ for (let i = 16; i < 80; i++) {
18
+ const x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16];
19
+ w[i] = x << 1 | x >>> 31;
20
+ }
21
+ let a = h0, b = h1, c = h2, d = h3, e = h4;
22
+ for (let i = 0; i < 80; i++) {
23
+ const [f, k] = i < 20 ? [b & c | ~b & d, 1518500249] : i < 40 ? [b ^ c ^ d, 1859775393] : i < 60 ? [b & c | b & d | c & d, 2400959708] : [b ^ c ^ d, 3395469782];
24
+ const t = (a << 5 | a >>> 27) + f + e + k + w[i] >>> 0;
25
+ e = d;
26
+ d = c;
27
+ c = b << 30 | b >>> 2;
28
+ b = a;
29
+ a = t;
30
+ }
31
+ h0 = h0 + a >>> 0;
32
+ h1 = h1 + b >>> 0;
33
+ h2 = h2 + c >>> 0;
34
+ h3 = h3 + d >>> 0;
35
+ h4 = h4 + e >>> 0;
36
+ }
37
+ const out = new Uint8Array(20);
38
+ const ov = new DataView(out.buffer);
39
+ [h0, h1, h2, h3, h4].forEach((h, i) => ov.setUint32(i * 4, h));
40
+ return out;
41
+ }
42
+ var hexBytes = (hex) => Uint8Array.from(hex.replace(/-/g, "").match(/../g).map((b) => parseInt(b, 16)));
43
+ function uuid5(ns, name) {
44
+ const nameBytes = new TextEncoder().encode(name);
45
+ const input = new Uint8Array(16 + nameBytes.length);
46
+ input.set(hexBytes(ns));
47
+ input.set(nameBytes, 16);
48
+ const b = sha1(input).slice(0, 16);
49
+ b[6] = b[6] & 15 | 80;
50
+ b[8] = b[8] & 63 | 128;
51
+ const h = [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
52
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
53
+ }
54
+ function keyedId(idempotencyKey, index) {
55
+ return uuid5(KEYED_IDS, `${idempotencyKey}
56
+ ${index}`);
57
+ }
58
+
1
59
  // src/v2/errors.ts
2
60
  var OwApiError = class extends Error {
3
61
  constructor(status, code, message, docUrl, detail, type = null, param = null, retryAfter = null) {
@@ -274,16 +332,19 @@ var OwV2Client = class {
274
332
  return this.request("GET", `/${type}/${id}/`, { query });
275
333
  }
276
334
  /**
277
- * POST /{type}/ -- create one element, minting a UUIDv7 id on the client when you leave id out.
278
- * The client-side id makes a retry carrying the same Idempotency-Key safe: it rewrites
279
- * the same element instead of creating a second one. You may still supply your own id.
335
+ * POST /{type}/ -- create one element, minting an id on the client when you leave id out.
336
+ * With an `idempotencyKey`, the minted id is derived from the key (a UUIDv5, the same one the
337
+ * Python package mints), so a retry with the same key and element sends the same body and keel
338
+ * replays its first answer: nothing is written twice, and the stored 201 comes back. Without a
339
+ * key, a fresh UUIDv7. You may still supply your own id. A new create meant to write again
340
+ * (an undo that re-creates) needs a new key, or none: within 24 h a reused key only replays.
280
341
  * `name` is the one required field, and required means the key is present: `''` and
281
342
  * `null` are accepted and stored as `''` (nameless Markers exist).
282
343
  */
283
344
  async create(type, element, opts = {}) {
284
345
  const body = sanitizePayload(element);
285
346
  if (body.id === void 0 || body.id === null || body.id === "") {
286
- body.id = mintUuid();
347
+ body.id = opts.idempotencyKey ? keyedId(opts.idempotencyKey, 0) : mintUuid();
287
348
  }
288
349
  return this.request("POST", `/${type}/`, {
289
350
  write: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.27.0",
3
+ "version": "4.27.1",
4
4
  "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
5
  "type": "module",
6
6
  "exports": {