@onlyworlds/sdk 4.62.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/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # For AI agents using @onlyworlds/sdk
2
2
 
3
- **Current as of**: SDK **4.x** · schema-dist **v0.33.0-dist.48** (canonical 00.33.00).
3
+ **Current as of**: SDK **4.x** · schema-dist **v0.33.0-dist.49** (canonical 00.33.00).
4
4
  This line is asserted by `codegen:check` in CI — if the pin moves and this file is not
5
5
  re-read against it, the check fails rather than letting this document rot quietly.
6
6
 
@@ -88,6 +88,8 @@ against live data). The v1 surface (`OnlyWorldsClient`, the `ElementType` enum)
88
88
  §4.7 (`.owworld`): the reader checks the central directory before inflating
89
89
  and ignores files beside the world's folder; the writer's output is byte-stable (marker
90
90
  first, UTF-8 name order, 1980 stamps) and holds only `world.json` and the folder's parts.
91
+ Producers marked `"private": true` in `records/RECORDS.json` are left out whole unless
92
+ `writeWorldFile(files, topDir, { includePrivateRecords: true })` (since 4.63.0; OW-8).
91
93
  - World calendars (since 4.20.0): `worldCalendars(elements)` finds the elements carrying
92
94
  `atlas_calendar` (Atlas owns the field: read it, never write it); `resolveCalendarLens` picks
93
95
  one; `formatStep(config, step)` names a point on the world's integer time line, `parseDate`
package/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ 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
+
15
+ ## [4.63.0] - 2026-10-23
16
+
17
+ ### Added
18
+
19
+ - **`writeWorldFile(files, topDir, { includePrivateRecords })`**, OW-8's private clause (folder format spec v0.3.15, §4.5 rule 11 and §4.7). By default the writer now leaves out, whole, every `records/<producer>/` directory whose entry in `records/RECORDS.json` says `"private": true`, so a tool's own notes do not travel the first time a world is shared. `RECORDS.json` itself travels byte-exact, its private entries kept, so a reader can see a producer was left out. Only the boolean `true` marks a directory private (`"true"`, a string, does not). A producer's name matches its directory by the archive's one fold (NFC, then simple case folding), so a manifest's `Fold` leaves out `records/fold/` on a disk that ignores case. With `includePrivateRecords: true` every record travels as before and the manifest is not read. No manifest, nothing is private. Type `OwWriteWorldFileOptions`.
20
+ - **A manifest the writer cannot read is refused, not guessed at**: a `RECORDS.json` that is not JSON, not an array, holds an entry that is not an object, or marks an entry private without a producer name throws a `TypeError` naming the file and the option, having written nothing, unless `includePrivateRecords: true` is passed. A world whose manifest is in an older shape now needs the option to be written. The options object refuses an unknown key, and an `includePrivateRecords` that is not a boolean, with a `TypeError`, before anything else.
21
+ - Tests: the conformance set's four private cases, written from its `sources.private` world (each source file checked by its sha256): the default side equals `pass-OW-8-private-left-out` and the asked side `pass-OW-8-private-asked`, entry for entry, and neither is `fail-OW-8-private-partial` or `fail-OW-8-private-manifest-rewritten`. Also the string `"true"`, a producer whose name is a prefix of another's, no manifest, each unreadable manifest refused and carried when asked, and the options. Each behaviour watched failing on its own mutant.
22
+
23
+ ### Changed
24
+
25
+ - **Schema repinned to schema-dist `v0.33.0-dist.49`** (same canonical 00.33.00; no schema file moved, no generated type changed, `ONLYWORLDS_VERSION` still `'00.33.00'`). The `.owworld` conformance set grows to 85 zips with the four OW-8 private-records writer cases on `sources.private`; none of the earlier 81 moved.
26
+
8
27
  ## [4.62.0] - 2026-10-22
9
28
 
10
29
  ### Added
package/README.md CHANGED
@@ -285,6 +285,8 @@ Files beside the world's folder and archive junk (`__MACOSX`, `.DS_Store`) are i
285
285
 
286
286
  The writer gives the same bytes for the same files: the `mimetype` marker first, then every entry in the UTF-8 order of its name, stamped 1980-01-01 00:00. It writes `world.json`, `elements/`, `spatial/`, `media/` and `records/`, and leaves any other file out. A tool's dot-folder you hand it travels too, which makes the file a private copy rather than a portable `.owworld`.
287
287
 
288
+ `records/` travels byte for byte, except a producer's directory that `records/RECORDS.json` marks `"private": true`: that one is left out whole, and `RECORDS.json` itself travels unchanged, so the reader can see a producer was left out. Pass `writeWorldFile(files, topDir, { includePrivateRecords: true })` to carry private records too. A `RECORDS.json` that is not a JSON array of objects is refused with a `TypeError` unless you pass that option, since the writer cannot tell which records are private.
289
+
288
290
  ## World calendars
289
291
 
290
292
  A world's time is an integer line (`time_range_current` is a point on it, and so is an Event's `start_date`). A calendar names its points: months, days, eras. Atlas keeps a calendar on an element (a Construct by convention) in its `atlas_calendar` field, and the SDK reads it with Atlas's own calendar code (since 4.20.0). Atlas owns that field and is its only writer; read it, never write it.
package/SCHEMA.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OnlyWorlds Schema Reference
2
2
 
3
- **Source**: https://github.com/OnlyWorlds/schema-dist @ **v0.33.0-dist.48** — canonical schema **00.33.00**, published 2026-10-22.
3
+ **Source**: https://github.com/OnlyWorlds/schema-dist @ **v0.33.0-dist.49** — canonical schema **00.33.00**, published 2026-10-22.
4
4
 
5
5
  GENERATED from the canonical schema YAML — do not hand-edit (regenerate: `python codegen/generate_types.py`).
6
6
  Written for both humans and AI agents reading this package locally.
package/dist/index.d.ts CHANGED
@@ -4324,7 +4324,9 @@ declare function typeWord(world: object | null | undefined, type: string, opts?:
4324
4324
  * THE WRITER emits a portable copy (OW-7, OW-12, OW-13): the `mimetype` marker
4325
4325
  * first and stored, then only the folder's parts, every entry stamped
4326
4326
  * 1980-01-01 00:00, in the UTF-8 byte order of its name, JSON deflated and
4327
- * everything else stored.
4327
+ * everything else stored. `records/` travels whole and byte-exact, except the
4328
+ * producers `records/RECORDS.json` marks private, left out whole unless the
4329
+ * caller asks (OW-8, §4.5 rule 11).
4328
4330
  *
4329
4331
  * A tool's own `.<tool>/` folder is the tool's (spec §4): the reader returns
4330
4332
  * it like any file (a caller leaves files out with `skip`), and the writer
@@ -4369,9 +4371,17 @@ interface OwReadWorldFileOptions {
4369
4371
  declare function simpleCaseFold(text: string): string;
4370
4372
  /**
4371
4373
  * Read a world file. Throws `OwWorldFileError` saying why, having returned
4372
- * 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.
4373
4376
  */
4374
4377
  declare function readWorldFile(bytes: Uint8Array, opts?: OwReadWorldFileOptions): OwReadWorldFile;
4378
+ interface OwWriteWorldFileOptions {
4379
+ /**
4380
+ * Carry the producers marked private in `records/RECORDS.json` (spec §4.5
4381
+ * rule 11, OW-8). Left out or false, their directories are left out whole.
4382
+ */
4383
+ includePrivateRecords?: boolean;
4384
+ }
4375
4385
  /**
4376
4386
  * Write a world file under one top-level folder `topDir`: the `mimetype`
4377
4387
  * marker first and stored, then `world.json` (without `api`) and the folder's
@@ -4379,12 +4389,26 @@ declare function readWorldFile(bytes: Uint8Array, opts?: OwReadWorldFileOptions)
4379
4389
  * deflated and everything else stored (spec §4.7, OW-6, OW-7, OW-12, OW-13).
4380
4390
  * Any other file is left out. A tool's dot-folder in `files` travels, and
4381
4391
  * makes the result a private copy, not a portable `.owworld` (OW-7): leave it
4382
- * out of `files` for a portable one. Refuses (`OwWorldFileError`) an unsafe
4383
- * path or `topDir`, a duplicate path, a name some disk cannot hold, two names
4384
- * that fold to one, and a world.json that is missing, will not parse (it may
4385
- * hold a key, so it is never zipped raw) or has no `id`.
4392
+ * out of `files` for a portable one.
4393
+ *
4394
+ * `records/` travels byte-exact, except private records (OW-8, spec §4.5 rule
4395
+ * 11): every `records/<producer>/` whose entry in `records/RECORDS.json` says
4396
+ * `"private": true` (the boolean; `"true"` is not private; the name matched by
4397
+ * OW-18's fold, so `Fold` names `records/fold/`) is left out whole,
4398
+ * and `RECORDS.json` itself travels byte-exact, its private entries kept, so a
4399
+ * reader learns a producer was left out. Pass `{ includePrivateRecords: true }`
4400
+ * to carry them; then the manifest is not read and every record travels. No
4401
+ * manifest, nothing is private.
4402
+ *
4403
+ * Throws a `TypeError`, having written nothing, for an unknown option, an
4404
+ * `includePrivateRecords` that is not a boolean, and (unless private records
4405
+ * are asked for) a `RECORDS.json` that is not a JSON array of objects or that
4406
+ * marks an entry private without a producer name. Refuses (`OwWorldFileError`)
4407
+ * an unsafe path or `topDir`, a duplicate path, a name some disk cannot hold,
4408
+ * two names that fold to one, and a world.json that is missing, will not parse
4409
+ * (it may hold a key, so it is never zipped raw) or has no `id`.
4386
4410
  */
4387
- declare function writeWorldFile(files: OwWorldFileEntry[], topDir: string): Uint8Array;
4411
+ declare function writeWorldFile(files: OwWorldFileEntry[], topDir: string, opts?: OwWriteWorldFileOptions): Uint8Array;
4388
4412
 
4389
4413
  /**
4390
4414
  * World lookup: find the elements a plain-language question is about, in a
@@ -4512,13 +4536,13 @@ declare function buildWorldIndex<T extends OwLookupElement>(elements: Iterable<T
4512
4536
  * elements whose other text holds the question's words. An empty question
4513
4537
  * returns no hits.
4514
4538
  */
4515
- 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>[];
4516
4540
  /**
4517
4541
  * A compact text card for one element, for a person or a model: name, type,
4518
4542
  * kind, description, its fields, its links by name (or how many point outside
4519
4543
  * the data), and what links to it.
4520
4544
  */
4521
- 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;
4522
4546
 
4523
4547
  /**
4524
4548
  * Context for keel's AI proxy (POST /api/v2/account/ask, Keel #159): the elements a
@@ -4618,9 +4642,9 @@ declare function planPhoto(source: {
4618
4642
  type: string;
4619
4643
  }, opts?: OwPhotoOptions): OwPhotoPlan;
4620
4644
  /**
4621
- * Shrink and convert a picture for upload. Throws `TypeError` outside a
4622
- * browser, for a file the browser cannot decode, or when the result is still
4623
- * 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`.
4624
4648
  */
4625
4649
  declare function preparePhoto(input: Blob, opts?: OwPhotoOptions): Promise<OwPreparedPhoto>;
4626
4650
 
@@ -4885,7 +4909,8 @@ declare function worldCalendars(elements: Iterable<OwCalendarElement>): WorldCal
4885
4909
  *
4886
4910
  * `fallbackToOnly`: when no calendar was ever chosen (`activeId` is `undefined`, not
4887
4911
  * `null`) and the world holds exactly one, use it. `null` is an explicit "no calendar" and
4888
- * 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).
4889
4914
  */
4890
4915
  declare function resolveCalendarLens(elements: Iterable<OwCalendarElement>, activeId: string | null | undefined, opts?: {
4891
4916
  fallbackToOnly?: boolean;
@@ -5195,4 +5220,4 @@ declare class TokenResource {
5195
5220
  getEncryptionInfo(): Promise<EncryptionInfo>;
5196
5221
  }
5197
5222
 
5198
- export { AGENT_DOORS, ASK_LIMITS, type AccessKeyResponse, CALENDAR_FIELD, CALENDAR_SUPERTYPE, type CalendarConfig, type CalendarEra, type CalendarIntercalary, type CalendarMonths, type CalendarWeek, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, type EraDomain, FAMILY_COLORS, FAMILY_ORDER, FIELD_ACRONYMS, FIELD_SCHEMA, FILE_EXTENSIONS, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, type OwAccountWorld, type OwAgentJoined, type OwAllowance, OwApiError, type OwAskAnswer, type OwAskContext, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwCalendarElement, type OwChange, type OwChangeDelete, type OwChangeUpsert, type OwChangesPage, type OwClientConfig, type OwCreatedWorldLink, type OwDeleteReason, type OwDraught, type OwDraughtComponent, type OwDraughtItem, type OwDraughtPolicy, type OwElement, type OwElementBase, type OwErrorBody, type OwFileAddedBy, type OwFileDownload, type OwFileKind, type OwFileRow, type OwFileTicket, type OwFrontDoor, type OwHiddenRow, type OwJoinPreview, type OwJoined, type OwJoinedMembership, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMe, type OwMediaTicket, type OwMember, type OwMemberRole, type OwMintedKey, type OwNamesMap, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, type OwPlayerJoined, OwPreconditionFailedError, type OwPreparedPhoto, type OwProposal, type OwProposalAccepted, type OwProposalConflict, type OwProposalList, type OwProposalQuery, type OwProposalState, type OwProposeInput, type OwProposed, OwProposedError, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwResyncReason, type OwSeat, type OwSeatCreated, type OwSignInBegin, type OwSignInBegun, type OwSignInComplete, OwSignInError, type OwSignInReason, type OwSignInResult, type OwSignInStorage, type OwSignedIn, type OwSortAddToRow, type OwSortAnswer, type OwSortAskRow, type OwSortLinkRow, type OwSortMakeRow, type OwSortNote, type OwSortRef, type OwSortRow, OwStaleProposalError, type OwUploadedFile, type OwUploadedImage, OwV2Client, type OwWordPair, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldFileRow, type OwWorldFiles, type OwWorldIndex, type OwWorldLink, type OwWorldMeta, type OwWorldProposalQuery, type OwWorldPublicPage, type OwWriteDoor, PHOTO_MAX_BYTES, PHOTO_TYPES, PUBLIC_FIELDS, type ParseError, type ParseOptions, type ParseResult, ROOM_PAIRS, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type StepBreakdown, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, WORLD_FILE_MEDIA_TYPE, type WorldCalendar, absYearForEraYear, absoluteYear, approxYearLength, base64url, breakdownStep, buildAskContext, buildWorldIndex, detectKeyKind, displayYear, elementColor, eraForStep, errorFromResponse, familyOf, fieldLabel, fileKindOf, floorDiv, floorMod, formatDuration, formatMidLabel, formatStep, formatYearLabel, getElementIcon, getElementLabel, isAgentDoor, isDemoKey, isKnownOf, isLeapYear, isParseError, isRoomElement, kindCanWrite, lookupWorld, monthStartSteps, parseDate, parseErrorEnvelope, pinExpectation, pkceChallenge, planPhoto, preparePhoto, readCalendarConfig, readCalendarValue, readWorldFile, renderElement, resolveCalendarLens, resolveEraDomains, simpleCaseFold, typeWord, validateCalendarConfig, weekdayForStep, worldCalendars, writeWorldFile, yearLength, yearStartStep };
5223
+ export { AGENT_DOORS, ASK_LIMITS, type AccessKeyResponse, CALENDAR_FIELD, CALENDAR_SUPERTYPE, type CalendarConfig, type CalendarEra, type CalendarIntercalary, type CalendarMonths, type CalendarWeek, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, type EraDomain, FAMILY_COLORS, FAMILY_ORDER, FIELD_ACRONYMS, FIELD_SCHEMA, FILE_EXTENSIONS, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, type OwAccountWorld, type OwAgentJoined, type OwAllowance, OwApiError, type OwAskAnswer, type OwAskContext, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwCalendarElement, type OwChange, type OwChangeDelete, type OwChangeUpsert, type OwChangesPage, type OwClientConfig, type OwCreatedWorldLink, type OwDeleteReason, type OwDraught, type OwDraughtComponent, type OwDraughtItem, type OwDraughtPolicy, type OwElement, type OwElementBase, type OwErrorBody, type OwFileAddedBy, type OwFileDownload, type OwFileKind, type OwFileRow, type OwFileTicket, type OwFrontDoor, type OwHiddenRow, type OwJoinPreview, type OwJoined, type OwJoinedMembership, type OwKeyKind, type OwLinkEdit, type OwLookupElement, type OwLookupHit, type OwLookupLink, type OwLookupOptions, type OwLookupReason, type OwMe, type OwMediaTicket, type OwMember, type OwMemberRole, type OwMintedKey, type OwNamesMap, OwNetworkError, type OwPage, type OwPhotoOptions, type OwPhotoPlan, type OwPlayerJoined, OwPreconditionFailedError, type OwPreparedPhoto, type OwProposal, type OwProposalAccepted, type OwProposalConflict, type OwProposalList, type OwProposalQuery, type OwProposalState, type OwProposeInput, type OwProposed, OwProposedError, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwRenderOptions, type OwResyncReason, type OwSeat, type OwSeatCreated, type OwSignInBegin, type OwSignInBegun, type OwSignInComplete, OwSignInError, type OwSignInReason, type OwSignInResult, type OwSignInStorage, type OwSignedIn, type OwSortAddToRow, type OwSortAnswer, type OwSortAskRow, type OwSortLinkRow, type OwSortMakeRow, type OwSortNote, type OwSortRef, type OwSortRow, OwStaleProposalError, type OwUploadedFile, type OwUploadedImage, OwV2Client, type OwWordPair, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldFileRow, type OwWorldFiles, type OwWorldIndex, type OwWorldLink, type OwWorldMeta, type OwWorldProposalQuery, type OwWorldPublicPage, type OwWriteDoor, type OwWriteWorldFileOptions, PHOTO_MAX_BYTES, PHOTO_TYPES, PUBLIC_FIELDS, type ParseError, type ParseOptions, type ParseResult, ROOM_PAIRS, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type StepBreakdown, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, WORLD_FILE_MEDIA_TYPE, type WorldCalendar, absYearForEraYear, absoluteYear, approxYearLength, base64url, breakdownStep, buildAskContext, buildWorldIndex, detectKeyKind, displayYear, elementColor, eraForStep, errorFromResponse, familyOf, fieldLabel, fileKindOf, floorDiv, floorMod, formatDuration, formatMidLabel, formatStep, formatYearLabel, getElementIcon, getElementLabel, isAgentDoor, isDemoKey, isKnownOf, isLeapYear, isParseError, isRoomElement, kindCanWrite, lookupWorld, monthStartSteps, parseDate, parseErrorEnvelope, pinExpectation, pkceChallenge, planPhoto, preparePhoto, readCalendarConfig, readCalendarValue, readWorldFile, renderElement, resolveCalendarLens, resolveEraDomains, simpleCaseFold, typeWord, validateCalendarConfig, weekdayForStep, worldCalendars, writeWorldFile, yearLength, yearStartStep };
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);
@@ -3249,7 +3281,43 @@ function carried(path) {
3249
3281
  return !segs.some((s) => s.startsWith(".")) && !path.endsWith(".meta");
3250
3282
  }
3251
3283
  var STAMP = new Date(1980, 0, 1, 0, 0, 0);
3252
- function writeWorldFile(files, topDir) {
3284
+ var WRITE_OPTION_KEYS = ["includePrivateRecords"];
3285
+ var RECORDS_MANIFEST = "records/RECORDS.json";
3286
+ function asksPrivate(opts) {
3287
+ if (opts === void 0 || opts === null) return false;
3288
+ if (typeof opts !== "object") throw new TypeError(`writeWorldFile: options must be an object, such as { includePrivateRecords: true }. Nothing was written.`);
3289
+ refuseUnknownOptions("writeWorldFile", opts, WRITE_OPTION_KEYS, "Nothing was written.");
3290
+ const asked = opts.includePrivateRecords;
3291
+ if (asked !== void 0 && typeof asked !== "boolean") {
3292
+ throw new TypeError(`writeWorldFile: includePrivateRecords must be true or false; got ${JSON.stringify(asked) ?? String(asked)}. Nothing was written.`);
3293
+ }
3294
+ return asked === true;
3295
+ }
3296
+ function privateProducers(files) {
3297
+ const manifest = files.find((f) => f.path === RECORDS_MANIFEST);
3298
+ if (!manifest) return [];
3299
+ const unreadable = (why) => {
3300
+ throw new TypeError(`writeWorldFile: ${RECORDS_MANIFEST} ${why}, so which records are private cannot be read and none is left out by guess (spec \xA74.7, OW-8). Fix the manifest, or pass { includePrivateRecords: true } to carry every record. Nothing was written.`);
3301
+ };
3302
+ let doc;
3303
+ try {
3304
+ doc = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(manifest.data));
3305
+ } catch {
3306
+ unreadable("is not JSON");
3307
+ }
3308
+ if (!Array.isArray(doc)) unreadable("is not a JSON array of entries");
3309
+ const out = [];
3310
+ for (const entry of doc) {
3311
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) unreadable("holds an entry that is not an object");
3312
+ const { producer, private: isPrivate } = entry;
3313
+ if (isPrivate !== true) continue;
3314
+ if (typeof producer !== "string" || producer === "") unreadable("marks an entry private without a producer name");
3315
+ out.push(producer);
3316
+ }
3317
+ return out;
3318
+ }
3319
+ function writeWorldFile(files, topDir, opts = {}) {
3320
+ const includePrivate = asksPrivate(opts);
3253
3321
  if (pathFault(topDir) || topDir.includes("/")) throw new OwWorldFileError("unsafe_path");
3254
3322
  const seen = /* @__PURE__ */ new Set();
3255
3323
  for (const f of files) {
@@ -3257,7 +3325,12 @@ function writeWorldFile(files, topDir) {
3257
3325
  if (seen.has(f.path)) throw new OwWorldFileError("unsafe_path", `The path ${f.path} is listed twice.`);
3258
3326
  seen.add(f.path);
3259
3327
  }
3260
- const kept = files.filter((f) => carried(f.path));
3328
+ const left = new Set(includePrivate ? [] : privateProducers(files).map(foldName));
3329
+ const inPrivate = (path) => {
3330
+ const segs = path.split("/");
3331
+ return segs.length > 2 && segs[0] === "records" && left.has(foldName(segs[1]));
3332
+ };
3333
+ const kept = files.filter((f) => carried(f.path) && !inPrivate(f.path));
3261
3334
  if (!kept.some((f) => f.path === WORLD_FILE)) throw new OwWorldFileError("not_a_world");
3262
3335
  const keys = kept.map((f) => `${topDir}/${f.path}`);
3263
3336
  for (const key of keys) {
@@ -3324,7 +3397,7 @@ function isRoomElement(el) {
3324
3397
 
3325
3398
  // src/names.ts
3326
3399
  var SLUGS = new Set(ELEMENT_TYPES);
3327
- var OPTION_KEYS = /* @__PURE__ */ new Set(["supertype", "many"]);
3400
+ var OPTION_KEYS = ["supertype", "many"];
3328
3401
  var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3329
3402
  function word(pair, many) {
3330
3403
  if (!isObject(pair) || typeof pair.one !== "string") return null;
@@ -3335,9 +3408,7 @@ function word(pair, many) {
3335
3408
  return plural === "" ? one : plural;
3336
3409
  }
3337
3410
  function typeWord(world, type, opts = {}) {
3338
- for (const key of Object.keys(opts ?? {})) {
3339
- if (!OPTION_KEYS.has(key)) throw new TypeError(`typeWord: unknown option "${key}" (known: supertype, many)`);
3340
- }
3411
+ refuseUnknownOptions("typeWord", opts, OPTION_KEYS);
3341
3412
  const map = world?.x_names;
3342
3413
  if (!isObject(map) || map.version !== 1) return null;
3343
3414
  const { types, supertypes } = map;
@@ -3360,6 +3431,8 @@ function typeWord(world, type, opts = {}) {
3360
3431
  }
3361
3432
 
3362
3433
  // src/lookup.ts
3434
+ var LOOKUP_OPTION_KEYS = ["limit"];
3435
+ var RENDER_OPTION_KEYS = ["descMax", "linkMax", "incomingMax"];
3363
3436
  var STOP = /* @__PURE__ */ new Set([
3364
3437
  "a",
3365
3438
  "an",
@@ -3663,7 +3736,9 @@ function similarWords(w, vocab) {
3663
3736
  }
3664
3737
  return res;
3665
3738
  }
3666
- function lookupWorld(index, question, { limit = 8 } = {}) {
3739
+ function lookupWorld(index, question, opts = {}) {
3740
+ refuseUnknownOptions("lookupWorld", opts, LOOKUP_OPTION_KEYS);
3741
+ const { limit = 8 } = opts;
3667
3742
  const seeds = 12;
3668
3743
  const q = words(normalize(question));
3669
3744
  const qSet = new Set(q);
@@ -3780,7 +3855,9 @@ function lookupWorld(index, question, { limit = 8 } = {}) {
3780
3855
  for (const [id, c] of cand) if (SPATIAL.has(index.byId.get(id).type)) c.score *= 0.85;
3781
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);
3782
3857
  }
3783
- 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;
3784
3861
  const el = element;
3785
3862
  const name = (id) => index.byId.get(id)?.name ?? "?";
3786
3863
  const kind = [el.supertype, el.subtype].filter((x) => x && x !== "undefined").join(" / ");
@@ -3864,7 +3941,9 @@ function buildAskContext(items, index) {
3864
3941
  // src/photo.ts
3865
3942
  var PHOTO_TYPES = ["image/webp", "image/png", "image/jpeg", "image/avif"];
3866
3943
  var PHOTO_MAX_BYTES = 15 * 1024 * 1024;
3944
+ var PHOTO_OPTION_KEYS = ["maxSide", "quality", "maxBytes"];
3867
3945
  function planPhoto(source, opts = {}) {
3946
+ refuseUnknownOptions("planPhoto", opts, PHOTO_OPTION_KEYS);
3868
3947
  const maxSide = opts.maxSide ?? 2048;
3869
3948
  if (!(maxSide > 0)) throw new TypeError("preparePhoto: maxSide must be a positive number");
3870
3949
  if (!(source.width > 0 && source.height > 0)) throw new TypeError("preparePhoto: the picture has no size");
@@ -3889,6 +3968,7 @@ function makeCanvas(width, height) {
3889
3968
  throw new TypeError("preparePhoto needs a browser: no canvas here");
3890
3969
  }
3891
3970
  async function preparePhoto(input, opts = {}) {
3971
+ refuseUnknownOptions("preparePhoto", opts, PHOTO_OPTION_KEYS);
3892
3972
  const quality = opts.quality ?? 0.82;
3893
3973
  const maxBytes = opts.maxBytes ?? PHOTO_MAX_BYTES;
3894
3974
  const decode = globalThis.createImageBitmap;
@@ -4227,6 +4307,7 @@ function formatMidLabel(cfg, step) {
4227
4307
  if (b.monthName != null && b.day != null) return `${b.day} ${b.monthName} ${b.year}`;
4228
4308
  return formatYearLabel(cfg, step);
4229
4309
  }
4310
+ var PARSE_OPTION_KEYS = ["now"];
4230
4311
  function absYearForEraYear(cfg, era, Y) {
4231
4312
  if (era.restart === false) return Y;
4232
4313
  const base = era.year_base ?? 1;
@@ -4245,6 +4326,7 @@ function defaultEra(cfg, now) {
4245
4326
  return [...pool].sort((a, b) => b.start - a.start)[0];
4246
4327
  }
4247
4328
  function parseDate(cfg, input, opts = {}) {
4329
+ refuseUnknownOptions("parseDate", opts, PARSE_OPTION_KEYS);
4248
4330
  const raw = input.trim();
4249
4331
  if (raw === "") return { error: "empty date" };
4250
4332
  if (/^-?\d+$/.test(raw)) return { step: parseInt(raw, 10) };
@@ -4375,6 +4457,7 @@ function worldCalendars(elements) {
4375
4457
  return out;
4376
4458
  }
4377
4459
  function resolveCalendarLens(elements, activeId, opts) {
4460
+ refuseUnknownOptions("resolveCalendarLens", opts, ["fallbackToOnly"]);
4378
4461
  if (activeId === void 0 && opts?.fallbackToOnly) {
4379
4462
  const all = worldCalendars(elements);
4380
4463
  return all.length === 1 ? all[0] : null;
@@ -4454,6 +4537,7 @@ var TokenResource = class {
4454
4537
  * ```
4455
4538
  */
4456
4539
  async consume(params) {
4540
+ refuseUnknownOptions("consume", params, ["amount", "service", "sessionId", "metadata"], "Nothing was sent.");
4457
4541
  return this.req("POST", "/tokens/consume/", {
4458
4542
  body: {
4459
4543
  amount: params.amount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.62.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": {