@takaro/module-test 0.0.0-dev.2f94734
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/README.md +260 -0
- package/dist/.source-hash +1 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/currencyLedger.d.ts +13 -0
- package/dist/currencyLedger.d.ts.map +1 -0
- package/dist/currencyLedger.js +39 -0
- package/dist/currencyLedger.js.map +1 -0
- package/dist/fakeClient.d.ts +48 -0
- package/dist/fakeClient.d.ts.map +1 -0
- package/dist/fakeClient.js +335 -0
- package/dist/fakeClient.js.map +1 -0
- package/dist/fixtureQuery.d.ts +33 -0
- package/dist/fixtureQuery.d.ts.map +1 -0
- package/dist/fixtureQuery.js +120 -0
- package/dist/fixtureQuery.js.map +1 -0
- package/dist/fixtures.d.ts +49 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +152 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/installation.d.ts +23 -0
- package/dist/installation.d.ts.map +1 -0
- package/dist/installation.js +170 -0
- package/dist/installation.js.map +1 -0
- package/dist/intrinsics.d.ts +9 -0
- package/dist/intrinsics.d.ts.map +1 -0
- package/dist/intrinsics.js +33 -0
- package/dist/intrinsics.js.map +1 -0
- package/dist/loadModule.d.ts +11 -0
- package/dist/loadModule.d.ts.map +1 -0
- package/dist/loadModule.js +173 -0
- package/dist/loadModule.js.map +1 -0
- package/dist/main.d.ts +22 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +12 -0
- package/dist/main.js.map +1 -0
- package/dist/moduleExport.d.ts +74 -0
- package/dist/moduleExport.d.ts.map +1 -0
- package/dist/moduleExport.js +2 -0
- package/dist/moduleExport.js.map +1 -0
- package/dist/moduleTest.d.ts +100 -0
- package/dist/moduleTest.d.ts.map +1 -0
- package/dist/moduleTest.js +195 -0
- package/dist/moduleTest.js.map +1 -0
- package/dist/query.d.ts +8 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +51 -0
- package/dist/query.js.map +1 -0
- package/dist/stubbedClient.d.ts +20 -0
- package/dist/stubbedClient.d.ts.map +1 -0
- package/dist/stubbedClient.js +44 -0
- package/dist/stubbedClient.js.map +1 -0
- package/dist/systemConfig.d.ts +19 -0
- package/dist/systemConfig.d.ts.map +1 -0
- package/dist/systemConfig.js +199 -0
- package/dist/systemConfig.js.map +1 -0
- package/dist/triggers.d.ts +53 -0
- package/dist/triggers.d.ts.map +1 -0
- package/dist/triggers.js +116 -0
- package/dist/triggers.js.map +1 -0
- package/dist/variableStore.d.ts +51 -0
- package/dist/variableStore.d.ts.map +1 -0
- package/dist/variableStore.js +201 -0
- package/dist/variableStore.js.map +1 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# @takaro/module-test
|
|
2
|
+
|
|
3
|
+
Unit-test your Takaro module's logic without a Takaro server. The harness runs your command, hook,
|
|
4
|
+
cron job and shop action code in the real module VM (`executeVM` from `@takaro/helpers`), against a
|
|
5
|
+
typed in-memory fake of the `@takaro/apiclient` `Client`. No database, no queue, no game server and
|
|
6
|
+
no HTTP.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm i -D @takaro/module-test
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Pick the version that matches the Takaro version you target. The package is released in lockstep
|
|
15
|
+
with Takaro and depends on the exact `@takaro/apiclient` and `@takaro/helpers` versions it fakes.
|
|
16
|
+
|
|
17
|
+
It needs Node 24 or newer.
|
|
18
|
+
|
|
19
|
+
## A minimal standalone repo
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
my-module/
|
|
23
|
+
package.json
|
|
24
|
+
module.json
|
|
25
|
+
src/commands/greet.js
|
|
26
|
+
test/greet.test.ts
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`package.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"name": "my-module",
|
|
34
|
+
"private": true,
|
|
35
|
+
"type": "module",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --experimental-vm-modules --test 'test/**/*.test.ts'"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@takaro/module-test": "<the version you target>"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`module.json` describes the module and points each component at a JavaScript file:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"name": "my-module",
|
|
50
|
+
"version": "1.0.0",
|
|
51
|
+
"description": "Greets players",
|
|
52
|
+
"config": {
|
|
53
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
54
|
+
"type": "object",
|
|
55
|
+
"properties": {
|
|
56
|
+
"greeting": { "type": "string", "default": "Hello" }
|
|
57
|
+
},
|
|
58
|
+
"required": [],
|
|
59
|
+
"additionalProperties": false
|
|
60
|
+
},
|
|
61
|
+
"commands": {
|
|
62
|
+
"greet": {
|
|
63
|
+
"function": "src/commands/greet.js",
|
|
64
|
+
"helpText": "Greet a player",
|
|
65
|
+
"arguments": [{ "name": "target", "type": "string", "defaultValue": "world", "position": 0 }]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`src/commands/greet.js`:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
import { takaro, data } from '@takaro/helpers';
|
|
75
|
+
|
|
76
|
+
const { greeting } = data.module.userConfig;
|
|
77
|
+
const { target } = data.arguments;
|
|
78
|
+
|
|
79
|
+
await data.player.pm(`${greeting}, ${target}!`);
|
|
80
|
+
await takaro.variable.variableControllerCreate({ key: 'lastGreeted', value: target, playerId: data.player.id });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## A complete test
|
|
84
|
+
|
|
85
|
+
`test/greet.test.ts`:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import assert from 'node:assert/strict';
|
|
89
|
+
import { describe } from 'node:test';
|
|
90
|
+
import { createModuleTest } from '@takaro/module-test';
|
|
91
|
+
|
|
92
|
+
describe('greet', () => {
|
|
93
|
+
createModuleTest({
|
|
94
|
+
name: 'greets the default target',
|
|
95
|
+
module: new URL('..', import.meta.url),
|
|
96
|
+
test: async function () {
|
|
97
|
+
const result = await this.runCommand('greet');
|
|
98
|
+
|
|
99
|
+
assert.equal(result.success, true, JSON.stringify(result.logs));
|
|
100
|
+
assert.deepEqual(this.pms(), ['Hello, world!']);
|
|
101
|
+
assert.deepEqual(
|
|
102
|
+
this.variables.all.map((row) => [row.key, row.value]),
|
|
103
|
+
[['lastGreeted', 'world']],
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
}).run();
|
|
107
|
+
|
|
108
|
+
createModuleTest({
|
|
109
|
+
name: 'greets a named target with a custom greeting',
|
|
110
|
+
module: new URL('..', import.meta.url),
|
|
111
|
+
userConfig: { greeting: 'Howdy' },
|
|
112
|
+
test: async function () {
|
|
113
|
+
await this.runCommand('greet', { target: 'partner' });
|
|
114
|
+
|
|
115
|
+
assert.deepEqual(this.pms(), ['Howdy, partner!']);
|
|
116
|
+
},
|
|
117
|
+
}).run();
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Run it:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
npm test
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**`--experimental-vm-modules` is required.** The module VM is built on `vm.SourceTextModule`, which
|
|
128
|
+
Node only exposes behind that flag. Without it the test fails with a message saying so. Node 24
|
|
129
|
+
strips TypeScript types natively; a `.js` test works the same way.
|
|
130
|
+
|
|
131
|
+
`module` takes one of:
|
|
132
|
+
|
|
133
|
+
- a **directory** in the registry source layout: a path (relative paths resolve against
|
|
134
|
+
`process.cwd()`) or a `file:` URL. `new URL('..', import.meta.url)` is the robust choice.
|
|
135
|
+
`loadModuleDirectory(dir)` is exported if you want the loaded object itself.
|
|
136
|
+
- a **module export object**: Takaro's module export format, the same shape as a registry's
|
|
137
|
+
`<module>/<version>.json`. Parse a published version file or an export downloaded from Takaro and
|
|
138
|
+
pass it in.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { readFileSync } from 'node:fs';
|
|
142
|
+
import { createModuleTest } from '@takaro/module-test';
|
|
143
|
+
|
|
144
|
+
const exported = JSON.parse(readFileSync('dist/my-module/1.0.0.json', 'utf8'));
|
|
145
|
+
createModuleTest({ name: 'from an export', module: exported, test: async function () {} }).run();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
A directory yields one version. An export object may carry several: the last is installed unless
|
|
149
|
+
`versionTag` picks another.
|
|
150
|
+
|
|
151
|
+
## Using another test runner
|
|
152
|
+
|
|
153
|
+
`run()` registers the test with `node:test`. For another runner, register it yourself:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { it } from 'vitest';
|
|
157
|
+
|
|
158
|
+
const t = createModuleTest({ name: 'greets', module: new URL('..', import.meta.url), test: async function () {} });
|
|
159
|
+
it(t.spec.name, t.testOptions(), () => t.runBody());
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`testOptions()` is `{ timeout?, skip? }` derived from the spec; `runBody()` builds the fixtures, runs
|
|
163
|
+
`setup`, then `test`. The runner must still be started with `--experimental-vm-modules`.
|
|
164
|
+
|
|
165
|
+
## Spec options
|
|
166
|
+
|
|
167
|
+
`name`, `module` and `test` are required.
|
|
168
|
+
|
|
169
|
+
| Option | Type | What it does |
|
|
170
|
+
| --------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
171
|
+
| `name` | `string` | The test name. |
|
|
172
|
+
| `module` | `string \| URL \| ModuleExport` | The module under test: a directory or an export object. |
|
|
173
|
+
| `versionTag` | `string` | Install this version instead of the last one. |
|
|
174
|
+
| `userConfig` | `Record<string, unknown>` | Overrides on the module's own config schema. Unset fields get **real** ajv defaults, and the result is validated. |
|
|
175
|
+
| `systemConfig` | `{ enabled?, commands?: { [name]: { enabled?, cost?, ... } } }` | Overrides on the derived system config schema (commands, hooks, cron jobs, shop actions). Unset fields get Takaro's own ajv defaults. |
|
|
176
|
+
| `alsoInstalled` | `Array<{ module, userConfig?, systemConfig? }>` | Further modules installed on the same game server, for module code that enumerates installations. |
|
|
177
|
+
| `permissions` | `'ROOT'` \| `Array<{ permission, count?, module? }>` | The acting player's permissions. Defaults to `'ROOT'`; read the `checkPermission` note below. A grant belongs to the module under test unless `module` names another installed module; only that module's checks see it. |
|
|
178
|
+
| `player` | `PlayerSpec` | The rest of the acting player's fixture: `name`, `gameId`, `playerId`, `ping`, `ip`, `online`, `position`, `steamId` and the other platform ids. |
|
|
179
|
+
| `user` | `{ userId? }` | Links the acting player to a Takaro user, putting `user` on command payloads. **Omitted means unlinked**, which is Takaro's default. Pass `{}` to link. |
|
|
180
|
+
| `otherPlayers` | `PlayerSpec[]` | Further players on the same game server, reachable as `this.otherPlayers`. |
|
|
181
|
+
| `settings` | `Record<string, string>` | Domain settings. `commandPrefix`, `serverChatName` and `currencyName` have defaults; anything else throws `MissingFixtureError` until you set it. |
|
|
182
|
+
| `variables` | `VariableSeed[]` | Rows seeded into the variable store: `{ key, value, expiresAt?, gameServerId?, playerId?, moduleId? }`. An omitted scope column is NULL. |
|
|
183
|
+
| `currency` | `number` | The acting player's starting balance. |
|
|
184
|
+
| `now` | `Date` | Freezes the VM's clock, and the store's TTL comparisons with it. |
|
|
185
|
+
| `random` | `number \| number[]` | Scripts `Math.random()` inside the VM. An array is consumed in order; the last value repeats once exhausted. |
|
|
186
|
+
| `timeout` | `number` | Passed to the test runner. |
|
|
187
|
+
| `quarantine` | `string` | Skip the test with this reason. |
|
|
188
|
+
| `setup` | `(this: ModuleTest) => void \| Promise<void>` | Runs after the fixtures are built and before `test`. Stub extra client methods here. |
|
|
189
|
+
| `test` | `(this: ModuleTest) => Promise<void>` | The test body. |
|
|
190
|
+
|
|
191
|
+
## Inside `setup` and `test`
|
|
192
|
+
|
|
193
|
+
`this` is the `ModuleTest`:
|
|
194
|
+
|
|
195
|
+
| Member | What it gives you |
|
|
196
|
+
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| `runCommand(name, args?, opts?)` | Executes a command's function and returns a `VMExecutionResult`. `opts.chatMessage` sets the raw chat line. |
|
|
198
|
+
| `runHook(name, opts?)` / `runCronJob(name)` / `runShopAction(name, opts)` | The other three trigger kinds. |
|
|
199
|
+
| `pms()` | `string[]`: messages sent privately to the acting player, in order. |
|
|
200
|
+
| `messages` | Every `gameServerControllerSendMessage` call, private or broadcast. |
|
|
201
|
+
| `client.pmsTo(gameId)` | `pms()` for any other player; pass `this.otherPlayers[n].pog.gameId`. |
|
|
202
|
+
| `variables` | The in-memory variable store. `variables.all` is every row, for assertions. |
|
|
203
|
+
| `currency` | The in-memory ledger. `currency.balanceOf(playerId)`. |
|
|
204
|
+
| `client.stubs` | The sinon stubs, typed against the generated `Client`, keyed by controller then method. |
|
|
205
|
+
| `userError(result)` | The `TakaroUserError` message a failed run produced. |
|
|
206
|
+
| `player` / `pog` | The acting player's fixtures. |
|
|
207
|
+
| `user` | The linked Takaro user fixture, or `undefined` when the spec declared no `user`. |
|
|
208
|
+
| `otherPlayers` | `Array<{ player, pog }>` for the `otherPlayers` specs, in order. |
|
|
209
|
+
| `gameServerId` / `moduleId` / `installation` | The ids module code saw. |
|
|
210
|
+
|
|
211
|
+
## Things that will catch you out
|
|
212
|
+
|
|
213
|
+
- **The VM has its own `Date` and `Math`.** Host fake timers and stubs on `Math.random` do not reach
|
|
214
|
+
module code. Pass `now:` and `random:` on the test instead.
|
|
215
|
+
- **`--experimental-vm-modules` is required**, see above.
|
|
216
|
+
- **Type-checking with `tsc` needs `skipLibCheck: true`.** The typings of `sinon`, which the harness
|
|
217
|
+
depends on, do not check cleanly on their own. Tests run under Node's type stripping need nothing.
|
|
218
|
+
- **`data` is a JSON snapshot.** Each payload goes through a JSON round-trip, as it does in Takaro,
|
|
219
|
+
so `data.pog.currency` is the balance at trigger time and a `Date` arrives as a string. Assert on
|
|
220
|
+
`this.currency`, `this.variables` or `this.pog` for the state after the run.
|
|
221
|
+
- **User errors come through `userError(result)`.** `executeVM` returns `{ success: false, logs }`;
|
|
222
|
+
delivering a `TakaroUserError` to the player happens outside the VM and is not covered.
|
|
223
|
+
- **Command cost is not modelled.** Takaro charges it around the VM call, so module code never sees it.
|
|
224
|
+
- **Config defaults are real.** `userConfig` and `systemConfig` are filled and validated with ajv. A
|
|
225
|
+
config Takaro would reject throws `InvalidUserConfigError` or `InvalidSystemConfigError`, and a
|
|
226
|
+
module whose schema has a required field with no default cannot be installed without it. A
|
|
227
|
+
`discord-message` hook requires `discordChannelId` in `systemConfig.hooks.<name>`.
|
|
228
|
+
- **`checkPermission` returns the boolean `true` for ROOT**, so `.count` is `undefined`. ROOT is the
|
|
229
|
+
default, which makes a count-limited test unable to fail; use
|
|
230
|
+
`permissions: [{ permission: 'MY_PERMISSION', count: 3 }]` when the count is the subject.
|
|
231
|
+
- **Arguments get defaults, not parsing.** The harness applies `defaultValue` from your command's
|
|
232
|
+
arguments. Tokenising a chat line belongs to Takaro; pass already-tokenised values.
|
|
233
|
+
- **The fake refuses what it cannot answer faithfully.** `UnstubbedMethodError` means module code
|
|
234
|
+
called an API method the fake does not cover. `MissingFixtureError` means a covered method has
|
|
235
|
+
nothing in this test to answer with; its message names the spec option to set.
|
|
236
|
+
`UnsupportedQueryError` means a query used a filter, sort or option the fake cannot honour.
|
|
237
|
+
Stub the method yourself instead.
|
|
238
|
+
|
|
239
|
+
### Stubbing a method the fake refuses
|
|
240
|
+
|
|
241
|
+
A stub replaces the whole response, so it must produce the axios envelope module code reads.
|
|
242
|
+
`ok`, `single` and `paginated` build it. Every method on `client.stubs` is optional, so `?.` is
|
|
243
|
+
required:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { createModuleTest, paginated } from '@takaro/module-test';
|
|
247
|
+
|
|
248
|
+
createModuleTest({
|
|
249
|
+
name: 'counts kills',
|
|
250
|
+
module: new URL('..', import.meta.url),
|
|
251
|
+
setup: function () {
|
|
252
|
+
this.client.stubs.event.eventControllerSearch?.callsFake(async () =>
|
|
253
|
+
paginated([{ id: 'e1', eventName: 'entity-killed', meta: { entity: 'zombie' } }]),
|
|
254
|
+
);
|
|
255
|
+
},
|
|
256
|
+
test: async function () {
|
|
257
|
+
// ...
|
|
258
|
+
},
|
|
259
|
+
}).run();
|
|
260
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
53e2b3f37588409c5957f389ab24219229d29d833d7c6d680b0752f858c1f566
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ModuleExportVersion } from './moduleExport.js';
|
|
2
|
+
export declare class InvalidUserConfigError extends Error {
|
|
3
|
+
constructor(moduleVersion: string, errors: string);
|
|
4
|
+
}
|
|
5
|
+
/** @internal Exported for tests only. */
|
|
6
|
+
export declare function resolveUserConfig(version: ModuleExportVersion, overrides?: Record<string, unknown>): Record<string, unknown>;
|
|
7
|
+
export interface SystemConfigOverrides {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
commands?: Record<string, Partial<CommandSystemConfig>>;
|
|
10
|
+
hooks?: Record<string, Partial<HookSystemConfig>>;
|
|
11
|
+
cronJobs?: Record<string, Partial<CronJobSystemConfig>>;
|
|
12
|
+
shopActions?: Record<string, Partial<ShopActionSystemConfig>>;
|
|
13
|
+
}
|
|
14
|
+
export interface CommandSystemConfig {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
delay: number;
|
|
17
|
+
announceDelay: boolean;
|
|
18
|
+
cooldown: number;
|
|
19
|
+
cost: number;
|
|
20
|
+
aliases?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface HookSystemConfig {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
delay: number;
|
|
25
|
+
cooldown: number;
|
|
26
|
+
cooldownType: 'player' | 'server' | 'global';
|
|
27
|
+
discordChannelId?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CronJobSystemConfig {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
temporalValue: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ShopActionSystemConfig {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
triggerOn: 'orderCreated' | 'orderClaimed' | 'both';
|
|
36
|
+
}
|
|
37
|
+
export interface SystemConfig {
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
commands?: Record<string, CommandSystemConfig>;
|
|
40
|
+
hooks?: Record<string, HookSystemConfig>;
|
|
41
|
+
cronJobs?: Record<string, CronJobSystemConfig>;
|
|
42
|
+
shopActions?: Record<string, ShopActionSystemConfig>;
|
|
43
|
+
}
|
|
44
|
+
export declare class InvalidSystemConfigError extends Error {
|
|
45
|
+
constructor(moduleVersion: string, errors: string);
|
|
46
|
+
}
|
|
47
|
+
/** @internal Exported for tests only. */
|
|
48
|
+
export declare function resolveSystemConfig(version: ModuleExportVersion, overrides?: SystemConfigOverrides): SystemConfig;
|
|
49
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAS7D,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAMhD;CACF;AAED,yCAAyC;AACzC,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,mBAAmB,EAC5B,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACtC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAG7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,cAAc,GAAG,cAAc,GAAG,MAAM,CAAC;CACrD;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACtD;AAGD,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,YAAY,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAMhD;CACF;AAID,yCAAyC;AACzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,EAAE,SAAS,GAAE,qBAA0B,GAAG,YAAY,CAcrH"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import { getSystemConfigSchema } from './systemConfig.js';
|
|
3
|
+
// Same ajv config `ModuleService.installModule` compiles with, so a default asserted here is a
|
|
4
|
+
// default a real install would carry.
|
|
5
|
+
const ajv = new Ajv.default({ useDefaults: true, strict: false, allErrors: true });
|
|
6
|
+
// A `userConfig` Takaro would have refused to install — guards against a rejected override
|
|
7
|
+
// (e.g. a misspelled field) silently passing, so its author believes it applied.
|
|
8
|
+
export class InvalidUserConfigError extends Error {
|
|
9
|
+
constructor(moduleVersion, errors) {
|
|
10
|
+
super(`userConfig is invalid against the module's own configSchema (version ${moduleVersion}): ${errors}. ` +
|
|
11
|
+
'Takaro validates the same schema with the same library, so this installation could not exist.');
|
|
12
|
+
this.name = 'InvalidUserConfigError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** @internal Exported for tests only. */
|
|
16
|
+
export function resolveUserConfig(version, overrides = {}) {
|
|
17
|
+
// JSON, not structuredClone: the real override crosses a JSON boundary, which drops `undefined`.
|
|
18
|
+
const config = JSON.parse(JSON.stringify(overrides));
|
|
19
|
+
// ajv fills defaults (mutating) before it checks `required`, so this call completes and judges.
|
|
20
|
+
const validate = ajv.compile(JSON.parse(version.configSchema));
|
|
21
|
+
if (!validate(config)) {
|
|
22
|
+
// `params` (not just `errorsText`) carries the offending property name for `additionalProperties`.
|
|
23
|
+
const errors = (validate.errors ?? [])
|
|
24
|
+
.map((error) => `${error.instancePath || 'userConfig'} ${error.message} ${JSON.stringify(error.params)}`)
|
|
25
|
+
.join('; ');
|
|
26
|
+
throw new InvalidUserConfigError(version.tag, errors);
|
|
27
|
+
}
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
// The systemConfig counterpart of `InvalidUserConfigError`, same reason.
|
|
31
|
+
export class InvalidSystemConfigError extends Error {
|
|
32
|
+
constructor(moduleVersion, errors) {
|
|
33
|
+
super(`systemConfig is invalid against the module's derived systemConfigSchema (version ${moduleVersion}): ${errors}. ` +
|
|
34
|
+
'Takaro validates the same schema with the same library, so this installation could not exist.');
|
|
35
|
+
this.name = 'InvalidSystemConfigError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Reproduces `ModuleService.installModule`. The shallow merge over `defaultSystemConfig` is
|
|
39
|
+
// production's: a `commands` override replaces that block wholesale, not merges into it.
|
|
40
|
+
/** @internal Exported for tests only. */
|
|
41
|
+
export function resolveSystemConfig(version, overrides = {}) {
|
|
42
|
+
const declaredDefaults = JSON.parse(version.defaultSystemConfig ?? '{}');
|
|
43
|
+
// Same JSON round-trip as `resolveUserConfig`, same reason.
|
|
44
|
+
const config = { ...declaredDefaults, ...JSON.parse(JSON.stringify(overrides)) };
|
|
45
|
+
const validate = ajv.compile(JSON.parse(getSystemConfigSchema(version)));
|
|
46
|
+
if (!validate(config)) {
|
|
47
|
+
const errors = (validate.errors ?? [])
|
|
48
|
+
.map((error) => `${error.instancePath || 'systemConfig'} ${error.message} ${JSON.stringify(error.params)}`)
|
|
49
|
+
.join('; ');
|
|
50
|
+
throw new InvalidSystemConfigError(version.tag, errors);
|
|
51
|
+
}
|
|
52
|
+
return config;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class InsufficientFundsError extends Error {
|
|
2
|
+
readonly status = 400;
|
|
3
|
+
constructor(playerId: string, requested: number, balance: number);
|
|
4
|
+
}
|
|
5
|
+
export declare class CurrencyLedger {
|
|
6
|
+
private balances;
|
|
7
|
+
constructor(seed?: Record<string, number>);
|
|
8
|
+
balanceOf(playerId: string): number;
|
|
9
|
+
add(playerId: string, amount: number): number;
|
|
10
|
+
deduct(playerId: string, amount: number): number;
|
|
11
|
+
transact(senderId: string, receiverId: string, amount: number): void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=currencyLedger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currencyLedger.d.ts","sourceRoot":"","sources":["../src/currencyLedger.ts"],"names":[],"mappings":"AAEA,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,MAAM,OAAO;IACtB,YAAY,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAG/D;CACF;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAA6B;IAE7C,YAAY,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAI5C;IAED,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElC;IAED,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI5C;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ/C;IAED,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAGnE;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Deliberately does NOT model command cost: that's deducted around the VM call in
|
|
2
|
+
// `executeFunction.ts`, and `lottery/commands/buyTicket.js` already compensates for it.
|
|
3
|
+
export class InsufficientFundsError extends Error {
|
|
4
|
+
constructor(playerId, requested, balance) {
|
|
5
|
+
super(`Request failed with status code 400 (player ${playerId} has ${balance}, needs ${requested})`);
|
|
6
|
+
this.status = 400;
|
|
7
|
+
this.name = 'InsufficientFundsError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class CurrencyLedger {
|
|
11
|
+
constructor(seed = {}) {
|
|
12
|
+
this.balances = new Map();
|
|
13
|
+
for (const [playerId, amount] of Object.entries(seed)) {
|
|
14
|
+
this.balances.set(playerId, amount);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
balanceOf(playerId) {
|
|
18
|
+
return this.balances.get(playerId) ?? 0;
|
|
19
|
+
}
|
|
20
|
+
add(playerId, amount) {
|
|
21
|
+
const next = this.balanceOf(playerId) + amount;
|
|
22
|
+
this.balances.set(playerId, next);
|
|
23
|
+
return next;
|
|
24
|
+
}
|
|
25
|
+
deduct(playerId, amount) {
|
|
26
|
+
const balance = this.balanceOf(playerId);
|
|
27
|
+
if (balance < amount) {
|
|
28
|
+
throw new InsufficientFundsError(playerId, amount, balance);
|
|
29
|
+
}
|
|
30
|
+
const next = balance - amount;
|
|
31
|
+
this.balances.set(playerId, next);
|
|
32
|
+
return next;
|
|
33
|
+
}
|
|
34
|
+
transact(senderId, receiverId, amount) {
|
|
35
|
+
this.deduct(senderId, amount);
|
|
36
|
+
this.add(receiverId, amount);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=currencyLedger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currencyLedger.js","sourceRoot":"","sources":["../src/currencyLedger.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,wFAAwF;AACxF,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAE/C,YAAY,QAAgB,EAAE,SAAiB,EAAE,OAAe;QAC9D,KAAK,CAAC,+CAA+C,QAAQ,QAAQ,OAAO,WAAW,SAAS,GAAG,CAAC,CAAC;QAF9F,WAAM,GAAG,GAAG,CAAC;QAGpB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,cAAc;IAGzB,YAAY,IAAI,GAA2B,EAAE;QAFrC,aAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;QAG3C,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,QAAgB;QACxB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED,GAAG,CAAC,QAAgB,EAAE,MAAc;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAC,QAAgB,EAAE,MAAc;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,OAAO,GAAG,MAAM,EAAE,CAAC;YACrB,MAAM,IAAI,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,GAAG,MAAM,CAAC;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,CAAC,QAAgB,EAAE,UAAkB,EAAE,MAAc;QAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AxiosResponse, Client, ModuleInstallationOutputDTO, PlayerOnGameserverOutputWithRolesDTO, PlayerOutputWithRolesDTO } from '@takaro/apiclient';
|
|
2
|
+
import { CurrencyLedger } from './currencyLedger.js';
|
|
3
|
+
import { VariableStore } from './variableStore.js';
|
|
4
|
+
import type { ClientStubs } from './stubbedClient.js';
|
|
5
|
+
export declare class MissingFixtureError extends Error {
|
|
6
|
+
constructor(method: string, hint: string);
|
|
7
|
+
}
|
|
8
|
+
export { UnsupportedQueryError } from './query.js';
|
|
9
|
+
export interface RecordedMessage {
|
|
10
|
+
gameServerId: string;
|
|
11
|
+
message: string;
|
|
12
|
+
recipientGameId?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface FakeClientOptions {
|
|
15
|
+
gameServerId: string;
|
|
16
|
+
installations: ModuleInstallationOutputDTO[];
|
|
17
|
+
players: PlayerOutputWithRolesDTO[];
|
|
18
|
+
pogs: PlayerOnGameserverOutputWithRolesDTO[];
|
|
19
|
+
variables?: VariableStore;
|
|
20
|
+
currency?: CurrencyLedger;
|
|
21
|
+
settings?: Record<string, string>;
|
|
22
|
+
/** Read for TTL expiry and for `expiresAt` comparisons, so it tracks the VM's clock. */
|
|
23
|
+
now?: () => Date;
|
|
24
|
+
}
|
|
25
|
+
export declare function ok<T>(data: T): AxiosResponse<T>;
|
|
26
|
+
export declare function single(data: unknown): AxiosResponse<never, any>;
|
|
27
|
+
export interface PageMeta {
|
|
28
|
+
total?: number;
|
|
29
|
+
page?: number;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function paginated(results: unknown[], meta?: PageMeta): AxiosResponse<never, any>;
|
|
33
|
+
export interface FakeClient {
|
|
34
|
+
/** Hand this to `executeVM({ clientOverride })`. */
|
|
35
|
+
asClient: Client;
|
|
36
|
+
stubs: ClientStubs;
|
|
37
|
+
variables: VariableStore;
|
|
38
|
+
currency: CurrencyLedger;
|
|
39
|
+
settings: Map<string, string>;
|
|
40
|
+
installations: ModuleInstallationOutputDTO[];
|
|
41
|
+
/** Every `gameServerControllerSendMessage` call, in order. */
|
|
42
|
+
messages: RecordedMessage[];
|
|
43
|
+
/** Just the messages addressed to one player — what `pm()` produces. */
|
|
44
|
+
pmsTo(gameId: string): string[];
|
|
45
|
+
}
|
|
46
|
+
/** @internal Exported for tests only. */
|
|
47
|
+
export declare function createFakeClient(options: FakeClientOptions): FakeClient;
|
|
48
|
+
//# sourceMappingURL=fakeClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fakeClient.d.ts","sourceRoot":"","sources":["../src/fakeClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,MAAM,EACN,2BAA2B,EAC3B,oCAAoC,EACpC,wBAAwB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAWvE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAGvC;CACF;AAID,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,2BAA2B,EAAE,CAAC;IAC7C,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,IAAI,EAAE,oCAAoC,EAAE,CAAC;IAC7C,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,wFAAwF;IACxF,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AASD,wBAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAQ/C;AAQD,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,6BAEnC;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,GAAE,QAAa,6BAUhE;AAwBD,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,aAAa,CAAC;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,aAAa,EAAE,2BAA2B,EAAE,CAAC;IAC7C,8DAA8D;IAC9D,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,wEAAwE;IACxE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACjC;AAED,yCAAyC;AACzC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA8UvE"}
|