@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 +7 -0
- package/dist/index.d.ts +9 -7
- package/dist/index.js +56 -17
- package/package.json +1 -1
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,
|
|
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>,
|
|
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`
|
|
4645
|
-
*
|
|
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,
|
|
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
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|