@crowdedkingdoms/crowdyjs 8.0.1 → 8.1.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.
@@ -0,0 +1,158 @@
1
+ import { inventoryNames } from './blueprints.js';
2
+ import { kitContainerProperties, kitInvoke, } from './shared.js';
3
+ /**
4
+ * Runtime helpers for the {@link inventoryBlueprint} conventions: find or
5
+ * create the player's inventory, list stacks, and mutate them through the
6
+ * owner-gated model functions. All state lives server-side; every mutation is
7
+ * authority-checked and atomic.
8
+ *
9
+ * Obtained via `client.kit(appId).inventory`.
10
+ */
11
+ export class InventoryKit {
12
+ constructor(appId, gameModel, options = {}) {
13
+ this.appId = appId;
14
+ this.gameModel = gameModel;
15
+ this.names = inventoryNames(options.typePrefix ?? '');
16
+ }
17
+ /**
18
+ * Find the caller's inventory container, creating it when absent. The
19
+ * server assigns ownership to the caller (the type is member-instantiable
20
+ * and `ownerUserId` is omitted on create).
21
+ *
22
+ * @param ownerUserId - The calling player's user id (a decimal string, e.g.
23
+ * from `client.users.me()`), used to recognize an existing inventory.
24
+ */
25
+ async ensure(ownerUserId, options = {}) {
26
+ const existing = await this.gameModel.containers({
27
+ appId: this.appId,
28
+ typeName: this.names.inventoryType,
29
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
30
+ });
31
+ const mine = existing.find((c) => c.ownerUserId != null && String(c.ownerUserId) === String(ownerUserId));
32
+ if (mine)
33
+ return mine;
34
+ return this.gameModel.createContainer({
35
+ appId: this.appId,
36
+ typeName: this.names.inventoryType,
37
+ displayName: options.displayName ?? `Inventory ${ownerUserId}`,
38
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
39
+ });
40
+ }
41
+ /**
42
+ * List a player's item stacks with parsed properties (`itemId`, `quantity`,
43
+ * `slot`). Fetches each stack's visible state in parallel.
44
+ */
45
+ async stacks(ownerUserId) {
46
+ const containers = await this.gameModel.containers({
47
+ appId: this.appId,
48
+ typeName: this.names.stackType,
49
+ });
50
+ const mine = containers.filter((c) => c.ownerUserId != null && String(c.ownerUserId) === String(ownerUserId));
51
+ return Promise.all(mine.map(async (c) => {
52
+ const props = await kitContainerProperties(this.gameModel, String(this.appId), c.containerId);
53
+ return {
54
+ containerId: c.containerId,
55
+ displayName: c.displayName,
56
+ ownerUserId: c.ownerUserId != null ? String(c.ownerUserId) : null,
57
+ itemId: String(props.item_id ?? ''),
58
+ quantity: Number(props.quantity ?? 0),
59
+ slot: Number(props.slot ?? 0),
60
+ };
61
+ }));
62
+ }
63
+ /**
64
+ * Create a new stack owned by the caller (server-assigned ownership).
65
+ * Use {@link grant} afterwards for authority-checked increments; the initial
66
+ * quantity here is a seed value on a container the caller owns anyway.
67
+ */
68
+ async createStack(input) {
69
+ return this.gameModel.createContainer({
70
+ appId: this.appId,
71
+ typeName: this.names.stackType,
72
+ displayName: input.displayName ?? `Stack ${input.itemId}`,
73
+ ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}),
74
+ properties: [
75
+ { key: 'item_id', valueType: 'string', valueJson: JSON.stringify(input.itemId) },
76
+ { key: 'quantity', valueType: 'int', valueJson: String(input.quantity ?? 0) },
77
+ { key: 'slot', valueType: 'int', valueJson: String(input.slot ?? 0) },
78
+ ],
79
+ });
80
+ }
81
+ /** Add items to a stack the caller owns. Resolves with the new quantity. */
82
+ async grant(stackId, amount) {
83
+ return kitInvoke(this.gameModel, {
84
+ appId: String(this.appId),
85
+ functionName: this.names.grantFn,
86
+ selfContainerId: stackId,
87
+ params: { amount },
88
+ });
89
+ }
90
+ /**
91
+ * Spend items from a stack the caller owns. The server refuses to overdraw
92
+ * (`success: false`, nothing written). Resolves with the new quantity.
93
+ */
94
+ async consume(stackId, amount) {
95
+ return kitInvoke(this.gameModel, {
96
+ appId: String(this.appId),
97
+ functionName: this.names.consumeFn,
98
+ selfContainerId: stackId,
99
+ params: { amount },
100
+ });
101
+ }
102
+ /** Move a stack to another slot. Resolves with the new (clamped) slot. */
103
+ async move(stackId, toSlot) {
104
+ return kitInvoke(this.gameModel, {
105
+ appId: String(this.appId),
106
+ functionName: this.names.moveFn,
107
+ selfContainerId: stackId,
108
+ params: { to_slot: toSlot },
109
+ });
110
+ }
111
+ /**
112
+ * Atomically move items between two stacks of the same item type — both
113
+ * writes commit or neither does. The caller must own the source stack.
114
+ * Resolves with the source stack's remaining quantity.
115
+ */
116
+ async transfer(fromStackId, toStackId, amount) {
117
+ return kitInvoke(this.gameModel, {
118
+ appId: String(this.appId),
119
+ functionName: this.names.transferFn,
120
+ selfContainerId: fromStackId,
121
+ params: { to_id: toStackId, amount },
122
+ });
123
+ }
124
+ /**
125
+ * Record that a stack belongs to an inventory with an
126
+ * `inventory_contains` edge, so {@link contents} can read the whole bag in
127
+ * one traversal.
128
+ */
129
+ async linkStack(inventoryId, stackId) {
130
+ return this.gameModel.addEdge({
131
+ appId: this.appId,
132
+ fromContainerId: inventoryId,
133
+ toContainerId: stackId,
134
+ relationshipType: this.names.containsEdge,
135
+ });
136
+ }
137
+ /** Read every stack linked to an inventory (via `inventory_contains` edges). */
138
+ async contents(inventoryId) {
139
+ const result = await this.gameModel.traverse({
140
+ appId: this.appId,
141
+ rootId: inventoryId,
142
+ relationshipType: this.names.containsEdge,
143
+ depth: 1,
144
+ });
145
+ const stacks = result.nodes.filter((n) => n.typeName === this.names.stackType);
146
+ return Promise.all(stacks.map(async (c) => {
147
+ const props = await kitContainerProperties(this.gameModel, String(this.appId), c.containerId);
148
+ return {
149
+ containerId: c.containerId,
150
+ displayName: c.displayName,
151
+ ownerUserId: c.ownerUserId != null ? String(c.ownerUserId) : null,
152
+ itemId: String(props.item_id ?? ''),
153
+ quantity: Number(props.quantity ?? 0),
154
+ slot: Number(props.slot ?? 0),
155
+ };
156
+ }));
157
+ }
158
+ }
@@ -0,0 +1,96 @@
1
+ import type { GameModelAPI } from '../domains/gameModel.js';
2
+ import type { GameModelSeedMutation, GameModelUpsertAutomationMutation, GameModelUpsertAutomationTriggerMutation, Scalars } from '../generated/graphql.js';
3
+ import { type KitBlueprint } from './blueprints.js';
4
+ import { InventoryKit, type InventoryKitOptions } from './inventory.js';
5
+ import { NpcsKit, type NpcsKitOptions } from './npcs.js';
6
+ import { ObjectsKit, type ObjectsKitOptions } from './objects.js';
7
+ /** Options for {@link GameKitClient}, configuring the runtime helpers to match your deployed blueprints. */
8
+ export interface GameKitOptions {
9
+ inventory?: InventoryKitOptions;
10
+ objects?: ObjectsKitOptions;
11
+ npcs?: NpcsKitOptions;
12
+ }
13
+ /** The result of {@link GameKitClient.deploy}: the seed outcome plus each automation/trigger upserted. */
14
+ export interface KitDeployResult {
15
+ seed: GameModelSeedMutation['gameModelSeed'];
16
+ automations: GameModelUpsertAutomationMutation['gameModelUpsertAutomation'][];
17
+ automationTriggers: GameModelUpsertAutomationTriggerMutation['gameModelUpsertAutomationTrigger'][];
18
+ /** Non-fatal static-analysis warnings from the seed. */
19
+ warnings: string[];
20
+ }
21
+ /**
22
+ * App-scoped **Game Kit** facade returned by `client.kit(appId)` — high-level
23
+ * building blocks that map traditional game concepts (inventory, lockable
24
+ * objects with custom permissions, NPCs) onto the Game Model + Automations
25
+ * API. Everything composes `client.gameModel`; no new server surface.
26
+ *
27
+ * Two phases, matching the platform's model:
28
+ *
29
+ * 1. **Studio (admin) loads the rules** — {@link deploy} takes declarative
30
+ * {@link KitBlueprint}s (built with `inventoryBlueprint`, `lockBlueprint`,
31
+ * `npcBlueprint`, or by hand) and seeds the container types, property
32
+ * schemas, policy-gated functions, and automations into the app in one
33
+ * idempotent pass. Requires the app-admin `manage_apps` permission — run
34
+ * it from a trusted admin context, never the shipped game client.
35
+ * 2. **The game client plays** — {@link inventory}, {@link objects}, and
36
+ * {@link npcs} wrap the runtime calls (create/read containers, invoke the
37
+ * gated functions) assuming the blueprint conventions. Authorization is
38
+ * enforced server-side on every call.
39
+ *
40
+ * See the docs guides "Game API → Modeling game concepts" and
41
+ * "CrowdyJS → Game Kit".
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * // Studio setup (admin token):
46
+ * const kit = admin.kit(appId);
47
+ * await kit.deploy([
48
+ * inventoryBlueprint(),
49
+ * lockBlueprint({ objectTypeName: 'Door', authority: { kind: 'key' } }),
50
+ * ]);
51
+ *
52
+ * // Game client (player token):
53
+ * const kit = game.kit(appId);
54
+ * const bag = await kit.inventory.ensure(me.userId);
55
+ * const result = await kit.objects.open(doorId, { keyId });
56
+ * if (!result.success) showLockedMessage(result.errorMessage);
57
+ * ```
58
+ */
59
+ export declare class GameKitClient {
60
+ private readonly appId;
61
+ private readonly gameModel;
62
+ /** Inventory helpers (per-player bags and item stacks). */
63
+ readonly inventory: InventoryKit;
64
+ /** Lockable-object helpers (doors/chests/gates with custom permissions). */
65
+ readonly objects: ObjectsKit;
66
+ /** NPC helpers (spawn/read instances, manage the automations behind them). */
67
+ readonly npcs: NpcsKit;
68
+ constructor(appId: Scalars['BigInt']['input'], gameModel: GameModelAPI, options?: GameKitOptions);
69
+ /**
70
+ * Helpers for an additional lockable object type deployed under a different
71
+ * type name (e.g. both `Door` and `Chest` lock blueprints in one app).
72
+ */
73
+ objectsFor(objectTypeName: string, keyTypeName?: string): ObjectsKit;
74
+ /**
75
+ * **Studio (admin)** — load blueprints into the app: one transactional
76
+ * `gameModelSeed` for the definitions (and any seed containers/edges),
77
+ * followed by an `upsertAutomation` per automation and an
78
+ * `upsertAutomationTrigger` per event trigger. Idempotent: definitions
79
+ * upsert on their names, automations key on the automation name.
80
+ *
81
+ * Requires the app-admin `manage_apps` permission.
82
+ *
83
+ * @param blueprints - The blueprints to deploy. Duplicate type/function/
84
+ * automation names across blueprints throw before anything is sent.
85
+ * @param options - `sessionId` scopes any seed containers to a session.
86
+ * @returns A {@link KitDeployResult} with the seed counts, the upserted
87
+ * automations/triggers, and any non-fatal seed `warnings`.
88
+ * @throws {CrowdyGraphQLError} `FORBIDDEN` (`requiredPermission ===
89
+ * 'manage_apps'`) without app-admin, or `BAD_USER_INPUT` for definitions
90
+ * that fail to compile.
91
+ */
92
+ deploy(blueprints: KitBlueprint | KitBlueprint[], options?: {
93
+ sessionId?: string;
94
+ }): Promise<KitDeployResult>;
95
+ }
96
+ //# sourceMappingURL=kit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kit.d.ts","sourceRoot":"","sources":["../../src/kit/kit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EACV,qBAAqB,EACrB,iCAAiC,EACjC,wCAAwC,EACxC,OAAO,EACR,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAElE,4GAA4G;AAC5G,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;AAED,0GAA0G;AAC1G,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAC;IAC7C,WAAW,EAAE,iCAAiC,CAAC,2BAA2B,CAAC,EAAE,CAAC;IAC9E,kBAAkB,EAAE,wCAAwC,CAAC,kCAAkC,CAAC,EAAE,CAAC;IACnG,wDAAwD;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBAAa,aAAa;IAStB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAT5B,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAGJ,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,SAAS,EAAE,YAAY,EACxC,OAAO,GAAE,cAAmB;IAO9B;;;OAGG;IACH,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU;IAOpE;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CACV,UAAU,EAAE,YAAY,GAAG,YAAY,EAAE,EACzC,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACnC,OAAO,CAAC,eAAe,CAAC;CAsB5B"}
@@ -0,0 +1,98 @@
1
+ import { mergeBlueprints } from './blueprints.js';
2
+ import { InventoryKit } from './inventory.js';
3
+ import { NpcsKit } from './npcs.js';
4
+ import { ObjectsKit } from './objects.js';
5
+ /**
6
+ * App-scoped **Game Kit** facade returned by `client.kit(appId)` — high-level
7
+ * building blocks that map traditional game concepts (inventory, lockable
8
+ * objects with custom permissions, NPCs) onto the Game Model + Automations
9
+ * API. Everything composes `client.gameModel`; no new server surface.
10
+ *
11
+ * Two phases, matching the platform's model:
12
+ *
13
+ * 1. **Studio (admin) loads the rules** — {@link deploy} takes declarative
14
+ * {@link KitBlueprint}s (built with `inventoryBlueprint`, `lockBlueprint`,
15
+ * `npcBlueprint`, or by hand) and seeds the container types, property
16
+ * schemas, policy-gated functions, and automations into the app in one
17
+ * idempotent pass. Requires the app-admin `manage_apps` permission — run
18
+ * it from a trusted admin context, never the shipped game client.
19
+ * 2. **The game client plays** — {@link inventory}, {@link objects}, and
20
+ * {@link npcs} wrap the runtime calls (create/read containers, invoke the
21
+ * gated functions) assuming the blueprint conventions. Authorization is
22
+ * enforced server-side on every call.
23
+ *
24
+ * See the docs guides "Game API → Modeling game concepts" and
25
+ * "CrowdyJS → Game Kit".
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Studio setup (admin token):
30
+ * const kit = admin.kit(appId);
31
+ * await kit.deploy([
32
+ * inventoryBlueprint(),
33
+ * lockBlueprint({ objectTypeName: 'Door', authority: { kind: 'key' } }),
34
+ * ]);
35
+ *
36
+ * // Game client (player token):
37
+ * const kit = game.kit(appId);
38
+ * const bag = await kit.inventory.ensure(me.userId);
39
+ * const result = await kit.objects.open(doorId, { keyId });
40
+ * if (!result.success) showLockedMessage(result.errorMessage);
41
+ * ```
42
+ */
43
+ export class GameKitClient {
44
+ constructor(appId, gameModel, options = {}) {
45
+ this.appId = appId;
46
+ this.gameModel = gameModel;
47
+ this.inventory = new InventoryKit(appId, gameModel, options.inventory);
48
+ this.objects = new ObjectsKit(appId, gameModel, options.objects);
49
+ this.npcs = new NpcsKit(appId, gameModel, options.npcs);
50
+ }
51
+ /**
52
+ * Helpers for an additional lockable object type deployed under a different
53
+ * type name (e.g. both `Door` and `Chest` lock blueprints in one app).
54
+ */
55
+ objectsFor(objectTypeName, keyTypeName) {
56
+ return new ObjectsKit(this.appId, this.gameModel, {
57
+ objectTypeName,
58
+ keyTypeName,
59
+ });
60
+ }
61
+ /**
62
+ * **Studio (admin)** — load blueprints into the app: one transactional
63
+ * `gameModelSeed` for the definitions (and any seed containers/edges),
64
+ * followed by an `upsertAutomation` per automation and an
65
+ * `upsertAutomationTrigger` per event trigger. Idempotent: definitions
66
+ * upsert on their names, automations key on the automation name.
67
+ *
68
+ * Requires the app-admin `manage_apps` permission.
69
+ *
70
+ * @param blueprints - The blueprints to deploy. Duplicate type/function/
71
+ * automation names across blueprints throw before anything is sent.
72
+ * @param options - `sessionId` scopes any seed containers to a session.
73
+ * @returns A {@link KitDeployResult} with the seed counts, the upserted
74
+ * automations/triggers, and any non-fatal seed `warnings`.
75
+ * @throws {CrowdyGraphQLError} `FORBIDDEN` (`requiredPermission ===
76
+ * 'manage_apps'`) without app-admin, or `BAD_USER_INPUT` for definitions
77
+ * that fail to compile.
78
+ */
79
+ async deploy(blueprints, options = {}) {
80
+ const list = Array.isArray(blueprints) ? blueprints : [blueprints];
81
+ const merged = mergeBlueprints(this.appId, list, options);
82
+ const seed = await this.gameModel.seed(merged.seedInput);
83
+ const automations = [];
84
+ for (const automation of merged.automations) {
85
+ automations.push(await this.gameModel.upsertAutomation(automation));
86
+ }
87
+ const automationTriggers = [];
88
+ for (const trigger of merged.automationTriggers) {
89
+ automationTriggers.push(await this.gameModel.upsertAutomationTrigger(trigger));
90
+ }
91
+ return {
92
+ seed,
93
+ automations,
94
+ automationTriggers,
95
+ warnings: [...(seed.warnings ?? [])],
96
+ };
97
+ }
98
+ }
@@ -0,0 +1,182 @@
1
+ import type { GameModelAPI } from '../domains/gameModel.js';
2
+ import type { Scalars, SeedPropertyInput } from '../generated/graphql.js';
3
+ /** Options for {@link NpcsKit}. Must match the deployed NPC blueprint. */
4
+ export interface NpcsKitOptions {
5
+ /** The `typeName` the NPC blueprint was deployed with. Defaults to `'Npc'`. */
6
+ typeName?: string;
7
+ }
8
+ /** A parsed view of one live NPC. */
9
+ export interface KitNpc {
10
+ containerId: string;
11
+ displayName: string;
12
+ role: string;
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ behaviorState: string;
17
+ health: number;
18
+ /** All visible properties, including any extras your blueprint added. */
19
+ properties: Record<string, unknown>;
20
+ }
21
+ /**
22
+ * Runtime helpers for the {@link npcBlueprint} conventions: spawn NPC
23
+ * instances, read their server-driven state, and manage/monitor the
24
+ * automations behind them. Behaviors run in the API server — clients only
25
+ * re-read state (or listen for model-driven notifications) and render.
26
+ *
27
+ * Spawning and the automation management/monitoring calls are studio/admin
28
+ * operations (`manage_apps`); reads are player-safe.
29
+ *
30
+ * Obtained via `client.kit(appId).npcs`.
31
+ */
32
+ export declare class NpcsKit {
33
+ private readonly appId;
34
+ private readonly gameModel;
35
+ private readonly typeName;
36
+ constructor(appId: Scalars['BigInt']['input'], gameModel: GameModelAPI, options?: NpcsKitOptions);
37
+ /** Spawn a live NPC instance (admin — the type is admin-instantiable). */
38
+ spawn(input: {
39
+ displayName: string;
40
+ role?: string;
41
+ position?: {
42
+ x: number;
43
+ y: number;
44
+ z: number;
45
+ };
46
+ properties?: SeedPropertyInput[];
47
+ sessionId?: string;
48
+ }): Promise<{
49
+ __typename?: "GmContainer";
50
+ containerId: string;
51
+ appId: string;
52
+ sessionId: string | null;
53
+ typeName: string;
54
+ displayName: string;
55
+ description: string | null;
56
+ ownerUserId: string | null;
57
+ metadataJson: string;
58
+ }>;
59
+ /**
60
+ * List live NPCs with parsed state, optionally filtered by `role`. Fetches
61
+ * each NPC's visible properties in parallel — fine for the bounded NPC
62
+ * populations automations are designed around.
63
+ */
64
+ list(options?: {
65
+ role?: string;
66
+ sessionId?: string;
67
+ }): Promise<KitNpc[]>;
68
+ /** Read one NPC's current server-side state. */
69
+ state(npcId: string): Promise<KitNpc>;
70
+ /** Run one of the NPC automations immediately (admin; useful for testing). */
71
+ runNow(automationName: string): Promise<{
72
+ __typename?: "GmAutomationRun";
73
+ runId: string;
74
+ appId: string;
75
+ automationId: string;
76
+ automationName: string;
77
+ triggerSource: string;
78
+ parentRunId: string | null;
79
+ cascadeDepth: number;
80
+ startedAt: string;
81
+ finishedAt: string | null;
82
+ durationUs: number;
83
+ targets: number;
84
+ invocations: number;
85
+ mutations: number;
86
+ fnCalls: number;
87
+ gasUsed: number;
88
+ success: boolean;
89
+ errorMessage: string | null;
90
+ circuitAction: string | null;
91
+ computeUnits: number;
92
+ }>;
93
+ /**
94
+ * Pause or resume an NPC automation (admin). Re-enabling also resets a
95
+ * tripped failure circuit.
96
+ */
97
+ setEnabled(automationName: string, enabled: boolean): Promise<{
98
+ __typename?: "GmAutomation";
99
+ automationId: string;
100
+ appId: string;
101
+ name: string;
102
+ description: string | null;
103
+ enabled: boolean;
104
+ functionName: string;
105
+ targetMode: string;
106
+ selfContainerId: string | null;
107
+ targetTypeName: string | null;
108
+ sessionId: string | null;
109
+ paramsJson: string;
110
+ selectorJson: string | null;
111
+ runAsUserId: string | null;
112
+ triggerType: string;
113
+ scheduleKind: string | null;
114
+ intervalMs: number | null;
115
+ cronExpr: string | null;
116
+ maxTargets: number;
117
+ maxFnDepth: number | null;
118
+ gasLimit: number | null;
119
+ runTimeoutMs: number | null;
120
+ maxRunsPerMinute: number;
121
+ failureThreshold: number;
122
+ cooldownMs: number;
123
+ circuitState: string;
124
+ consecutiveFailures: number;
125
+ pausedUntil: string | null;
126
+ lastError: string | null;
127
+ lastRunAt: string | null;
128
+ nextRunAt: string | null;
129
+ }>;
130
+ /** Aggregate "what are my NPCs doing" stats over a recent window (admin). */
131
+ stats(windowMinutes?: number): Promise<{
132
+ __typename?: "GmAutomationStats";
133
+ windowMinutes: number;
134
+ totalRuns: number;
135
+ failedRuns: number;
136
+ failureRatePct: number;
137
+ runsPerMinute: number;
138
+ totalInvocations: number;
139
+ totalMutations: number;
140
+ totalComputeUnits: number;
141
+ avgDurationUs: number;
142
+ byAutomation: Array<{
143
+ __typename?: "GmAutomationStat";
144
+ automationName: string;
145
+ runs: number;
146
+ failures: number;
147
+ invocations: number;
148
+ computeUnits: number;
149
+ avgDurationUs: number;
150
+ circuitState: string;
151
+ }>;
152
+ }>;
153
+ /** Recent automation run history, newest first (admin). */
154
+ runs(options?: {
155
+ automationName?: string;
156
+ success?: boolean;
157
+ limit?: number;
158
+ }): Promise<{
159
+ __typename?: "GmAutomationRun";
160
+ runId: string;
161
+ appId: string;
162
+ automationId: string;
163
+ automationName: string;
164
+ triggerSource: string;
165
+ parentRunId: string | null;
166
+ cascadeDepth: number;
167
+ startedAt: string;
168
+ finishedAt: string | null;
169
+ durationUs: number;
170
+ targets: number;
171
+ invocations: number;
172
+ mutations: number;
173
+ fnCalls: number;
174
+ gasUsed: number;
175
+ success: boolean;
176
+ errorMessage: string | null;
177
+ circuitAction: string | null;
178
+ computeUnits: number;
179
+ }[]>;
180
+ private toNpc;
181
+ }
182
+ //# sourceMappingURL=npcs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npcs.d.ts","sourceRoot":"","sources":["../../src/kit/npcs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG1E,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qCAAqC;AACrC,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,OAAO;IAIhB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAJ5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAGf,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,SAAS,EAAE,YAAY,EACxC,OAAO,GAAE,cAAmB;IAK9B,0EAA0E;IACpE,KAAK,CAAC,KAAK,EAAE;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/C,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;;;;;;;;IAuBD;;;;OAIG;IACG,IAAI,CAAC,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAclF,gDAAgD;IAC1C,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ3C,8EAA8E;IACxE,MAAM,CAAC,cAAc,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;IAInC;;;OAGG;IACG,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAQzD,6EAA6E;IACvE,KAAK,CAAC,aAAa,CAAC,EAAE,MAAM;;;;;;;;;;;;sBA+B8lgc,CAAC;;;;;;;;;;IAxBjogc,2DAA2D;IACrD,IAAI,CAAC,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO;;;;;;;;;;;;;;;;;;;;;;YAIzE,KAAK;CAkBpB"}
@@ -0,0 +1,106 @@
1
+ import { kitContainerProperties } from './shared.js';
2
+ /**
3
+ * Runtime helpers for the {@link npcBlueprint} conventions: spawn NPC
4
+ * instances, read their server-driven state, and manage/monitor the
5
+ * automations behind them. Behaviors run in the API server — clients only
6
+ * re-read state (or listen for model-driven notifications) and render.
7
+ *
8
+ * Spawning and the automation management/monitoring calls are studio/admin
9
+ * operations (`manage_apps`); reads are player-safe.
10
+ *
11
+ * Obtained via `client.kit(appId).npcs`.
12
+ */
13
+ export class NpcsKit {
14
+ constructor(appId, gameModel, options = {}) {
15
+ this.appId = appId;
16
+ this.gameModel = gameModel;
17
+ this.typeName = options.typeName ?? 'Npc';
18
+ }
19
+ /** Spawn a live NPC instance (admin — the type is admin-instantiable). */
20
+ async spawn(input) {
21
+ const properties = [
22
+ ...(input.role !== undefined
23
+ ? [{ key: 'role', valueType: 'string', valueJson: JSON.stringify(input.role) }]
24
+ : []),
25
+ ...(input.position
26
+ ? [
27
+ { key: 'x', valueType: 'float', valueJson: String(input.position.x) },
28
+ { key: 'y', valueType: 'float', valueJson: String(input.position.y) },
29
+ { key: 'z', valueType: 'float', valueJson: String(input.position.z) },
30
+ ]
31
+ : []),
32
+ ...(input.properties ?? []),
33
+ ];
34
+ return this.gameModel.createContainer({
35
+ appId: this.appId,
36
+ typeName: this.typeName,
37
+ displayName: input.displayName,
38
+ ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}),
39
+ ...(properties.length ? { properties } : {}),
40
+ });
41
+ }
42
+ /**
43
+ * List live NPCs with parsed state, optionally filtered by `role`. Fetches
44
+ * each NPC's visible properties in parallel — fine for the bounded NPC
45
+ * populations automations are designed around.
46
+ */
47
+ async list(options = {}) {
48
+ const containers = await this.gameModel.containers({
49
+ appId: this.appId,
50
+ typeName: this.typeName,
51
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
52
+ });
53
+ const npcs = await Promise.all(containers.map((c) => this.toNpc(c.containerId, c.displayName)));
54
+ return options.role !== undefined
55
+ ? npcs.filter((n) => n.role === options.role)
56
+ : npcs;
57
+ }
58
+ /** Read one NPC's current server-side state. */
59
+ async state(npcId) {
60
+ const container = await this.gameModel.container({
61
+ appId: this.appId,
62
+ containerId: npcId,
63
+ });
64
+ return this.toNpc(container.containerId, container.displayName);
65
+ }
66
+ /** Run one of the NPC automations immediately (admin; useful for testing). */
67
+ async runNow(automationName) {
68
+ return this.gameModel.runAutomation({ appId: this.appId, name: automationName });
69
+ }
70
+ /**
71
+ * Pause or resume an NPC automation (admin). Re-enabling also resets a
72
+ * tripped failure circuit.
73
+ */
74
+ async setEnabled(automationName, enabled) {
75
+ return this.gameModel.setAutomationEnabled({
76
+ appId: this.appId,
77
+ name: automationName,
78
+ enabled,
79
+ });
80
+ }
81
+ /** Aggregate "what are my NPCs doing" stats over a recent window (admin). */
82
+ async stats(windowMinutes) {
83
+ return this.gameModel.automationStats({
84
+ appId: this.appId,
85
+ ...(windowMinutes !== undefined ? { windowMinutes } : {}),
86
+ });
87
+ }
88
+ /** Recent automation run history, newest first (admin). */
89
+ async runs(options = {}) {
90
+ return this.gameModel.automationRuns({ appId: this.appId, ...options });
91
+ }
92
+ async toNpc(containerId, displayName) {
93
+ const props = await kitContainerProperties(this.gameModel, String(this.appId), containerId);
94
+ return {
95
+ containerId,
96
+ displayName,
97
+ role: String(props.role ?? ''),
98
+ x: Number(props.x ?? 0),
99
+ y: Number(props.y ?? 0),
100
+ z: Number(props.z ?? 0),
101
+ behaviorState: String(props.behavior_state ?? ''),
102
+ health: Number(props.health ?? 0),
103
+ properties: props,
104
+ };
105
+ }
106
+ }