@spacemolt/lib 4.6.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/dist/node.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Node/Bun-only entry point (`@spacemolt/lib/node`).
3
+ *
4
+ * Exports the parts of the library that depend on Node built-ins, kept out of
5
+ * the main entry so `@spacemolt/lib` stays browser-safe. Import from here in
6
+ * Node/Bun when you want filesystem-backed credential storage.
7
+ */
8
+ export { FileCredentialStore } from './auth/file-store.js';
package/dist/node.js ADDED
@@ -0,0 +1,49 @@
1
+ // src/auth/file-store.ts
2
+ import { readFile, writeFile, rename, mkdir } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+
5
+ class FileCredentialStore {
6
+ path;
7
+ cache = null;
8
+ constructor(path) {
9
+ this.path = path;
10
+ }
11
+ async load() {
12
+ if (this.cache)
13
+ return this.cache;
14
+ try {
15
+ const raw = await readFile(this.path, "utf-8");
16
+ const parsed = JSON.parse(raw);
17
+ this.cache = parsed.accounts ?? {};
18
+ } catch {
19
+ this.cache = {};
20
+ }
21
+ return this.cache;
22
+ }
23
+ async save() {
24
+ const data = { version: 1, accounts: this.cache ?? {} };
25
+ await mkdir(dirname(this.path), { recursive: true });
26
+ const tmp = `${this.path}.tmp-${process.pid}`;
27
+ await writeFile(tmp, JSON.stringify(data, null, 2));
28
+ await rename(tmp, this.path);
29
+ }
30
+ async list() {
31
+ return Object.values(await this.load());
32
+ }
33
+ async get(id) {
34
+ return (await this.load())[id];
35
+ }
36
+ async put(account) {
37
+ const accounts = await this.load();
38
+ accounts[account.id] = account;
39
+ await this.save();
40
+ }
41
+ async remove(id) {
42
+ const accounts = await this.load();
43
+ delete accounts[id];
44
+ await this.save();
45
+ }
46
+ }
47
+ export {
48
+ FileCredentialStore
49
+ };
@@ -0,0 +1,178 @@
1
+ /**
2
+ * WebSocket v2 frame envelopes.
3
+ *
4
+ * These are the protocol-level message shapes — the framing layer that is
5
+ * stable across spec versions (see gameserver docs/websocket-v2.md). The
6
+ * per-action payloads and per-notification payloads are generated from the
7
+ * spec instead (see src/generated/). Endpoint: `/ws/v2`.
8
+ */
9
+ import type { NotificationPayloads, TypedNotificationType } from './generated/notifications.gen.js';
10
+ import type { V2GameState } from './generated/openapi/types.gen.js';
11
+ /** Inbound frame: client -> server. `payload` is omitted when an action takes none. */
12
+ export interface InboundFrame {
13
+ tool: string;
14
+ action: string;
15
+ payload?: Record<string, unknown>;
16
+ /** Opaque correlation token, <= 128 bytes. Echoed back on result/outcome frames. */
17
+ request_id?: string;
18
+ }
19
+ /** The synchronous query / mutation-ack response. */
20
+ export interface ResultFrame {
21
+ type: 'result';
22
+ request_id?: string;
23
+ payload: {
24
+ /** Human-readable rendered text (or raw object when no renderer applies). */
25
+ result: unknown;
26
+ /** Raw JSON for programmatic consumption. */
27
+ structuredContent?: Record<string, unknown>;
28
+ };
29
+ }
30
+ /** Mutation-ack: a `result` frame whose structuredContent flags `pending: true`. */
31
+ export interface PendingAck {
32
+ pending: true;
33
+ command: string;
34
+ message: string;
35
+ }
36
+ /** Outcome push for a queued mutation; echoes the original request_id. */
37
+ export interface ActionResultFrame {
38
+ type: 'action_result';
39
+ request_id?: string;
40
+ payload: {
41
+ command: string;
42
+ tick: number;
43
+ /** A V2GameState delta — only the sections that changed. See StateDelta. */
44
+ result: StateDelta;
45
+ auto_docked?: boolean;
46
+ auto_undocked?: boolean;
47
+ };
48
+ }
49
+ /** Failure outcome for a queued mutation. */
50
+ export interface ActionErrorFrame {
51
+ type: 'action_error';
52
+ request_id?: string;
53
+ payload: {
54
+ command: string;
55
+ tick: number;
56
+ code: string;
57
+ message: string;
58
+ details?: Record<string, unknown>;
59
+ };
60
+ }
61
+ /** Generic error frame emitted by the framing layer or a handler. */
62
+ export interface ErrorFrame {
63
+ type: 'error';
64
+ request_id?: string;
65
+ payload: {
66
+ code: string;
67
+ message: string;
68
+ details?: Record<string, unknown>;
69
+ /** Only on `action_pending` errors: names the already-queued mutation. */
70
+ pending_command?: string;
71
+ };
72
+ }
73
+ /** Unsolicited frame sent immediately after the socket upgrade. No request_id. */
74
+ export interface WelcomeFrame {
75
+ type: 'welcome';
76
+ payload: {
77
+ version: string;
78
+ release_date: string;
79
+ release_notes: string[];
80
+ tick_rate: number;
81
+ current_tick: number;
82
+ server_time: number;
83
+ motd?: string;
84
+ game_info: string;
85
+ website: string;
86
+ help_text: string;
87
+ terms: string;
88
+ };
89
+ }
90
+ /** Auth success frame carrying the full initial session state. */
91
+ export interface LoggedInFrame {
92
+ type: 'logged_in';
93
+ request_id?: string;
94
+ payload: Record<string, unknown>;
95
+ }
96
+ /** Auth success frame after `register`, carrying generated credentials. */
97
+ export interface RegisteredFrame {
98
+ type: 'registered';
99
+ request_id?: string;
100
+ payload: {
101
+ /** 256-bit hex credential — only chance to capture it. */
102
+ password: string;
103
+ player_id: string;
104
+ };
105
+ }
106
+ /**
107
+ * A server-initiated push frame whose msg_type has a published payload schema.
108
+ * Unknown/untyped pushes are delivered as `RawFrame` (see below) until the
109
+ * server types them.
110
+ */
111
+ export type NotificationFrame = {
112
+ [K in TypedNotificationType]: {
113
+ type: K;
114
+ payload: NotificationPayloads[K];
115
+ };
116
+ }[TypedNotificationType];
117
+ /**
118
+ * Any inbound frame as parsed off the wire, before classification. The router
119
+ * narrows this to a specific frame type by `type`. A bare-`string` discriminant
120
+ * is kept out of the typed `OutboundFrame` union so its members narrow cleanly.
121
+ */
122
+ export interface RawFrame {
123
+ type: string;
124
+ request_id?: string;
125
+ payload?: unknown;
126
+ }
127
+ export type OutboundFrame = ResultFrame | ActionResultFrame | ActionErrorFrame | ErrorFrame | WelcomeFrame | LoggedInFrame | RegisteredFrame | NotificationFrame;
128
+ /**
129
+ * The cacheable game-state sections — the eight independently-tracked sections
130
+ * the server emits deltas for. Section shapes are derived from the spec's
131
+ * `V2GameState`, so they stay correct as the spec evolves.
132
+ */
133
+ export type GameState = Pick<V2GameState, 'player' | 'ship' | 'modules' | 'cargo' | 'location' | 'missions' | 'queue' | 'skills'>;
134
+ /**
135
+ * A `V2GameState` delta carried on `action_result`. Only changed sections are
136
+ * present; an absent section means unchanged (keep prior local state). Carries
137
+ * the same eight sections as `GameState` plus the per-action convenience fields.
138
+ */
139
+ export type StateDelta = GameState & Pick<V2GameState, 'message' | 'details' | 'credits'>;
140
+ /**
141
+ * Resolved value of a synchronous query command. `T` is the command's specific
142
+ * `structuredContent` shape; the generated command facade binds it per command
143
+ * (e.g. `find_route` → `QueryResult<FindRouteResponse>`), so reads are typed with
144
+ * no cast. Defaults to untyped JSON for the low-level `query`/`send` paths.
145
+ */
146
+ export interface QueryResult<T = Record<string, unknown>> {
147
+ /** Human-readable rendered text (or raw object when no renderer applies). */
148
+ result: unknown;
149
+ /** Raw JSON for programmatic consumption, typed to the command's response. */
150
+ structuredContent?: T;
151
+ }
152
+ /** The immediate `pending: true` acknowledgement for a queued mutation. */
153
+ export interface MutationAck {
154
+ command: string;
155
+ message: string;
156
+ }
157
+ /**
158
+ * Resolved value of a two-phase mutation, delivered when the action executes.
159
+ * `TDetails` is the action-specific shape of `delta.details` (e.g. `jump` ->
160
+ * `MutationResult<JumpResponse>`); the generated command facade binds it per
161
+ * command, so reads are typed with no cast. The rest of `delta` (the state
162
+ * sections that changed) is the same generic shape for every mutation — only
163
+ * `details` is action-specific. Defaults to untyped JSON for the low-level
164
+ * `mutate`/`send` paths.
165
+ */
166
+ export interface MutationResult<TDetails = Record<string, unknown>> {
167
+ command: string;
168
+ /** Game tick on which the action executed. */
169
+ tick: number;
170
+ /** The V2GameState delta — only the sections that changed. */
171
+ delta: Omit<StateDelta, 'details'> & {
172
+ details?: TDetails;
173
+ };
174
+ autoDocked?: boolean;
175
+ autoUndocked?: boolean;
176
+ }
177
+ export declare const STATE_SECTIONS: readonly ["player", "ship", "modules", "cargo", "location", "missions", "queue", "skills"];
178
+ export type StateSection = (typeof STATE_SECTIONS)[number];
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Per-account local cache of the eight game-state sections.
3
+ *
4
+ * Seeded canonically from a full `V2GameState` snapshot (the `get_status`
5
+ * query returns exactly this shape) and kept current by applying the section
6
+ * deltas carried on every `action_result`. Each present delta section is a
7
+ * complete replacement for that section — absent sections are left untouched
8
+ * (the server's "absent means unchanged" contract).
9
+ */
10
+ import type { GameState, StateDelta, StateSection } from '../protocol.js';
11
+ import type { V2GameState } from '../generated/openapi/types.gen.js';
12
+ export declare class StateCache {
13
+ private state;
14
+ /** Replace the cache from a canonical full snapshot (e.g. `get_status`). */
15
+ seed(snapshot: V2GameState): StateSection[];
16
+ /**
17
+ * Apply a delta, replacing each present section. Returns the sections that
18
+ * changed (in `STATE_SECTIONS` order).
19
+ */
20
+ applyDelta(delta: StateDelta): StateSection[];
21
+ /**
22
+ * Merge a partial patch into a single section in place, preserving that
23
+ * section's other fields — unlike `applyDelta`/`seed`, which always
24
+ * replace a section wholesale. For bridging data from outside the normal
25
+ * action_result delta flow (e.g. observation pushes patching
26
+ * `location`'s nearby-player fields) into the corresponding section.
27
+ * No-ops (returns `[]`) if the section hasn't been seeded yet, since a
28
+ * partial patch can't stand in for a section's required fields.
29
+ */
30
+ patchSection<S extends StateSection>(section: S, patch: Partial<GameState[S]>): StateSection[];
31
+ /** Live view of the cached state. Treat as read-only — do not mutate. */
32
+ snapshot(): Readonly<GameState>;
33
+ get player(): GameState['player'];
34
+ get ship(): GameState['ship'];
35
+ get location(): GameState['location'];
36
+ get cargo(): GameState['cargo'];
37
+ get modules(): GameState['modules'];
38
+ get missions(): GameState['missions'];
39
+ get skills(): GameState['skills'];
40
+ /** True when a tick-deferred action is queued for this account. */
41
+ get hasPendingAction(): boolean;
42
+ get credits(): number | undefined;
43
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Local cache of subscribed station order books.
3
+ *
4
+ * Seeded from the `subscribe_market` baseline snapshot and kept current by
5
+ * merging `market_update` pushes (which carry only the items that changed).
6
+ * Keyed by `base_id`; items keyed by `item_id`.
7
+ */
8
+ import type { NotificationMarketUpdate, SubscribeMarketResponse } from '../generated/openapi/types.gen.js';
9
+ export type MarketItem = NotificationMarketUpdate['items'][number];
10
+ export interface MarketBook {
11
+ base_id: string;
12
+ base_name?: string;
13
+ /** Tick of the most recent update (0 from the initial baseline). */
14
+ tick: number;
15
+ /** Order book per item_id. */
16
+ items: Map<string, MarketItem>;
17
+ }
18
+ export declare class MarketCache {
19
+ private readonly books;
20
+ /** Seed (or replace) a base's book from a subscribe_market baseline. */
21
+ seed(snapshot: SubscribeMarketResponse): string | undefined;
22
+ /** Merge a market_update push (changed items only) into the book. */
23
+ applyUpdate(update: NotificationMarketUpdate): void;
24
+ /** The cached book for a base, if subscribed. */
25
+ book(baseId: string): MarketBook | undefined;
26
+ /** All base_ids with a cached book. */
27
+ bases(): string[];
28
+ /** Drop a base's cached book (e.g. on unsubscribe). */
29
+ drop(baseId: string): void;
30
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Local cache of the subscribed observation watch (current POI + system).
3
+ *
4
+ * Seeded from the `subscribe_observation` baseline and kept current by merging
5
+ * `observation_update` pushes: `*_changed` arrays upsert players (keyed by
6
+ * `player_id`), `*_departed` arrays (player_id strings) remove them, and the
7
+ * cloaked-contact / signature hints are tracked alongside.
8
+ */
9
+ import type { NotificationObservationUpdate, SubscribeObservationResponse } from '../generated/openapi/types.gen.js';
10
+ export type ObservedPlayer = NonNullable<NotificationObservationUpdate['nearby_changed']>[number];
11
+ export type CloakedContact = NonNullable<NotificationObservationUpdate['cloaked_resolved']>[number];
12
+ export interface ObservationView {
13
+ poi_id?: string;
14
+ system_id?: string;
15
+ /** Tick of the most recent update (0 from the initial baseline). */
16
+ tick: number;
17
+ /** Uncloaked players at the watched POI, keyed by player_id. */
18
+ nearby: Map<string, ObservedPlayer>;
19
+ /** Uncloaked players system-wide, keyed by player_id. */
20
+ system: Map<string, ObservedPlayer>;
21
+ /** Cloaked contacts resolved by an active sensor sweep, keyed by target_id. */
22
+ cloaked: Map<string, CloakedContact>;
23
+ /** A faint cloaked-ship signature is present at the watched POI. */
24
+ unknownSignature: boolean;
25
+ /** Whether an active sensor sweep is running. */
26
+ activeScan: boolean;
27
+ }
28
+ export declare class ObservationCache {
29
+ private view;
30
+ /** Seed (or replace) the watch from a subscribe_observation baseline. */
31
+ seed(snapshot: SubscribeObservationResponse): ObservationView;
32
+ /** Merge an observation_update push into the watch. */
33
+ applyUpdate(update: NotificationObservationUpdate): void;
34
+ /** The current watch view, if subscribed. */
35
+ current(): ObservationView | null;
36
+ /** Clear the watch (e.g. on unsubscribe). */
37
+ clear(): void;
38
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Correlates server response frames to in-flight requests by `request_id`.
3
+ *
4
+ * Two request shapes:
5
+ * - query resolves on the synchronous `result` frame.
6
+ * - mutation is usually two-phase: a `result` ack (pending marker set)
7
+ * arrives first, then — possibly many ticks later — an
8
+ * `action_result` (resolve) or `action_error` (reject). A
9
+ * synchronous validation failure short-circuits to an `error`
10
+ * frame (reject).
11
+ * But some mutation-classified actions resolve synchronously in
12
+ * a single `result` frame with no pending marker anywhere —
13
+ * nothing was queued, so no `action_result` is ever coming (e.g.
14
+ * `craft`/`recycle` with `dry_run: true`, which explicitly
15
+ * queues nothing). That `result` frame is treated as the final
16
+ * outcome instead of an ack, or the request would wait forever
17
+ * for an `action_result` the server was never going to send.
18
+ *
19
+ * The pending marker's location in structuredContent is NOT consistent
20
+ * across actions — verified live against the real server: `scan`/`undock`
21
+ * carry it at the top level (`{pending: true, command, message}`), while
22
+ * `jump` nests it under `details` (`{details: {pending: true, command,
23
+ * message}, location: {...}, queue: {...}}`). Checking only the top level
24
+ * (an earlier version of this file did) misreads a real jump ack as the
25
+ * "nothing queued" case, resolving it immediately instead of waiting for the
26
+ * real outcome — which released the next queued mutation before the
27
+ * previous one actually executed, and the server correctly rejected it with
28
+ * `action_pending`, collapsing a whole multi-hop route in milliseconds.
29
+ *
30
+ * Frames may interleave arbitrarily on the wire, so nothing here assumes
31
+ * ordering beyond per-request_id sequencing.
32
+ */
33
+ import type { MutationAck, MutationResult, QueryResult, RawFrame } from '../protocol.js';
34
+ import { ConnectionClosedError, SpacemoltError } from '../errors.js';
35
+ export type RequestKind = 'query' | 'mutation';
36
+ export declare class Correlator {
37
+ private readonly pending;
38
+ /** Register a query request; resolves on its `result` frame. */
39
+ awaitQuery(requestId: string): Promise<QueryResult>;
40
+ /** Register a mutation request; resolves on its `action_result` outcome. */
41
+ awaitMutation(requestId: string, onAck?: (ack: MutationAck) => void): Promise<MutationResult>;
42
+ has(requestId: string): boolean;
43
+ /** Drop a pending request without settling it (e.g. after the caller gave up on a timeout). */
44
+ cancel(requestId: string): void;
45
+ /**
46
+ * Feed a frame to the correlator. Returns true if the frame was consumed
47
+ * (matched an in-flight request), false otherwise (caller treats it as a
48
+ * push frame).
49
+ */
50
+ handle(frame: RawFrame): boolean;
51
+ /** Reject every in-flight request — used when the socket closes. */
52
+ rejectAll(err: ConnectionClosedError | SpacemoltError): void;
53
+ private settle;
54
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Thin lifecycle wrapper over a web-standard WebSocket.
3
+ *
4
+ * Parses inbound text frames to `OutboundFrame` and hands them to `onFrame`;
5
+ * serializes outbound `InboundFrame`s. Reconnection and re-auth are M4 — this
6
+ * layer just owns one connection. The WebSocket implementation is injectable
7
+ * (`webSocketFactory`) so the transport can be driven by a mock in tests and
8
+ * so the browser/Bun/Node global `WebSocket` is used by default.
9
+ */
10
+ import type { InboundFrame, RawFrame } from '../protocol.js';
11
+ import { ConnectionClosedError } from '../errors.js';
12
+ /** The minimal web-standard WebSocket surface this library depends on. */
13
+ export interface WebSocketLike {
14
+ send(data: string): void;
15
+ close(code?: number, reason?: string): void;
16
+ addEventListener(type: 'open', listener: () => void): void;
17
+ addEventListener(type: 'message', listener: (ev: {
18
+ data: unknown;
19
+ }) => void): void;
20
+ addEventListener(type: 'close', listener: (ev: {
21
+ code?: number;
22
+ reason?: string;
23
+ }) => void): void;
24
+ addEventListener(type: 'error', listener: (ev: unknown) => void): void;
25
+ }
26
+ export type WebSocketFactory = (url: string) => WebSocketLike;
27
+ export interface SocketOptions {
28
+ url: string;
29
+ webSocketFactory?: WebSocketFactory;
30
+ }
31
+ export declare class Socket {
32
+ private readonly opts;
33
+ private ws;
34
+ private opened;
35
+ private closed;
36
+ /** Called for every parsed inbound frame. */
37
+ onFrame?: (frame: RawFrame) => void;
38
+ /** Called once when the connection closes (for any reason). */
39
+ onClose?: (err: ConnectionClosedError) => void;
40
+ constructor(opts: SocketOptions);
41
+ /** Open the socket; resolves when the connection is established. */
42
+ connect(): Promise<void>;
43
+ send(frame: InboundFrame): void;
44
+ close(): void;
45
+ /**
46
+ * The server batches multiple pushes into a single WebSocket message as
47
+ * newline-delimited JSON under load (confirmed live: a burst of
48
+ * reconnect-related frames arrived as one message,
49
+ * `{"type":"reconnected",...}\n{"type":"logged_in",...}`). A JSON string
50
+ * value's own newlines are always escaped (`\n`, two characters) by any
51
+ * conforming serializer, so a raw newline byte in the message text is only
52
+ * ever a frame separator, never content — splitting on it is safe. Each
53
+ * line is parsed and routed independently, so one malformed line in a
54
+ * batch no longer takes every other frame in that same message down with
55
+ * it (the previous behavior: parsing the whole batch as one JSON value,
56
+ * which always failed whenever more than one frame arrived together).
57
+ */
58
+ private handleMessage;
59
+ private markClosed;
60
+ }
package/llms.txt ADDED
@@ -0,0 +1,23 @@
1
+ # @spacemolt/lib
2
+
3
+ > The definitive TypeScript library for the game [SpaceMolt](https://www.spacemolt.com). Speaks the WebSocket v2 protocol, keeps a live local cache of your state, is multi-account native, and regenerates its own typed command surface from the server's OpenAPI spec. Runs on Bun, Node 22+, and the browser.
4
+
5
+ If you are an AI agent writing code against this library, start with AGENTS.md
6
+ (orientation) and COMMANDS.md (the full command list). The whole API is typed —
7
+ run `tsc --noEmit` before executing a script to catch a wrong command or
8
+ parameter as a compile error rather than a runtime surprise.
9
+
10
+ ## Start here
11
+
12
+ - [AGENTS.md](./AGENTS.md): How to write against this library — the calling pattern, how to discover commands, and the typecheck-first workflow.
13
+ - [COMMANDS.md](./COMMANDS.md): Generated reference of every command (250+) with its full typed signature, query/mutation kind, and description. Greppable.
14
+
15
+ ## Docs
16
+
17
+ - [README.md](./README.md): Full guide — quickstart, queries vs mutations, the local state cache, live events, subscriptions, multi-account (Clerk API key), and bulk reference data.
18
+ - [docs/live-testing.md](./docs/live-testing.md): Running against the real server — registration, the smoke test, and gotchas.
19
+ - [CLAUDE.md](./CLAUDE.md): Developer guide for working *on* the library itself (codegen, layout, milestones).
20
+
21
+ ## Optional
22
+
23
+ - [docs/gameserver-todo.md](./docs/gameserver-todo.md): Server-side gaps that further improve type coverage as they land.
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@spacemolt/lib",
3
+ "version": "4.6.0",
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
+ "type": "module",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./node": {
14
+ "types": "./dist/node.d.ts",
15
+ "import": "./dist/node.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "COMMANDS.md",
21
+ "AGENTS.md",
22
+ "llms.txt"
23
+ ],
24
+ "scripts": {
25
+ "fetch-spec": "bun run scripts/fetch-spec.ts",
26
+ "generate": "bun run openapi-ts && bun run scripts/generate.ts",
27
+ "typecheck": "bun x tsc --noEmit",
28
+ "prepare": "bun run build",
29
+ "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",
30
+ "build:browser-check": "bun build src/index.ts --target browser --outfile /dev/null",
31
+ "test": "bun test"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/SpaceMolt/spacemolt-lib.git"
36
+ },
37
+ "homepage": "https://www.spacemolt.com",
38
+ "license": "MIT",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "devDependencies": {
43
+ "@hey-api/openapi-ts": "^0.73.0",
44
+ "@types/bun": "^1.3.9"
45
+ },
46
+ "peerDependencies": {
47
+ "typescript": "^5"
48
+ }
49
+ }