@spacemolt/lib 15.2.0 → 16.0.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/COMMANDS.md +11 -6
- package/README.md +8 -4
- package/dist/account.d.ts +23 -23
- package/dist/auth/clerk.d.ts +3 -1
- package/dist/generated/actions.gen.d.ts +2 -2
- package/dist/generated/commands.gen.d.ts +27 -7
- package/dist/generated/notifications.gen.d.ts +4 -3
- package/dist/generated/openapi/types.gen.d.ts +933 -160
- package/dist/index.js +99 -46
- package/dist/index.js.map +33 -0
- package/dist/node.js +26 -0
- package/dist/node.js.map +12 -0
- package/dist/state/observation.d.ts +17 -9
- package/package.json +7 -9
package/dist/node.js
CHANGED
|
@@ -6,6 +6,11 @@ import { dirname } from "node:path";
|
|
|
6
6
|
function isRecord(value) {
|
|
7
7
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
8
|
}
|
|
9
|
+
function requireRecord(value, source) {
|
|
10
|
+
if (!isRecord(value))
|
|
11
|
+
throw new TypeError(`${source} must be a JSON object`);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
9
14
|
|
|
10
15
|
// src/auth/credentials.ts
|
|
11
16
|
function hasString(value, key) {
|
|
@@ -29,6 +34,24 @@ function isStoredAccount(value) {
|
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
class MemoryCredentialStore {
|
|
38
|
+
accounts = new Map;
|
|
39
|
+
list() {
|
|
40
|
+
return Promise.resolve([...this.accounts.values()]);
|
|
41
|
+
}
|
|
42
|
+
get(id) {
|
|
43
|
+
return Promise.resolve(this.accounts.get(id));
|
|
44
|
+
}
|
|
45
|
+
put(account) {
|
|
46
|
+
this.accounts.set(account.id, account);
|
|
47
|
+
return Promise.resolve();
|
|
48
|
+
}
|
|
49
|
+
remove(id) {
|
|
50
|
+
this.accounts.delete(id);
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
// src/auth/file-store.ts
|
|
33
56
|
class FileCredentialStore {
|
|
34
57
|
path;
|
|
@@ -91,3 +114,6 @@ function isMissingFileError(error) {
|
|
|
91
114
|
export {
|
|
92
115
|
FileCredentialStore
|
|
93
116
|
};
|
|
117
|
+
|
|
118
|
+
//# debugId=120575AA408B48D964756E2164756E21
|
|
119
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/auth/file-store.ts", "../src/validation.ts", "../src/auth/credentials.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * File-backed credential store (Node / Bun).\n *\n * Persists accounts as JSON. Credentials are stored in plaintext — the same\n * tradeoff the CLI makes — so point it at a path your environment protects.\n * Imports Node built-ins; do not import this from browser bundles (use a\n * custom `CredentialStore` there instead).\n */\n\nimport { readFile, writeFile, rename, mkdir } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { isStoredAccount, type CredentialStore, type StoredAccount } from './credentials.ts';\nimport { isRecord } from '../validation.ts';\n\ninterface FileShape {\n version: 1;\n accounts: Record<string, StoredAccount>;\n}\n\nexport class FileCredentialStore implements CredentialStore {\n private cache: Record<string, StoredAccount> | null = null;\n\n constructor(private readonly path: string) {}\n\n private async load(): Promise<Record<string, StoredAccount>> {\n if (this.cache) return this.cache;\n try {\n const raw = await readFile(this.path, 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (!isRecord(parsed) || parsed.version !== 1 || !isRecord(parsed.accounts)) {\n throw new Error(`Invalid credential store format in ${this.path}`);\n }\n const accounts: Record<string, StoredAccount> = {};\n for (const [id, account] of Object.entries(parsed.accounts)) {\n if (!isStoredAccount(account) || account.id !== id) {\n throw new Error(`Invalid account ${JSON.stringify(id)} in credential store ${this.path}`);\n }\n accounts[id] = account;\n }\n this.cache = accounts;\n } catch (error) {\n if (isMissingFileError(error)) this.cache = {};\n else throw error;\n }\n return this.cache;\n }\n\n private async save(): Promise<void> {\n const data: FileShape = { version: 1, accounts: this.cache ?? {} };\n await mkdir(dirname(this.path), { recursive: true });\n // Atomic write: temp file + rename, so a crash can't truncate the store.\n const tmp = `${this.path}.tmp-${process.pid}`;\n await writeFile(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });\n await rename(tmp, this.path);\n }\n\n async list(): Promise<StoredAccount[]> {\n return Object.values(await this.load());\n }\n async get(id: string): Promise<StoredAccount | undefined> {\n return (await this.load())[id];\n }\n async put(account: StoredAccount): Promise<void> {\n const accounts = await this.load();\n accounts[account.id] = account;\n await this.save();\n }\n async remove(id: string): Promise<void> {\n const accounts = await this.load();\n delete accounts[id];\n await this.save();\n }\n}\n\nfunction isMissingFileError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && 'code' in error && error.code === 'ENOENT';\n}\n",
|
|
6
|
+
"/** True when a value is a non-null, non-array object with string keys. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Require an object at an external-data boundary and include its source in failures. */\nexport function requireRecord(value: unknown, source: string): Record<string, unknown> {\n if (!isRecord(value)) throw new TypeError(`${source} must be a JSON object`);\n return value;\n}\n",
|
|
7
|
+
"/**\n * Pluggable credential storage for multi-account use.\n *\n * The `CredentialStore` interface is the only persistence seam in the library;\n * swap the implementation to control where credentials live (memory, a file,\n * the browser, a secrets manager, …). The core never imports Node built-ins —\n * the file-backed store does, and lives here in `src/auth/` for that reason.\n */\n\nimport { isRecord } from '../validation.ts';\n\n/**\n * How an account authenticates. Raw credentials (login / login_token / register)\n * plus `clerk`: a Clerk API key + the owned player's id, from which the account\n * mints a fresh single-use WS token on each (re)connect (see `src/auth/clerk.ts`).\n */\nexport type AuthCredentials =\n | { kind: 'login'; username: string; password: string }\n | { kind: 'login_token'; token: string }\n | { kind: 'clerk'; apiKey: string; playerId: string; httpBaseUrl: string }\n | { kind: 'register'; username: string; empire: string; registration_code?: string };\n\nexport interface StoredAccount {\n /** Stable handle for this account within the client (e.g. the username). */\n id: string;\n credentials: AuthCredentials;\n /** Filled in after a successful auth. */\n playerId?: string;\n}\n\nexport interface CredentialStore {\n list(): Promise<StoredAccount[]>;\n get(id: string): Promise<StoredAccount | undefined>;\n put(account: StoredAccount): Promise<void>;\n remove(id: string): Promise<void>;\n}\n\nfunction hasString(value: Record<string, unknown>, key: string): boolean {\n return typeof value[key] === 'string';\n}\n\nexport function isStoredAccount(value: unknown): value is StoredAccount {\n if (!isRecord(value) || !hasString(value, 'id') || !isRecord(value.credentials)) return false;\n const credentials = value.credentials;\n switch (credentials.kind) {\n case 'login':\n return hasString(credentials, 'username') && hasString(credentials, 'password');\n case 'login_token':\n return hasString(credentials, 'token');\n case 'clerk':\n return (\n hasString(credentials, 'apiKey') && hasString(credentials, 'playerId') && hasString(credentials, 'httpBaseUrl')\n );\n case 'register':\n return (\n hasString(credentials, 'username') &&\n hasString(credentials, 'empire') &&\n (credentials.registration_code === undefined || typeof credentials.registration_code === 'string')\n );\n default:\n return false;\n }\n}\n\n/** In-memory store. The default; nothing is persisted across process restarts. */\nexport class MemoryCredentialStore implements CredentialStore {\n private readonly accounts = new Map<string, StoredAccount>();\n\n list(): Promise<StoredAccount[]> {\n return Promise.resolve([...this.accounts.values()]);\n }\n get(id: string): Promise<StoredAccount | undefined> {\n return Promise.resolve(this.accounts.get(id));\n }\n put(account: StoredAccount): Promise<void> {\n this.accounts.set(account.id, account);\n return Promise.resolve();\n }\n remove(id: string): Promise<void> {\n this.accounts.delete(id);\n return Promise.resolve();\n }\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "120575AA408B48D964756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Local cache of the subscribed observation watch (current POI
|
|
2
|
+
* Local cache of the subscribed observation watch (current POI).
|
|
3
3
|
*
|
|
4
4
|
* Seeded from the `subscribe_observation` baseline and kept current by merging
|
|
5
5
|
* `observation_update` pushes: `*_changed` arrays upsert entities (keyed by
|
|
@@ -7,13 +7,17 @@
|
|
|
7
7
|
* cloaked-contact / signature hints are tracked alongside.
|
|
8
8
|
*
|
|
9
9
|
* Not a players-only feed: the baseline and every update also carry the
|
|
10
|
-
* pirate, empire-NPC, wildlife
|
|
11
|
-
* each with the same changed/departed delta shape as players
|
|
12
|
-
* of `get_nearby`'s
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* pirate, empire-NPC, wildlife, intact-prize and arena-NPC presence at the
|
|
11
|
+
* watched POI, each with the same changed/departed delta shape as players —
|
|
12
|
+
* all six of `get_nearby`'s classes. The player list matches `get_nearby`:
|
|
13
|
+
* above 10 players, offline ones collapse into `offlineCollapsed`.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here is system-wide except `shipCounts`: with a working ship
|
|
16
|
+
* scanner, the per-POI ship count for every charted POI in the system. The
|
|
17
|
+
* server sends the full map whenever any count changes, so it replaces the
|
|
18
|
+
* previous map rather than merging.
|
|
15
19
|
*/
|
|
16
|
-
import type { CreatureInfo, EmpireNpcInfo, NotificationObservationUpdate, PirateInfo, PrizeInfo, SubscribeObservationResponse } from '../generated/openapi/types.gen.js';
|
|
20
|
+
import type { ArenaNpcInfo, CreatureInfo, EmpireNpcInfo, NotificationObservationUpdate, PirateInfo, PrizeInfo, SubscribeObservationResponse } from '../generated/openapi/types.gen.js';
|
|
17
21
|
export type ObservedPlayer = NonNullable<NotificationObservationUpdate['nearby_changed']>[number];
|
|
18
22
|
export type CloakedContact = NonNullable<NotificationObservationUpdate['cloaked_resolved']>[number];
|
|
19
23
|
export interface ObservationView {
|
|
@@ -23,8 +27,6 @@ export interface ObservationView {
|
|
|
23
27
|
tick: number;
|
|
24
28
|
/** Uncloaked players at the watched POI, keyed by player_id. */
|
|
25
29
|
nearby: Map<string, ObservedPlayer>;
|
|
26
|
-
/** Uncloaked players system-wide, keyed by player_id. */
|
|
27
|
-
system: Map<string, ObservedPlayer>;
|
|
28
30
|
/** Pirate NPCs at the watched POI, keyed by pirate_id. */
|
|
29
31
|
pirates: Map<string, PirateInfo>;
|
|
30
32
|
/** Empire NPCs at the watched POI, keyed by npc_id. */
|
|
@@ -33,6 +35,12 @@ export interface ObservationView {
|
|
|
33
35
|
creatures: Map<string, CreatureInfo>;
|
|
34
36
|
/** Intact captured ships at the watched POI, keyed by prize_id. */
|
|
35
37
|
prizes: Map<string, PrizeInfo>;
|
|
38
|
+
/** Arena challenge enemies at the watched POI, keyed by npc_id. */
|
|
39
|
+
arenaNpcs: Map<string, ArenaNpcInfo>;
|
|
40
|
+
/** Offline players at the watched POI not listed in `nearby` (0 when all are listed). */
|
|
41
|
+
offlineCollapsed: number;
|
|
42
|
+
/** Ship scanner counts per charted POI in the system, keyed by POI ID. Empty without a working ship scanner. */
|
|
43
|
+
shipCounts: Map<string, number>;
|
|
36
44
|
/** Cloaked contacts resolved by an active sensor sweep, keyed by target_id. */
|
|
37
45
|
cloaked: Map<string, CloakedContact>;
|
|
38
46
|
/** A faint cloaked-ship signature is present at the watched POI. */
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spacemolt/lib",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.0",
|
|
4
4
|
"description": "The definitive TypeScript library for SpaceMolt. WebSocket-v2-first, multi-account, with local state caches. Internals are regenerated from the server's OpenAPI spec.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"module": "./dist/index.js",
|
|
7
6
|
"types": "./dist/index.d.ts",
|
|
8
7
|
"exports": {
|
|
9
8
|
".": {
|
|
@@ -25,16 +24,17 @@
|
|
|
25
24
|
"fetch-spec": "bun run scripts/fetch-spec.ts",
|
|
26
25
|
"generate": "bun run openapi-ts && bun run scripts/generate.ts",
|
|
27
26
|
"generate:check": "bun run scripts/check-generated.ts",
|
|
28
|
-
"check": "bun run format:check && bun run lint && bun run generate:check && bun run typecheck && bun test && bun run build:browser-check && bun run build && bun run build:smoke",
|
|
27
|
+
"check": "bun run format:check && bun run lint && bun run generate:check && bun run typecheck && bun test && bun run build:browser-check && bun run build && bun run build:smoke && bun run check:consumer",
|
|
29
28
|
"format": "biome format --write .",
|
|
30
29
|
"format:check": "biome format .",
|
|
31
30
|
"lint": "biome lint .",
|
|
32
31
|
"typecheck": "bun x tsc --noEmit",
|
|
33
|
-
"
|
|
34
|
-
"build": "rm -rf dist && bun build src/index.ts --outfile dist/index.js --target node --format esm && bun build src/node.ts --outfile dist/node.js --target node --format esm && bun x tsc -p tsconfig.build.json && bun run scripts/fix-dts-extensions.ts",
|
|
32
|
+
"build": "rm -rf dist && bun build src/index.ts src/node.ts --outdir dist --target node --format esm --sourcemap=linked && bun x tsc -p tsconfig.build.json && bun run scripts/fix-dts-extensions.ts",
|
|
35
33
|
"build:smoke": "node scripts/check-build.mjs",
|
|
36
34
|
"build:browser-check": "bun run scripts/check-browser-build.ts",
|
|
37
|
-
"test": "bun test"
|
|
35
|
+
"test": "bun test",
|
|
36
|
+
"prepack": "bun run build",
|
|
37
|
+
"check:consumer": "bun run scripts/check-consumer.ts"
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
@@ -52,9 +52,7 @@
|
|
|
52
52
|
"@biomejs/biome": "^2.5.3",
|
|
53
53
|
"@hey-api/openapi-ts": "^0.73.0",
|
|
54
54
|
"@types/bun": "^1.3.9",
|
|
55
|
+
"@types/node": "^22",
|
|
55
56
|
"typescript": "^5.9.3"
|
|
56
|
-
},
|
|
57
|
-
"peerDependencies": {
|
|
58
|
-
"typescript": "^5.5"
|
|
59
57
|
}
|
|
60
58
|
}
|