@onlyworlds/sdk 4.63.0 → 4.64.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
@@ -5,6 +5,13 @@ earlier history lives in git log only.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.64.0] - 2026-10-23
9
+
10
+ ### Changed
11
+
12
+ - **Every options object in the package now refuses an unknown key**, not only the client's typed methods (4.51.0 to 4.53.0): `readWorldFile`, `parseDate`, `resolveCalendarLens`, `lookupWorld`, `renderElement`, `planPhoto`, `preparePhoto`, the client's `request()` escape hatch, `listAll` (with its own keys: a `cursor` it was handed was overwritten unread), the deprecated `tokens.consume` and `new OwSignInError`'s third argument throw a `TypeError` naming the function, the key and the keys it takes, before anything is read, decoded, parsed or sent. A dropped key was never harmless: `readWorldFile(bytes, { maxbytes: 1e6 })` read with the 2 GiB budget, `parseDate(cfg, input, { Now: 12 })` parsed as if the world had no now, and W11's `list('character', { name__icontains })` read every Character and a cleanup script deleted 99 of them (2026-10-22). A plain-JavaScript caller passing a misspelt key will now see a `TypeError` where the call used to run with the default; TypeScript callers were already refused at compile time. `writeWorldFile` and `typeWord` keep their refusals on the same shared check; `typeWord`'s message now reads like the others (`typeWord: unknown option 'plural'. The keys are supertype, many.`).
13
+ - Tests: one test calls every options parameter of the exported surface with `{ __unknown__: 1 }` and asserts a `TypeError` naming the key, nothing sent; a second reads `src/index.ts` with the TypeScript checker and fails on any exported object parameter that is neither in that list nor named as data (a calendar config, a world index, an element), so a new options object cannot ship unfenced. Watched failing on mutants of the `readWorldFile`, `parseDate`, `preparePhoto` and `request()` fences, and on an exported options function added without one.
14
+
8
15
  ## [4.63.0] - 2026-10-23
9
16
 
10
17
  ### Added
package/dist/index.d.ts CHANGED
@@ -4371,7 +4371,8 @@ interface OwReadWorldFileOptions {
4371
4371
  declare function simpleCaseFold(text: string): string;
4372
4372
  /**
4373
4373
  * Read a world file. Throws `OwWorldFileError` saying why, having returned
4374
- * nothing; the caller writes only what this returns.
4374
+ * nothing; the caller writes only what this returns. An unknown option is a
4375
+ * `TypeError` before the bytes are looked at.
4375
4376
  */
4376
4377
  declare function readWorldFile(bytes: Uint8Array, opts?: OwReadWorldFileOptions): OwReadWorldFile;
4377
4378
  interface OwWriteWorldFileOptions {
@@ -4535,13 +4536,13 @@ declare function buildWorldIndex<T extends OwLookupElement>(elements: Iterable<T
4535
4536
  * elements whose other text holds the question's words. An empty question
4536
4537
  * returns no hits.
4537
4538
  */
4538
- declare function lookupWorld<T extends OwLookupElement>(index: OwWorldIndex<T>, question: string, { limit }?: OwLookupOptions): OwLookupHit<T>[];
4539
+ declare function lookupWorld<T extends OwLookupElement>(index: OwWorldIndex<T>, question: string, opts?: OwLookupOptions): OwLookupHit<T>[];
4539
4540
  /**
4540
4541
  * A compact text card for one element, for a person or a model: name, type,
4541
4542
  * kind, description, its fields, its links by name (or how many point outside
4542
4543
  * the data), and what links to it.
4543
4544
  */
4544
- declare function renderElement<T extends OwLookupElement>(element: T, index: OwWorldIndex<T>, { descMax, linkMax, incomingMax }?: OwRenderOptions): string;
4545
+ declare function renderElement<T extends OwLookupElement>(element: T, index: OwWorldIndex<T>, opts?: OwRenderOptions): string;
4545
4546
 
4546
4547
  /**
4547
4548
  * Context for keel's AI proxy (POST /api/v2/account/ask, Keel #159): the elements a
@@ -4641,9 +4642,9 @@ declare function planPhoto(source: {
4641
4642
  type: string;
4642
4643
  }, opts?: OwPhotoOptions): OwPhotoPlan;
4643
4644
  /**
4644
- * Shrink and convert a picture for upload. Throws `TypeError` outside a
4645
- * browser, for a file the browser cannot decode, or when the result is still
4646
- * over `maxBytes`.
4645
+ * Shrink and convert a picture for upload. Throws `TypeError` for an unknown
4646
+ * option (before anything is decoded), outside a browser, for a file the
4647
+ * browser cannot decode, or when the result is still over `maxBytes`.
4647
4648
  */
4648
4649
  declare function preparePhoto(input: Blob, opts?: OwPhotoOptions): Promise<OwPreparedPhoto>;
4649
4650
 
@@ -4908,7 +4909,8 @@ declare function worldCalendars(elements: Iterable<OwCalendarElement>): WorldCal
4908
4909
  *
4909
4910
  * `fallbackToOnly`: when no calendar was ever chosen (`activeId` is `undefined`, not
4910
4911
  * `null`) and the world holds exactly one, use it. `null` is an explicit "no calendar" and
4911
- * stays null. With several calendars and no choice, nothing is guessed.
4912
+ * stays null. With several calendars and no choice, nothing is guessed. An unknown option is a
4913
+ * TypeError (the caller's bug, not the world's data).
4912
4914
  */
4913
4915
  declare function resolveCalendarLens(elements: Iterable<OwCalendarElement>, activeId: string | null | undefined, opts?: {
4914
4916
  fallbackToOnly?: boolean;
package/dist/index.js CHANGED
@@ -56,6 +56,19 @@ function keyedId(idempotencyKey, index) {
56
56
  ${index}`);
57
57
  }
58
58
 
59
+ // src/options.ts
60
+ function unknownKey(opts, keys) {
61
+ if (!opts || typeof opts !== "object") return void 0;
62
+ for (const k of Object.keys(opts)) if (!keys.includes(k)) return k;
63
+ return void 0;
64
+ }
65
+ function refuseUnknownOptions(fn, opts, keys, after = "") {
66
+ const k = unknownKey(opts, keys);
67
+ if (k !== void 0) {
68
+ throw new TypeError(`${fn}: unknown option '${k}'. The keys are ${keys.join(", ")}.${after ? ` ${after}` : ""}`);
69
+ }
70
+ }
71
+
59
72
  // src/v2/errors.ts
60
73
  var OwApiError = class extends Error {
61
74
  constructor(status, code, message, docUrl, detail, type = null, param = null, retryAfter = null) {
@@ -342,6 +355,7 @@ var OwNetworkError = class extends Error {
342
355
  };
343
356
  var OwSignInError = class extends Error {
344
357
  constructor(reason, message, opts = {}) {
358
+ refuseUnknownOptions("OwSignInError", opts, ["status", "code", "retryAfter"]);
345
359
  super(message);
346
360
  this.name = "OwSignInError";
347
361
  this.reason = reason;
@@ -652,7 +666,7 @@ var OwV2Client = class {
652
666
  // -- Element CRUD --------------------------------------------------------
653
667
  /** GET /{type}/ -- one cursor page. */
654
668
  async list(type, params = {}) {
655
- only("list", params, LIST_PARAM_KEYS, "A wire filter (name, name__icontains, supertype, subtype, characters) goes under filter: { ... }.");
669
+ only("list", params, LIST_PARAM_KEYS, LIST_FILTER_HINT);
656
670
  const query = buildQuery({
657
671
  limit: params.limit ?? this.pageSize,
658
672
  cursor: params.cursor,
@@ -691,6 +705,7 @@ var OwV2Client = class {
691
705
  }
692
706
  /** Cursor-walk every page of a type. Politeness: uses config pageSize. */
693
707
  async *listAll(type, params = {}) {
708
+ only("listAll", params, LIST_ALL_PARAM_KEYS, LIST_FILTER_HINT);
694
709
  let cursor;
695
710
  do {
696
711
  const page = await this.list(type, { ...params, cursor });
@@ -1718,6 +1733,7 @@ var OwV2Client = class {
1718
1733
  * sanitizePayload — callers own their body shape.
1719
1734
  */
1720
1735
  async request(method, path, opts = {}) {
1736
+ only("request", opts, REQUEST_OPTION_KEYS);
1721
1737
  if (opts.ifMatch !== void 0 && !(Number.isInteger(opts.ifMatch) && opts.ifMatch >= 0)) {
1722
1738
  throw new TypeError(`ifMatch must be a whole number (an element's change_seq), got ${String(opts.ifMatch)}`);
1723
1739
  }
@@ -1930,12 +1946,26 @@ function objectKey(keyOrUrl) {
1930
1946
  return decodeURIComponent(new URL(keyOrUrl).pathname.replace(/^\/+/, ""));
1931
1947
  }
1932
1948
  var LIST_PARAM_KEYS = ["limit", "cursor", "ids", "createdBy", "expand", "fields", "filter", "viewAs"];
1949
+ var LIST_ALL_PARAM_KEYS = LIST_PARAM_KEYS.filter((k) => k !== "cursor");
1950
+ var LIST_FILTER_HINT = "A wire filter (name, name__icontains, supertype, subtype, characters) goes under filter: { ... }.";
1951
+ var REQUEST_OPTION_KEYS = [
1952
+ "query",
1953
+ "body",
1954
+ "idempotencyKey",
1955
+ "auth",
1956
+ "allowEmpty",
1957
+ "replayAware",
1958
+ "write",
1959
+ "elementWrite",
1960
+ "proposable",
1961
+ "ifMatch",
1962
+ "ifNoneMatch",
1963
+ "onEtag"
1964
+ ];
1933
1965
  function only(method, obj, keys, hint = "") {
1934
- if (!obj || typeof obj !== "object") return;
1935
- for (const k of Object.keys(obj)) {
1936
- if (!keys.includes(k)) {
1937
- throw new TypeError(`${method}: unknown parameter '${k}'. ${hint ? hint + " " : ""}The keys are ${keys.join(", ")}. Nothing was sent.`);
1938
- }
1966
+ const k = unknownKey(obj, keys);
1967
+ if (k !== void 0) {
1968
+ throw new TypeError(`${method}: unknown parameter '${k}'. ${hint ? hint + " " : ""}The keys are ${keys.join(", ")}. Nothing was sent.`);
1939
1969
  }
1940
1970
  }
1941
1971
  var CLIENT_CONFIG_KEYS = ["apiKey", "apiPin", "baseUrl", "pageSize", "changesPageSize", "fetch", "agent", "client"];
@@ -2884,6 +2914,7 @@ var OwWorldFileError = class extends Error {
2884
2914
  this.reason = reason;
2885
2915
  }
2886
2916
  };
2917
+ var READ_OPTION_KEYS = ["maxBytes", "maxEntries", "skip"];
2887
2918
  var refuse = (reason, message) => {
2888
2919
  throw new OwWorldFileError(reason, message);
2889
2920
  };
@@ -3163,6 +3194,7 @@ function checkFileAndFolder(files) {
3163
3194
  }
3164
3195
  }
3165
3196
  function readWorldFile(bytes, opts = {}) {
3197
+ refuseUnknownOptions("readWorldFile", opts, READ_OPTION_KEYS, "Nothing was read.");
3166
3198
  const maxBytes = opts.maxBytes ?? WORLD_FILE_MAX_BYTES;
3167
3199
  const maxEntries = opts.maxEntries ?? WORLD_FILE_MAX_ENTRIES;
3168
3200
  const entries = centralDirectory(bytes);
@@ -3254,11 +3286,7 @@ var RECORDS_MANIFEST = "records/RECORDS.json";
3254
3286
  function asksPrivate(opts) {
3255
3287
  if (opts === void 0 || opts === null) return false;
3256
3288
  if (typeof opts !== "object") throw new TypeError(`writeWorldFile: options must be an object, such as { includePrivateRecords: true }. Nothing was written.`);
3257
- for (const k of Object.keys(opts)) {
3258
- if (!WRITE_OPTION_KEYS.includes(k)) {
3259
- throw new TypeError(`writeWorldFile: unknown option '${k}'. The keys are ${WRITE_OPTION_KEYS.join(", ")}. Nothing was written.`);
3260
- }
3261
- }
3289
+ refuseUnknownOptions("writeWorldFile", opts, WRITE_OPTION_KEYS, "Nothing was written.");
3262
3290
  const asked = opts.includePrivateRecords;
3263
3291
  if (asked !== void 0 && typeof asked !== "boolean") {
3264
3292
  throw new TypeError(`writeWorldFile: includePrivateRecords must be true or false; got ${JSON.stringify(asked) ?? String(asked)}. Nothing was written.`);
@@ -3369,7 +3397,7 @@ function isRoomElement(el) {
3369
3397
 
3370
3398
  // src/names.ts
3371
3399
  var SLUGS = new Set(ELEMENT_TYPES);
3372
- var OPTION_KEYS = /* @__PURE__ */ new Set(["supertype", "many"]);
3400
+ var OPTION_KEYS = ["supertype", "many"];
3373
3401
  var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3374
3402
  function word(pair, many) {
3375
3403
  if (!isObject(pair) || typeof pair.one !== "string") return null;
@@ -3380,9 +3408,7 @@ function word(pair, many) {
3380
3408
  return plural === "" ? one : plural;
3381
3409
  }
3382
3410
  function typeWord(world, type, opts = {}) {
3383
- for (const key of Object.keys(opts ?? {})) {
3384
- if (!OPTION_KEYS.has(key)) throw new TypeError(`typeWord: unknown option "${key}" (known: supertype, many)`);
3385
- }
3411
+ refuseUnknownOptions("typeWord", opts, OPTION_KEYS);
3386
3412
  const map = world?.x_names;
3387
3413
  if (!isObject(map) || map.version !== 1) return null;
3388
3414
  const { types, supertypes } = map;
@@ -3405,6 +3431,8 @@ function typeWord(world, type, opts = {}) {
3405
3431
  }
3406
3432
 
3407
3433
  // src/lookup.ts
3434
+ var LOOKUP_OPTION_KEYS = ["limit"];
3435
+ var RENDER_OPTION_KEYS = ["descMax", "linkMax", "incomingMax"];
3408
3436
  var STOP = /* @__PURE__ */ new Set([
3409
3437
  "a",
3410
3438
  "an",
@@ -3708,7 +3736,9 @@ function similarWords(w, vocab) {
3708
3736
  }
3709
3737
  return res;
3710
3738
  }
3711
- function lookupWorld(index, question, { limit = 8 } = {}) {
3739
+ function lookupWorld(index, question, opts = {}) {
3740
+ refuseUnknownOptions("lookupWorld", opts, LOOKUP_OPTION_KEYS);
3741
+ const { limit = 8 } = opts;
3712
3742
  const seeds = 12;
3713
3743
  const q = words(normalize(question));
3714
3744
  const qSet = new Set(q);
@@ -3825,7 +3855,9 @@ function lookupWorld(index, question, { limit = 8 } = {}) {
3825
3855
  for (const [id, c] of cand) if (SPATIAL.has(index.byId.get(id).type)) c.score *= 0.85;
3826
3856
  return [...cand.entries()].map(([id, c]) => ({ element: index.byId.get(id), score: Math.round(c.score * 10) / 10, reasons: c.reasons })).sort((a, b) => b.score - a.score || String(a.element.name).length - String(b.element.name).length || (a.element.id < b.element.id ? -1 : 1)).slice(0, limit);
3827
3857
  }
3828
- function renderElement(element, index, { descMax = 500, linkMax = 12, incomingMax = 10 } = {}) {
3858
+ function renderElement(element, index, opts = {}) {
3859
+ refuseUnknownOptions("renderElement", opts, RENDER_OPTION_KEYS);
3860
+ const { descMax = 500, linkMax = 12, incomingMax = 10 } = opts;
3829
3861
  const el = element;
3830
3862
  const name = (id) => index.byId.get(id)?.name ?? "?";
3831
3863
  const kind = [el.supertype, el.subtype].filter((x) => x && x !== "undefined").join(" / ");
@@ -3909,7 +3941,9 @@ function buildAskContext(items, index) {
3909
3941
  // src/photo.ts
3910
3942
  var PHOTO_TYPES = ["image/webp", "image/png", "image/jpeg", "image/avif"];
3911
3943
  var PHOTO_MAX_BYTES = 15 * 1024 * 1024;
3944
+ var PHOTO_OPTION_KEYS = ["maxSide", "quality", "maxBytes"];
3912
3945
  function planPhoto(source, opts = {}) {
3946
+ refuseUnknownOptions("planPhoto", opts, PHOTO_OPTION_KEYS);
3913
3947
  const maxSide = opts.maxSide ?? 2048;
3914
3948
  if (!(maxSide > 0)) throw new TypeError("preparePhoto: maxSide must be a positive number");
3915
3949
  if (!(source.width > 0 && source.height > 0)) throw new TypeError("preparePhoto: the picture has no size");
@@ -3934,6 +3968,7 @@ function makeCanvas(width, height) {
3934
3968
  throw new TypeError("preparePhoto needs a browser: no canvas here");
3935
3969
  }
3936
3970
  async function preparePhoto(input, opts = {}) {
3971
+ refuseUnknownOptions("preparePhoto", opts, PHOTO_OPTION_KEYS);
3937
3972
  const quality = opts.quality ?? 0.82;
3938
3973
  const maxBytes = opts.maxBytes ?? PHOTO_MAX_BYTES;
3939
3974
  const decode = globalThis.createImageBitmap;
@@ -4272,6 +4307,7 @@ function formatMidLabel(cfg, step) {
4272
4307
  if (b.monthName != null && b.day != null) return `${b.day} ${b.monthName} ${b.year}`;
4273
4308
  return formatYearLabel(cfg, step);
4274
4309
  }
4310
+ var PARSE_OPTION_KEYS = ["now"];
4275
4311
  function absYearForEraYear(cfg, era, Y) {
4276
4312
  if (era.restart === false) return Y;
4277
4313
  const base = era.year_base ?? 1;
@@ -4290,6 +4326,7 @@ function defaultEra(cfg, now) {
4290
4326
  return [...pool].sort((a, b) => b.start - a.start)[0];
4291
4327
  }
4292
4328
  function parseDate(cfg, input, opts = {}) {
4329
+ refuseUnknownOptions("parseDate", opts, PARSE_OPTION_KEYS);
4293
4330
  const raw = input.trim();
4294
4331
  if (raw === "") return { error: "empty date" };
4295
4332
  if (/^-?\d+$/.test(raw)) return { step: parseInt(raw, 10) };
@@ -4420,6 +4457,7 @@ function worldCalendars(elements) {
4420
4457
  return out;
4421
4458
  }
4422
4459
  function resolveCalendarLens(elements, activeId, opts) {
4460
+ refuseUnknownOptions("resolveCalendarLens", opts, ["fallbackToOnly"]);
4423
4461
  if (activeId === void 0 && opts?.fallbackToOnly) {
4424
4462
  const all = worldCalendars(elements);
4425
4463
  return all.length === 1 ? all[0] : null;
@@ -4499,6 +4537,7 @@ var TokenResource = class {
4499
4537
  * ```
4500
4538
  */
4501
4539
  async consume(params) {
4540
+ refuseUnknownOptions("consume", params, ["amount", "service", "sessionId", "metadata"], "Nothing was sent.");
4502
4541
  return this.req("POST", "/tokens/consume/", {
4503
4542
  body: {
4504
4543
  amount: params.amount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.63.0",
3
+ "version": "4.64.0",
4
4
  "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
5
  "type": "module",
6
6
  "exports": {