@onlyworlds/sdk 3.1.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 +4 -0
- package/CHANGELOG.md +86 -44
- package/README.md +83 -271
- package/SCHEMA.md +630 -0
- package/dist/index.d.ts +463 -1469
- package/dist/index.js +691 -994
- package/package.json +14 -6
- package/dist/index.d.mts +0 -3517
- package/dist/index.mjs +0 -1576
package/dist/index.js
CHANGED
|
@@ -1,117 +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
|
-
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
|
+
}
|
|
280
|
+
};
|
|
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;
|
|
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"
|
|
105
364
|
};
|
|
106
|
-
function getElementLabel(elementType) {
|
|
107
|
-
return ELEMENT_LABELS[elementType];
|
|
108
|
-
}
|
|
109
365
|
var ELEMENT_SECTIONS = {
|
|
110
|
-
|
|
366
|
+
ability: [
|
|
111
367
|
{ name: "Mechanics", order: 1, fields: ["activation", "duration", "potency", "range", "effects", "challenges", "talents", "requisites"] },
|
|
112
368
|
{ name: "World", order: 2, fields: ["prevalence", "tradition", "source", "locus", "instruments", "systems"] }
|
|
113
369
|
],
|
|
114
|
-
|
|
370
|
+
character: [
|
|
115
371
|
{ name: "Constitution", order: 1, fields: ["physicality", "mentality", "height", "weight", "species", "traits", "abilities"] },
|
|
116
372
|
{ name: "Origins", order: 2, fields: ["background", "motivations", "birth_date", "birthplace", "languages"] },
|
|
117
373
|
{ name: "World", order: 3, fields: ["reputation", "location", "objects", "institutions"] },
|
|
@@ -119,43 +375,43 @@ var ELEMENT_SECTIONS = {
|
|
|
119
375
|
{ name: "Social", order: 5, fields: ["family", "friends", "rivals"] },
|
|
120
376
|
{ name: "TTRPG", order: 6, fields: ["level", "hit_points", "STR", "DEX", "CON", "INT", "WIS", "CHA"] }
|
|
121
377
|
],
|
|
122
|
-
|
|
378
|
+
collective: [
|
|
123
379
|
{ name: "Formation", order: 1, fields: ["composition", "count", "formation_date", "operator", "equipment"] },
|
|
124
380
|
{ name: "Dynamics", order: 2, fields: ["activity", "disposition", "state", "abilities", "symbolism"] },
|
|
125
381
|
{ name: "World", order: 3, fields: ["species", "characters", "creatures", "phenomena"] }
|
|
126
382
|
],
|
|
127
|
-
|
|
383
|
+
construct: [
|
|
128
384
|
{ name: "Nature", order: 1, fields: ["rationale", "history", "status", "reach", "start_date", "end_date", "founder", "custodian"] },
|
|
129
385
|
{ name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives"] }
|
|
130
386
|
],
|
|
131
|
-
|
|
387
|
+
creature: [
|
|
132
388
|
{ name: "Biology", order: 1, fields: ["appearance", "weight", "height", "species"] },
|
|
133
|
-
{ name: "
|
|
389
|
+
{ name: "Behavior", order: 2, fields: ["habits", "demeanor", "traits", "abilities", "languages"] },
|
|
134
390
|
{ name: "World", order: 3, fields: ["status", "birth_date", "location", "zone"] },
|
|
135
391
|
{ name: "TTRPG", order: 4, fields: ["challenge_rating", "hit_points", "armor_class", "speed", "actions"] }
|
|
136
392
|
],
|
|
137
|
-
|
|
393
|
+
event: [
|
|
138
394
|
{ name: "Nature", order: 1, fields: ["history", "challenges", "consequences", "start_date", "end_date", "triggers"] },
|
|
139
395
|
{ name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs"] }
|
|
140
396
|
],
|
|
141
|
-
|
|
397
|
+
family: [
|
|
142
398
|
{ name: "Identity", order: 1, fields: ["spirit", "history", "traditions", "traits", "abilities", "languages", "ancestors"] },
|
|
143
399
|
{ name: "World", order: 2, fields: ["reputation", "estates", "governs", "heirlooms", "creatures"] }
|
|
144
400
|
],
|
|
145
|
-
|
|
401
|
+
institution: [
|
|
146
402
|
{ name: "Foundation", order: 1, fields: ["doctrine", "founding_date", "parent_institution"] },
|
|
147
403
|
{ name: "Claims", order: 2, fields: ["zones", "objects", "creatures"] },
|
|
148
404
|
{ name: "World", order: 3, fields: ["status", "allies", "adversaries", "constructs"] }
|
|
149
405
|
],
|
|
150
|
-
|
|
406
|
+
language: [
|
|
151
407
|
{ name: "Structure", order: 1, fields: ["phonology", "grammar", "lexicon", "writing", "classification"] },
|
|
152
408
|
{ name: "World", order: 2, fields: ["status", "spread", "dialects"] }
|
|
153
409
|
],
|
|
154
|
-
|
|
410
|
+
law: [
|
|
155
411
|
{ name: "Code", order: 1, fields: ["declaration", "purpose", "date", "parent_law", "penalties"] },
|
|
156
412
|
{ name: "World", order: 2, fields: ["author", "locations", "zones", "prohibitions", "adjudicators", "enforcers"] }
|
|
157
413
|
],
|
|
158
|
-
|
|
414
|
+
location: [
|
|
159
415
|
{ name: "Setting", order: 1, fields: ["form", "function", "founding_date", "parent_location", "populations"] },
|
|
160
416
|
{ name: "Politics", order: 2, fields: ["political_climate", "primary_power", "governing_title", "secondary_powers", "zone", "rival", "partner"] },
|
|
161
417
|
{ name: "World", order: 3, fields: ["customs", "founders", "cults", "delicacies"] },
|
|
@@ -164,78 +420,95 @@ var ELEMENT_SECTIONS = {
|
|
|
164
420
|
{ name: "Construction", order: 6, fields: ["architecture", "buildings", "building_methods"] },
|
|
165
421
|
{ name: "Defense", order: 7, fields: ["defensibility", "elevation", "fighters", "defensive_objects"] }
|
|
166
422
|
],
|
|
167
|
-
|
|
423
|
+
map: [
|
|
168
424
|
{ name: "Details", order: 1, fields: ["background_color", "hierarchy", "width", "height", "depth", "parent_map", "location"] }
|
|
169
425
|
],
|
|
170
|
-
|
|
426
|
+
marker: [
|
|
171
427
|
{ name: "Details", order: 1, fields: ["map", "zone", "x", "y", "z", "order"] }
|
|
172
428
|
],
|
|
173
|
-
|
|
429
|
+
narrative: [
|
|
174
430
|
{ name: "Context", order: 1, fields: ["story", "consequences", "start_date", "end_date", "order", "parent_narrative", "protagonist", "antagonist", "narrator", "conservator"] },
|
|
175
431
|
{ name: "Involves", order: 2, fields: ["events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "laws"] }
|
|
176
432
|
],
|
|
177
|
-
|
|
433
|
+
object: [
|
|
178
434
|
{ name: "Form", order: 1, fields: ["aesthetics", "weight", "amount", "parent_object", "materials", "technology"] },
|
|
179
435
|
{ name: "Function", order: 2, fields: ["utility", "effects", "abilities", "consumes"] },
|
|
180
436
|
{ name: "World", order: 3, fields: ["origins", "location", "language", "affinities"] }
|
|
181
437
|
],
|
|
182
|
-
|
|
438
|
+
phenomenon: [
|
|
183
439
|
{ name: "Mechanics", order: 1, fields: ["expression", "effects", "duration", "catalysts", "empowerments"] },
|
|
184
440
|
{ name: "World", order: 2, fields: ["mythology", "system", "triggers", "wielders", "environments"] }
|
|
185
441
|
],
|
|
186
|
-
|
|
187
|
-
{ name: "Details", order: 1, fields: ["map", "
|
|
442
|
+
pin: [
|
|
443
|
+
{ name: "Details", order: 1, fields: ["map", "element", "x", "y", "z"] }
|
|
188
444
|
],
|
|
189
|
-
|
|
445
|
+
relation: [
|
|
190
446
|
{ 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", "
|
|
447
|
+
{ name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "titles", "constructs", "events", "narratives"] }
|
|
192
448
|
],
|
|
193
|
-
|
|
449
|
+
species: [
|
|
194
450
|
{ name: "Biology", order: 1, fields: ["appearance", "life_span", "weight", "nourishment", "reproduction", "adaptations"] },
|
|
195
451
|
{ name: "Psychology", order: 2, fields: ["instincts", "sociality", "temperament", "communication", "aggression", "traits"] },
|
|
196
452
|
{ name: "World", order: 3, fields: ["role", "parent_species", "locations", "zones", "affinities"] }
|
|
197
453
|
],
|
|
198
|
-
|
|
454
|
+
title: [
|
|
199
455
|
{ name: "Mandate", order: 1, fields: ["authority", "eligibility", "grant_date", "revoke_date", "issuer", "body", "superior_title", "holders", "symbols"] },
|
|
200
456
|
{ name: "World", order: 2, fields: ["status", "history", "characters", "institutions", "families", "zones", "locations", "objects", "constructs", "laws", "collectives", "creatures", "phenomena", "species", "languages"] }
|
|
201
457
|
],
|
|
202
|
-
|
|
458
|
+
trait: [
|
|
203
459
|
{ name: "Qualitative", order: 1, fields: ["social_effects", "physical_effects", "functional_effects", "personality_effects", "behaviour_effects"] },
|
|
204
460
|
{ name: "Quantitative", order: 2, fields: ["charisma", "coercion", "competence", "compassion", "creativity", "courage"] },
|
|
205
461
|
{ name: "World", order: 3, fields: ["significance", "anti_trait", "empowered_abilities"] }
|
|
206
462
|
],
|
|
207
|
-
|
|
463
|
+
zone: [
|
|
208
464
|
{ name: "Scope", order: 1, fields: ["role", "start_date", "end_date", "phenomena", "linked_zones"] },
|
|
209
465
|
{ name: "World", order: 2, fields: ["context", "populations", "titles", "principles"] }
|
|
210
466
|
]
|
|
211
467
|
};
|
|
212
|
-
|
|
213
|
-
|
|
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];
|
|
481
|
+
}
|
|
482
|
+
function elementColor(type, mode = "dark") {
|
|
483
|
+
return FAMILY_COLORS[ELEMENT_FAMILIES[type]][mode];
|
|
214
484
|
}
|
|
485
|
+
var FAMILY_ORDER = ["agents", "world", "abstract", "temporal"];
|
|
486
|
+
|
|
487
|
+
// src/v2/constants.ts
|
|
215
488
|
var ONLYWORLDS_VERSION = "00.30.00";
|
|
216
|
-
var
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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" },
|
|
@@ -787,846 +1060,270 @@ var FIELD_SCHEMA = {
|
|
|
787
1060
|
languages: { type: "multi_link", target: "language" }
|
|
788
1061
|
},
|
|
789
1062
|
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;
|
|
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 });
|
|
1392
|
-
}
|
|
1393
|
-
/**
|
|
1394
|
-
* POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
|
|
1395
|
-
* caller omits one (design ruling D29d) so a retry carrying the same
|
|
1396
|
-
* Idempotency-Key is structurally safe. Callers MAY still supply their own id.
|
|
1397
|
-
*/
|
|
1398
|
-
async create(type, element, opts = {}) {
|
|
1399
|
-
const body = sanitizePayload(element);
|
|
1400
|
-
if (body.id === void 0 || body.id === null || body.id === "") {
|
|
1401
|
-
body.id = mintUuid();
|
|
1402
|
-
}
|
|
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) });
|
|
1148
|
+
// src/token-resource.ts
|
|
1149
|
+
var TokenResource = class {
|
|
1150
|
+
constructor(client) {
|
|
1151
|
+
this.client = client;
|
|
1411
1152
|
}
|
|
1412
1153
|
/**
|
|
1413
|
-
*
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
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
|
+
* ```
|
|
1416
1167
|
*/
|
|
1417
|
-
async
|
|
1418
|
-
return this.request("
|
|
1168
|
+
async getStatus() {
|
|
1169
|
+
return this.client.request("GET", "/tokens/status/");
|
|
1419
1170
|
}
|
|
1420
1171
|
/**
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
1423
|
-
*
|
|
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
|
+
* ```
|
|
1424
1198
|
*/
|
|
1425
|
-
async
|
|
1426
|
-
|
|
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
|
+
});
|
|
1427
1208
|
}
|
|
1428
1209
|
/**
|
|
1429
|
-
*
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
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
|
+
* ```
|
|
1433
1234
|
*/
|
|
1434
|
-
async
|
|
1435
|
-
return this.request("
|
|
1235
|
+
async getAccessKey() {
|
|
1236
|
+
return this.client.request("GET", "/tokens/access-key/");
|
|
1436
1237
|
}
|
|
1437
|
-
// -- Bulk ----------------------------------------------------------------
|
|
1438
1238
|
/**
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
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
|
+
* ```
|
|
1446
1250
|
*/
|
|
1447
|
-
async
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
return this.request("POST", "/bulk/", {
|
|
1453
|
-
body,
|
|
1454
|
-
idempotencyKey: opts.idempotencyKey,
|
|
1455
|
-
replayAware: true
|
|
1456
|
-
});
|
|
1251
|
+
async revokeSession(sessionId) {
|
|
1252
|
+
return this.client.request(
|
|
1253
|
+
"POST",
|
|
1254
|
+
`/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}`
|
|
1255
|
+
);
|
|
1457
1256
|
}
|
|
1458
|
-
// -- Changes feed --------------------------------------------------------
|
|
1459
1257
|
/**
|
|
1460
|
-
*
|
|
1461
|
-
*
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
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
|
+
* ```
|
|
1466
1269
|
*/
|
|
1467
|
-
async
|
|
1468
|
-
|
|
1469
|
-
return this.request("GET", "/changes/", { query });
|
|
1270
|
+
async revokeAllSessions() {
|
|
1271
|
+
return this.client.request("POST", "/tokens/revoke-all-sessions/");
|
|
1470
1272
|
}
|
|
1471
1273
|
/**
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
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
|
+
* ```
|
|
1475
1287
|
*/
|
|
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));
|
|
1288
|
+
async getEncryptionInfo() {
|
|
1289
|
+
return this.client.request("GET", "/tokens/encryption-info/");
|
|
1552
1290
|
}
|
|
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
1291
|
};
|
|
1582
1292
|
|
|
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 = {
|
|
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 {
|
|
1602
1304
|
ELEMENT_FAMILIES,
|
|
1603
1305
|
ELEMENT_ICONS,
|
|
1604
1306
|
ELEMENT_LABELS,
|
|
1605
1307
|
ELEMENT_SECTIONS,
|
|
1606
1308
|
ELEMENT_TYPES,
|
|
1607
|
-
ElementType,
|
|
1608
1309
|
FAMILY_COLORS,
|
|
1609
1310
|
FAMILY_ORDER,
|
|
1610
1311
|
FIELD_SCHEMA,
|
|
1611
1312
|
GameTier,
|
|
1612
1313
|
ONLYWORLDS_VERSION,
|
|
1613
|
-
OnlyWorldsClient,
|
|
1614
1314
|
OwApiError,
|
|
1615
1315
|
OwNetworkError,
|
|
1616
1316
|
OwV2Client,
|
|
1617
1317
|
SPATIAL_TYPES,
|
|
1618
|
-
|
|
1619
|
-
createElementId,
|
|
1620
|
-
createElementIds,
|
|
1318
|
+
TokenResource,
|
|
1621
1319
|
detectKeyKind,
|
|
1622
1320
|
elementColor,
|
|
1623
1321
|
errorFromResponse,
|
|
1624
1322
|
familyOf,
|
|
1625
1323
|
getElementIcon,
|
|
1626
1324
|
getElementLabel,
|
|
1627
|
-
getElementSections,
|
|
1628
1325
|
isDemoKey,
|
|
1629
1326
|
kindCanWrite,
|
|
1630
1327
|
parseEnvelope,
|
|
1631
1328
|
pinExpectation
|
|
1632
|
-
}
|
|
1329
|
+
};
|