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