@onlyworlds/sdk 3.0.0 → 4.0.0-alpha.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 +34 -0
- package/CHANGELOG.md +86 -0
- package/README.md +83 -248
- package/SCHEMA.md +630 -0
- package/dist/index.d.ts +464 -1414
- package/dist/index.js +770 -1024
- package/package.json +18 -6
- package/dist/index.d.mts +0 -3461
- package/dist/index.mjs +0 -1532
package/dist/index.js
CHANGED
|
@@ -1,112 +1,373 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// src/v2/errors.ts
|
|
2
|
+
var OwApiError = class extends Error {
|
|
3
|
+
constructor(status, code, message, docUrl, detail, type = null, param = null) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "OwApiError";
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.type = type;
|
|
9
|
+
this.param = param;
|
|
10
|
+
this.docUrl = docUrl;
|
|
11
|
+
this.detail = detail;
|
|
12
|
+
}
|
|
13
|
+
get isAuthError() {
|
|
14
|
+
return this.code === "invalid_credentials" || this.code === "key_revoked" || this.code === "world_gone";
|
|
15
|
+
}
|
|
16
|
+
/** 422s/400s name the offending param/field -- typos error loudly platform-wide. */
|
|
17
|
+
get isValidationError() {
|
|
18
|
+
return this.status === 422 || this.status === 400;
|
|
19
|
+
}
|
|
20
|
+
/** Same Idempotency-Key replayed with a different payload. */
|
|
21
|
+
get isIdempotencyConflict() {
|
|
22
|
+
return this.status === 409;
|
|
23
|
+
}
|
|
9
24
|
};
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
var OwNetworkError = class extends Error {
|
|
26
|
+
constructor(message, cause) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "OwNetworkError";
|
|
29
|
+
this.cause2 = cause;
|
|
15
30
|
}
|
|
16
|
-
return to;
|
|
17
31
|
};
|
|
18
|
-
|
|
32
|
+
function parseEnvelope(status, body) {
|
|
33
|
+
const env = body && typeof body === "object" ? body : {};
|
|
34
|
+
const nested = typeof env.error === "object" && env.error !== null ? env.error : void 0;
|
|
35
|
+
const code = env.code ?? nested?.code ?? (typeof env.error === "string" ? env.error : null) ?? null;
|
|
36
|
+
const type = env.type ?? nested?.type ?? null;
|
|
37
|
+
const param = env.param ?? nested?.param ?? null;
|
|
38
|
+
const docUrl = env.doc_url ?? nested?.doc_url ?? null;
|
|
39
|
+
const message = env.message ?? nested?.message ?? (typeof env.detail === "string" ? env.detail : void 0) ?? `OnlyWorlds API error ${status}${code ? ` (${code})` : ""}`;
|
|
40
|
+
return new OwApiError(status, code, message, docUrl, body, type, param);
|
|
41
|
+
}
|
|
42
|
+
async function errorFromResponse(res) {
|
|
43
|
+
let body = null;
|
|
44
|
+
let text = "";
|
|
45
|
+
try {
|
|
46
|
+
text = await res.text();
|
|
47
|
+
body = text ? JSON.parse(text) : null;
|
|
48
|
+
} catch {
|
|
49
|
+
body = text || null;
|
|
50
|
+
}
|
|
51
|
+
return parseEnvelope(res.status, body);
|
|
52
|
+
}
|
|
19
53
|
|
|
20
|
-
// src/
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
module.exports = __toCommonJS(index_exports);
|
|
54
|
+
// src/v2/keys.ts
|
|
55
|
+
var LEGACY_RE = /^[0-9]{10}$/;
|
|
56
|
+
function isDemoKey(key) {
|
|
57
|
+
return LEGACY_RE.test(key) && key >= "0000000000" && key <= "0000000009";
|
|
58
|
+
}
|
|
59
|
+
function detectKeyKind(key) {
|
|
60
|
+
if (key.startsWith("ow_w_")) return "write";
|
|
61
|
+
if (key.startsWith("ow_r_")) return "read";
|
|
62
|
+
if (key.startsWith("ow_a_")) return "account";
|
|
63
|
+
if (LEGACY_RE.test(key)) return "legacy";
|
|
64
|
+
return "unknown";
|
|
65
|
+
}
|
|
66
|
+
function kindCanWrite(kind) {
|
|
67
|
+
return kind === "write" || kind === "legacy";
|
|
68
|
+
}
|
|
69
|
+
function pinExpectation(kind) {
|
|
70
|
+
switch (kind) {
|
|
71
|
+
case "read":
|
|
72
|
+
return "never";
|
|
73
|
+
case "account":
|
|
74
|
+
return "never";
|
|
75
|
+
case "write":
|
|
76
|
+
return "optional";
|
|
77
|
+
case "legacy":
|
|
78
|
+
return "required-for-private-reads";
|
|
79
|
+
default:
|
|
80
|
+
return "optional";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
50
83
|
|
|
51
|
-
// src/
|
|
52
|
-
var
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
84
|
+
// src/v2/client.ts
|
|
85
|
+
var DEFAULT_BASE_URL = "https://www.onlyworlds.com/api/v2";
|
|
86
|
+
var DEFAULT_PAGE_SIZE = 100;
|
|
87
|
+
var DEFAULT_CHANGES_PAGE_SIZE = 100;
|
|
88
|
+
var OwV2Client = class {
|
|
89
|
+
constructor(config) {
|
|
90
|
+
if (!config.apiKey) throw new Error("OwV2Client: apiKey is required");
|
|
91
|
+
this.apiKey = config.apiKey;
|
|
92
|
+
this.apiPin = config.apiPin || void 0;
|
|
93
|
+
this.keyKind = detectKeyKind(config.apiKey);
|
|
94
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
95
|
+
this.pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
96
|
+
this.changesPageSize = config.changesPageSize ?? DEFAULT_CHANGES_PAGE_SIZE;
|
|
97
|
+
this.fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
98
|
+
if (typeof this.fetchImpl !== "function") {
|
|
99
|
+
throw new Error("OwV2Client: no fetch available -- supply config.fetch");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// -- Health & world meta -------------------------------------------------
|
|
103
|
+
/** GET /health -- unauthenticated liveness pulse. */
|
|
104
|
+
async health() {
|
|
105
|
+
return this.request("GET", "/health/", { auth: false });
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* GET /world -- world meta (name, calendar/time fields, public_read).
|
|
109
|
+
* GOTCHA (by server design): world-meta edits do NOT appear in /changes and
|
|
110
|
+
* do not bump change_seq. Poll getWorld().updated_at for meta freshness.
|
|
111
|
+
*/
|
|
112
|
+
async getWorld() {
|
|
113
|
+
return this.request("GET", "/world/");
|
|
114
|
+
}
|
|
115
|
+
/** PATCH /world -- partial world-meta update. */
|
|
116
|
+
async patchWorld(partial) {
|
|
117
|
+
return this.request("PATCH", "/world/", { body: sanitizePayload(partial) });
|
|
118
|
+
}
|
|
119
|
+
// -- Element CRUD --------------------------------------------------------
|
|
120
|
+
/** GET /{type}/ -- one cursor page. */
|
|
121
|
+
async list(type, params = {}) {
|
|
122
|
+
const query = buildQuery({
|
|
123
|
+
limit: params.limit ?? this.pageSize,
|
|
124
|
+
cursor: params.cursor,
|
|
125
|
+
expand: params.expand?.join(","),
|
|
126
|
+
fields: params.fields?.join(","),
|
|
127
|
+
...params.filter
|
|
128
|
+
});
|
|
129
|
+
return this.request("GET", `/${type}/`, { query });
|
|
130
|
+
}
|
|
131
|
+
/** Cursor-walk every page of a type. Politeness: uses config pageSize. */
|
|
132
|
+
async *listAll(type, params = {}) {
|
|
133
|
+
let cursor;
|
|
134
|
+
do {
|
|
135
|
+
const page = await this.list(type, { ...params, cursor });
|
|
136
|
+
for (const el of page.data) yield el;
|
|
137
|
+
cursor = page.has_more && page.next_cursor ? page.next_cursor : void 0;
|
|
138
|
+
} while (cursor);
|
|
139
|
+
}
|
|
140
|
+
/** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
|
|
141
|
+
async get(type, id, opts = {}) {
|
|
142
|
+
const query = buildQuery({ expand: opts.expand?.join(","), fields: opts.fields?.join(",") });
|
|
143
|
+
return this.request("GET", `/${type}/${id}/`, { query });
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
|
|
147
|
+
* caller omits one (design ruling D29d) so a retry carrying the same
|
|
148
|
+
* Idempotency-Key is structurally safe. Callers MAY still supply their own id.
|
|
149
|
+
*/
|
|
150
|
+
async create(type, element, opts = {}) {
|
|
151
|
+
const body = sanitizePayload(element);
|
|
152
|
+
if (body.id === void 0 || body.id === null || body.id === "") {
|
|
153
|
+
body.id = mintUuid();
|
|
154
|
+
}
|
|
155
|
+
return this.request("POST", `/${type}/`, {
|
|
156
|
+
body,
|
|
157
|
+
idempotencyKey: opts.idempotencyKey
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** PUT /{type}/{id}/ -- upsert-by-client-id. The local-first write primitive. */
|
|
161
|
+
async upsert(type, id, element) {
|
|
162
|
+
return this.request("PUT", `/${type}/${id}/`, { body: sanitizePayload(element) });
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
|
|
166
|
+
* replace wholesale, omitted fields stay untouched. For link arrays prefer
|
|
167
|
+
* editLinks() -- atomic server-side merge, no read-before-write.
|
|
168
|
+
*/
|
|
169
|
+
async patch(type, id, partial) {
|
|
170
|
+
return this.request("PATCH", `/${type}/${id}/`, { body: sanitizePayload(partial) });
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* DELETE /{type}/{id}/ -- idempotent (204 on absent). Server writes a
|
|
174
|
+
* tombstone AND scrubs the id from every other element's links in the same
|
|
175
|
+
* transaction -- no client-side unlink pass needed, ever.
|
|
176
|
+
*/
|
|
177
|
+
async delete(type, id) {
|
|
178
|
+
await this.request("DELETE", `/${type}/${id}/`, { allowEmpty: true });
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* POST /{type}/{id}/links/{field} with {add, remove} -- atomic link merge.
|
|
182
|
+
* Dedupes, tolerates already-present/already-absent ids. Returns the FULL
|
|
183
|
+
* updated element (fixture P5). Use this for all relationship editing; it
|
|
184
|
+
* retires the read-merge-PATCH dance.
|
|
185
|
+
*/
|
|
186
|
+
async editLinks(type, id, field, edit) {
|
|
187
|
+
return this.request("POST", `/${type}/${id}/links/${field}`, { body: edit });
|
|
188
|
+
}
|
|
189
|
+
// -- Bulk ----------------------------------------------------------------
|
|
190
|
+
/**
|
|
191
|
+
* POST /bulk -- up to ~1000 items. Partial success by default (HTTP 200
|
|
192
|
+
* always; inspect per-slot numeric `status` + top-level `errors` flag);
|
|
193
|
+
* atomic:true for all-or-nothing. Link validation runs against batch U
|
|
194
|
+
* database -- send in any order, cycles included; no client topo-sort.
|
|
195
|
+
* Success slots echo server-authoritative timestamps: set your sync baseline
|
|
196
|
+
* from this response alone. When an idempotencyKey is replayed, the returned
|
|
197
|
+
* response carries wasReplay:true (read from the Idempotent-Replay header).
|
|
198
|
+
*/
|
|
199
|
+
async bulk(items, opts = {}) {
|
|
200
|
+
const body = {
|
|
201
|
+
items: items.map((it) => ({ type: it.type, element: sanitizePayload(it.element) })),
|
|
202
|
+
atomic: opts.atomic ?? false
|
|
203
|
+
};
|
|
204
|
+
return this.request("POST", "/bulk/", {
|
|
205
|
+
body,
|
|
206
|
+
idempotencyKey: opts.idempotencyKey,
|
|
207
|
+
replayAware: true
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// -- Changes feed --------------------------------------------------------
|
|
211
|
+
/**
|
|
212
|
+
* GET /changes -- one page of the world's ordered change feed.
|
|
213
|
+
* Cursor is OPAQUE and never expires: persist verbatim, never parse.
|
|
214
|
+
* Zero/absent cursor = full export (byte-aligned with the Folder Format).
|
|
215
|
+
* Rewind rule: if your persisted position is ahead of page.head, the server
|
|
216
|
+
* was restored -- re-baseline from cursor zero; do not assume caught-up.
|
|
217
|
+
* Citizenship: heaviest route on the platform; default page size is polite.
|
|
218
|
+
*/
|
|
219
|
+
async changes(opts = {}) {
|
|
220
|
+
const query = buildQuery({ since: opts.since, limit: opts.limit ?? this.changesPageSize });
|
|
221
|
+
return this.request("GET", "/changes/", { query });
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Walk the feed from `since` (or from zero = full export) to the current
|
|
225
|
+
* tail, yielding ops in order. Returns the final cursor via the generator's
|
|
226
|
+
* return value; persist it for the next incremental pull.
|
|
227
|
+
*/
|
|
228
|
+
async *changesAll(since) {
|
|
229
|
+
let cursor = since;
|
|
230
|
+
let page;
|
|
231
|
+
do {
|
|
232
|
+
page = await this.changes({ since: cursor });
|
|
233
|
+
for (const op of page.changes) yield op;
|
|
234
|
+
cursor = page.cursor;
|
|
235
|
+
} while (page.has_more);
|
|
236
|
+
return { cursor: page.cursor, head: page.head };
|
|
237
|
+
}
|
|
238
|
+
// -- Core request machinery ----------------------------------------------
|
|
239
|
+
/**
|
|
240
|
+
* Raw authenticated request against this client's baseUrl. Public since 4.0
|
|
241
|
+
* so auxiliary resources can ride the same transport — it structurally
|
|
242
|
+
* satisfies `TokenTransport` (`new TokenResource(client)`). Prefer the typed
|
|
243
|
+
* methods for element CRUD; this is the escape hatch, and it does NOT apply
|
|
244
|
+
* sanitizePayload — callers own their body shape.
|
|
245
|
+
*/
|
|
246
|
+
async request(method, path, opts = {}) {
|
|
247
|
+
const url = `${this.baseUrl}${path}${opts.query ? `?${opts.query}` : ""}`;
|
|
248
|
+
const headers = {};
|
|
249
|
+
if (opts.auth !== false) {
|
|
250
|
+
if (this.keyKind === "account") {
|
|
251
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
252
|
+
} else {
|
|
253
|
+
headers["API-Key"] = this.apiKey;
|
|
254
|
+
if (this.apiPin) headers["API-Pin"] = this.apiPin;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (opts.body !== void 0) headers["Content-Type"] = "application/json";
|
|
258
|
+
if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
259
|
+
let res;
|
|
260
|
+
try {
|
|
261
|
+
res = await this.fetchImpl(url, {
|
|
262
|
+
method,
|
|
263
|
+
headers,
|
|
264
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
265
|
+
});
|
|
266
|
+
} catch (cause) {
|
|
267
|
+
throw new OwNetworkError(`OnlyWorlds request failed: ${method} ${path}`, cause);
|
|
268
|
+
}
|
|
269
|
+
if (!res.ok) throw await errorFromResponse(res);
|
|
270
|
+
if (res.status === 204 || opts.allowEmpty) {
|
|
271
|
+
const text = await res.text();
|
|
272
|
+
return text ? JSON.parse(text) : null;
|
|
273
|
+
}
|
|
274
|
+
const parsed = await res.json();
|
|
275
|
+
if (opts.replayAware && parsed && typeof parsed === "object") {
|
|
276
|
+
parsed.wasReplay = readReplayHeader(res.headers);
|
|
277
|
+
}
|
|
278
|
+
return parsed;
|
|
279
|
+
}
|
|
100
280
|
};
|
|
101
|
-
|
|
102
|
-
|
|
281
|
+
var READ_ONLY_FIELDS = ["world", "type", "created_at", "updated_at", "change_seq"];
|
|
282
|
+
function sanitizePayload(payload) {
|
|
283
|
+
if (!payload || typeof payload !== "object") return payload;
|
|
284
|
+
const rest = { ...payload };
|
|
285
|
+
for (const field of READ_ONLY_FIELDS) delete rest[field];
|
|
286
|
+
return rest;
|
|
103
287
|
}
|
|
288
|
+
function readReplayHeader(headers) {
|
|
289
|
+
const v = headers.get("Idempotent-Replay");
|
|
290
|
+
return v != null && v.toLowerCase() === "true";
|
|
291
|
+
}
|
|
292
|
+
function mintUuid() {
|
|
293
|
+
const c = globalThis.crypto;
|
|
294
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
295
|
+
const bytes = new Uint8Array(16);
|
|
296
|
+
if (c && typeof c.getRandomValues === "function") {
|
|
297
|
+
c.getRandomValues(bytes);
|
|
298
|
+
} else {
|
|
299
|
+
for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
300
|
+
}
|
|
301
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
302
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
303
|
+
const hex = [];
|
|
304
|
+
for (let i = 0; i < 256; i++) hex.push((i + 256).toString(16).slice(1));
|
|
305
|
+
return hex[bytes[0]] + hex[bytes[1]] + hex[bytes[2]] + hex[bytes[3]] + "-" + hex[bytes[4]] + hex[bytes[5]] + "-" + hex[bytes[6]] + hex[bytes[7]] + "-" + hex[bytes[8]] + hex[bytes[9]] + "-" + hex[bytes[10]] + hex[bytes[11]] + hex[bytes[12]] + hex[bytes[13]] + hex[bytes[14]] + hex[bytes[15]];
|
|
306
|
+
}
|
|
307
|
+
function buildQuery(params) {
|
|
308
|
+
const q = new URLSearchParams();
|
|
309
|
+
for (const [k, v] of Object.entries(params)) {
|
|
310
|
+
if (v !== void 0 && v !== null && v !== "") q.set(k, String(v));
|
|
311
|
+
}
|
|
312
|
+
return q.toString();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/v2/types.generated.ts
|
|
316
|
+
var ELEMENT_TYPES = ["ability", "character", "collective", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"];
|
|
317
|
+
var ELEMENT_FAMILIES = {
|
|
318
|
+
ability: "abstract",
|
|
319
|
+
character: "agents",
|
|
320
|
+
collective: "agents",
|
|
321
|
+
construct: "world",
|
|
322
|
+
creature: "agents",
|
|
323
|
+
event: "temporal",
|
|
324
|
+
family: "agents",
|
|
325
|
+
institution: "agents",
|
|
326
|
+
language: "abstract",
|
|
327
|
+
law: "abstract",
|
|
328
|
+
location: "world",
|
|
329
|
+
map: "world",
|
|
330
|
+
marker: "world",
|
|
331
|
+
narrative: "temporal",
|
|
332
|
+
object: "world",
|
|
333
|
+
phenomenon: "temporal",
|
|
334
|
+
pin: "world",
|
|
335
|
+
relation: "temporal",
|
|
336
|
+
species: "agents",
|
|
337
|
+
title: "abstract",
|
|
338
|
+
trait: "abstract",
|
|
339
|
+
zone: "world"
|
|
340
|
+
};
|
|
341
|
+
var ELEMENT_ICONS = {
|
|
342
|
+
ability: "auto_fix_normal",
|
|
343
|
+
character: "person",
|
|
344
|
+
collective: "groups_3",
|
|
345
|
+
construct: "api",
|
|
346
|
+
creature: "bug_report",
|
|
347
|
+
event: "saved_search",
|
|
348
|
+
family: "supervisor_account",
|
|
349
|
+
institution: "business",
|
|
350
|
+
language: "edit_road",
|
|
351
|
+
law: "gpp_bad",
|
|
352
|
+
location: "castle",
|
|
353
|
+
map: "map",
|
|
354
|
+
marker: "place",
|
|
355
|
+
narrative: "menu_book",
|
|
356
|
+
object: "webhook",
|
|
357
|
+
phenomenon: "thunderstorm",
|
|
358
|
+
pin: "push_pin",
|
|
359
|
+
relation: "link",
|
|
360
|
+
species: "crib",
|
|
361
|
+
title: "military_tech",
|
|
362
|
+
trait: "flaky",
|
|
363
|
+
zone: "architecture"
|
|
364
|
+
};
|
|
104
365
|
var ELEMENT_SECTIONS = {
|
|
105
|
-
|
|
366
|
+
ability: [
|
|
106
367
|
{ name: "Mechanics", order: 1, fields: ["activation", "duration", "potency", "range", "effects", "challenges", "talents", "requisites"] },
|
|
107
368
|
{ name: "World", order: 2, fields: ["prevalence", "tradition", "source", "locus", "instruments", "systems"] }
|
|
108
369
|
],
|
|
109
|
-
|
|
370
|
+
character: [
|
|
110
371
|
{ name: "Constitution", order: 1, fields: ["physicality", "mentality", "height", "weight", "species", "traits", "abilities"] },
|
|
111
372
|
{ name: "Origins", order: 2, fields: ["background", "motivations", "birth_date", "birthplace", "languages"] },
|
|
112
373
|
{ name: "World", order: 3, fields: ["reputation", "location", "objects", "institutions"] },
|
|
@@ -114,43 +375,43 @@ var ELEMENT_SECTIONS = {
|
|
|
114
375
|
{ name: "Social", order: 5, fields: ["family", "friends", "rivals"] },
|
|
115
376
|
{ name: "TTRPG", order: 6, fields: ["level", "hit_points", "STR", "DEX", "CON", "INT", "WIS", "CHA"] }
|
|
116
377
|
],
|
|
117
|
-
|
|
378
|
+
collective: [
|
|
118
379
|
{ name: "Formation", order: 1, fields: ["composition", "count", "formation_date", "operator", "equipment"] },
|
|
119
380
|
{ name: "Dynamics", order: 2, fields: ["activity", "disposition", "state", "abilities", "symbolism"] },
|
|
120
381
|
{ name: "World", order: 3, fields: ["species", "characters", "creatures", "phenomena"] }
|
|
121
382
|
],
|
|
122
|
-
|
|
383
|
+
construct: [
|
|
123
384
|
{ name: "Nature", order: 1, fields: ["rationale", "history", "status", "reach", "start_date", "end_date", "founder", "custodian"] },
|
|
124
385
|
{ name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives"] }
|
|
125
386
|
],
|
|
126
|
-
|
|
387
|
+
creature: [
|
|
127
388
|
{ name: "Biology", order: 1, fields: ["appearance", "weight", "height", "species"] },
|
|
128
|
-
{ name: "
|
|
389
|
+
{ name: "Behavior", order: 2, fields: ["habits", "demeanor", "traits", "abilities", "languages"] },
|
|
129
390
|
{ name: "World", order: 3, fields: ["status", "birth_date", "location", "zone"] },
|
|
130
391
|
{ name: "TTRPG", order: 4, fields: ["challenge_rating", "hit_points", "armor_class", "speed", "actions"] }
|
|
131
392
|
],
|
|
132
|
-
|
|
393
|
+
event: [
|
|
133
394
|
{ name: "Nature", order: 1, fields: ["history", "challenges", "consequences", "start_date", "end_date", "triggers"] },
|
|
134
395
|
{ name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs"] }
|
|
135
396
|
],
|
|
136
|
-
|
|
397
|
+
family: [
|
|
137
398
|
{ name: "Identity", order: 1, fields: ["spirit", "history", "traditions", "traits", "abilities", "languages", "ancestors"] },
|
|
138
399
|
{ name: "World", order: 2, fields: ["reputation", "estates", "governs", "heirlooms", "creatures"] }
|
|
139
400
|
],
|
|
140
|
-
|
|
401
|
+
institution: [
|
|
141
402
|
{ name: "Foundation", order: 1, fields: ["doctrine", "founding_date", "parent_institution"] },
|
|
142
403
|
{ name: "Claims", order: 2, fields: ["zones", "objects", "creatures"] },
|
|
143
404
|
{ name: "World", order: 3, fields: ["status", "allies", "adversaries", "constructs"] }
|
|
144
405
|
],
|
|
145
|
-
|
|
406
|
+
language: [
|
|
146
407
|
{ name: "Structure", order: 1, fields: ["phonology", "grammar", "lexicon", "writing", "classification"] },
|
|
147
408
|
{ name: "World", order: 2, fields: ["status", "spread", "dialects"] }
|
|
148
409
|
],
|
|
149
|
-
|
|
410
|
+
law: [
|
|
150
411
|
{ name: "Code", order: 1, fields: ["declaration", "purpose", "date", "parent_law", "penalties"] },
|
|
151
412
|
{ name: "World", order: 2, fields: ["author", "locations", "zones", "prohibitions", "adjudicators", "enforcers"] }
|
|
152
413
|
],
|
|
153
|
-
|
|
414
|
+
location: [
|
|
154
415
|
{ name: "Setting", order: 1, fields: ["form", "function", "founding_date", "parent_location", "populations"] },
|
|
155
416
|
{ name: "Politics", order: 2, fields: ["political_climate", "primary_power", "governing_title", "secondary_powers", "zone", "rival", "partner"] },
|
|
156
417
|
{ name: "World", order: 3, fields: ["customs", "founders", "cults", "delicacies"] },
|
|
@@ -159,78 +420,95 @@ var ELEMENT_SECTIONS = {
|
|
|
159
420
|
{ name: "Construction", order: 6, fields: ["architecture", "buildings", "building_methods"] },
|
|
160
421
|
{ name: "Defense", order: 7, fields: ["defensibility", "elevation", "fighters", "defensive_objects"] }
|
|
161
422
|
],
|
|
162
|
-
|
|
423
|
+
map: [
|
|
163
424
|
{ name: "Details", order: 1, fields: ["background_color", "hierarchy", "width", "height", "depth", "parent_map", "location"] }
|
|
164
425
|
],
|
|
165
|
-
|
|
426
|
+
marker: [
|
|
166
427
|
{ name: "Details", order: 1, fields: ["map", "zone", "x", "y", "z", "order"] }
|
|
167
428
|
],
|
|
168
|
-
|
|
429
|
+
narrative: [
|
|
169
430
|
{ name: "Context", order: 1, fields: ["story", "consequences", "start_date", "end_date", "order", "parent_narrative", "protagonist", "antagonist", "narrator", "conservator"] },
|
|
170
431
|
{ name: "Involves", order: 2, fields: ["events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "laws"] }
|
|
171
432
|
],
|
|
172
|
-
|
|
433
|
+
object: [
|
|
173
434
|
{ name: "Form", order: 1, fields: ["aesthetics", "weight", "amount", "parent_object", "materials", "technology"] },
|
|
174
435
|
{ name: "Function", order: 2, fields: ["utility", "effects", "abilities", "consumes"] },
|
|
175
436
|
{ name: "World", order: 3, fields: ["origins", "location", "language", "affinities"] }
|
|
176
437
|
],
|
|
177
|
-
|
|
438
|
+
phenomenon: [
|
|
178
439
|
{ name: "Mechanics", order: 1, fields: ["expression", "effects", "duration", "catalysts", "empowerments"] },
|
|
179
440
|
{ name: "World", order: 2, fields: ["mythology", "system", "triggers", "wielders", "environments"] }
|
|
180
441
|
],
|
|
181
|
-
|
|
182
|
-
{ name: "Details", order: 1, fields: ["map", "
|
|
442
|
+
pin: [
|
|
443
|
+
{ name: "Details", order: 1, fields: ["map", "element", "x", "y", "z"] }
|
|
183
444
|
],
|
|
184
|
-
|
|
445
|
+
relation: [
|
|
185
446
|
{ name: "Nature", order: 1, fields: ["background", "start_date", "end_date", "intensity", "actor", "events"] },
|
|
186
|
-
{ name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "
|
|
447
|
+
{ name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "titles", "constructs", "events", "narratives"] }
|
|
187
448
|
],
|
|
188
|
-
|
|
449
|
+
species: [
|
|
189
450
|
{ name: "Biology", order: 1, fields: ["appearance", "life_span", "weight", "nourishment", "reproduction", "adaptations"] },
|
|
190
451
|
{ name: "Psychology", order: 2, fields: ["instincts", "sociality", "temperament", "communication", "aggression", "traits"] },
|
|
191
452
|
{ name: "World", order: 3, fields: ["role", "parent_species", "locations", "zones", "affinities"] }
|
|
192
453
|
],
|
|
193
|
-
|
|
454
|
+
title: [
|
|
194
455
|
{ name: "Mandate", order: 1, fields: ["authority", "eligibility", "grant_date", "revoke_date", "issuer", "body", "superior_title", "holders", "symbols"] },
|
|
195
456
|
{ name: "World", order: 2, fields: ["status", "history", "characters", "institutions", "families", "zones", "locations", "objects", "constructs", "laws", "collectives", "creatures", "phenomena", "species", "languages"] }
|
|
196
457
|
],
|
|
197
|
-
|
|
458
|
+
trait: [
|
|
198
459
|
{ name: "Qualitative", order: 1, fields: ["social_effects", "physical_effects", "functional_effects", "personality_effects", "behaviour_effects"] },
|
|
199
460
|
{ name: "Quantitative", order: 2, fields: ["charisma", "coercion", "competence", "compassion", "creativity", "courage"] },
|
|
200
461
|
{ name: "World", order: 3, fields: ["significance", "anti_trait", "empowered_abilities"] }
|
|
201
462
|
],
|
|
202
|
-
|
|
463
|
+
zone: [
|
|
203
464
|
{ name: "Scope", order: 1, fields: ["role", "start_date", "end_date", "phenomena", "linked_zones"] },
|
|
204
465
|
{ name: "World", order: 2, fields: ["context", "populations", "titles", "principles"] }
|
|
205
466
|
]
|
|
206
467
|
};
|
|
207
|
-
|
|
208
|
-
|
|
468
|
+
|
|
469
|
+
// src/v2/types.ts
|
|
470
|
+
var SPATIAL_TYPES = ["map", "pin", "marker", "zone"];
|
|
471
|
+
|
|
472
|
+
// src/v2/palette.ts
|
|
473
|
+
var FAMILY_COLORS = {
|
|
474
|
+
agents: { light: "#2a78d6", dark: "#3987e5" },
|
|
475
|
+
world: { light: "#008300", dark: "#008300" },
|
|
476
|
+
abstract: { light: "#e87ba4", dark: "#d55181" },
|
|
477
|
+
temporal: { light: "#eda100", dark: "#c98500" }
|
|
478
|
+
};
|
|
479
|
+
function familyOf(type) {
|
|
480
|
+
return ELEMENT_FAMILIES[type];
|
|
209
481
|
}
|
|
482
|
+
function elementColor(type, mode = "dark") {
|
|
483
|
+
return FAMILY_COLORS[ELEMENT_FAMILIES[type]][mode];
|
|
484
|
+
}
|
|
485
|
+
var FAMILY_ORDER = ["agents", "world", "abstract", "temporal"];
|
|
486
|
+
|
|
487
|
+
// src/v2/constants.ts
|
|
210
488
|
var ONLYWORLDS_VERSION = "00.30.00";
|
|
211
|
-
var
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
489
|
+
var ELEMENT_LABELS = {
|
|
490
|
+
ability: "Abilities",
|
|
491
|
+
character: "Characters",
|
|
492
|
+
collective: "Collectives",
|
|
493
|
+
construct: "Constructs",
|
|
494
|
+
creature: "Creatures",
|
|
495
|
+
event: "Events",
|
|
496
|
+
family: "Families",
|
|
497
|
+
institution: "Institutions",
|
|
498
|
+
language: "Languages",
|
|
499
|
+
law: "Laws",
|
|
500
|
+
location: "Locations",
|
|
501
|
+
map: "Maps",
|
|
502
|
+
marker: "Markers",
|
|
503
|
+
narrative: "Narratives",
|
|
504
|
+
object: "Objects",
|
|
505
|
+
phenomenon: "Phenomena",
|
|
506
|
+
pin: "Pins",
|
|
507
|
+
relation: "Relations",
|
|
508
|
+
species: "Species",
|
|
509
|
+
title: "Titles",
|
|
510
|
+
trait: "Traits",
|
|
511
|
+
zone: "Zones"
|
|
234
512
|
};
|
|
235
513
|
var FIELD_SCHEMA = {
|
|
236
514
|
ability: {
|
|
@@ -242,9 +520,9 @@ var FIELD_SCHEMA = {
|
|
|
242
520
|
image_url: { type: "text", required: false },
|
|
243
521
|
// Mechanics
|
|
244
522
|
activation: { type: "text" },
|
|
245
|
-
duration: { type: "
|
|
246
|
-
potency: { type: "
|
|
247
|
-
range: { type: "
|
|
523
|
+
duration: { type: "integer" },
|
|
524
|
+
potency: { type: "integer" },
|
|
525
|
+
range: { type: "integer" },
|
|
248
526
|
effects: { type: "multi_link", target: "phenomenon" },
|
|
249
527
|
challenges: { type: "text" },
|
|
250
528
|
talents: { type: "multi_link", target: "trait" },
|
|
@@ -267,15 +545,15 @@ var FIELD_SCHEMA = {
|
|
|
267
545
|
// Constitution
|
|
268
546
|
physicality: { type: "text" },
|
|
269
547
|
mentality: { type: "text" },
|
|
270
|
-
height: { type: "
|
|
271
|
-
weight: { type: "
|
|
548
|
+
height: { type: "integer" },
|
|
549
|
+
weight: { type: "integer" },
|
|
272
550
|
species: { type: "multi_link", target: "species" },
|
|
273
551
|
traits: { type: "multi_link", target: "trait" },
|
|
274
552
|
abilities: { type: "multi_link", target: "ability" },
|
|
275
553
|
// Origins
|
|
276
554
|
background: { type: "text" },
|
|
277
555
|
motivations: { type: "text" },
|
|
278
|
-
birth_date: { type: "
|
|
556
|
+
birth_date: { type: "integer" },
|
|
279
557
|
birthplace: { type: "single_link", target: "location" },
|
|
280
558
|
languages: { type: "multi_link", target: "language" },
|
|
281
559
|
// World
|
|
@@ -284,25 +562,25 @@ var FIELD_SCHEMA = {
|
|
|
284
562
|
objects: { type: "multi_link", target: "object" },
|
|
285
563
|
institutions: { type: "multi_link", target: "institution" },
|
|
286
564
|
// Personality
|
|
287
|
-
charisma: { type: "
|
|
288
|
-
coercion: { type: "
|
|
289
|
-
competence: { type: "
|
|
290
|
-
compassion: { type: "
|
|
291
|
-
creativity: { type: "
|
|
292
|
-
courage: { type: "
|
|
565
|
+
charisma: { type: "integer" },
|
|
566
|
+
coercion: { type: "integer" },
|
|
567
|
+
competence: { type: "integer" },
|
|
568
|
+
compassion: { type: "integer" },
|
|
569
|
+
creativity: { type: "integer" },
|
|
570
|
+
courage: { type: "integer" },
|
|
293
571
|
// Social
|
|
294
572
|
family: { type: "multi_link", target: "family" },
|
|
295
573
|
friends: { type: "multi_link", target: "character" },
|
|
296
574
|
rivals: { type: "multi_link", target: "character" },
|
|
297
575
|
// TTRPG
|
|
298
|
-
level: { type: "
|
|
299
|
-
hit_points: { type: "
|
|
300
|
-
STR: { type: "
|
|
301
|
-
DEX: { type: "
|
|
302
|
-
CON: { type: "
|
|
303
|
-
INT: { type: "
|
|
304
|
-
WIS: { type: "
|
|
305
|
-
CHA: { type: "
|
|
576
|
+
level: { type: "integer" },
|
|
577
|
+
hit_points: { type: "integer" },
|
|
578
|
+
STR: { type: "integer" },
|
|
579
|
+
DEX: { type: "integer" },
|
|
580
|
+
CON: { type: "integer" },
|
|
581
|
+
INT: { type: "integer" },
|
|
582
|
+
WIS: { type: "integer" },
|
|
583
|
+
CHA: { type: "integer" }
|
|
306
584
|
},
|
|
307
585
|
collective: {
|
|
308
586
|
// Base fields (shared by all elements)
|
|
@@ -313,8 +591,8 @@ var FIELD_SCHEMA = {
|
|
|
313
591
|
image_url: { type: "text", required: false },
|
|
314
592
|
// Formation
|
|
315
593
|
composition: { type: "text" },
|
|
316
|
-
count: { type: "
|
|
317
|
-
formation_date: { type: "
|
|
594
|
+
count: { type: "integer" },
|
|
595
|
+
formation_date: { type: "integer" },
|
|
318
596
|
operator: { type: "single_link", target: "institution" },
|
|
319
597
|
equipment: { type: "multi_link", target: "construct" },
|
|
320
598
|
// Dynamics
|
|
@@ -341,8 +619,8 @@ var FIELD_SCHEMA = {
|
|
|
341
619
|
history: { type: "text" },
|
|
342
620
|
status: { type: "text" },
|
|
343
621
|
reach: { type: "text" },
|
|
344
|
-
start_date: { type: "
|
|
345
|
-
end_date: { type: "
|
|
622
|
+
start_date: { type: "integer" },
|
|
623
|
+
end_date: { type: "integer" },
|
|
346
624
|
founder: { type: "single_link", target: "character" },
|
|
347
625
|
custodian: { type: "single_link", target: "institution" },
|
|
348
626
|
// Involves
|
|
@@ -374,8 +652,8 @@ var FIELD_SCHEMA = {
|
|
|
374
652
|
image_url: { type: "text", required: false },
|
|
375
653
|
// Biology
|
|
376
654
|
appearance: { type: "text" },
|
|
377
|
-
weight: { type: "
|
|
378
|
-
height: { type: "
|
|
655
|
+
weight: { type: "integer" },
|
|
656
|
+
height: { type: "integer" },
|
|
379
657
|
species: { type: "multi_link", target: "species" },
|
|
380
658
|
// Behaviour
|
|
381
659
|
habits: { type: "text" },
|
|
@@ -385,14 +663,14 @@ var FIELD_SCHEMA = {
|
|
|
385
663
|
languages: { type: "multi_link", target: "language" },
|
|
386
664
|
// World
|
|
387
665
|
status: { type: "text" },
|
|
388
|
-
birth_date: { type: "
|
|
666
|
+
birth_date: { type: "integer" },
|
|
389
667
|
location: { type: "single_link", target: "location" },
|
|
390
668
|
zone: { type: "single_link", target: "zone" },
|
|
391
669
|
// TTRPG
|
|
392
|
-
challenge_rating: { type: "
|
|
393
|
-
hit_points: { type: "
|
|
394
|
-
armor_class: { type: "
|
|
395
|
-
speed: { type: "
|
|
670
|
+
challenge_rating: { type: "integer" },
|
|
671
|
+
hit_points: { type: "integer" },
|
|
672
|
+
armor_class: { type: "integer" },
|
|
673
|
+
speed: { type: "integer" },
|
|
396
674
|
actions: { type: "multi_link", target: "ability" }
|
|
397
675
|
},
|
|
398
676
|
event: {
|
|
@@ -406,8 +684,8 @@ var FIELD_SCHEMA = {
|
|
|
406
684
|
history: { type: "text" },
|
|
407
685
|
challenges: { type: "text" },
|
|
408
686
|
consequences: { type: "text" },
|
|
409
|
-
start_date: { type: "
|
|
410
|
-
end_date: { type: "
|
|
687
|
+
start_date: { type: "integer" },
|
|
688
|
+
end_date: { type: "integer" },
|
|
411
689
|
triggers: { type: "multi_link", target: "event" },
|
|
412
690
|
// Involves
|
|
413
691
|
characters: { type: "multi_link", target: "character" },
|
|
@@ -458,7 +736,7 @@ var FIELD_SCHEMA = {
|
|
|
458
736
|
image_url: { type: "text", required: false },
|
|
459
737
|
// Foundation
|
|
460
738
|
doctrine: { type: "text" },
|
|
461
|
-
founding_date: { type: "
|
|
739
|
+
founding_date: { type: "integer" },
|
|
462
740
|
parent_institution: { type: "single_link", target: "institution" },
|
|
463
741
|
// Claims
|
|
464
742
|
zones: { type: "multi_link", target: "zone" },
|
|
@@ -498,7 +776,7 @@ var FIELD_SCHEMA = {
|
|
|
498
776
|
// Code
|
|
499
777
|
declaration: { type: "text" },
|
|
500
778
|
purpose: { type: "text" },
|
|
501
|
-
date: { type: "
|
|
779
|
+
date: { type: "integer" },
|
|
502
780
|
parent_law: { type: "single_link", target: "law" },
|
|
503
781
|
penalties: { type: "multi_link", target: "construct" },
|
|
504
782
|
// World
|
|
@@ -519,7 +797,7 @@ var FIELD_SCHEMA = {
|
|
|
519
797
|
// Setting
|
|
520
798
|
form: { type: "text" },
|
|
521
799
|
function: { type: "text" },
|
|
522
|
-
founding_date: { type: "
|
|
800
|
+
founding_date: { type: "integer" },
|
|
523
801
|
parent_location: { type: "single_link", target: "location" },
|
|
524
802
|
populations: { type: "multi_link", target: "collective" },
|
|
525
803
|
// Politics
|
|
@@ -551,7 +829,7 @@ var FIELD_SCHEMA = {
|
|
|
551
829
|
building_methods: { type: "multi_link", target: "construct" },
|
|
552
830
|
// Defense
|
|
553
831
|
defensibility: { type: "text" },
|
|
554
|
-
elevation: { type: "
|
|
832
|
+
elevation: { type: "integer" },
|
|
555
833
|
fighters: { type: "multi_link", target: "construct" },
|
|
556
834
|
defensive_objects: { type: "multi_link", target: "object" }
|
|
557
835
|
},
|
|
@@ -564,10 +842,10 @@ var FIELD_SCHEMA = {
|
|
|
564
842
|
image_url: { type: "text", required: false },
|
|
565
843
|
// Details
|
|
566
844
|
background_color: { type: "text" },
|
|
567
|
-
hierarchy: { type: "
|
|
568
|
-
width: { type: "
|
|
569
|
-
height: { type: "
|
|
570
|
-
depth: { type: "
|
|
845
|
+
hierarchy: { type: "integer" },
|
|
846
|
+
width: { type: "integer" },
|
|
847
|
+
height: { type: "integer" },
|
|
848
|
+
depth: { type: "integer" },
|
|
571
849
|
parent_map: { type: "single_link", target: "map" },
|
|
572
850
|
location: { type: "single_link", target: "location" }
|
|
573
851
|
},
|
|
@@ -581,10 +859,10 @@ var FIELD_SCHEMA = {
|
|
|
581
859
|
// Details
|
|
582
860
|
map: { type: "single_link", target: "map", required: true },
|
|
583
861
|
zone: { type: "single_link", target: "zone", required: true },
|
|
584
|
-
x: { type: "
|
|
585
|
-
y: { type: "
|
|
586
|
-
z: { type: "
|
|
587
|
-
order: { type: "
|
|
862
|
+
x: { type: "integer", required: true },
|
|
863
|
+
y: { type: "integer", required: true },
|
|
864
|
+
z: { type: "integer" },
|
|
865
|
+
order: { type: "integer", required: true }
|
|
588
866
|
},
|
|
589
867
|
narrative: {
|
|
590
868
|
// Base fields (shared by all elements)
|
|
@@ -596,9 +874,9 @@ var FIELD_SCHEMA = {
|
|
|
596
874
|
// Context
|
|
597
875
|
story: { type: "text" },
|
|
598
876
|
consequences: { type: "text" },
|
|
599
|
-
start_date: { type: "
|
|
600
|
-
end_date: { type: "
|
|
601
|
-
order: { type: "
|
|
877
|
+
start_date: { type: "integer" },
|
|
878
|
+
end_date: { type: "integer" },
|
|
879
|
+
order: { type: "integer" },
|
|
602
880
|
parent_narrative: { type: "single_link", target: "narrative" },
|
|
603
881
|
protagonist: { type: "single_link", target: "character" },
|
|
604
882
|
antagonist: { type: "single_link", target: "character" },
|
|
@@ -633,8 +911,8 @@ var FIELD_SCHEMA = {
|
|
|
633
911
|
image_url: { type: "text", required: false },
|
|
634
912
|
// Form
|
|
635
913
|
aesthetics: { type: "text" },
|
|
636
|
-
weight: { type: "
|
|
637
|
-
amount: { type: "
|
|
914
|
+
weight: { type: "integer" },
|
|
915
|
+
amount: { type: "integer" },
|
|
638
916
|
parent_object: { type: "single_link", target: "object" },
|
|
639
917
|
materials: { type: "multi_link", target: "construct" },
|
|
640
918
|
technology: { type: "multi_link", target: "construct" },
|
|
@@ -659,7 +937,7 @@ var FIELD_SCHEMA = {
|
|
|
659
937
|
// Mechanics
|
|
660
938
|
expression: { type: "text" },
|
|
661
939
|
effects: { type: "text" },
|
|
662
|
-
duration: { type: "
|
|
940
|
+
duration: { type: "integer" },
|
|
663
941
|
catalysts: { type: "multi_link", target: "object" },
|
|
664
942
|
empowerments: { type: "multi_link", target: "ability" },
|
|
665
943
|
// World
|
|
@@ -682,9 +960,9 @@ var FIELD_SCHEMA = {
|
|
|
682
960
|
// ElementType enum value; YAML 'element' generic-link is split into _type + _id
|
|
683
961
|
element_id: { type: "single_link", target: "any", required: true },
|
|
684
962
|
// Can reference any element
|
|
685
|
-
x: { type: "
|
|
686
|
-
y: { type: "
|
|
687
|
-
z: { type: "
|
|
963
|
+
x: { type: "integer", required: true },
|
|
964
|
+
y: { type: "integer", required: true },
|
|
965
|
+
z: { type: "integer" }
|
|
688
966
|
},
|
|
689
967
|
relation: {
|
|
690
968
|
// Base fields (shared by all elements)
|
|
@@ -693,891 +971,359 @@ var FIELD_SCHEMA = {
|
|
|
693
971
|
supertype: { type: "text", required: false },
|
|
694
972
|
subtype: { type: "text", required: false },
|
|
695
973
|
image_url: { type: "text", required: false },
|
|
696
|
-
// Nature
|
|
697
|
-
background: { type: "text" },
|
|
698
|
-
start_date: { type: "
|
|
699
|
-
end_date: { type: "
|
|
700
|
-
intensity: { type: "
|
|
701
|
-
actor: { type: "single_link", target: "character" },
|
|
702
|
-
events: { type: "multi_link", target: "event" },
|
|
703
|
-
// Involves
|
|
704
|
-
characters: { type: "multi_link", target: "character" },
|
|
705
|
-
objects: { type: "multi_link", target: "object" },
|
|
706
|
-
locations: { type: "multi_link", target: "location" },
|
|
707
|
-
species: { type: "multi_link", target: "species" },
|
|
708
|
-
creatures: { type: "multi_link", target: "creature" },
|
|
709
|
-
institutions: { type: "multi_link", target: "institution" },
|
|
710
|
-
traits: { type: "multi_link", target: "trait" },
|
|
711
|
-
collectives: { type: "multi_link", target: "collective" },
|
|
712
|
-
zones: { type: "multi_link", target: "zone" },
|
|
713
|
-
abilities: { type: "multi_link", target: "ability" },
|
|
714
|
-
phenomena: { type: "multi_link", target: "phenomenon" },
|
|
715
|
-
languages: { type: "multi_link", target: "language" },
|
|
716
|
-
families: { type: "multi_link", target: "family" },
|
|
717
|
-
relations: { type: "multi_link", target: "relation" },
|
|
718
|
-
titles: { type: "multi_link", target: "title" },
|
|
719
|
-
constructs: { type: "multi_link", target: "construct" },
|
|
720
|
-
narratives: { type: "multi_link", target: "narrative" }
|
|
721
|
-
},
|
|
722
|
-
species: {
|
|
723
|
-
// Base fields (shared by all elements)
|
|
724
|
-
name: { type: "text", required: true },
|
|
725
|
-
description: { type: "text", required: false },
|
|
726
|
-
supertype: { type: "text", required: false },
|
|
727
|
-
subtype: { type: "text", required: false },
|
|
728
|
-
image_url: { type: "text", required: false },
|
|
729
|
-
// Biology
|
|
730
|
-
appearance: { type: "text" },
|
|
731
|
-
life_span: { type: "number" },
|
|
732
|
-
weight: { type: "number" },
|
|
733
|
-
nourishment: { type: "multi_link", target: "species" },
|
|
734
|
-
reproduction: { type: "multi_link", target: "construct" },
|
|
735
|
-
adaptations: { type: "multi_link", target: "ability" },
|
|
736
|
-
// Psychology
|
|
737
|
-
instincts: { type: "text" },
|
|
738
|
-
sociality: { type: "text" },
|
|
739
|
-
temperament: { type: "text" },
|
|
740
|
-
communication: { type: "text" },
|
|
741
|
-
aggression: { type: "number" },
|
|
742
|
-
traits: { type: "multi_link", target: "trait" },
|
|
743
|
-
// World
|
|
744
|
-
role: { type: "text" },
|
|
745
|
-
parent_species: { type: "single_link", target: "species" },
|
|
746
|
-
locations: { type: "multi_link", target: "location" },
|
|
747
|
-
zones: { type: "multi_link", target: "zone" },
|
|
748
|
-
affinities: { type: "multi_link", target: "phenomenon" }
|
|
749
|
-
},
|
|
750
|
-
title: {
|
|
751
|
-
// Base fields (shared by all elements)
|
|
752
|
-
name: { type: "text", required: true },
|
|
753
|
-
description: { type: "text", required: false },
|
|
754
|
-
supertype: { type: "text", required: false },
|
|
755
|
-
subtype: { type: "text", required: false },
|
|
756
|
-
image_url: { type: "text", required: false },
|
|
757
|
-
// Mandate
|
|
758
|
-
authority: { type: "text" },
|
|
759
|
-
eligibility: { type: "text" },
|
|
760
|
-
grant_date: { type: "number" },
|
|
761
|
-
revoke_date: { type: "number" },
|
|
762
|
-
issuer: { type: "single_link", target: "institution" },
|
|
763
|
-
body: { type: "single_link", target: "institution" },
|
|
764
|
-
superior_title: { type: "single_link", target: "title" },
|
|
765
|
-
holders: { type: "multi_link", target: "character" },
|
|
766
|
-
symbols: { type: "multi_link", target: "object" },
|
|
767
|
-
// World
|
|
768
|
-
status: { type: "text" },
|
|
769
|
-
history: { type: "text" },
|
|
770
|
-
characters: { type: "multi_link", target: "character" },
|
|
771
|
-
institutions: { type: "multi_link", target: "institution" },
|
|
772
|
-
families: { type: "multi_link", target: "family" },
|
|
773
|
-
zones: { type: "multi_link", target: "zone" },
|
|
774
|
-
locations: { type: "multi_link", target: "location" },
|
|
775
|
-
objects: { type: "multi_link", target: "object" },
|
|
776
|
-
constructs: { type: "multi_link", target: "construct" },
|
|
777
|
-
laws: { type: "multi_link", target: "law" },
|
|
778
|
-
collectives: { type: "multi_link", target: "collective" },
|
|
779
|
-
creatures: { type: "multi_link", target: "creature" },
|
|
780
|
-
phenomena: { type: "multi_link", target: "phenomenon" },
|
|
781
|
-
species: { type: "multi_link", target: "species" },
|
|
782
|
-
languages: { type: "multi_link", target: "language" }
|
|
783
|
-
},
|
|
784
|
-
trait: {
|
|
785
|
-
// Base fields (shared by all elements)
|
|
786
|
-
name: { type: "text", required: true },
|
|
787
|
-
description: { type: "text", required: false },
|
|
788
|
-
supertype: { type: "text", required: false },
|
|
789
|
-
subtype: { type: "text", required: false },
|
|
790
|
-
image_url: { type: "text", required: false },
|
|
791
|
-
// Qualitative
|
|
792
|
-
social_effects: { type: "text" },
|
|
793
|
-
physical_effects: { type: "text" },
|
|
794
|
-
functional_effects: { type: "text" },
|
|
795
|
-
personality_effects: { type: "text" },
|
|
796
|
-
behaviour_effects: { type: "text" },
|
|
797
|
-
// Quantitative
|
|
798
|
-
charisma: { type: "number" },
|
|
799
|
-
coercion: { type: "number" },
|
|
800
|
-
competence: { type: "number" },
|
|
801
|
-
compassion: { type: "number" },
|
|
802
|
-
creativity: { type: "number" },
|
|
803
|
-
courage: { type: "number" },
|
|
804
|
-
// World
|
|
805
|
-
significance: { type: "text" },
|
|
806
|
-
anti_trait: { type: "single_link", target: "trait" },
|
|
807
|
-
empowered_abilities: { type: "multi_link", target: "ability" }
|
|
974
|
+
// Nature
|
|
975
|
+
background: { type: "text" },
|
|
976
|
+
start_date: { type: "integer" },
|
|
977
|
+
end_date: { type: "integer" },
|
|
978
|
+
intensity: { type: "integer" },
|
|
979
|
+
actor: { type: "single_link", target: "character" },
|
|
980
|
+
events: { type: "multi_link", target: "event" },
|
|
981
|
+
// Involves
|
|
982
|
+
characters: { type: "multi_link", target: "character" },
|
|
983
|
+
objects: { type: "multi_link", target: "object" },
|
|
984
|
+
locations: { type: "multi_link", target: "location" },
|
|
985
|
+
species: { type: "multi_link", target: "species" },
|
|
986
|
+
creatures: { type: "multi_link", target: "creature" },
|
|
987
|
+
institutions: { type: "multi_link", target: "institution" },
|
|
988
|
+
traits: { type: "multi_link", target: "trait" },
|
|
989
|
+
collectives: { type: "multi_link", target: "collective" },
|
|
990
|
+
zones: { type: "multi_link", target: "zone" },
|
|
991
|
+
abilities: { type: "multi_link", target: "ability" },
|
|
992
|
+
phenomena: { type: "multi_link", target: "phenomenon" },
|
|
993
|
+
languages: { type: "multi_link", target: "language" },
|
|
994
|
+
families: { type: "multi_link", target: "family" },
|
|
995
|
+
relations: { type: "multi_link", target: "relation" },
|
|
996
|
+
titles: { type: "multi_link", target: "title" },
|
|
997
|
+
constructs: { type: "multi_link", target: "construct" },
|
|
998
|
+
narratives: { type: "multi_link", target: "narrative" }
|
|
808
999
|
},
|
|
809
|
-
|
|
1000
|
+
species: {
|
|
810
1001
|
// Base fields (shared by all elements)
|
|
811
1002
|
name: { type: "text", required: true },
|
|
812
1003
|
description: { type: "text", required: false },
|
|
813
1004
|
supertype: { type: "text", required: false },
|
|
814
1005
|
subtype: { type: "text", required: false },
|
|
815
1006
|
image_url: { type: "text", required: false },
|
|
816
|
-
//
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1007
|
+
// Biology
|
|
1008
|
+
appearance: { type: "text" },
|
|
1009
|
+
life_span: { type: "integer" },
|
|
1010
|
+
weight: { type: "integer" },
|
|
1011
|
+
nourishment: { type: "multi_link", target: "species" },
|
|
1012
|
+
reproduction: { type: "multi_link", target: "construct" },
|
|
1013
|
+
adaptations: { type: "multi_link", target: "ability" },
|
|
1014
|
+
// Psychology
|
|
1015
|
+
instincts: { type: "text" },
|
|
1016
|
+
sociality: { type: "text" },
|
|
1017
|
+
temperament: { type: "text" },
|
|
1018
|
+
communication: { type: "text" },
|
|
1019
|
+
aggression: { type: "integer" },
|
|
1020
|
+
traits: { type: "multi_link", target: "trait" },
|
|
822
1021
|
// World
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
* See base-tool/src/llm/token-service.ts:99-155 for full implementation example
|
|
907
|
-
* including client-side decryption with the 'fernet' npm package.
|
|
908
|
-
*
|
|
909
|
-
* @returns Encrypted access key and session info
|
|
910
|
-
* @throws Error if insufficient tokens (< 100)
|
|
911
|
-
* @example
|
|
912
|
-
* ```typescript
|
|
913
|
-
* // Get encrypted key
|
|
914
|
-
* const access = await client.tokens.getAccessKey();
|
|
915
|
-
*
|
|
916
|
-
* // Decrypt using fernet library (see base-tool for full example)
|
|
917
|
-
* // 1. Derive key from world ID using SHA-256
|
|
918
|
-
* // 2. Use 'fernet' npm package to decrypt
|
|
919
|
-
* // 3. Use decrypted OpenAI key for direct API calls
|
|
920
|
-
* // 4. Report usage with access.session_id
|
|
921
|
-
*
|
|
922
|
-
* console.log('Session:', access.session_id);
|
|
923
|
-
* console.log('Expires:', access.expires_at);
|
|
924
|
-
* ```
|
|
925
|
-
*/
|
|
926
|
-
async getAccessKey() {
|
|
927
|
-
return this.client.request("GET", "/tokens/access-key/");
|
|
928
|
-
}
|
|
929
|
-
/**
|
|
930
|
-
* Revoke a specific token session
|
|
931
|
-
*
|
|
932
|
-
* Invalidates the session ID obtained from getAccessKey().
|
|
933
|
-
* Use when cleaning up or on logout.
|
|
934
|
-
*
|
|
935
|
-
* @param sessionId - Session ID to revoke
|
|
936
|
-
* @returns Revocation result
|
|
937
|
-
* @example
|
|
938
|
-
* ```typescript
|
|
939
|
-
* await client.tokens.revokeSession('session-id-here');
|
|
940
|
-
* ```
|
|
941
|
-
*/
|
|
942
|
-
async revokeSession(sessionId) {
|
|
943
|
-
return this.client.request(
|
|
944
|
-
"POST",
|
|
945
|
-
`/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}`
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Revoke all active sessions (emergency use)
|
|
950
|
-
*
|
|
951
|
-
* Invalidates all token sessions for the authenticated user.
|
|
952
|
-
* Use for security cleanup or when sessions are stuck.
|
|
953
|
-
*
|
|
954
|
-
* @returns Revocation result with count of revoked sessions
|
|
955
|
-
* @example
|
|
956
|
-
* ```typescript
|
|
957
|
-
* const result = await client.tokens.revokeAllSessions();
|
|
958
|
-
* console.log(`Revoked ${result.sessions_revoked} sessions`);
|
|
959
|
-
* ```
|
|
960
|
-
*/
|
|
961
|
-
async revokeAllSessions() {
|
|
962
|
-
return this.client.request("POST", "/tokens/revoke-all-sessions/");
|
|
963
|
-
}
|
|
964
|
-
/**
|
|
965
|
-
* Get public encryption info (no auth required)
|
|
966
|
-
*
|
|
967
|
-
* Returns algorithm details and example code for client-side decryption.
|
|
968
|
-
* Public endpoint - can be called without authentication.
|
|
969
|
-
*
|
|
970
|
-
* @returns Encryption algorithm and implementation details
|
|
971
|
-
* @example
|
|
972
|
-
* ```typescript
|
|
973
|
-
* const info = await client.tokens.getEncryptionInfo();
|
|
974
|
-
* console.log('Algorithm:', info.algorithm);
|
|
975
|
-
* console.log('Key derivation:', info.key_derivation);
|
|
976
|
-
* console.log(info.javascript_example);
|
|
977
|
-
* ```
|
|
978
|
-
*/
|
|
979
|
-
async getEncryptionInfo() {
|
|
980
|
-
return this.client.request("GET", "/tokens/encryption-info/");
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
|
|
984
|
-
// src/client.ts
|
|
985
|
-
var Resource = class {
|
|
986
|
-
constructor(client, elementType) {
|
|
987
|
-
this.client = client;
|
|
988
|
-
this.elementType = elementType;
|
|
989
|
-
}
|
|
990
|
-
async list(options) {
|
|
991
|
-
const response = await this.client.request("GET", `/${this.elementType}/`, { params: options });
|
|
992
|
-
if (Array.isArray(response)) {
|
|
993
|
-
return {
|
|
994
|
-
count: response.length,
|
|
995
|
-
next: null,
|
|
996
|
-
previous: null,
|
|
997
|
-
results: response
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
return response;
|
|
1001
|
-
}
|
|
1002
|
-
async get(id) {
|
|
1003
|
-
return this.client.request("GET", `/${this.elementType}/${id}/`);
|
|
1004
|
-
}
|
|
1005
|
-
async create(data) {
|
|
1006
|
-
let body = OnlyWorldsClient.prepareRelations(data, this.elementType);
|
|
1007
|
-
if (this.elementType === "pin") body = this.roundPinCoordinates(body);
|
|
1008
|
-
return this.client.request("POST", `/${this.elementType}/`, { body });
|
|
1009
|
-
}
|
|
1010
|
-
async update(id, data) {
|
|
1011
|
-
let body = OnlyWorldsClient.prepareRelations(data, this.elementType);
|
|
1012
|
-
if (this.elementType === "pin") body = this.roundPinCoordinates(body);
|
|
1013
|
-
return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body });
|
|
1014
|
-
}
|
|
1015
|
-
async delete(id) {
|
|
1016
|
-
return this.client.request("DELETE", `/${this.elementType}/${id}/`);
|
|
1017
|
-
}
|
|
1018
|
-
/**
|
|
1019
|
-
* Round Pin coordinates to integers (API requirement)
|
|
1020
|
-
* @private
|
|
1021
|
-
*/
|
|
1022
|
-
roundPinCoordinates(data) {
|
|
1023
|
-
const rounded = { ...data };
|
|
1024
|
-
if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x);
|
|
1025
|
-
if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y);
|
|
1026
|
-
if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z);
|
|
1027
|
-
return rounded;
|
|
1028
|
-
}
|
|
1029
|
-
};
|
|
1030
|
-
var WorldResource = class {
|
|
1031
|
-
constructor(client) {
|
|
1032
|
-
this.client = client;
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Get the world associated with the current API key
|
|
1036
|
-
* Returns the world directly (not wrapped in pagination)
|
|
1037
|
-
*/
|
|
1038
|
-
async get() {
|
|
1039
|
-
return this.client.request("GET", "/world/");
|
|
1040
|
-
}
|
|
1041
|
-
/**
|
|
1042
|
-
* Update the current world
|
|
1043
|
-
*/
|
|
1044
|
-
async update(data) {
|
|
1045
|
-
return this.client.request("PATCH", "/world/", { body: data });
|
|
1046
|
-
}
|
|
1047
|
-
};
|
|
1048
|
-
var OnlyWorldsClient = class {
|
|
1049
|
-
constructor(config) {
|
|
1050
|
-
this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi";
|
|
1051
|
-
this.headers = {
|
|
1052
|
-
"Content-Type": "application/json",
|
|
1053
|
-
"API-Key": config.apiKey,
|
|
1054
|
-
"API-Pin": config.apiPin
|
|
1055
|
-
};
|
|
1056
|
-
this.worlds = new WorldResource(this);
|
|
1057
|
-
this.tokens = new TokenResource(this);
|
|
1058
|
-
this.abilities = new Resource(this, "ability");
|
|
1059
|
-
this.characters = new Resource(this, "character");
|
|
1060
|
-
this.collectives = new Resource(this, "collective");
|
|
1061
|
-
this.constructs = new Resource(this, "construct");
|
|
1062
|
-
this.creatures = new Resource(this, "creature");
|
|
1063
|
-
this.events = new Resource(this, "event");
|
|
1064
|
-
this.families = new Resource(this, "family");
|
|
1065
|
-
this.institutions = new Resource(this, "institution");
|
|
1066
|
-
this.languages = new Resource(this, "language");
|
|
1067
|
-
this.laws = new Resource(this, "law");
|
|
1068
|
-
this.locations = new Resource(this, "location");
|
|
1069
|
-
this.maps = new Resource(this, "map");
|
|
1070
|
-
this.markers = new Resource(this, "marker");
|
|
1071
|
-
this.narratives = new Resource(this, "narrative");
|
|
1072
|
-
this.objects = new Resource(this, "object");
|
|
1073
|
-
this.phenomena = new Resource(this, "phenomenon");
|
|
1074
|
-
this.pins = new Resource(this, "pin");
|
|
1075
|
-
this.relations = new Resource(this, "relation");
|
|
1076
|
-
this.species = new Resource(this, "species");
|
|
1077
|
-
this.titles = new Resource(this, "title");
|
|
1078
|
-
this.traits = new Resource(this, "trait");
|
|
1079
|
-
this.zones = new Resource(this, "zone");
|
|
1080
|
-
}
|
|
1081
|
-
/**
|
|
1082
|
-
* Make a request to the OnlyWorlds API
|
|
1083
|
-
*/
|
|
1084
|
-
async request(method, path, options) {
|
|
1085
|
-
const url = new URL(`${this.baseUrl}${path}`);
|
|
1086
|
-
if (options?.params) {
|
|
1087
|
-
Object.entries(options.params).forEach(([key, value]) => {
|
|
1088
|
-
if (value !== void 0 && value !== null) {
|
|
1089
|
-
url.searchParams.append(key, String(value));
|
|
1090
|
-
}
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
const fetchOptions = {
|
|
1094
|
-
method,
|
|
1095
|
-
headers: this.headers
|
|
1096
|
-
};
|
|
1097
|
-
if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) {
|
|
1098
|
-
fetchOptions.body = JSON.stringify(options.body);
|
|
1099
|
-
}
|
|
1100
|
-
const response = await fetch(url.toString(), fetchOptions);
|
|
1101
|
-
if (!response.ok) {
|
|
1102
|
-
let errorMessage = `API Error ${response.status}`;
|
|
1103
|
-
try {
|
|
1104
|
-
const errorText = await response.text();
|
|
1105
|
-
if (errorText) {
|
|
1106
|
-
try {
|
|
1107
|
-
const errorJson = JSON.parse(errorText);
|
|
1108
|
-
if (Array.isArray(errorJson.detail)) {
|
|
1109
|
-
const validationErrors = errorJson.detail.map((err) => {
|
|
1110
|
-
const location = err.loc ? err.loc.join(".") : "unknown";
|
|
1111
|
-
return `${location}: ${err.msg}`;
|
|
1112
|
-
}).join("; ");
|
|
1113
|
-
errorMessage += `: ${validationErrors}`;
|
|
1114
|
-
} else {
|
|
1115
|
-
errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`;
|
|
1116
|
-
}
|
|
1117
|
-
} catch {
|
|
1118
|
-
errorMessage += `: ${errorText}`;
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
} catch {
|
|
1122
|
-
}
|
|
1123
|
-
throw new Error(errorMessage);
|
|
1124
|
-
}
|
|
1125
|
-
if (response.status === 204) {
|
|
1126
|
-
return void 0;
|
|
1127
|
-
}
|
|
1128
|
-
return response.json();
|
|
1129
|
-
}
|
|
1130
|
-
/**
|
|
1131
|
-
* Helper to convert nested objects to _id/_ids format (legacy method)
|
|
1132
|
-
* @deprecated Use prepareRelations instead - it's called automatically in create/update
|
|
1133
|
-
*/
|
|
1134
|
-
static prepareInput(data) {
|
|
1135
|
-
const result = { ...data };
|
|
1136
|
-
for (const [key, value] of Object.entries(result)) {
|
|
1137
|
-
if (value && typeof value === "object" && !Array.isArray(value) && "id" in value) {
|
|
1138
|
-
delete result[key];
|
|
1139
|
-
result[`${key}_id`] = value.id;
|
|
1140
|
-
} else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
|
|
1141
|
-
delete result[key];
|
|
1142
|
-
result[`${key}_ids`] = value.map((item) => item.id);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
return result;
|
|
1146
|
-
}
|
|
1147
|
-
/**
|
|
1148
|
-
* Convert relation fields to API format (_id/_ids suffix)
|
|
1149
|
-
*
|
|
1150
|
-
* The OnlyWorlds API expects:
|
|
1151
|
-
* - single_link fields: fieldname_id (e.g., birthplace_id)
|
|
1152
|
-
* - multi_link fields: fieldname_ids (e.g., species_ids)
|
|
1153
|
-
*
|
|
1154
|
-
* This method auto-converts based on FIELD_SCHEMA:
|
|
1155
|
-
* - { species: ["id1", "id2"] } → { species_ids: ["id1", "id2"] }
|
|
1156
|
-
* - { birthplace: "location-id" } → { birthplace_id: "location-id" }
|
|
1157
|
-
* - { species: [{id: "id1", name: "X"}] } → { species_ids: ["id1"] }
|
|
1158
|
-
*
|
|
1159
|
-
* Called automatically by create() and update() methods.
|
|
1160
|
-
*/
|
|
1161
|
-
static prepareRelations(data, elementType) {
|
|
1162
|
-
const schema = FIELD_SCHEMA[elementType];
|
|
1163
|
-
if (!schema) return data;
|
|
1164
|
-
const result = { ...data };
|
|
1165
|
-
for (const [key, value] of Object.entries(result)) {
|
|
1166
|
-
const fieldDef = schema[key];
|
|
1167
|
-
if (!fieldDef) continue;
|
|
1168
|
-
if (fieldDef.type === "single_link") {
|
|
1169
|
-
delete result[key];
|
|
1170
|
-
if (value && typeof value === "object" && "id" in value) {
|
|
1171
|
-
result[`${key}_id`] = value.id;
|
|
1172
|
-
} else if (typeof value === "string" || value === null) {
|
|
1173
|
-
result[`${key}_id`] = value;
|
|
1174
|
-
}
|
|
1175
|
-
} else if (fieldDef.type === "multi_link") {
|
|
1176
|
-
delete result[key];
|
|
1177
|
-
if (Array.isArray(value)) {
|
|
1178
|
-
if (value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
|
|
1179
|
-
result[`${key}_ids`] = value.map((item) => item.id);
|
|
1180
|
-
} else {
|
|
1181
|
-
result[`${key}_ids`] = value;
|
|
1182
|
-
}
|
|
1183
|
-
} else {
|
|
1184
|
-
result[`${key}_ids`] = [];
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
return result;
|
|
1022
|
+
role: { type: "text" },
|
|
1023
|
+
parent_species: { type: "single_link", target: "species" },
|
|
1024
|
+
locations: { type: "multi_link", target: "location" },
|
|
1025
|
+
zones: { type: "multi_link", target: "zone" },
|
|
1026
|
+
affinities: { type: "multi_link", target: "phenomenon" }
|
|
1027
|
+
},
|
|
1028
|
+
title: {
|
|
1029
|
+
// Base fields (shared by all elements)
|
|
1030
|
+
name: { type: "text", required: true },
|
|
1031
|
+
description: { type: "text", required: false },
|
|
1032
|
+
supertype: { type: "text", required: false },
|
|
1033
|
+
subtype: { type: "text", required: false },
|
|
1034
|
+
image_url: { type: "text", required: false },
|
|
1035
|
+
// Mandate
|
|
1036
|
+
authority: { type: "text" },
|
|
1037
|
+
eligibility: { type: "text" },
|
|
1038
|
+
grant_date: { type: "integer" },
|
|
1039
|
+
revoke_date: { type: "integer" },
|
|
1040
|
+
issuer: { type: "single_link", target: "institution" },
|
|
1041
|
+
body: { type: "single_link", target: "institution" },
|
|
1042
|
+
superior_title: { type: "single_link", target: "title" },
|
|
1043
|
+
holders: { type: "multi_link", target: "character" },
|
|
1044
|
+
symbols: { type: "multi_link", target: "object" },
|
|
1045
|
+
// World
|
|
1046
|
+
status: { type: "text" },
|
|
1047
|
+
history: { type: "text" },
|
|
1048
|
+
characters: { type: "multi_link", target: "character" },
|
|
1049
|
+
institutions: { type: "multi_link", target: "institution" },
|
|
1050
|
+
families: { type: "multi_link", target: "family" },
|
|
1051
|
+
zones: { type: "multi_link", target: "zone" },
|
|
1052
|
+
locations: { type: "multi_link", target: "location" },
|
|
1053
|
+
objects: { type: "multi_link", target: "object" },
|
|
1054
|
+
constructs: { type: "multi_link", target: "construct" },
|
|
1055
|
+
laws: { type: "multi_link", target: "law" },
|
|
1056
|
+
collectives: { type: "multi_link", target: "collective" },
|
|
1057
|
+
creatures: { type: "multi_link", target: "creature" },
|
|
1058
|
+
phenomena: { type: "multi_link", target: "phenomenon" },
|
|
1059
|
+
species: { type: "multi_link", target: "species" },
|
|
1060
|
+
languages: { type: "multi_link", target: "language" }
|
|
1061
|
+
},
|
|
1062
|
+
trait: {
|
|
1063
|
+
// Base fields (shared by all elements)
|
|
1064
|
+
name: { type: "text", required: true },
|
|
1065
|
+
description: { type: "text", required: false },
|
|
1066
|
+
supertype: { type: "text", required: false },
|
|
1067
|
+
subtype: { type: "text", required: false },
|
|
1068
|
+
image_url: { type: "text", required: false },
|
|
1069
|
+
// Qualitative
|
|
1070
|
+
social_effects: { type: "text" },
|
|
1071
|
+
physical_effects: { type: "text" },
|
|
1072
|
+
functional_effects: { type: "text" },
|
|
1073
|
+
personality_effects: { type: "text" },
|
|
1074
|
+
behaviour_effects: { type: "text" },
|
|
1075
|
+
// Quantitative
|
|
1076
|
+
charisma: { type: "integer" },
|
|
1077
|
+
coercion: { type: "integer" },
|
|
1078
|
+
competence: { type: "integer" },
|
|
1079
|
+
compassion: { type: "integer" },
|
|
1080
|
+
creativity: { type: "integer" },
|
|
1081
|
+
courage: { type: "integer" },
|
|
1082
|
+
// World
|
|
1083
|
+
significance: { type: "text" },
|
|
1084
|
+
anti_trait: { type: "single_link", target: "trait" },
|
|
1085
|
+
empowered_abilities: { type: "multi_link", target: "ability" }
|
|
1086
|
+
},
|
|
1087
|
+
zone: {
|
|
1088
|
+
// Base fields (shared by all elements)
|
|
1089
|
+
name: { type: "text", required: true },
|
|
1090
|
+
description: { type: "text", required: false },
|
|
1091
|
+
supertype: { type: "text", required: false },
|
|
1092
|
+
subtype: { type: "text", required: false },
|
|
1093
|
+
image_url: { type: "text", required: false },
|
|
1094
|
+
// Scope
|
|
1095
|
+
role: { type: "text" },
|
|
1096
|
+
start_date: { type: "integer" },
|
|
1097
|
+
end_date: { type: "integer" },
|
|
1098
|
+
phenomena: { type: "multi_link", target: "phenomenon" },
|
|
1099
|
+
linked_zones: { type: "multi_link", target: "zone" },
|
|
1100
|
+
// World
|
|
1101
|
+
context: { type: "text" },
|
|
1102
|
+
populations: { type: "multi_link", target: "collective" },
|
|
1103
|
+
titles: { type: "multi_link", target: "title" },
|
|
1104
|
+
principles: { type: "multi_link", target: "construct" }
|
|
1189
1105
|
}
|
|
1190
1106
|
};
|
|
1191
|
-
|
|
1192
|
-
// src/token-types.ts
|
|
1193
|
-
var GameTier = /* @__PURE__ */ ((GameTier2) => {
|
|
1194
|
-
GameTier2["FREE"] = "free";
|
|
1195
|
-
GameTier2["SILVER"] = "silver";
|
|
1196
|
-
GameTier2["GOLD"] = "gold";
|
|
1197
|
-
GameTier2["PLATINUM"] = "platinum";
|
|
1198
|
-
GameTier2["DIAMOND"] = "diamond";
|
|
1199
|
-
GameTier2["DELUXE"] = "deluxe";
|
|
1200
|
-
return GameTier2;
|
|
1201
|
-
})(GameTier || {});
|
|
1202
|
-
|
|
1203
|
-
// src/icon-utils.ts
|
|
1204
1107
|
var PLURAL_TO_SINGULAR = {
|
|
1205
|
-
abilities: "ability"
|
|
1206
|
-
characters: "character"
|
|
1207
|
-
collectives: "collective"
|
|
1208
|
-
constructs: "construct"
|
|
1209
|
-
creatures: "creature"
|
|
1210
|
-
events: "event"
|
|
1211
|
-
families: "family"
|
|
1212
|
-
institutions: "institution"
|
|
1213
|
-
languages: "language"
|
|
1214
|
-
laws: "law"
|
|
1215
|
-
locations: "location"
|
|
1216
|
-
maps: "map"
|
|
1217
|
-
markers: "marker"
|
|
1218
|
-
narratives: "narrative"
|
|
1219
|
-
objects: "object"
|
|
1220
|
-
phenomena: "phenomenon"
|
|
1108
|
+
abilities: "ability",
|
|
1109
|
+
characters: "character",
|
|
1110
|
+
collectives: "collective",
|
|
1111
|
+
constructs: "construct",
|
|
1112
|
+
creatures: "creature",
|
|
1113
|
+
events: "event",
|
|
1114
|
+
families: "family",
|
|
1115
|
+
institutions: "institution",
|
|
1116
|
+
languages: "language",
|
|
1117
|
+
laws: "law",
|
|
1118
|
+
locations: "location",
|
|
1119
|
+
maps: "map",
|
|
1120
|
+
markers: "marker",
|
|
1121
|
+
narratives: "narrative",
|
|
1122
|
+
objects: "object",
|
|
1123
|
+
phenomena: "phenomenon",
|
|
1221
1124
|
// irregular plural
|
|
1222
|
-
pins: "pin"
|
|
1223
|
-
relations: "relation"
|
|
1224
|
-
species: "species"
|
|
1125
|
+
pins: "pin",
|
|
1126
|
+
relations: "relation",
|
|
1127
|
+
species: "species",
|
|
1225
1128
|
// same singular/plural
|
|
1226
|
-
titles: "title"
|
|
1227
|
-
traits: "trait"
|
|
1228
|
-
zones: "zone"
|
|
1129
|
+
titles: "title",
|
|
1130
|
+
traits: "trait",
|
|
1131
|
+
zones: "zone"
|
|
1229
1132
|
};
|
|
1230
1133
|
function getElementIcon(type) {
|
|
1231
1134
|
const lower = type.toLowerCase();
|
|
1232
1135
|
if (lower in PLURAL_TO_SINGULAR) {
|
|
1233
1136
|
return ELEMENT_ICONS[PLURAL_TO_SINGULAR[lower]];
|
|
1234
1137
|
}
|
|
1235
|
-
const singular =
|
|
1236
|
-
(et) => et.toLowerCase() === lower
|
|
1237
|
-
);
|
|
1138
|
+
const singular = ELEMENT_TYPES.find((et) => et.toLowerCase() === lower);
|
|
1238
1139
|
if (singular) {
|
|
1239
1140
|
return ELEMENT_ICONS[singular];
|
|
1240
1141
|
}
|
|
1241
1142
|
return "help_outline";
|
|
1242
1143
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
var OwApiError = class extends Error {
|
|
1246
|
-
constructor(status, code, message, docUrl, detail, type = null, param = null) {
|
|
1247
|
-
super(message);
|
|
1248
|
-
this.name = "OwApiError";
|
|
1249
|
-
this.status = status;
|
|
1250
|
-
this.code = code;
|
|
1251
|
-
this.type = type;
|
|
1252
|
-
this.param = param;
|
|
1253
|
-
this.docUrl = docUrl;
|
|
1254
|
-
this.detail = detail;
|
|
1255
|
-
}
|
|
1256
|
-
get isAuthError() {
|
|
1257
|
-
return this.code === "invalid_credentials" || this.code === "key_revoked" || this.code === "world_gone";
|
|
1258
|
-
}
|
|
1259
|
-
/** 422s/400s name the offending param/field -- typos error loudly platform-wide. */
|
|
1260
|
-
get isValidationError() {
|
|
1261
|
-
return this.status === 422 || this.status === 400;
|
|
1262
|
-
}
|
|
1263
|
-
/** Same Idempotency-Key replayed with a different payload. */
|
|
1264
|
-
get isIdempotencyConflict() {
|
|
1265
|
-
return this.status === 409;
|
|
1266
|
-
}
|
|
1267
|
-
};
|
|
1268
|
-
var OwNetworkError = class extends Error {
|
|
1269
|
-
constructor(message, cause) {
|
|
1270
|
-
super(message);
|
|
1271
|
-
this.name = "OwNetworkError";
|
|
1272
|
-
this.cause2 = cause;
|
|
1273
|
-
}
|
|
1274
|
-
};
|
|
1275
|
-
function parseEnvelope(status, body) {
|
|
1276
|
-
const env = body && typeof body === "object" ? body : {};
|
|
1277
|
-
const nested = typeof env.error === "object" && env.error !== null ? env.error : void 0;
|
|
1278
|
-
const code = env.code ?? nested?.code ?? (typeof env.error === "string" ? env.error : null) ?? null;
|
|
1279
|
-
const type = env.type ?? nested?.type ?? null;
|
|
1280
|
-
const param = env.param ?? nested?.param ?? null;
|
|
1281
|
-
const docUrl = env.doc_url ?? nested?.doc_url ?? null;
|
|
1282
|
-
const message = env.message ?? nested?.message ?? (typeof env.detail === "string" ? env.detail : void 0) ?? `OnlyWorlds API error ${status}${code ? ` (${code})` : ""}`;
|
|
1283
|
-
return new OwApiError(status, code, message, docUrl, body, type, param);
|
|
1284
|
-
}
|
|
1285
|
-
async function errorFromResponse(res) {
|
|
1286
|
-
let body = null;
|
|
1287
|
-
let text = "";
|
|
1288
|
-
try {
|
|
1289
|
-
text = await res.text();
|
|
1290
|
-
body = text ? JSON.parse(text) : null;
|
|
1291
|
-
} catch {
|
|
1292
|
-
body = text || null;
|
|
1293
|
-
}
|
|
1294
|
-
return parseEnvelope(res.status, body);
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// src/v2/keys.ts
|
|
1298
|
-
var LEGACY_RE = /^[0-9]{10}$/;
|
|
1299
|
-
function isDemoKey(key) {
|
|
1300
|
-
return LEGACY_RE.test(key) && key >= "0000000000" && key <= "0000000009";
|
|
1301
|
-
}
|
|
1302
|
-
function detectKeyKind(key) {
|
|
1303
|
-
if (key.startsWith("ow_w_")) return "write";
|
|
1304
|
-
if (key.startsWith("ow_r_")) return "read";
|
|
1305
|
-
if (key.startsWith("ow_a_")) return "account";
|
|
1306
|
-
if (LEGACY_RE.test(key)) return "legacy";
|
|
1307
|
-
return "unknown";
|
|
1308
|
-
}
|
|
1309
|
-
function kindCanWrite(kind) {
|
|
1310
|
-
return kind === "write" || kind === "legacy";
|
|
1311
|
-
}
|
|
1312
|
-
function pinExpectation(kind) {
|
|
1313
|
-
switch (kind) {
|
|
1314
|
-
case "read":
|
|
1315
|
-
return "never";
|
|
1316
|
-
case "account":
|
|
1317
|
-
return "never";
|
|
1318
|
-
case "write":
|
|
1319
|
-
return "optional";
|
|
1320
|
-
case "legacy":
|
|
1321
|
-
return "required-for-private-reads";
|
|
1322
|
-
default:
|
|
1323
|
-
return "optional";
|
|
1324
|
-
}
|
|
1144
|
+
function getElementLabel(elementType) {
|
|
1145
|
+
return ELEMENT_LABELS[elementType];
|
|
1325
1146
|
}
|
|
1326
1147
|
|
|
1327
|
-
// src/
|
|
1328
|
-
var
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
var OwV2Client = class {
|
|
1332
|
-
constructor(config) {
|
|
1333
|
-
if (!config.apiKey) throw new Error("OwV2Client: apiKey is required");
|
|
1334
|
-
this.apiKey = config.apiKey;
|
|
1335
|
-
this.apiPin = config.apiPin || void 0;
|
|
1336
|
-
this.keyKind = detectKeyKind(config.apiKey);
|
|
1337
|
-
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1338
|
-
this.pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
1339
|
-
this.changesPageSize = config.changesPageSize ?? DEFAULT_CHANGES_PAGE_SIZE;
|
|
1340
|
-
this.fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1341
|
-
if (typeof this.fetchImpl !== "function") {
|
|
1342
|
-
throw new Error("OwV2Client: no fetch available -- supply config.fetch");
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
// -- Health & world meta -------------------------------------------------
|
|
1346
|
-
/** GET /health -- unauthenticated liveness pulse. */
|
|
1347
|
-
async health() {
|
|
1348
|
-
return this.request("GET", "/health/", { auth: false });
|
|
1349
|
-
}
|
|
1350
|
-
/**
|
|
1351
|
-
* GET /world -- world meta (name, calendar/time fields, public_read).
|
|
1352
|
-
* GOTCHA (by server design): world-meta edits do NOT appear in /changes and
|
|
1353
|
-
* do not bump change_seq. Poll getWorld().updated_at for meta freshness.
|
|
1354
|
-
*/
|
|
1355
|
-
async getWorld() {
|
|
1356
|
-
return this.request("GET", "/world/");
|
|
1357
|
-
}
|
|
1358
|
-
/** PATCH /world -- partial world-meta update. */
|
|
1359
|
-
async patchWorld(partial) {
|
|
1360
|
-
return this.request("PATCH", "/world/", { body: sanitizePayload(partial) });
|
|
1361
|
-
}
|
|
1362
|
-
// -- Element CRUD --------------------------------------------------------
|
|
1363
|
-
/** GET /{type}/ -- one cursor page. */
|
|
1364
|
-
async list(type, params = {}) {
|
|
1365
|
-
const query = buildQuery({
|
|
1366
|
-
limit: params.limit ?? this.pageSize,
|
|
1367
|
-
cursor: params.cursor,
|
|
1368
|
-
expand: params.expand?.join(","),
|
|
1369
|
-
fields: params.fields?.join(","),
|
|
1370
|
-
...params.filter
|
|
1371
|
-
});
|
|
1372
|
-
return this.request("GET", `/${type}/`, { query });
|
|
1373
|
-
}
|
|
1374
|
-
/** Cursor-walk every page of a type. Politeness: uses config pageSize. */
|
|
1375
|
-
async *listAll(type, params = {}) {
|
|
1376
|
-
let cursor;
|
|
1377
|
-
do {
|
|
1378
|
-
const page = await this.list(type, { ...params, cursor });
|
|
1379
|
-
for (const el of page.data) yield el;
|
|
1380
|
-
cursor = page.has_more && page.next_cursor ? page.next_cursor : void 0;
|
|
1381
|
-
} while (cursor);
|
|
1382
|
-
}
|
|
1383
|
-
/** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
|
|
1384
|
-
async get(type, id, opts = {}) {
|
|
1385
|
-
const query = buildQuery({ expand: opts.expand?.join(","), fields: opts.fields?.join(",") });
|
|
1386
|
-
return this.request("GET", `/${type}/${id}/`, { query });
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
|
|
1390
|
-
* caller omits one (design ruling D29d) so a retry carrying the same
|
|
1391
|
-
* Idempotency-Key is structurally safe. Callers MAY still supply their own id.
|
|
1392
|
-
*/
|
|
1393
|
-
async create(type, element, opts = {}) {
|
|
1394
|
-
const body = sanitizePayload(element);
|
|
1395
|
-
if (body.id === void 0 || body.id === null || body.id === "") {
|
|
1396
|
-
body.id = mintUuid();
|
|
1397
|
-
}
|
|
1398
|
-
return this.request("POST", `/${type}/`, {
|
|
1399
|
-
body,
|
|
1400
|
-
idempotencyKey: opts.idempotencyKey
|
|
1401
|
-
});
|
|
1402
|
-
}
|
|
1403
|
-
/** PUT /{type}/{id}/ -- upsert-by-client-id. The local-first write primitive. */
|
|
1404
|
-
async upsert(type, id, element) {
|
|
1405
|
-
return this.request("PUT", `/${type}/${id}/`, { body: sanitizePayload(element) });
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
|
|
1409
|
-
* replace wholesale, omitted fields stay untouched. For link arrays prefer
|
|
1410
|
-
* editLinks() -- atomic server-side merge, no read-before-write.
|
|
1411
|
-
*/
|
|
1412
|
-
async patch(type, id, partial) {
|
|
1413
|
-
return this.request("PATCH", `/${type}/${id}/`, { body: sanitizePayload(partial) });
|
|
1148
|
+
// src/token-resource.ts
|
|
1149
|
+
var TokenResource = class {
|
|
1150
|
+
constructor(client) {
|
|
1151
|
+
this.client = client;
|
|
1414
1152
|
}
|
|
1415
1153
|
/**
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1154
|
+
* Get current token status for authenticated user
|
|
1155
|
+
*
|
|
1156
|
+
* Returns daily token allowance, usage, and availability.
|
|
1157
|
+
* Matches base-tool's checkStatus() pattern.
|
|
1158
|
+
*
|
|
1159
|
+
* @returns Current token status
|
|
1160
|
+
* @example
|
|
1161
|
+
* ```typescript
|
|
1162
|
+
* const status = await client.tokens.getStatus();
|
|
1163
|
+
* console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`);
|
|
1164
|
+
* console.log(`Used today: ${status.tokens_used_today}`);
|
|
1165
|
+
* console.log(`Active sessions: ${status.sessions_active}`);
|
|
1166
|
+
* ```
|
|
1419
1167
|
*/
|
|
1420
|
-
async
|
|
1421
|
-
|
|
1168
|
+
async getStatus() {
|
|
1169
|
+
return this.client.request("GET", "/tokens/status/");
|
|
1422
1170
|
}
|
|
1423
1171
|
/**
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1172
|
+
* Consume tokens for service usage
|
|
1173
|
+
*
|
|
1174
|
+
* Reports token consumption to track daily usage. Allows consumption even if
|
|
1175
|
+
* exceeds available tokens (tracks as debt), but warns via error field.
|
|
1176
|
+
* Matches base-tool's reportUsage() pattern.
|
|
1177
|
+
*
|
|
1178
|
+
* @param params - Token consumption parameters
|
|
1179
|
+
* @returns Consumption result with updated balance
|
|
1180
|
+
* @example
|
|
1181
|
+
* ```typescript
|
|
1182
|
+
* const result = await client.tokens.consume({
|
|
1183
|
+
* amount: 500,
|
|
1184
|
+
* service: 'worldbuilding_tool',
|
|
1185
|
+
* metadata: {
|
|
1186
|
+
* feature: 'character_generation',
|
|
1187
|
+
* model: 'gpt-4',
|
|
1188
|
+
* prompt_tokens: 300,
|
|
1189
|
+
* completion_tokens: 200
|
|
1190
|
+
* }
|
|
1191
|
+
* });
|
|
1192
|
+
*
|
|
1193
|
+
* if (result.error) {
|
|
1194
|
+
* console.warn('Token warning:', result.error);
|
|
1195
|
+
* }
|
|
1196
|
+
* console.log(`${result.tokens_remaining} tokens remaining`);
|
|
1197
|
+
* ```
|
|
1428
1198
|
*/
|
|
1429
|
-
async
|
|
1430
|
-
return this.request("POST",
|
|
1199
|
+
async consume(params) {
|
|
1200
|
+
return this.client.request("POST", "/tokens/consume/", {
|
|
1201
|
+
body: {
|
|
1202
|
+
amount: params.amount,
|
|
1203
|
+
service: params.service || "sdk_client",
|
|
1204
|
+
session_id: params.sessionId ?? null,
|
|
1205
|
+
metadata: params.metadata ?? null
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1431
1208
|
}
|
|
1432
|
-
// -- Bulk ----------------------------------------------------------------
|
|
1433
1209
|
/**
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1210
|
+
* Get encrypted OpenAI API key (advanced use case)
|
|
1211
|
+
*
|
|
1212
|
+
* Requires minimum 100 tokens available. Creates a 1-hour session for tracking.
|
|
1213
|
+
* Returns encrypted key that must be decrypted client-side using Fernet.
|
|
1214
|
+
*
|
|
1215
|
+
* See base-tool/src/llm/token-service.ts:99-155 for full implementation example
|
|
1216
|
+
* including client-side decryption with the 'fernet' npm package.
|
|
1217
|
+
*
|
|
1218
|
+
* @returns Encrypted access key and session info
|
|
1219
|
+
* @throws Error if insufficient tokens (< 100)
|
|
1220
|
+
* @example
|
|
1221
|
+
* ```typescript
|
|
1222
|
+
* // Get encrypted key
|
|
1223
|
+
* const access = await client.tokens.getAccessKey();
|
|
1224
|
+
*
|
|
1225
|
+
* // Decrypt using fernet library (see base-tool for full example)
|
|
1226
|
+
* // 1. Derive key from world ID using SHA-256
|
|
1227
|
+
* // 2. Use 'fernet' npm package to decrypt
|
|
1228
|
+
* // 3. Use decrypted OpenAI key for direct API calls
|
|
1229
|
+
* // 4. Report usage with access.session_id
|
|
1230
|
+
*
|
|
1231
|
+
* console.log('Session:', access.session_id);
|
|
1232
|
+
* console.log('Expires:', access.expires_at);
|
|
1233
|
+
* ```
|
|
1441
1234
|
*/
|
|
1442
|
-
async
|
|
1443
|
-
|
|
1444
|
-
items: items.map((it) => ({ type: it.type, element: sanitizePayload(it.element) })),
|
|
1445
|
-
atomic: opts.atomic ?? false
|
|
1446
|
-
};
|
|
1447
|
-
return this.request("POST", "/bulk/", {
|
|
1448
|
-
body,
|
|
1449
|
-
idempotencyKey: opts.idempotencyKey,
|
|
1450
|
-
replayAware: true
|
|
1451
|
-
});
|
|
1235
|
+
async getAccessKey() {
|
|
1236
|
+
return this.client.request("GET", "/tokens/access-key/");
|
|
1452
1237
|
}
|
|
1453
|
-
// -- Changes feed --------------------------------------------------------
|
|
1454
1238
|
/**
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1239
|
+
* Revoke a specific token session
|
|
1240
|
+
*
|
|
1241
|
+
* Invalidates the session ID obtained from getAccessKey().
|
|
1242
|
+
* Use when cleaning up or on logout.
|
|
1243
|
+
*
|
|
1244
|
+
* @param sessionId - Session ID to revoke
|
|
1245
|
+
* @returns Revocation result
|
|
1246
|
+
* @example
|
|
1247
|
+
* ```typescript
|
|
1248
|
+
* await client.tokens.revokeSession('session-id-here');
|
|
1249
|
+
* ```
|
|
1461
1250
|
*/
|
|
1462
|
-
async
|
|
1463
|
-
|
|
1464
|
-
|
|
1251
|
+
async revokeSession(sessionId) {
|
|
1252
|
+
return this.client.request(
|
|
1253
|
+
"POST",
|
|
1254
|
+
`/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}`
|
|
1255
|
+
);
|
|
1465
1256
|
}
|
|
1466
1257
|
/**
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1469
|
-
*
|
|
1258
|
+
* Revoke all active sessions (emergency use)
|
|
1259
|
+
*
|
|
1260
|
+
* Invalidates all token sessions for the authenticated user.
|
|
1261
|
+
* Use for security cleanup or when sessions are stuck.
|
|
1262
|
+
*
|
|
1263
|
+
* @returns Revocation result with count of revoked sessions
|
|
1264
|
+
* @example
|
|
1265
|
+
* ```typescript
|
|
1266
|
+
* const result = await client.tokens.revokeAllSessions();
|
|
1267
|
+
* console.log(`Revoked ${result.sessions_revoked} sessions`);
|
|
1268
|
+
* ```
|
|
1470
1269
|
*/
|
|
1471
|
-
async
|
|
1472
|
-
|
|
1473
|
-
let page;
|
|
1474
|
-
do {
|
|
1475
|
-
page = await this.changes({ since: cursor });
|
|
1476
|
-
for (const op of page.changes) yield op;
|
|
1477
|
-
cursor = page.cursor;
|
|
1478
|
-
} while (page.has_more);
|
|
1479
|
-
return { cursor: page.cursor, head: page.head };
|
|
1270
|
+
async revokeAllSessions() {
|
|
1271
|
+
return this.client.request("POST", "/tokens/revoke-all-sessions/");
|
|
1480
1272
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
method,
|
|
1499
|
-
headers,
|
|
1500
|
-
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
1501
|
-
});
|
|
1502
|
-
} catch (cause) {
|
|
1503
|
-
throw new OwNetworkError(`OnlyWorlds request failed: ${method} ${path}`, cause);
|
|
1504
|
-
}
|
|
1505
|
-
if (!res.ok) throw await errorFromResponse(res);
|
|
1506
|
-
if (res.status === 204 || opts.allowEmpty) {
|
|
1507
|
-
const text = await res.text();
|
|
1508
|
-
return text ? JSON.parse(text) : null;
|
|
1509
|
-
}
|
|
1510
|
-
const parsed = await res.json();
|
|
1511
|
-
if (opts.replayAware && parsed && typeof parsed === "object") {
|
|
1512
|
-
parsed.wasReplay = readReplayHeader(res.headers);
|
|
1513
|
-
}
|
|
1514
|
-
return parsed;
|
|
1273
|
+
/**
|
|
1274
|
+
* Get public encryption info (no auth required)
|
|
1275
|
+
*
|
|
1276
|
+
* Returns algorithm details and example code for client-side decryption.
|
|
1277
|
+
* Public endpoint - can be called without authentication.
|
|
1278
|
+
*
|
|
1279
|
+
* @returns Encryption algorithm and implementation details
|
|
1280
|
+
* @example
|
|
1281
|
+
* ```typescript
|
|
1282
|
+
* const info = await client.tokens.getEncryptionInfo();
|
|
1283
|
+
* console.log('Algorithm:', info.algorithm);
|
|
1284
|
+
* console.log('Key derivation:', info.key_derivation);
|
|
1285
|
+
* console.log(info.javascript_example);
|
|
1286
|
+
* ```
|
|
1287
|
+
*/
|
|
1288
|
+
async getEncryptionInfo() {
|
|
1289
|
+
return this.client.request("GET", "/tokens/encryption-info/");
|
|
1515
1290
|
}
|
|
1516
1291
|
};
|
|
1517
|
-
var READ_ONLY_FIELDS = ["world", "type", "created_at", "updated_at", "change_seq"];
|
|
1518
|
-
function sanitizePayload(payload) {
|
|
1519
|
-
if (!payload || typeof payload !== "object") return payload;
|
|
1520
|
-
const rest = { ...payload };
|
|
1521
|
-
for (const field of READ_ONLY_FIELDS) delete rest[field];
|
|
1522
|
-
return rest;
|
|
1523
|
-
}
|
|
1524
|
-
function readReplayHeader(headers) {
|
|
1525
|
-
const v = headers.get("Idempotent-Replay");
|
|
1526
|
-
return v != null && v.toLowerCase() === "true";
|
|
1527
|
-
}
|
|
1528
|
-
function mintUuid() {
|
|
1529
|
-
const c = globalThis.crypto;
|
|
1530
|
-
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
1531
|
-
const bytes = new Uint8Array(16);
|
|
1532
|
-
if (c && typeof c.getRandomValues === "function") {
|
|
1533
|
-
c.getRandomValues(bytes);
|
|
1534
|
-
} else {
|
|
1535
|
-
for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
1536
|
-
}
|
|
1537
|
-
bytes[6] = bytes[6] & 15 | 64;
|
|
1538
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
1539
|
-
const hex = [];
|
|
1540
|
-
for (let i = 0; i < 256; i++) hex.push((i + 256).toString(16).slice(1));
|
|
1541
|
-
return hex[bytes[0]] + hex[bytes[1]] + hex[bytes[2]] + hex[bytes[3]] + "-" + hex[bytes[4]] + hex[bytes[5]] + "-" + hex[bytes[6]] + hex[bytes[7]] + "-" + hex[bytes[8]] + hex[bytes[9]] + "-" + hex[bytes[10]] + hex[bytes[11]] + hex[bytes[12]] + hex[bytes[13]] + hex[bytes[14]] + hex[bytes[15]];
|
|
1542
|
-
}
|
|
1543
|
-
function buildQuery(params) {
|
|
1544
|
-
const q = new URLSearchParams();
|
|
1545
|
-
for (const [k, v] of Object.entries(params)) {
|
|
1546
|
-
if (v !== void 0 && v !== null && v !== "") q.set(k, String(v));
|
|
1547
|
-
}
|
|
1548
|
-
return q.toString();
|
|
1549
|
-
}
|
|
1550
1292
|
|
|
1551
|
-
// src/
|
|
1552
|
-
var
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1293
|
+
// src/token-types.ts
|
|
1294
|
+
var GameTier = /* @__PURE__ */ ((GameTier2) => {
|
|
1295
|
+
GameTier2["FREE"] = "free";
|
|
1296
|
+
GameTier2["SILVER"] = "silver";
|
|
1297
|
+
GameTier2["GOLD"] = "gold";
|
|
1298
|
+
GameTier2["PLATINUM"] = "platinum";
|
|
1299
|
+
GameTier2["DIAMOND"] = "diamond";
|
|
1300
|
+
GameTier2["DELUXE"] = "deluxe";
|
|
1301
|
+
return GameTier2;
|
|
1302
|
+
})(GameTier || {});
|
|
1303
|
+
export {
|
|
1304
|
+
ELEMENT_FAMILIES,
|
|
1558
1305
|
ELEMENT_ICONS,
|
|
1559
1306
|
ELEMENT_LABELS,
|
|
1560
1307
|
ELEMENT_SECTIONS,
|
|
1561
1308
|
ELEMENT_TYPES,
|
|
1562
|
-
|
|
1309
|
+
FAMILY_COLORS,
|
|
1310
|
+
FAMILY_ORDER,
|
|
1563
1311
|
FIELD_SCHEMA,
|
|
1564
1312
|
GameTier,
|
|
1565
1313
|
ONLYWORLDS_VERSION,
|
|
1566
|
-
OnlyWorldsClient,
|
|
1567
1314
|
OwApiError,
|
|
1568
1315
|
OwNetworkError,
|
|
1569
1316
|
OwV2Client,
|
|
1570
1317
|
SPATIAL_TYPES,
|
|
1571
|
-
|
|
1572
|
-
createElementId,
|
|
1573
|
-
createElementIds,
|
|
1318
|
+
TokenResource,
|
|
1574
1319
|
detectKeyKind,
|
|
1320
|
+
elementColor,
|
|
1575
1321
|
errorFromResponse,
|
|
1322
|
+
familyOf,
|
|
1576
1323
|
getElementIcon,
|
|
1577
1324
|
getElementLabel,
|
|
1578
|
-
getElementSections,
|
|
1579
1325
|
isDemoKey,
|
|
1580
1326
|
kindCanWrite,
|
|
1581
1327
|
parseEnvelope,
|
|
1582
1328
|
pinExpectation
|
|
1583
|
-
}
|
|
1329
|
+
};
|