@idosgames/module-sdk 0.1.11 → 0.2.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/index.cjs +1 -1
- package/dist/index.d.cts +154 -5
- package/dist/index.d.ts +154 -5
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';function t(e){return e}exports.defineModule=
|
|
1
|
+
'use strict';var m=/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*@[1-9]\d*$/;function x(e){return typeof e=="string"&&m.test(e)}function M(e){let t=e.indexOf(":");return t<0?"":e.slice(0,t)}var l=/^(string|number|boolean)(\[\])?(\?)?$/;function u(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function c(e,t){return e==="number"?typeof t=="number"&&Number.isFinite(t):typeof t===e}function f(e,t,r){if(!u(t))return `${r||"payload"} must be an object`;for(let[n,o]of Object.entries(e)){let i=r?`${r}.${n}`:n,a=t[n];if(typeof o!="string"){let p=f(o,a,i);if(p)return p;continue}let d=l.exec(o);if(!d)return `${i}: invalid descriptor "${o}"`;let[,s="",g,S]=d;if(a===void 0){if(S)continue;return `${i} is missing`}if(g){if(!Array.isArray(a))return `${i} must be ${s}[]`;if(!a.every(p=>c(s,p)))return `${i} must contain only ${s} values`;continue}if(!c(s,a))return `${i} must be a ${s}`}return null}function y(e,t){for(let[r,n]of Object.entries(e)){let o=t?`${t}.${r}`:r;if(typeof n=="string"){if(!l.test(n))throw new Error(`shape(): field "${o}" has invalid type "${n}" \u2014 use "string" | "number" | "boolean", optionally with "[]" and/or a trailing "?"`)}else if(u(n))y(n,o);else throw new Error(`shape(): field "${o}" must be a type string or an object`)}}function h(e){return y(e,""),{descriptor:e,check:t=>f(e,t,"")}}function v(e,t){if(!x(e))throw new Error(`defineTopic(): "${e}" is not a valid topic name \u2014 expected "<module-id>:<event>@<major>", e.g. "idle-rpg:character-upgraded@1"`);return {name:e,shape:t}}var E={modeChanged:v("host:mode-changed@1",h({from:"string?",to:"string"}))};var T=["currency-bar","wallet","status","account"];function $(e){return typeof e=="string"&&T.includes(e)}function D(e){return e}exports.SHARED_UI_ROLES=T;exports.TOPIC_NAME_RE=m;exports.defineModule=D;exports.defineTopic=v;exports.hostTopics=E;exports.isSharedUiRole=$;exports.isTopicName=x;exports.shape=h;exports.topicNamespace=M;
|
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,136 @@ import { IDosGamesClient } from '@idosgames/core';
|
|
|
2
2
|
export { IDosGamesClient, SdkEvents } from '@idosgames/core';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
|
|
5
|
+
/** `"<module-id>:<event>@<major>"`, e.g. `"idle-rpg:character-upgraded@1"`. */
|
|
6
|
+
type TopicName = `${string}:${string}@${number}`;
|
|
7
|
+
/** The exact grammar of a topic name. The part before ":" is the owning module's id. */
|
|
8
|
+
declare const TOPIC_NAME_RE: RegExp;
|
|
9
|
+
declare function isTopicName(value: unknown): value is TopicName;
|
|
10
|
+
/** The namespace (owning module id) of a topic name: `"idle-rpg:x@1"` → `"idle-rpg"`. */
|
|
11
|
+
declare function topicNamespace(name: string): string;
|
|
12
|
+
type ShapeLeaf = "string" | "number" | "boolean";
|
|
13
|
+
/** A leaf field: a type, optionally an array (`[]`), optionally optional (`?`, always last). */
|
|
14
|
+
type ShapeLeafSpec = ShapeLeaf | `${ShapeLeaf}[]` | `${ShapeLeaf}?` | `${ShapeLeaf}[]?`;
|
|
15
|
+
/** A flat JSON payload descriptor. Nested objects are allowed (and required when present). */
|
|
16
|
+
interface ShapeDescriptor {
|
|
17
|
+
readonly [field: string]: ShapeLeafSpec | ShapeDescriptor;
|
|
18
|
+
}
|
|
19
|
+
type LeafType<L> = L extends "string" ? string : L extends "number" ? number : L extends "boolean" ? boolean : never;
|
|
20
|
+
type StripOptional<S extends string> = S extends `${infer B}?` ? B : S;
|
|
21
|
+
type SpecType<S> = S extends string ? StripOptional<S> extends `${infer L}[]` ? LeafType<L>[] : LeafType<StripOptional<S>> : S extends ShapeDescriptor ? InferShape<S> : never;
|
|
22
|
+
type OptionalKeys<D> = {
|
|
23
|
+
[K in keyof D]: D[K] extends `${string}?` ? K : never;
|
|
24
|
+
}[keyof D];
|
|
25
|
+
type Simplify<T> = {
|
|
26
|
+
[K in keyof T]: T[K];
|
|
27
|
+
} & {};
|
|
28
|
+
/** The TypeScript payload type described by a `ShapeDescriptor`. */
|
|
29
|
+
type InferShape<D> = Simplify<{
|
|
30
|
+
-readonly [K in Exclude<keyof D, OptionalKeys<D>>]: SpecType<D[K]>;
|
|
31
|
+
} & {
|
|
32
|
+
-readonly [K in OptionalKeys<D>]?: SpecType<D[K]>;
|
|
33
|
+
}>;
|
|
34
|
+
/** A payload shape: the descriptor plus its runtime check. `T` is the inferred payload type. */
|
|
35
|
+
interface Shape<T> {
|
|
36
|
+
readonly descriptor: ShapeDescriptor;
|
|
37
|
+
/** `null` when `value` fits, otherwise a short reason naming the first bad field. */
|
|
38
|
+
check(value: unknown): string | null;
|
|
39
|
+
/** Type-only marker carrying `T`; never set at runtime. */
|
|
40
|
+
readonly __payload?: T;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Describe a payload. `shape({ characterId: "string", level: "number", tags: "string[]?" })` is typed
|
|
44
|
+
* `{ characterId: string; level: number; tags?: string[] }` and validates the same thing at runtime.
|
|
45
|
+
*/
|
|
46
|
+
declare function shape<const D extends ShapeDescriptor>(descriptor: D): Shape<InferShape<D>>;
|
|
47
|
+
/** A topic token: name + payload shape. Create with `defineTopic`, pass it to `ctx.events`. */
|
|
48
|
+
interface Topic<T> {
|
|
49
|
+
readonly name: TopicName;
|
|
50
|
+
readonly shape: Shape<T>;
|
|
51
|
+
}
|
|
52
|
+
/** The payload type of a topic token. */
|
|
53
|
+
type TopicPayload<X> = X extends Topic<infer T> ? T : never;
|
|
54
|
+
/**
|
|
55
|
+
* Declare a topic. The name must start with the OWNING module's id — the host refuses to deliver an
|
|
56
|
+
* emit into another module's namespace. Declare it in `module.meta.json` too (`events.emits` for your
|
|
57
|
+
* own topics, `events.listens` for another module's), so the catalog and the agent can see it.
|
|
58
|
+
*/
|
|
59
|
+
declare function defineTopic<T>(name: TopicName, payload: Shape<T>): Topic<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Topics the HOST emits. The only topics a module may import: their owner is this npm package, not a
|
|
62
|
+
* module. The `host:` namespace is closed to modules.
|
|
63
|
+
*/
|
|
64
|
+
declare const hostTopics: {
|
|
65
|
+
/** The player switched modes via the nav. `from` is absent on the first activation. */
|
|
66
|
+
readonly modeChanged: Topic<{
|
|
67
|
+
to: string;
|
|
68
|
+
from?: string | undefined;
|
|
69
|
+
}>;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* `ctx.events` — this module's handle on the host's event bus.
|
|
73
|
+
*
|
|
74
|
+
* - `emit` only into your own namespace (`"<your-id>:…"`); anything else is dropped with an error.
|
|
75
|
+
* - Subscribe in `setup()`, not in a panel effect: `activeOnly` panels unmount with their mode and
|
|
76
|
+
* would miss events. Subscriptions live for the session and the host removes them on logout.
|
|
77
|
+
* - An event is a SIGNAL, not state, and is never replayed. Money, inventory and progress go through
|
|
78
|
+
* `ctx.client`; "what exists right now" goes through `ctx.sharedUi` and the SDK.
|
|
79
|
+
* - Payloads are plain JSON — no functions, class instances or engine objects.
|
|
80
|
+
*/
|
|
81
|
+
interface ModuleEvents {
|
|
82
|
+
emit<T>(topic: Topic<T>, payload: T): void;
|
|
83
|
+
/** @deprecated Untyped string topics. Use `defineTopic(...)` and pass the token. */
|
|
84
|
+
emit(topic: string, payload: unknown): void;
|
|
85
|
+
/** Subscribe; returns an unsubscribe function. The handler only sees payloads that fit YOUR shape. */
|
|
86
|
+
on<T>(topic: Topic<T>, handler: (payload: T) => void): () => void;
|
|
87
|
+
/** @deprecated Untyped string topics. Use `defineTopic(...)` and pass the token. */
|
|
88
|
+
on(topic: string, handler: (payload: unknown) => void): () => void;
|
|
89
|
+
}
|
|
90
|
+
/** One topic a module emits, as declared in `module.meta.json`. */
|
|
91
|
+
interface ModuleEventEmit {
|
|
92
|
+
topic: TopicName;
|
|
93
|
+
/** When it fires, in plain words. */
|
|
94
|
+
when?: string;
|
|
95
|
+
/** The descriptor passed to `shape()` — copied verbatim by listeners. */
|
|
96
|
+
payload: ShapeDescriptor;
|
|
97
|
+
}
|
|
98
|
+
/** One topic a module listens to. Always optional: the module must work without the emitter. */
|
|
99
|
+
interface ModuleEventListen {
|
|
100
|
+
topic: TopicName;
|
|
101
|
+
/** What the module does with it. */
|
|
102
|
+
why?: string;
|
|
103
|
+
}
|
|
104
|
+
interface ModuleEventsManifest {
|
|
105
|
+
emits?: ModuleEventEmit[];
|
|
106
|
+
listens?: ModuleEventListen[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The closed list of roles. Closed on purpose: a role is a promise "I draw this for everybody", so
|
|
111
|
+
* each one has to mean the same thing across the whole catalog. A new role is a minor release of
|
|
112
|
+
* this package; anything that does not fit a role is plain events + SDK data.
|
|
113
|
+
*/
|
|
114
|
+
declare const SHARED_UI_ROLES: readonly ["currency-bar", "wallet", "status", "account"];
|
|
115
|
+
type SharedUiRole = (typeof SHARED_UI_ROLES)[number];
|
|
116
|
+
/** Static declaration on a module (`Module.sharedUi`). */
|
|
117
|
+
interface ModuleSharedUi {
|
|
118
|
+
/** Roles this module draws for every mode. The first provider in `src/modules.ts` wins a role. */
|
|
119
|
+
provides?: readonly SharedUiRole[];
|
|
120
|
+
/**
|
|
121
|
+
* Roles this module relies on but does not draw (a shop that shows prices but no balance bar).
|
|
122
|
+
* With no provider installed the host warns in dev, and the catalog tells the agent to install one.
|
|
123
|
+
*/
|
|
124
|
+
requires?: readonly SharedUiRole[];
|
|
125
|
+
}
|
|
126
|
+
/** `ctx.sharedUi` — the host's answer, fixed for the whole session. */
|
|
127
|
+
interface SharedUiContext {
|
|
128
|
+
/** Which module draws `role` for everybody, or `null` when nobody took it. */
|
|
129
|
+
ownerOf(role: SharedUiRole): string | null;
|
|
130
|
+
/** Should THIS module draw its own copy: nobody took the role, or this module is its owner. */
|
|
131
|
+
shouldDraw(role: SharedUiRole): boolean;
|
|
132
|
+
}
|
|
133
|
+
declare function isSharedUiRole(value: unknown): value is SharedUiRole;
|
|
134
|
+
|
|
5
135
|
/** What a module is, for catalog/UI purposes. Not every module is a game. */
|
|
6
136
|
type ModuleType = "game" | "app" | "ai-app";
|
|
7
137
|
/**
|
|
@@ -77,6 +207,12 @@ interface ModuleManifest {
|
|
|
77
207
|
author?: ModuleAuthor;
|
|
78
208
|
/** Semver of the module content; defaults to the module's package.json `version`. */
|
|
79
209
|
version?: string;
|
|
210
|
+
/**
|
|
211
|
+
* The module's event contract: topics it emits (with the payload descriptor listeners copy) and
|
|
212
|
+
* topics of OTHER modules it listens to (always optional). `npm run check` in the SDK repo and the
|
|
213
|
+
* AI Coder's hygiene notes compare this with the `defineTopic("…")` literals in `src/`.
|
|
214
|
+
*/
|
|
215
|
+
events?: ModuleEventsManifest;
|
|
80
216
|
}
|
|
81
217
|
/**
|
|
82
218
|
* The manifest a module exports. The host calls `setup` exactly once, passing shared services;
|
|
@@ -86,6 +222,13 @@ interface Module {
|
|
|
86
222
|
/** Stable unique id — also the mode id the nav switches by. */
|
|
87
223
|
id: string;
|
|
88
224
|
meta: ModuleMeta;
|
|
225
|
+
/**
|
|
226
|
+
* Shared chrome this module draws for every mode (`provides`) or needs someone to draw
|
|
227
|
+
* (`requires`). Static on purpose: the host reads it for ALL modules before the first `setup()`,
|
|
228
|
+
* so `ctx.sharedUi` answers correctly whatever the module order. A template that draws its own
|
|
229
|
+
* wallet/balances/status hides them when `ctx.sharedUi.shouldDraw(role)` is false.
|
|
230
|
+
*/
|
|
231
|
+
sharedUi?: ModuleSharedUi;
|
|
89
232
|
setup(ctx: ModuleContext): void;
|
|
90
233
|
}
|
|
91
234
|
/** Services the host injects into every module at `setup` time. */
|
|
@@ -98,8 +241,14 @@ interface ModuleContext {
|
|
|
98
241
|
* and a module that disagrees with its host talks to a different title's data.
|
|
99
242
|
*/
|
|
100
243
|
titleId: string;
|
|
101
|
-
/**
|
|
102
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Cross-module bus, scoped to this module: emit typed topics of your own namespace
|
|
246
|
+
* (`defineTopic("<your-id>:<event>@1", shape({…}))`), listen to others' with your own copy of their
|
|
247
|
+
* descriptor. Subscribe in `setup()`; the host removes the subscriptions on logout.
|
|
248
|
+
*/
|
|
249
|
+
events: ModuleEvents;
|
|
250
|
+
/** Who draws the shared chrome (balances, wallet, status, account) — see `Module.sharedUi`. */
|
|
251
|
+
sharedUi: SharedUiContext;
|
|
103
252
|
/** Requests host-managed DOM surfaces (canvas hosts, HUD slots) when a module needs one directly. */
|
|
104
253
|
surface: SurfaceAllocator;
|
|
105
254
|
/** Register a rendered scene (Three/Phaser/custom). The host drives its lifecycle. */
|
|
@@ -211,8 +360,8 @@ interface RouteEntry {
|
|
|
211
360
|
order?: number;
|
|
212
361
|
}
|
|
213
362
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
363
|
+
* @deprecated The pre-0.2 string-topic bus. `ctx.events` is now `ModuleEvents` (typed topic tokens,
|
|
364
|
+
* see `defineTopic`); this type stays only so older code that named it still compiles.
|
|
216
365
|
*/
|
|
217
366
|
interface SharedEventBus<Events extends Record<string, unknown> = Record<string, unknown>> {
|
|
218
367
|
emit<K extends keyof Events & string>(topic: K, payload: Events[K]): void;
|
|
@@ -222,4 +371,4 @@ interface SharedEventBus<Events extends Record<string, unknown> = Record<string,
|
|
|
222
371
|
/** Identity helper that pins a module literal to the `Module` type for editor help and errors. */
|
|
223
372
|
declare function defineModule(module: Module): Module;
|
|
224
373
|
|
|
225
|
-
export { type EngineScene, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleType, type PanelSlot, type RouteEntry, type SceneMountContext, type SharedEventBus, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, type UiPanel, defineModule };
|
|
374
|
+
export { type EngineScene, type InferShape, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleEventEmit, type ModuleEventListen, type ModuleEvents, type ModuleEventsManifest, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleSharedUi, type ModuleType, type PanelSlot, type RouteEntry, SHARED_UI_ROLES, type SceneMountContext, type Shape, type ShapeDescriptor, type ShapeLeaf, type ShapeLeafSpec, type SharedEventBus, type SharedUiContext, type SharedUiRole, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, TOPIC_NAME_RE, type Topic, type TopicName, type TopicPayload, type UiPanel, defineModule, defineTopic, hostTopics, isSharedUiRole, isTopicName, shape, topicNamespace };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,136 @@ import { IDosGamesClient } from '@idosgames/core';
|
|
|
2
2
|
export { IDosGamesClient, SdkEvents } from '@idosgames/core';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
|
|
5
|
+
/** `"<module-id>:<event>@<major>"`, e.g. `"idle-rpg:character-upgraded@1"`. */
|
|
6
|
+
type TopicName = `${string}:${string}@${number}`;
|
|
7
|
+
/** The exact grammar of a topic name. The part before ":" is the owning module's id. */
|
|
8
|
+
declare const TOPIC_NAME_RE: RegExp;
|
|
9
|
+
declare function isTopicName(value: unknown): value is TopicName;
|
|
10
|
+
/** The namespace (owning module id) of a topic name: `"idle-rpg:x@1"` → `"idle-rpg"`. */
|
|
11
|
+
declare function topicNamespace(name: string): string;
|
|
12
|
+
type ShapeLeaf = "string" | "number" | "boolean";
|
|
13
|
+
/** A leaf field: a type, optionally an array (`[]`), optionally optional (`?`, always last). */
|
|
14
|
+
type ShapeLeafSpec = ShapeLeaf | `${ShapeLeaf}[]` | `${ShapeLeaf}?` | `${ShapeLeaf}[]?`;
|
|
15
|
+
/** A flat JSON payload descriptor. Nested objects are allowed (and required when present). */
|
|
16
|
+
interface ShapeDescriptor {
|
|
17
|
+
readonly [field: string]: ShapeLeafSpec | ShapeDescriptor;
|
|
18
|
+
}
|
|
19
|
+
type LeafType<L> = L extends "string" ? string : L extends "number" ? number : L extends "boolean" ? boolean : never;
|
|
20
|
+
type StripOptional<S extends string> = S extends `${infer B}?` ? B : S;
|
|
21
|
+
type SpecType<S> = S extends string ? StripOptional<S> extends `${infer L}[]` ? LeafType<L>[] : LeafType<StripOptional<S>> : S extends ShapeDescriptor ? InferShape<S> : never;
|
|
22
|
+
type OptionalKeys<D> = {
|
|
23
|
+
[K in keyof D]: D[K] extends `${string}?` ? K : never;
|
|
24
|
+
}[keyof D];
|
|
25
|
+
type Simplify<T> = {
|
|
26
|
+
[K in keyof T]: T[K];
|
|
27
|
+
} & {};
|
|
28
|
+
/** The TypeScript payload type described by a `ShapeDescriptor`. */
|
|
29
|
+
type InferShape<D> = Simplify<{
|
|
30
|
+
-readonly [K in Exclude<keyof D, OptionalKeys<D>>]: SpecType<D[K]>;
|
|
31
|
+
} & {
|
|
32
|
+
-readonly [K in OptionalKeys<D>]?: SpecType<D[K]>;
|
|
33
|
+
}>;
|
|
34
|
+
/** A payload shape: the descriptor plus its runtime check. `T` is the inferred payload type. */
|
|
35
|
+
interface Shape<T> {
|
|
36
|
+
readonly descriptor: ShapeDescriptor;
|
|
37
|
+
/** `null` when `value` fits, otherwise a short reason naming the first bad field. */
|
|
38
|
+
check(value: unknown): string | null;
|
|
39
|
+
/** Type-only marker carrying `T`; never set at runtime. */
|
|
40
|
+
readonly __payload?: T;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Describe a payload. `shape({ characterId: "string", level: "number", tags: "string[]?" })` is typed
|
|
44
|
+
* `{ characterId: string; level: number; tags?: string[] }` and validates the same thing at runtime.
|
|
45
|
+
*/
|
|
46
|
+
declare function shape<const D extends ShapeDescriptor>(descriptor: D): Shape<InferShape<D>>;
|
|
47
|
+
/** A topic token: name + payload shape. Create with `defineTopic`, pass it to `ctx.events`. */
|
|
48
|
+
interface Topic<T> {
|
|
49
|
+
readonly name: TopicName;
|
|
50
|
+
readonly shape: Shape<T>;
|
|
51
|
+
}
|
|
52
|
+
/** The payload type of a topic token. */
|
|
53
|
+
type TopicPayload<X> = X extends Topic<infer T> ? T : never;
|
|
54
|
+
/**
|
|
55
|
+
* Declare a topic. The name must start with the OWNING module's id — the host refuses to deliver an
|
|
56
|
+
* emit into another module's namespace. Declare it in `module.meta.json` too (`events.emits` for your
|
|
57
|
+
* own topics, `events.listens` for another module's), so the catalog and the agent can see it.
|
|
58
|
+
*/
|
|
59
|
+
declare function defineTopic<T>(name: TopicName, payload: Shape<T>): Topic<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Topics the HOST emits. The only topics a module may import: their owner is this npm package, not a
|
|
62
|
+
* module. The `host:` namespace is closed to modules.
|
|
63
|
+
*/
|
|
64
|
+
declare const hostTopics: {
|
|
65
|
+
/** The player switched modes via the nav. `from` is absent on the first activation. */
|
|
66
|
+
readonly modeChanged: Topic<{
|
|
67
|
+
to: string;
|
|
68
|
+
from?: string | undefined;
|
|
69
|
+
}>;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* `ctx.events` — this module's handle on the host's event bus.
|
|
73
|
+
*
|
|
74
|
+
* - `emit` only into your own namespace (`"<your-id>:…"`); anything else is dropped with an error.
|
|
75
|
+
* - Subscribe in `setup()`, not in a panel effect: `activeOnly` panels unmount with their mode and
|
|
76
|
+
* would miss events. Subscriptions live for the session and the host removes them on logout.
|
|
77
|
+
* - An event is a SIGNAL, not state, and is never replayed. Money, inventory and progress go through
|
|
78
|
+
* `ctx.client`; "what exists right now" goes through `ctx.sharedUi` and the SDK.
|
|
79
|
+
* - Payloads are plain JSON — no functions, class instances or engine objects.
|
|
80
|
+
*/
|
|
81
|
+
interface ModuleEvents {
|
|
82
|
+
emit<T>(topic: Topic<T>, payload: T): void;
|
|
83
|
+
/** @deprecated Untyped string topics. Use `defineTopic(...)` and pass the token. */
|
|
84
|
+
emit(topic: string, payload: unknown): void;
|
|
85
|
+
/** Subscribe; returns an unsubscribe function. The handler only sees payloads that fit YOUR shape. */
|
|
86
|
+
on<T>(topic: Topic<T>, handler: (payload: T) => void): () => void;
|
|
87
|
+
/** @deprecated Untyped string topics. Use `defineTopic(...)` and pass the token. */
|
|
88
|
+
on(topic: string, handler: (payload: unknown) => void): () => void;
|
|
89
|
+
}
|
|
90
|
+
/** One topic a module emits, as declared in `module.meta.json`. */
|
|
91
|
+
interface ModuleEventEmit {
|
|
92
|
+
topic: TopicName;
|
|
93
|
+
/** When it fires, in plain words. */
|
|
94
|
+
when?: string;
|
|
95
|
+
/** The descriptor passed to `shape()` — copied verbatim by listeners. */
|
|
96
|
+
payload: ShapeDescriptor;
|
|
97
|
+
}
|
|
98
|
+
/** One topic a module listens to. Always optional: the module must work without the emitter. */
|
|
99
|
+
interface ModuleEventListen {
|
|
100
|
+
topic: TopicName;
|
|
101
|
+
/** What the module does with it. */
|
|
102
|
+
why?: string;
|
|
103
|
+
}
|
|
104
|
+
interface ModuleEventsManifest {
|
|
105
|
+
emits?: ModuleEventEmit[];
|
|
106
|
+
listens?: ModuleEventListen[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The closed list of roles. Closed on purpose: a role is a promise "I draw this for everybody", so
|
|
111
|
+
* each one has to mean the same thing across the whole catalog. A new role is a minor release of
|
|
112
|
+
* this package; anything that does not fit a role is plain events + SDK data.
|
|
113
|
+
*/
|
|
114
|
+
declare const SHARED_UI_ROLES: readonly ["currency-bar", "wallet", "status", "account"];
|
|
115
|
+
type SharedUiRole = (typeof SHARED_UI_ROLES)[number];
|
|
116
|
+
/** Static declaration on a module (`Module.sharedUi`). */
|
|
117
|
+
interface ModuleSharedUi {
|
|
118
|
+
/** Roles this module draws for every mode. The first provider in `src/modules.ts` wins a role. */
|
|
119
|
+
provides?: readonly SharedUiRole[];
|
|
120
|
+
/**
|
|
121
|
+
* Roles this module relies on but does not draw (a shop that shows prices but no balance bar).
|
|
122
|
+
* With no provider installed the host warns in dev, and the catalog tells the agent to install one.
|
|
123
|
+
*/
|
|
124
|
+
requires?: readonly SharedUiRole[];
|
|
125
|
+
}
|
|
126
|
+
/** `ctx.sharedUi` — the host's answer, fixed for the whole session. */
|
|
127
|
+
interface SharedUiContext {
|
|
128
|
+
/** Which module draws `role` for everybody, or `null` when nobody took it. */
|
|
129
|
+
ownerOf(role: SharedUiRole): string | null;
|
|
130
|
+
/** Should THIS module draw its own copy: nobody took the role, or this module is its owner. */
|
|
131
|
+
shouldDraw(role: SharedUiRole): boolean;
|
|
132
|
+
}
|
|
133
|
+
declare function isSharedUiRole(value: unknown): value is SharedUiRole;
|
|
134
|
+
|
|
5
135
|
/** What a module is, for catalog/UI purposes. Not every module is a game. */
|
|
6
136
|
type ModuleType = "game" | "app" | "ai-app";
|
|
7
137
|
/**
|
|
@@ -77,6 +207,12 @@ interface ModuleManifest {
|
|
|
77
207
|
author?: ModuleAuthor;
|
|
78
208
|
/** Semver of the module content; defaults to the module's package.json `version`. */
|
|
79
209
|
version?: string;
|
|
210
|
+
/**
|
|
211
|
+
* The module's event contract: topics it emits (with the payload descriptor listeners copy) and
|
|
212
|
+
* topics of OTHER modules it listens to (always optional). `npm run check` in the SDK repo and the
|
|
213
|
+
* AI Coder's hygiene notes compare this with the `defineTopic("…")` literals in `src/`.
|
|
214
|
+
*/
|
|
215
|
+
events?: ModuleEventsManifest;
|
|
80
216
|
}
|
|
81
217
|
/**
|
|
82
218
|
* The manifest a module exports. The host calls `setup` exactly once, passing shared services;
|
|
@@ -86,6 +222,13 @@ interface Module {
|
|
|
86
222
|
/** Stable unique id — also the mode id the nav switches by. */
|
|
87
223
|
id: string;
|
|
88
224
|
meta: ModuleMeta;
|
|
225
|
+
/**
|
|
226
|
+
* Shared chrome this module draws for every mode (`provides`) or needs someone to draw
|
|
227
|
+
* (`requires`). Static on purpose: the host reads it for ALL modules before the first `setup()`,
|
|
228
|
+
* so `ctx.sharedUi` answers correctly whatever the module order. A template that draws its own
|
|
229
|
+
* wallet/balances/status hides them when `ctx.sharedUi.shouldDraw(role)` is false.
|
|
230
|
+
*/
|
|
231
|
+
sharedUi?: ModuleSharedUi;
|
|
89
232
|
setup(ctx: ModuleContext): void;
|
|
90
233
|
}
|
|
91
234
|
/** Services the host injects into every module at `setup` time. */
|
|
@@ -98,8 +241,14 @@ interface ModuleContext {
|
|
|
98
241
|
* and a module that disagrees with its host talks to a different title's data.
|
|
99
242
|
*/
|
|
100
243
|
titleId: string;
|
|
101
|
-
/**
|
|
102
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Cross-module bus, scoped to this module: emit typed topics of your own namespace
|
|
246
|
+
* (`defineTopic("<your-id>:<event>@1", shape({…}))`), listen to others' with your own copy of their
|
|
247
|
+
* descriptor. Subscribe in `setup()`; the host removes the subscriptions on logout.
|
|
248
|
+
*/
|
|
249
|
+
events: ModuleEvents;
|
|
250
|
+
/** Who draws the shared chrome (balances, wallet, status, account) — see `Module.sharedUi`. */
|
|
251
|
+
sharedUi: SharedUiContext;
|
|
103
252
|
/** Requests host-managed DOM surfaces (canvas hosts, HUD slots) when a module needs one directly. */
|
|
104
253
|
surface: SurfaceAllocator;
|
|
105
254
|
/** Register a rendered scene (Three/Phaser/custom). The host drives its lifecycle. */
|
|
@@ -211,8 +360,8 @@ interface RouteEntry {
|
|
|
211
360
|
order?: number;
|
|
212
361
|
}
|
|
213
362
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
363
|
+
* @deprecated The pre-0.2 string-topic bus. `ctx.events` is now `ModuleEvents` (typed topic tokens,
|
|
364
|
+
* see `defineTopic`); this type stays only so older code that named it still compiles.
|
|
216
365
|
*/
|
|
217
366
|
interface SharedEventBus<Events extends Record<string, unknown> = Record<string, unknown>> {
|
|
218
367
|
emit<K extends keyof Events & string>(topic: K, payload: Events[K]): void;
|
|
@@ -222,4 +371,4 @@ interface SharedEventBus<Events extends Record<string, unknown> = Record<string,
|
|
|
222
371
|
/** Identity helper that pins a module literal to the `Module` type for editor help and errors. */
|
|
223
372
|
declare function defineModule(module: Module): Module;
|
|
224
373
|
|
|
225
|
-
export { type EngineScene, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleType, type PanelSlot, type RouteEntry, type SceneMountContext, type SharedEventBus, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, type UiPanel, defineModule };
|
|
374
|
+
export { type EngineScene, type InferShape, type Module, type ModuleAgentApi, type ModuleAuthor, type ModuleContext, type ModuleEngine, type ModuleEventEmit, type ModuleEventListen, type ModuleEvents, type ModuleEventsManifest, type ModuleManifest, type ModuleMedia, type ModuleMeta, type ModuleSharedUi, type ModuleType, type PanelSlot, type RouteEntry, SHARED_UI_ROLES, type SceneMountContext, type Shape, type ShapeDescriptor, type ShapeLeaf, type ShapeLeafSpec, type SharedEventBus, type SharedUiContext, type SharedUiRole, type SurfaceAllocator, type SurfaceHandle, type SurfaceKind, TOPIC_NAME_RE, type Topic, type TopicName, type TopicPayload, type UiPanel, defineModule, defineTopic, hostTopics, isSharedUiRole, isTopicName, shape, topicNamespace };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function t(e){return e}
|
|
1
|
+
var m=/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*@[1-9]\d*$/;function x(e){return typeof e=="string"&&m.test(e)}function M(e){let t=e.indexOf(":");return t<0?"":e.slice(0,t)}var l=/^(string|number|boolean)(\[\])?(\?)?$/;function u(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function c(e,t){return e==="number"?typeof t=="number"&&Number.isFinite(t):typeof t===e}function f(e,t,r){if(!u(t))return `${r||"payload"} must be an object`;for(let[n,o]of Object.entries(e)){let i=r?`${r}.${n}`:n,a=t[n];if(typeof o!="string"){let p=f(o,a,i);if(p)return p;continue}let d=l.exec(o);if(!d)return `${i}: invalid descriptor "${o}"`;let[,s="",g,S]=d;if(a===void 0){if(S)continue;return `${i} is missing`}if(g){if(!Array.isArray(a))return `${i} must be ${s}[]`;if(!a.every(p=>c(s,p)))return `${i} must contain only ${s} values`;continue}if(!c(s,a))return `${i} must be a ${s}`}return null}function y(e,t){for(let[r,n]of Object.entries(e)){let o=t?`${t}.${r}`:r;if(typeof n=="string"){if(!l.test(n))throw new Error(`shape(): field "${o}" has invalid type "${n}" \u2014 use "string" | "number" | "boolean", optionally with "[]" and/or a trailing "?"`)}else if(u(n))y(n,o);else throw new Error(`shape(): field "${o}" must be a type string or an object`)}}function h(e){return y(e,""),{descriptor:e,check:t=>f(e,t,"")}}function v(e,t){if(!x(e))throw new Error(`defineTopic(): "${e}" is not a valid topic name \u2014 expected "<module-id>:<event>@<major>", e.g. "idle-rpg:character-upgraded@1"`);return {name:e,shape:t}}var E={modeChanged:v("host:mode-changed@1",h({from:"string?",to:"string"}))};var T=["currency-bar","wallet","status","account"];function $(e){return typeof e=="string"&&T.includes(e)}function D(e){return e}export{T as SHARED_UI_ROLES,m as TOPIC_NAME_RE,D as defineModule,v as defineTopic,E as hostTopics,$ as isSharedUiRole,x as isTopicName,h as shape,M as topicNamespace};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idosgames/module-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The composable-module contract for the iDosGames host shell: Module / ModuleContext types plus tiny authoring helpers. Framework-neutral at runtime — a module can be a game (Three/Phaser), a plain app, or an AI app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"//": "@idosgames/core is a real range, not '*': a published contract must name the version of core whose IDosGamesClient/SdkEvents shapes it references. Bump in step with core (see RELEASING.md). react is an OPTIONAL peer — the contract references React's ComponentType type only (a UI panel is a React component), which is erased at build, so a runtime-only module needs no React.",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@idosgames/core": "^0.
|
|
49
|
+
"@idosgames/core": "^0.12.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "^18.3.1 || ^19.0.0"
|