@onlyworlds/sdk 4.9.0 → 4.10.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 +4 -0
- package/CHANGELOG.md +22 -0
- package/README.md +16 -0
- package/dist/index.d.ts +68 -1
- package/dist/index.js +140 -1
- package/package.json +4 -1
package/AGENTS.md
CHANGED
|
@@ -40,6 +40,10 @@ against live data). The v1 surface (`OnlyWorldsClient`, the `ElementType` enum)
|
|
|
40
40
|
- Images: `uploadImage(bytes)` returns `{ url, ... }`; set `url` as `image_url`. The bytes go
|
|
41
41
|
to the edge with a single-use ticket from keel, never with the API key. `removeImage(urlOrKey)`
|
|
42
42
|
takes one back (its uploader or the world's owner); it leaves `image_url` on elements as is.
|
|
43
|
+
- World files (no account needed; since 4.10.0): `readWorldFile(bytes)` opens a `.zip` holding one
|
|
44
|
+
world folder and returns `{ worldId, name, world, files: [{ path, data }] }`;
|
|
45
|
+
`writeWorldFile(files, topDir)` makes one. Refusals throw `OwWorldFileError` with a `reason`.
|
|
46
|
+
`world.json`'s `api` block (link and key) is removed both ways.
|
|
43
47
|
- Ids: the client mints **UUIDv7** on an id-less `create` (since 4.2.0; keel mints v7 too).
|
|
44
48
|
A v4 or v7 id you supply is accepted. **Never sort elements by id**: worlds mix v7, v4
|
|
45
49
|
and legacy `06x…` ids (nibble 7 too, but seconds-first). For creation order use
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ earlier history lives in git log only.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [4.10.0] — 2026-10-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **World files.** `readWorldFile(bytes, { maxBytes, maxEntries, skip })` opens a `.zip` that
|
|
12
|
+
holds one world folder (Folder Format §4.1, a portable copy) and returns
|
|
13
|
+
`{ worldId, name, world, files }`; `writeWorldFile(files, topDir)` makes one. Bytes in,
|
|
14
|
+
bytes out: no DOM or file system, so it runs in the browser and in Node. Absorbed from
|
|
15
|
+
Atlas's reader (Temper's), so Atlas, Lantern and scripts open one file the same way.
|
|
16
|
+
The reader refuses before returning anything, with **`OwWorldFileError`** and a `reason`:
|
|
17
|
+
`unsafe_path` (climbs out, absolute, a drive, a backslash, an empty part, a path that is
|
|
18
|
+
both a file and a folder), `too_big` / `too_many` (1 GiB and 200,000 files uncompressed,
|
|
19
|
+
`WORLD_FILE_MAX_BYTES` / `WORLD_FILE_MAX_ENTRIES`; the inflated bytes are counted again,
|
|
20
|
+
since a declared size can lie), `not_a_world` (no `world.json`, unreadable, no string
|
|
21
|
+
`id`), `more_than_one` (two worlds, loose files beside the folder, a nested world.json),
|
|
22
|
+
`not_openable` (not a zip). Archive junk (`__MACOSX`, `.DS_Store`, `Thumbs.db`) is
|
|
23
|
+
dropped. `world.json`'s `api` (the link and its key) is removed on read and on write; a
|
|
24
|
+
`world.json` without one keeps its exact bytes. The writer refuses a `world.json` that will
|
|
25
|
+
not parse rather than zip it raw. Every world in a real Atlas folder (15, up to 3,832
|
|
26
|
+
files) round-trips byte for byte.
|
|
27
|
+
- **First runtime dependency**: `fflate` (zip, no dependencies of its own, the one Atlas
|
|
28
|
+
uses). Tree-shaken away for code that never calls the world-file functions.
|
|
29
|
+
|
|
8
30
|
## [4.9.0] — 2026-10-11
|
|
9
31
|
|
|
10
32
|
### Added
|
package/README.md
CHANGED
|
@@ -135,6 +135,22 @@ try {
|
|
|
135
135
|
|
|
136
136
|
The check is per element and the server never merges. Compare the copy you edited from with `err.current`: when the fields you changed are not the ones that moved, send again with `ifMatch: err.current.change_seq`; when the same field moved on both sides, ask the user which to keep. Without `ifMatch` a write is unconditional, as before.
|
|
137
137
|
|
|
138
|
+
## World files
|
|
139
|
+
|
|
140
|
+
A world file is one `.zip` holding a whole world folder (`world.json` and `elements/<type>/*.json`, as Atlas keeps a world on disk), the way Atlas downloads and opens one. It needs no account and works in the browser and in Node.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { readWorldFile, writeWorldFile, OwWorldFileError } from '@onlyworlds/sdk';
|
|
144
|
+
|
|
145
|
+
const world = readWorldFile(bytes); // a Uint8Array
|
|
146
|
+
world.worldId; world.name; world.world; // world.json, parsed
|
|
147
|
+
world.files; // [{ path, data }], relative to the world's folder, sorted
|
|
148
|
+
|
|
149
|
+
const zip = writeWorldFile(world.files, 'hollow-reach-0192a000'); // one top-level folder
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The reader refuses a file before returning anything: a path that could land outside the folder, more than 1 GiB or 200,000 files uncompressed (`maxBytes`, `maxEntries` to change them), no `world.json` with an `id`, or more than one world. Check `err.reason` on the `OwWorldFileError` it throws. The world's OnlyWorlds link and key (`world.json`'s `api`) never travel: both functions remove them. A tool's own folder (`.atlas/` and the like) comes back like any other file; pass `skip: path => …` to leave files out.
|
|
153
|
+
|
|
138
154
|
## Errors
|
|
139
155
|
|
|
140
156
|
Every non-2xx response throws `OwApiError` with the API's envelope: `.status`, `.type`, `.code`, `.param` (the field that failed) and `.docUrl`, which is worth showing to your users. A transport failure throws `OwNetworkError`. `err.isValidationError` covers the common case. A write sent with `ifMatch` that found the element moved throws `OwPreconditionFailedError` (412, `err.isPreconditionFailed`), an `OwApiError` that also carries `current`.
|
package/dist/index.d.ts
CHANGED
|
@@ -2459,6 +2459,73 @@ declare function getElementIcon(type: string): string;
|
|
|
2459
2459
|
/** Plural display label for an element type (e.g. phenomenon → "Phenomena"). */
|
|
2460
2460
|
declare function getElementLabel(elementType: ElementType): string;
|
|
2461
2461
|
|
|
2462
|
+
/**
|
|
2463
|
+
* The world file: one `.zip` holding one whole world as a portable copy
|
|
2464
|
+
* (Folder Format spec §4.1, "a portable copy"), and the reader that opens one
|
|
2465
|
+
* back. Absorbed from Atlas's `src/store/world-file.ts` (Temper's, Atlas
|
|
2466
|
+
* 1.17), so Atlas, Lantern and any script read one file the same way.
|
|
2467
|
+
*
|
|
2468
|
+
* THE FILE: one top-level folder holding every file of the world as it sits
|
|
2469
|
+
* on disk, except the OnlyWorlds link and its key (`world.json` without `api`).
|
|
2470
|
+
* JSON is deflated, everything else stored. A world.json at the root, with no
|
|
2471
|
+
* folder, reads the same.
|
|
2472
|
+
*
|
|
2473
|
+
* THE READER takes bytes and returns bytes: no DOM, `File` or file system, so
|
|
2474
|
+
* it runs in a browser and in Node alike. It refuses, before returning
|
|
2475
|
+
* anything: a path that climbs out, is absolute, names a drive, holds a
|
|
2476
|
+
* backslash or an empty part, or is both a file and a folder; a declared size
|
|
2477
|
+
* or a file count past the budget (and the inflated bytes are counted again,
|
|
2478
|
+
* since a declared size can lie); no world, more than one world, files
|
|
2479
|
+
* outside the world's folder, a world.json nested inside the world; and a
|
|
2480
|
+
* world.json that will not parse or has no string `id`. Archive junk
|
|
2481
|
+
* (`__MACOSX`, `.DS_Store`, `Thumbs.db`) is dropped.
|
|
2482
|
+
*
|
|
2483
|
+
* A tool's own `.<tool>/` folder is the tool's (spec §4): this module knows
|
|
2484
|
+
* none of them. A caller that leaves some of its files out passes `skip`.
|
|
2485
|
+
*/
|
|
2486
|
+
/** What one world file may hold, uncompressed: Atlas's budget, the writer's and the reader's alike. */
|
|
2487
|
+
declare const WORLD_FILE_MAX_BYTES: number;
|
|
2488
|
+
declare const WORLD_FILE_MAX_ENTRIES = 200000;
|
|
2489
|
+
type OwWorldFileReason = 'unsafe_path' | 'too_big' | 'too_many' | 'not_a_world' | 'more_than_one' | 'not_openable';
|
|
2490
|
+
/** A world file was refused; nothing was returned. `reason` says why, for a tool to word its own message. */
|
|
2491
|
+
declare class OwWorldFileError extends Error {
|
|
2492
|
+
readonly reason: OwWorldFileReason;
|
|
2493
|
+
constructor(reason: OwWorldFileReason, message?: string);
|
|
2494
|
+
}
|
|
2495
|
+
interface OwWorldFileEntry {
|
|
2496
|
+
/** Relative to the world's folder, `/`-separated. */
|
|
2497
|
+
path: string;
|
|
2498
|
+
data: Uint8Array;
|
|
2499
|
+
}
|
|
2500
|
+
interface OwReadWorldFile {
|
|
2501
|
+
worldId: string;
|
|
2502
|
+
/** `world.json`'s `name` as the file has it; `''` when it has none. */
|
|
2503
|
+
name: string;
|
|
2504
|
+
/** `world.json`, parsed, without `api`. */
|
|
2505
|
+
world: Record<string, unknown>;
|
|
2506
|
+
/** The world's files, relative to its folder, `world.json` included (without `api`), sorted by path. */
|
|
2507
|
+
files: OwWorldFileEntry[];
|
|
2508
|
+
}
|
|
2509
|
+
interface OwReadWorldFileOptions {
|
|
2510
|
+
maxBytes?: number;
|
|
2511
|
+
maxEntries?: number;
|
|
2512
|
+
/** Leave a file out of `files` (a path relative to the world's folder). It still counts against the budget. */
|
|
2513
|
+
skip?: (path: string) => boolean;
|
|
2514
|
+
}
|
|
2515
|
+
/**
|
|
2516
|
+
* Read a world file. Throws `OwWorldFileError` saying why, having returned
|
|
2517
|
+
* nothing; the caller writes only what this returns.
|
|
2518
|
+
*/
|
|
2519
|
+
declare function readWorldFile(bytes: Uint8Array, opts?: OwReadWorldFileOptions): OwReadWorldFile;
|
|
2520
|
+
/**
|
|
2521
|
+
* Write a world file: every file under one top-level folder `topDir`, JSON
|
|
2522
|
+
* deflated, everything else stored, `world.json` without `api`. Refuses
|
|
2523
|
+
* (`OwWorldFileError`) an unsafe path or `topDir`, a duplicate path, and a
|
|
2524
|
+
* world.json that is missing, will not parse (it may hold a key, so it is
|
|
2525
|
+
* never zipped raw) or has no `id`.
|
|
2526
|
+
*/
|
|
2527
|
+
declare function writeWorldFile(files: OwWorldFileEntry[], topDir: string): Uint8Array;
|
|
2528
|
+
|
|
2462
2529
|
/**
|
|
2463
2530
|
* OnlyWorlds Token Management API Types
|
|
2464
2531
|
*
|
|
@@ -2743,4 +2810,4 @@ declare class TokenResource {
|
|
|
2743
2810
|
getEncryptionInfo(): Promise<EncryptionInfo>;
|
|
2744
2811
|
}
|
|
2745
2812
|
|
|
2746
|
-
export { type AccessKeyResponse, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, FAMILY_COLORS, FAMILY_ORDER, FIELD_SCHEMA, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, OwApiError, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwChange, type OwChangesPage, type OwClientConfig, type OwElement, type OwElementBase, type OwErrorBody, type OwKeyKind, type OwLinkEdit, type OwMediaTicket, OwNetworkError, type OwPage, OwPreconditionFailedError, type OwRemovalTicket, type OwRemovedImage, type OwUploadedImage, OwV2Client, type OwWorldMeta, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, detectKeyKind, elementColor, errorFromResponse, familyOf, getElementIcon, getElementLabel, isDemoKey, kindCanWrite, parseErrorEnvelope, pinExpectation };
|
|
2813
|
+
export { type AccessKeyResponse, ELEMENT_FAMILIES, ELEMENT_ICONS, ELEMENT_LABELS, ELEMENT_SECTIONS, ELEMENT_TYPES, type ElementFamily, type ElementType, type EncryptionInfo, FAMILY_COLORS, FAMILY_ORDER, FIELD_SCHEMA, type FieldInfo, type FieldType, GameTier, type ListParams, ONLYWORLDS_VERSION, OwApiError, type OwAuthErrorCode, type OwBulkItem, type OwBulkItemResult, type OwBulkResponse, type OwChange, type OwChangesPage, type OwClientConfig, type OwElement, type OwElementBase, type OwErrorBody, type OwKeyKind, type OwLinkEdit, type OwMediaTicket, OwNetworkError, type OwPage, OwPreconditionFailedError, type OwReadWorldFile, type OwReadWorldFileOptions, type OwRemovalTicket, type OwRemovedImage, type OwUploadedImage, OwV2Client, type OwWorldFileEntry, OwWorldFileError, type OwWorldFileReason, type OwWorldMeta, type RevokeAllSessionsResponse, type RevokeSessionResponse, SPATIAL_TYPES, type SectionInfo, type TokenConsumeParams, type TokenConsumeResponse, TokenResource, type TokenStatus, type TokenTransport, WORLD_FILE_MAX_BYTES, WORLD_FILE_MAX_ENTRIES, detectKeyKind, elementColor, errorFromResponse, familyOf, getElementIcon, getElementLabel, isDemoKey, kindCanWrite, parseErrorEnvelope, pinExpectation, readWorldFile, writeWorldFile };
|
package/dist/index.js
CHANGED
|
@@ -1333,6 +1333,140 @@ function getElementLabel(elementType) {
|
|
|
1333
1333
|
return ELEMENT_LABELS[elementType];
|
|
1334
1334
|
}
|
|
1335
1335
|
|
|
1336
|
+
// src/world-file.ts
|
|
1337
|
+
import { unzipSync, zipSync } from "fflate";
|
|
1338
|
+
var WORLD_FILE = "world.json";
|
|
1339
|
+
var WORLD_FILE_MAX_BYTES = 1024 ** 3;
|
|
1340
|
+
var WORLD_FILE_MAX_ENTRIES = 2e5;
|
|
1341
|
+
var MESSAGES = {
|
|
1342
|
+
unsafe_path: "The world file holds a path that cannot be written safely.",
|
|
1343
|
+
too_big: "The world file is bigger than the budget allows.",
|
|
1344
|
+
too_many: "The world file holds more files than the budget allows.",
|
|
1345
|
+
not_a_world: "The file is not a world: it has no readable world.json with an id.",
|
|
1346
|
+
more_than_one: "The file holds more than one world, or files outside the world's folder.",
|
|
1347
|
+
not_openable: "The file is not a zip that can be read."
|
|
1348
|
+
};
|
|
1349
|
+
var OwWorldFileError = class extends Error {
|
|
1350
|
+
constructor(reason, message = MESSAGES[reason]) {
|
|
1351
|
+
super(message);
|
|
1352
|
+
this.name = "OwWorldFileError";
|
|
1353
|
+
this.reason = reason;
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
function isSafePath(path) {
|
|
1357
|
+
if (!path || path.includes("\\") || path.startsWith("/") || /^[a-zA-Z]:/.test(path)) return false;
|
|
1358
|
+
return path.split("/").every((seg) => seg !== "" && seg !== "." && seg !== "..");
|
|
1359
|
+
}
|
|
1360
|
+
function isJunk(path) {
|
|
1361
|
+
return path.split("/").some((seg) => seg === "__MACOSX" || seg === ".DS_Store" || seg === "Thumbs.db");
|
|
1362
|
+
}
|
|
1363
|
+
function parseWorld(data) {
|
|
1364
|
+
let meta;
|
|
1365
|
+
try {
|
|
1366
|
+
meta = JSON.parse(new TextDecoder().decode(data));
|
|
1367
|
+
} catch {
|
|
1368
|
+
throw new OwWorldFileError("not_a_world");
|
|
1369
|
+
}
|
|
1370
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) throw new OwWorldFileError("not_a_world");
|
|
1371
|
+
const id = meta.id;
|
|
1372
|
+
if (typeof id !== "string" || !id.trim()) throw new OwWorldFileError("not_a_world");
|
|
1373
|
+
return meta;
|
|
1374
|
+
}
|
|
1375
|
+
function withoutLink(data, meta) {
|
|
1376
|
+
if (!("api" in meta)) return { data, world: meta };
|
|
1377
|
+
const { api: _link, ...rest } = meta;
|
|
1378
|
+
return { data: new TextEncoder().encode(JSON.stringify(rest, null, 2) + "\n"), world: rest };
|
|
1379
|
+
}
|
|
1380
|
+
function readWorldFile(bytes, opts = {}) {
|
|
1381
|
+
const maxBytes = opts.maxBytes ?? WORLD_FILE_MAX_BYTES;
|
|
1382
|
+
const maxEntries = opts.maxEntries ?? WORLD_FILE_MAX_ENTRIES;
|
|
1383
|
+
let declared = 0;
|
|
1384
|
+
let count = 0;
|
|
1385
|
+
let refusal = null;
|
|
1386
|
+
let raw;
|
|
1387
|
+
try {
|
|
1388
|
+
raw = unzipSync(bytes, {
|
|
1389
|
+
filter: (file) => {
|
|
1390
|
+
if (refusal) return false;
|
|
1391
|
+
if (file.name.endsWith("/")) return false;
|
|
1392
|
+
if (!isSafePath(file.name)) {
|
|
1393
|
+
refusal = "unsafe_path";
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
if (isJunk(file.name)) return false;
|
|
1397
|
+
count++;
|
|
1398
|
+
declared += file.originalSize;
|
|
1399
|
+
if (count > maxEntries) {
|
|
1400
|
+
refusal = "too_many";
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
if (declared > maxBytes) {
|
|
1404
|
+
refusal = "too_big";
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
return true;
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
} catch {
|
|
1411
|
+
throw new OwWorldFileError(refusal ?? "not_openable");
|
|
1412
|
+
}
|
|
1413
|
+
if (refusal) throw new OwWorldFileError(refusal);
|
|
1414
|
+
const names = Object.keys(raw);
|
|
1415
|
+
if (names.reduce((n, k) => n + raw[k].length, 0) > maxBytes) throw new OwWorldFileError("too_big");
|
|
1416
|
+
const nameSet = new Set(names);
|
|
1417
|
+
for (const n of names) {
|
|
1418
|
+
const segs = n.split("/");
|
|
1419
|
+
for (let i = 1; i < segs.length; i++) {
|
|
1420
|
+
if (nameSet.has(segs.slice(0, i).join("/"))) throw new OwWorldFileError("unsafe_path");
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
let prefix;
|
|
1424
|
+
if (nameSet.has(WORLD_FILE)) {
|
|
1425
|
+
prefix = "";
|
|
1426
|
+
} else {
|
|
1427
|
+
const tops = new Set(names.map((n) => n.includes("/") ? n.split("/")[0] + "/" : n));
|
|
1428
|
+
const worldTops = [...tops].filter((t) => t.endsWith("/") && nameSet.has(`${t}${WORLD_FILE}`));
|
|
1429
|
+
if (worldTops.length === 0) throw new OwWorldFileError("not_a_world");
|
|
1430
|
+
if (worldTops.length > 1 || tops.size > 1) throw new OwWorldFileError("more_than_one");
|
|
1431
|
+
prefix = worldTops[0];
|
|
1432
|
+
}
|
|
1433
|
+
const worldName = `${prefix}${WORLD_FILE}`;
|
|
1434
|
+
if (names.some((n) => n !== worldName && n.endsWith(`/${WORLD_FILE}`))) throw new OwWorldFileError("more_than_one");
|
|
1435
|
+
const parsed = withoutLink(raw[worldName], parseWorld(raw[worldName]));
|
|
1436
|
+
const files = [];
|
|
1437
|
+
for (const n of names) {
|
|
1438
|
+
const path = n.slice(prefix.length);
|
|
1439
|
+
if (opts.skip?.(path)) continue;
|
|
1440
|
+
files.push({ path, data: n === worldName ? parsed.data : raw[n] });
|
|
1441
|
+
}
|
|
1442
|
+
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
1443
|
+
const name = parsed.world.name;
|
|
1444
|
+
return {
|
|
1445
|
+
worldId: parsed.world.id,
|
|
1446
|
+
name: typeof name === "string" ? name : "",
|
|
1447
|
+
world: parsed.world,
|
|
1448
|
+
files
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
function writeWorldFile(files, topDir) {
|
|
1452
|
+
if (!isSafePath(topDir) || topDir.includes("/")) throw new OwWorldFileError("unsafe_path");
|
|
1453
|
+
const input = {};
|
|
1454
|
+
let sawWorld = false;
|
|
1455
|
+
for (const f of files) {
|
|
1456
|
+
if (!isSafePath(f.path)) throw new OwWorldFileError("unsafe_path");
|
|
1457
|
+
const key = `${topDir}/${f.path}`;
|
|
1458
|
+
if (key in input) throw new OwWorldFileError("unsafe_path", `The path ${f.path} is listed twice.`);
|
|
1459
|
+
let data = f.data;
|
|
1460
|
+
if (f.path === WORLD_FILE) {
|
|
1461
|
+
data = withoutLink(data, parseWorld(data)).data;
|
|
1462
|
+
sawWorld = true;
|
|
1463
|
+
}
|
|
1464
|
+
input[key] = [data, { level: /\.json$/i.test(f.path) ? 6 : 0 }];
|
|
1465
|
+
}
|
|
1466
|
+
if (!sawWorld) throw new OwWorldFileError("not_a_world");
|
|
1467
|
+
return zipSync(input);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1336
1470
|
// src/token-resource.ts
|
|
1337
1471
|
var TokenResource = class {
|
|
1338
1472
|
constructor(client) {
|
|
@@ -1518,8 +1652,11 @@ export {
|
|
|
1518
1652
|
OwNetworkError,
|
|
1519
1653
|
OwPreconditionFailedError,
|
|
1520
1654
|
OwV2Client,
|
|
1655
|
+
OwWorldFileError,
|
|
1521
1656
|
SPATIAL_TYPES,
|
|
1522
1657
|
TokenResource,
|
|
1658
|
+
WORLD_FILE_MAX_BYTES,
|
|
1659
|
+
WORLD_FILE_MAX_ENTRIES,
|
|
1523
1660
|
detectKeyKind,
|
|
1524
1661
|
elementColor,
|
|
1525
1662
|
errorFromResponse,
|
|
@@ -1529,5 +1666,7 @@ export {
|
|
|
1529
1666
|
isDemoKey,
|
|
1530
1667
|
kindCanWrite,
|
|
1531
1668
|
parseErrorEnvelope,
|
|
1532
|
-
pinExpectation
|
|
1669
|
+
pinExpectation,
|
|
1670
|
+
readWorldFile,
|
|
1671
|
+
writeWorldFile
|
|
1533
1672
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlyworlds/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.10.0",
|
|
4
4
|
"description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -60,5 +60,8 @@
|
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": ">=18.0.0"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"fflate": "^0.8.3"
|
|
63
66
|
}
|
|
64
67
|
}
|