@voltro/plugin-inventory 0.0.0-stage → 0.87.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/CHANGELOG.md +24329 -0
- package/LICENSE +61 -0
- package/README.md +441 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +7737 -0
- package/dist/capture.js +53 -0
- package/dist/catalog.d.ts +63 -0
- package/dist/catalog.js +5 -0
- package/dist/cmsCatalog.d.ts +95 -0
- package/dist/cmsCatalog.js +80 -0
- package/dist/components.d.ts +247 -0
- package/dist/components.js +321 -0
- package/dist/crafting.d.ts +211 -0
- package/dist/crafting.js +193 -0
- package/dist/declarationFixtures.js +66 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +22 -0
- package/dist/index.d.ts +576 -0
- package/dist/index.js +8 -0
- package/dist/inventoryPort.fixture.js +66 -0
- package/dist/inventoryPresentation.js +109 -0
- package/dist/inventoryStore.js +1576 -0
- package/dist/inventoryUiCopy.js +63 -0
- package/dist/item.d.ts +73 -0
- package/dist/item.js +105 -0
- package/dist/plugin.js +407 -0
- package/dist/port.js +5 -0
- package/dist/reservation.js +85 -0
- package/dist/rpc.d.ts +145 -0
- package/dist/rpc.js +135 -0
- package/dist/server.d.ts +816 -0
- package/dist/server.js +4 -0
- package/dist/sqlCatalog.js +185 -0
- package/dist/strategies.d.ts +64 -0
- package/dist/strategies.js +47 -0
- package/dist/systemFixtures.js +172 -0
- package/dist/tables.entity.js +164 -0
- package/dist/testing.d.ts +448 -0
- package/dist/testing.js +4 -0
- package/dist/web.d.ts +60 -0
- package/dist/web.js +7 -0
- package/package.json +104 -4
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
import { DataStore } from '@voltro/database';
|
|
3
|
+
import { Effect } from 'effect';
|
|
4
|
+
import { PluginPrivacyTarget } from '@voltro/protocol';
|
|
5
|
+
import { PreparedSqlMutation } from '@voltro/database/sql';
|
|
6
|
+
import { Schema } from 'effect';
|
|
7
|
+
import { Subject } from '@voltro/protocol';
|
|
8
|
+
import { VoltroPlugin } from '@voltro/protocol';
|
|
9
|
+
import { YieldableError } from 'effect/Cause';
|
|
10
|
+
|
|
11
|
+
/** Admit actual child receipt or retained craft evidence without rerunning a wallet callback. */
|
|
12
|
+
export declare const captureCraftPayment: (input: unknown, recipe: Recipe, batches: number) => CraftPaymentReceipt | null;
|
|
13
|
+
|
|
14
|
+
/** Admit retained intent without calling historical custom policies. */
|
|
15
|
+
export declare const captureCraftPlan: (original: unknown, input: CraftInput) => CraftPlan;
|
|
16
|
+
|
|
17
|
+
declare interface CatalogImport extends CatalogScope {
|
|
18
|
+
readonly revision: string;
|
|
19
|
+
readonly items: readonly CatalogItem[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Serializable content uses a declared item's version, never a stored Schema object. */
|
|
23
|
+
export declare interface CatalogItem {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly schemaVersion: number;
|
|
26
|
+
readonly kind: ItemKind;
|
|
27
|
+
readonly stackable: boolean;
|
|
28
|
+
readonly maxStack: number;
|
|
29
|
+
readonly tradable: boolean;
|
|
30
|
+
readonly rarity: string;
|
|
31
|
+
readonly tags: readonly string[];
|
|
32
|
+
readonly equipSlot: string | null;
|
|
33
|
+
readonly expiresAfter: number | null;
|
|
34
|
+
readonly display: {
|
|
35
|
+
readonly nameKey: string;
|
|
36
|
+
readonly icon: string | null;
|
|
37
|
+
};
|
|
38
|
+
readonly attributes: JsonValue;
|
|
39
|
+
readonly instanceDefaults: JsonValue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export declare interface CatalogScope {
|
|
43
|
+
readonly tenantId: string;
|
|
44
|
+
readonly channel: string;
|
|
45
|
+
readonly locale: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export declare interface CatalogSnapshot {
|
|
49
|
+
readonly revision: string;
|
|
50
|
+
readonly items: readonly CatalogItem[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export declare class CatalogSource extends CatalogSource_base {
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare const CatalogSource_base: Context.ServiceClass<CatalogSource, "@voltro/CatalogSource", CatalogSourceService>;
|
|
57
|
+
|
|
58
|
+
/** Read only. Call at join/workflow boundaries, never from a room tick. */
|
|
59
|
+
export declare interface CatalogSourceService {
|
|
60
|
+
readonly read: (scope: CatalogScope) => Effect.Effect<CatalogSnapshot, InventoryError>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export declare interface CraftEconomyParticipant {
|
|
64
|
+
readonly prepareTransfer: (subject: Subject, input: {
|
|
65
|
+
readonly tenantId: string;
|
|
66
|
+
readonly requestId: string;
|
|
67
|
+
readonly currency: string;
|
|
68
|
+
readonly amount: string;
|
|
69
|
+
readonly from: {
|
|
70
|
+
readonly kind: 'wallet';
|
|
71
|
+
readonly ownerKind: 'player' | 'guild';
|
|
72
|
+
readonly ownerId: string;
|
|
73
|
+
};
|
|
74
|
+
readonly to: {
|
|
75
|
+
readonly kind: 'system';
|
|
76
|
+
readonly id: string;
|
|
77
|
+
};
|
|
78
|
+
readonly reason: string;
|
|
79
|
+
readonly ref: {
|
|
80
|
+
readonly kind: string;
|
|
81
|
+
readonly id: string;
|
|
82
|
+
};
|
|
83
|
+
}) => Effect.Effect<PreparedSqlMutation<CraftPaymentReceipt>, {
|
|
84
|
+
readonly code: string;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export declare interface CraftingOptions {
|
|
89
|
+
readonly recipes: readonly (RecipeDefinition | Recipe)[];
|
|
90
|
+
readonly craftResolver?: CraftResolver | undefined;
|
|
91
|
+
readonly version?: string | undefined;
|
|
92
|
+
readonly service?: CraftingService | undefined;
|
|
93
|
+
readonly economy?: CraftEconomyParticipant | undefined;
|
|
94
|
+
readonly systemAccount?: string | undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export declare class CraftingPort extends CraftingPort_base {
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
declare const CraftingPort_base: Context.ServiceClass<CraftingPort, "@voltro/CraftingPort", CraftingService>;
|
|
101
|
+
|
|
102
|
+
/** Small independent seam; InventoryPort retains its eight-method surface. */
|
|
103
|
+
export declare interface CraftingService {
|
|
104
|
+
readonly craft: (subject: Subject, input: CraftRequest) => Effect.Effect<InventoryReceipt, InventoryError>;
|
|
105
|
+
readonly recipes: (subject: Subject) => Effect.Effect<readonly Recipe[], InventoryError>;
|
|
106
|
+
readonly exportSubject: (target: PluginPrivacyTarget) => Effect.Effect<unknown, InventoryError>;
|
|
107
|
+
readonly eraseSubject: (target: PluginPrivacyTarget) => Effect.Effect<void, InventoryError>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export declare interface CraftInput {
|
|
111
|
+
readonly recipe: Recipe;
|
|
112
|
+
readonly batches: number;
|
|
113
|
+
readonly at: number;
|
|
114
|
+
readonly available: readonly InventoryInstance[];
|
|
115
|
+
readonly excludedIds: readonly string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export declare interface CraftPaymentReceipt {
|
|
119
|
+
readonly txId: string;
|
|
120
|
+
readonly currency: string;
|
|
121
|
+
readonly amount: string;
|
|
122
|
+
readonly at: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export declare interface CraftPlan {
|
|
126
|
+
readonly consume: readonly {
|
|
127
|
+
readonly instanceId: string;
|
|
128
|
+
readonly qty: number;
|
|
129
|
+
}[];
|
|
130
|
+
readonly outputs: readonly RecipeOutput[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export declare interface CraftRequest {
|
|
134
|
+
readonly tenantId: string;
|
|
135
|
+
readonly owner: InventoryOwner;
|
|
136
|
+
readonly recipeId: string;
|
|
137
|
+
readonly requestId: string;
|
|
138
|
+
readonly batches?: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export declare type CraftResolver = (input: CraftInput) => CraftPlan;
|
|
142
|
+
|
|
143
|
+
/** SQL source owns immutable snapshots and a tenant-wide identity/version registry. */
|
|
144
|
+
declare const dataStoreCatalog: (store: DataStore, input: SqlCatalogOptions) => Readonly<{
|
|
145
|
+
read: (scope: CatalogScope) => Effect.Effect<CatalogSnapshot, InventoryError>;
|
|
146
|
+
importSnapshot: (subject: Subject, input: CatalogImport) => Effect.Effect<CatalogSnapshot, InventoryError>;
|
|
147
|
+
}>;
|
|
148
|
+
|
|
149
|
+
/** Deterministic oldest acquisitions first; equipped, locked and expired items never enter a recipe. */
|
|
150
|
+
export declare const defaultCraftResolver: CraftResolver;
|
|
151
|
+
|
|
152
|
+
export declare const defaultExpiryPolicy: ExpiryPolicy;
|
|
153
|
+
|
|
154
|
+
export declare const defaultStackPolicy: StackPolicy;
|
|
155
|
+
|
|
156
|
+
export declare const defineItem: (input: ItemDefinition) => ItemContract;
|
|
157
|
+
|
|
158
|
+
/** Immediate recipe; currency cost requires an installed exact-store prepared participant. Duration/prerequisites refuse. */
|
|
159
|
+
export declare const defineRecipe: (input: RecipeDefinition) => Recipe;
|
|
160
|
+
|
|
161
|
+
declare interface ExpiryInput {
|
|
162
|
+
readonly item: CatalogItem;
|
|
163
|
+
readonly acquiredAt: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export declare type ExpiryPolicy = (input: ExpiryInput) => number | null;
|
|
167
|
+
|
|
168
|
+
declare interface InventoryAudit {
|
|
169
|
+
readonly instances: number;
|
|
170
|
+
readonly ledgerRows: number;
|
|
171
|
+
readonly matches: boolean;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export declare interface InventoryConsume {
|
|
175
|
+
readonly tenantId: string;
|
|
176
|
+
readonly owner: InventoryOwner;
|
|
177
|
+
readonly requestId: string;
|
|
178
|
+
readonly instanceId: string;
|
|
179
|
+
readonly qty: number;
|
|
180
|
+
readonly reason: string;
|
|
181
|
+
readonly ref: InventoryRef;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export declare interface InventoryEquip {
|
|
185
|
+
readonly slotSet: string;
|
|
186
|
+
readonly slot: string;
|
|
187
|
+
readonly instanceId: string | null;
|
|
188
|
+
readonly requestId: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Public errors carry codes, never subjects, catalog values or provider causes. */
|
|
192
|
+
export declare class InventoryError extends InventoryError_base {
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
declare const InventoryError_base: Schema.Class<InventoryError, Schema.TaggedStruct<"InventoryError", {
|
|
196
|
+
readonly code: Schema.Literals<readonly ["invalid_input", "invalid_record", "invalid_item", "invalid_attributes", "conflict", "forbidden", "not_found", "unavailable", "limit_exceeded", "erased", "insufficient_items", "insufficient_funds", "payment_refused", "locked", "expired", "invalid_slot"]>;
|
|
197
|
+
}>, YieldableError>;
|
|
198
|
+
|
|
199
|
+
export declare interface InventoryExpire {
|
|
200
|
+
readonly tenantId: string;
|
|
201
|
+
readonly owner: InventoryOwner;
|
|
202
|
+
readonly requestId: string;
|
|
203
|
+
readonly until: number;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export declare interface InventoryGrant {
|
|
207
|
+
readonly tenantId: string;
|
|
208
|
+
readonly owner: InventoryOwner;
|
|
209
|
+
readonly requestId: string;
|
|
210
|
+
readonly itemId: string;
|
|
211
|
+
readonly qty: number;
|
|
212
|
+
readonly attributes?: JsonValue;
|
|
213
|
+
readonly reason: string;
|
|
214
|
+
readonly ref: InventoryRef;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export declare interface InventoryInstance {
|
|
218
|
+
readonly id: string;
|
|
219
|
+
readonly itemId: string;
|
|
220
|
+
readonly schemaVersion: number;
|
|
221
|
+
readonly qty: number;
|
|
222
|
+
readonly attributes: JsonValue;
|
|
223
|
+
readonly acquiredAt: number;
|
|
224
|
+
readonly expiresAt: number | null;
|
|
225
|
+
readonly locked: boolean;
|
|
226
|
+
readonly source: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export declare interface InventoryLoadout {
|
|
230
|
+
readonly slotSet: string;
|
|
231
|
+
readonly slot: string;
|
|
232
|
+
readonly instanceId: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export declare interface InventoryOwner {
|
|
236
|
+
readonly ownerKind: 'player' | 'guild';
|
|
237
|
+
readonly ownerId: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export declare interface InventoryPlugin extends VoltroPlugin {
|
|
241
|
+
readonly inventory: {
|
|
242
|
+
readonly service: () => InventoryService;
|
|
243
|
+
readonly crafting: () => CraftingService;
|
|
244
|
+
readonly importCatalog: (subject: Subject, input: CatalogImport) => ReturnType<ReturnType<typeof dataStoreCatalog>['importSnapshot']>;
|
|
245
|
+
readonly prepareReservationTransfer: InventorySqlService['prepareReservationTransfer'];
|
|
246
|
+
readonly prepareReserveTransfer: InventorySqlService['prepareReserveTransfer'];
|
|
247
|
+
readonly prepareReserve: InventorySqlService['prepareReserve'];
|
|
248
|
+
readonly prepareRelease: InventorySqlService['prepareRelease'];
|
|
249
|
+
readonly reservation: InventorySqlService['reservation'];
|
|
250
|
+
readonly reservationEvidence: InventorySqlService['reservationEvidence'];
|
|
251
|
+
readonly prepareGrant: InventorySqlService['prepareGrant'];
|
|
252
|
+
readonly prepareCraft: InventorySqlService['prepareCraft'];
|
|
253
|
+
readonly auditInventory: InventorySqlService['auditInventory'];
|
|
254
|
+
readonly rebuildInventory: InventorySqlService['rebuildInventory'];
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export declare const inventoryPlugin: (input: InventoryPluginOptions) => InventoryPlugin;
|
|
259
|
+
|
|
260
|
+
export declare interface InventoryPluginOptions extends Omit<InventoryStoreOptions, 'catalogSource' | 'channel' | 'locale'> {
|
|
261
|
+
readonly catalogSource?: CatalogSourceService;
|
|
262
|
+
readonly channel?: string;
|
|
263
|
+
readonly locale?: string;
|
|
264
|
+
readonly maxCatalogItems?: number;
|
|
265
|
+
readonly maxCatalogVersions?: number;
|
|
266
|
+
/** A replacement implements the same authority, receipt and privacy contract. */
|
|
267
|
+
readonly service?: InventoryService;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export declare class InventoryPort extends InventoryPort_base {
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
declare const InventoryPort_base: Context.ServiceClass<InventoryPort, "@voltro/InventoryPort", InventoryService>;
|
|
274
|
+
|
|
275
|
+
export declare interface InventoryReceipt {
|
|
276
|
+
readonly operationId: string;
|
|
277
|
+
readonly kind: 'grant' | 'consume' | 'equip' | 'expire' | 'craft' | 'reserve' | 'release' | 'transfer-out' | 'transfer-in';
|
|
278
|
+
readonly changes: readonly {
|
|
279
|
+
readonly instanceId: string;
|
|
280
|
+
readonly delta: number;
|
|
281
|
+
}[];
|
|
282
|
+
readonly at: number;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
declare interface InventoryRef {
|
|
286
|
+
readonly kind: string;
|
|
287
|
+
readonly id: string;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
declare interface InventoryReleaseInput {
|
|
291
|
+
readonly tenantId: string;
|
|
292
|
+
readonly owner: InventoryOwner;
|
|
293
|
+
readonly reservationId: string;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Original custody proof, separate from current availability. A redacted peer
|
|
297
|
+
* has no serialized receipt; its retained movement conservation is still checked. */
|
|
298
|
+
declare interface InventoryReservationEvidence {
|
|
299
|
+
readonly reservationId: string;
|
|
300
|
+
readonly state: InventoryReservationView['state'];
|
|
301
|
+
readonly items: InventoryReservationView['items'];
|
|
302
|
+
readonly reserve: InventoryReceipt | null;
|
|
303
|
+
readonly release: InventoryReceipt | null;
|
|
304
|
+
readonly transfer: {
|
|
305
|
+
readonly from: InventoryReceipt;
|
|
306
|
+
readonly to: InventoryReceipt | null;
|
|
307
|
+
} | null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
declare interface InventoryReservationTransferInput {
|
|
311
|
+
readonly tenantId: string;
|
|
312
|
+
readonly from: InventoryOwner;
|
|
313
|
+
readonly to: InventoryOwner;
|
|
314
|
+
readonly reservationId: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
declare interface InventoryReservationTransferReceipt {
|
|
318
|
+
readonly reservationId: string;
|
|
319
|
+
readonly from: InventoryReceipt;
|
|
320
|
+
readonly to: InventoryReceipt;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
declare interface InventoryReservationView {
|
|
324
|
+
readonly reservationId: string;
|
|
325
|
+
readonly state: 'not-observed' | 'reserved' | 'released' | 'transferred';
|
|
326
|
+
readonly items: readonly {
|
|
327
|
+
readonly qty: number;
|
|
328
|
+
readonly instance: InventoryInstance;
|
|
329
|
+
}[];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
declare interface InventoryReservedTransferReceipt {
|
|
333
|
+
readonly reserve: InventoryReceipt;
|
|
334
|
+
readonly transfer: InventoryReservationTransferReceipt;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Trusted server participant input. Selecting quantities never proves ownership. */
|
|
338
|
+
declare interface InventoryReserveInput {
|
|
339
|
+
readonly tenantId: string;
|
|
340
|
+
readonly owner: InventoryOwner;
|
|
341
|
+
readonly reservationId: string;
|
|
342
|
+
readonly items: readonly {
|
|
343
|
+
readonly instanceId: string;
|
|
344
|
+
readonly qty: number;
|
|
345
|
+
}[];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
declare interface InventoryReserveTransferInput extends InventoryReservationTransferInput {
|
|
349
|
+
readonly items: readonly {
|
|
350
|
+
readonly instanceId: string;
|
|
351
|
+
readonly qty: number;
|
|
352
|
+
}[];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Installed routes and trusted callers use this same replaceable service. */
|
|
356
|
+
export declare interface InventoryService {
|
|
357
|
+
readonly grant: (subject: Subject, input: InventoryGrant) => Effect.Effect<InventoryReceipt, InventoryError>;
|
|
358
|
+
readonly consume: (subject: Subject, input: InventoryConsume) => Effect.Effect<InventoryReceipt, InventoryError>;
|
|
359
|
+
readonly mine: (subject: Subject) => Effect.Effect<readonly InventoryInstance[], InventoryError>;
|
|
360
|
+
readonly loadout: (subject: Subject, slotSet: string) => Effect.Effect<readonly InventoryLoadout[], InventoryError>;
|
|
361
|
+
readonly equip: (subject: Subject, input: InventoryEquip) => Effect.Effect<InventoryReceipt, InventoryError>;
|
|
362
|
+
readonly expire: (subject: Subject, input: InventoryExpire) => Effect.Effect<InventoryReceipt, InventoryError>;
|
|
363
|
+
readonly exportSubject: (target: PluginPrivacyTarget) => Effect.Effect<unknown, InventoryError>;
|
|
364
|
+
readonly eraseSubject: (target: PluginPrivacyTarget) => Effect.Effect<void, InventoryError>;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
declare interface InventorySqlService extends InventoryService {
|
|
368
|
+
readonly prepareConsume: (subject: Subject, input: Parameters<InventoryService['consume']>[1]) => Effect.Effect<PreparedSqlMutation<InventoryReceipt>, InventoryError>;
|
|
369
|
+
readonly prepareReserveTransfer: (subject: Subject, input: InventoryReserveTransferInput) => Effect.Effect<PreparedSqlMutation<InventoryReservedTransferReceipt>, InventoryError>;
|
|
370
|
+
readonly prepareReservationTransfer: (subject: Subject, input: InventoryReservationTransferInput) => Effect.Effect<PreparedSqlMutation<InventoryReservationTransferReceipt>, InventoryError>;
|
|
371
|
+
readonly prepareReserve: (subject: Subject, input: InventoryReserveInput) => Effect.Effect<PreparedSqlMutation<InventoryReceipt>, InventoryError>;
|
|
372
|
+
readonly prepareRelease: (subject: Subject, input: InventoryReleaseInput) => Effect.Effect<PreparedSqlMutation<InventoryReceipt>, InventoryError>;
|
|
373
|
+
readonly reservation: (subject: Subject, input: InventoryReleaseInput) => Effect.Effect<InventoryReservationView, InventoryError>;
|
|
374
|
+
readonly reservationEvidence: (subject: Subject, input: InventoryReleaseInput) => Effect.Effect<InventoryReservationEvidence, InventoryError>;
|
|
375
|
+
readonly prepareGrant: (subject: Subject, input: InventoryGrant) => Effect.Effect<PreparedSqlMutation<InventoryReceipt>, InventoryError>;
|
|
376
|
+
readonly prepareCraft: (subject: Subject, input: CraftRequest) => Effect.Effect<PreparedSqlMutation<InventoryReceipt>, InventoryError>;
|
|
377
|
+
readonly craft: (subject: Subject, input: CraftRequest) => Effect.Effect<InventoryReceipt, InventoryError>;
|
|
378
|
+
readonly auditInventory: (subject: Subject, tenantId: string, owner: InventoryOwner) => Effect.Effect<InventoryAudit, InventoryError>;
|
|
379
|
+
readonly rebuildInventory: (subject: Subject, tenantId: string, owner: InventoryOwner) => Effect.Effect<InventoryAudit, InventoryError>;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
declare interface InventoryStoreOptions {
|
|
383
|
+
readonly items: readonly ItemDefinition[];
|
|
384
|
+
readonly catalogSource: CatalogSourceService;
|
|
385
|
+
readonly channel: string;
|
|
386
|
+
readonly locale: string;
|
|
387
|
+
readonly allowedServices?: readonly string[] | undefined;
|
|
388
|
+
readonly slotSets?: Readonly<Record<string, readonly string[]>> | undefined;
|
|
389
|
+
readonly maxInstances?: number | undefined;
|
|
390
|
+
readonly maxLedgerRows?: number | undefined;
|
|
391
|
+
readonly now?: (() => number) | undefined;
|
|
392
|
+
readonly stackPolicy?: StackPolicy | undefined;
|
|
393
|
+
readonly expiryPolicy?: ExpiryPolicy | undefined;
|
|
394
|
+
readonly crafting?: CraftingOptions | undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** System21 manifest: strategy slots, ports and events this system declares. */
|
|
398
|
+
export declare const inventorySystemContract: Readonly<{
|
|
399
|
+
slots: {
|
|
400
|
+
stackPolicy: {
|
|
401
|
+
defaultExport: string;
|
|
402
|
+
fixtureExport: string;
|
|
403
|
+
};
|
|
404
|
+
expiryPolicy: {
|
|
405
|
+
defaultExport: string;
|
|
406
|
+
fixtureExport: string;
|
|
407
|
+
};
|
|
408
|
+
craftResolver: {
|
|
409
|
+
defaultExport: string;
|
|
410
|
+
fixtureExport: string;
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
ports: {
|
|
414
|
+
inventory: {
|
|
415
|
+
tagExport: string;
|
|
416
|
+
fakeLayerExport: string;
|
|
417
|
+
fixtureExport: string;
|
|
418
|
+
};
|
|
419
|
+
crafting: {
|
|
420
|
+
tagExport: string;
|
|
421
|
+
fakeLayerExport: string;
|
|
422
|
+
fixtureExport: string;
|
|
423
|
+
};
|
|
424
|
+
catalogSource: {
|
|
425
|
+
tagExport: string;
|
|
426
|
+
fakeLayerExport: string;
|
|
427
|
+
fixtureExport: string;
|
|
428
|
+
};
|
|
429
|
+
};
|
|
430
|
+
events: readonly string[];
|
|
431
|
+
}>;
|
|
432
|
+
|
|
433
|
+
declare const ITEM_KINDS: readonly ["consumable", "equipment", "cosmetic", "currencyBundle", "key", "mod"];
|
|
434
|
+
|
|
435
|
+
export declare const itemContent: (contract: ItemContract, attributes: unknown, instanceDefaults: unknown) => CatalogItem;
|
|
436
|
+
|
|
437
|
+
export declare interface ItemContract extends Omit<ItemDefinition, 'schemaVersion' | 'equipSlot' | 'expiresAfter' | 'display'> {
|
|
438
|
+
readonly schemaVersion: number;
|
|
439
|
+
readonly equipSlot: string | null;
|
|
440
|
+
readonly expiresAfter: number | null;
|
|
441
|
+
readonly display: {
|
|
442
|
+
readonly nameKey: string;
|
|
443
|
+
readonly icon: string | null;
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export declare interface ItemDefinition {
|
|
448
|
+
readonly id: string;
|
|
449
|
+
readonly kind: ItemKind;
|
|
450
|
+
readonly schemaVersion?: number;
|
|
451
|
+
readonly stackable: boolean;
|
|
452
|
+
readonly maxStack: number;
|
|
453
|
+
readonly tradable: boolean;
|
|
454
|
+
readonly rarity: string;
|
|
455
|
+
readonly tags: readonly string[];
|
|
456
|
+
readonly attributes: Schema.Top;
|
|
457
|
+
readonly instanceAttributes: Schema.Top;
|
|
458
|
+
readonly equipSlot?: string | null;
|
|
459
|
+
/** Milliseconds; resolved on grant, never by a tick. */
|
|
460
|
+
readonly expiresAfter?: number | null;
|
|
461
|
+
readonly display: {
|
|
462
|
+
readonly nameKey: string;
|
|
463
|
+
readonly icon?: string | null;
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export declare type ItemKind = typeof ITEM_KINDS[number];
|
|
468
|
+
|
|
469
|
+
declare type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
470
|
+
readonly [key: string]: JsonValue;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
export declare interface Recipe {
|
|
474
|
+
readonly id: string;
|
|
475
|
+
readonly version: number;
|
|
476
|
+
readonly inputs: readonly RecipeIngredient[];
|
|
477
|
+
readonly outputs: readonly RecipeOutput[];
|
|
478
|
+
readonly maxBatches: number;
|
|
479
|
+
readonly cost: RecipeCurrencyCost | null;
|
|
480
|
+
readonly timeMs: 0;
|
|
481
|
+
readonly requires: null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export declare interface RecipeCurrencyCost {
|
|
485
|
+
readonly currency: string;
|
|
486
|
+
readonly amount: string;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export declare interface RecipeDefinition {
|
|
490
|
+
readonly id: string;
|
|
491
|
+
readonly version?: number;
|
|
492
|
+
readonly inputs: readonly RecipeIngredient[];
|
|
493
|
+
readonly outputs: readonly RecipeOutput[];
|
|
494
|
+
readonly maxBatches?: number;
|
|
495
|
+
readonly cost?: RecipeCurrencyCost | null;
|
|
496
|
+
readonly timeMs?: 0;
|
|
497
|
+
readonly requires?: null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export declare interface RecipeIngredient {
|
|
501
|
+
readonly itemId: string;
|
|
502
|
+
readonly qty: number;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export declare interface RecipeOutput extends RecipeIngredient {
|
|
506
|
+
readonly attributes?: JsonValue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export declare const resolveCraftingOptions: (input: CraftingOptions) => Readonly<{
|
|
510
|
+
economy: Readonly<{
|
|
511
|
+
prepareTransfer: (subject: Subject, input: {
|
|
512
|
+
readonly tenantId: string;
|
|
513
|
+
readonly requestId: string;
|
|
514
|
+
readonly currency: string;
|
|
515
|
+
readonly amount: string;
|
|
516
|
+
readonly from: {
|
|
517
|
+
readonly kind: "wallet";
|
|
518
|
+
readonly ownerKind: "player" | "guild";
|
|
519
|
+
readonly ownerId: string;
|
|
520
|
+
};
|
|
521
|
+
readonly to: {
|
|
522
|
+
readonly kind: "system";
|
|
523
|
+
readonly id: string;
|
|
524
|
+
};
|
|
525
|
+
readonly reason: string;
|
|
526
|
+
readonly ref: {
|
|
527
|
+
readonly kind: string;
|
|
528
|
+
readonly id: string;
|
|
529
|
+
};
|
|
530
|
+
}) => Effect.Effect<PreparedSqlMutation<CraftPaymentReceipt>, {
|
|
531
|
+
readonly code: string;
|
|
532
|
+
}>;
|
|
533
|
+
}> | undefined;
|
|
534
|
+
systemAccount: string | undefined;
|
|
535
|
+
recipes: readonly Recipe[];
|
|
536
|
+
craftResolver: CraftResolver;
|
|
537
|
+
version: string;
|
|
538
|
+
service: CraftingService | undefined;
|
|
539
|
+
}>;
|
|
540
|
+
|
|
541
|
+
/** Custom selection may choose instances and valid output attributes, never amounts or additional value. */
|
|
542
|
+
export declare const resolveCraftPlan: (input: CraftInput, resolver?: CraftResolver) => CraftPlan;
|
|
543
|
+
|
|
544
|
+
export declare const resolveExpiry: (input: ExpiryInput, policy?: ExpiryPolicy) => number | null;
|
|
545
|
+
|
|
546
|
+
/** A custom policy cannot mint, discard, duplicate or exceed a stack's cap. */
|
|
547
|
+
export declare const resolveStack: (input: StackInput, policy?: StackPolicy) => StackPlan;
|
|
548
|
+
|
|
549
|
+
declare interface SqlCatalogOptions {
|
|
550
|
+
readonly items: readonly ItemDefinition[];
|
|
551
|
+
readonly allowedServices?: readonly string[];
|
|
552
|
+
readonly maxCatalogItems?: number;
|
|
553
|
+
readonly maxCatalogVersions?: number;
|
|
554
|
+
readonly now?: () => number;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
declare interface StackInput {
|
|
558
|
+
readonly item: CatalogItem;
|
|
559
|
+
readonly quantity: number;
|
|
560
|
+
readonly available: readonly {
|
|
561
|
+
readonly id: string;
|
|
562
|
+
readonly quantity: number;
|
|
563
|
+
}[];
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
declare interface StackPlan {
|
|
567
|
+
readonly existing: readonly {
|
|
568
|
+
readonly id: string;
|
|
569
|
+
readonly add: number;
|
|
570
|
+
}[];
|
|
571
|
+
readonly create: readonly number[];
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export declare type StackPolicy = (input: StackInput) => StackPlan;
|
|
575
|
+
|
|
576
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { InventoryError as e } from "./errors.js";
|
|
2
|
+
import { CraftingPort as t, captureCraftPayment as n, captureCraftPlan as r, defaultCraftResolver as i, defineRecipe as a, resolveCraftPlan as o, resolveCraftingOptions as s } from "./crafting.js";
|
|
3
|
+
import { defineItem as c, itemContent as l } from "./item.js";
|
|
4
|
+
import { CatalogSource as u } from "./catalog.js";
|
|
5
|
+
import { defaultExpiryPolicy as d, defaultStackPolicy as f, resolveExpiry as p, resolveStack as m } from "./strategies.js";
|
|
6
|
+
import { InventoryPort as h } from "./port.js";
|
|
7
|
+
import { inventoryPlugin as g, inventorySystemContract as _ } from "./plugin.js";
|
|
8
|
+
export { u as CatalogSource, t as CraftingPort, e as InventoryError, h as InventoryPort, n as captureCraftPayment, r as captureCraftPlan, i as defaultCraftResolver, d as defaultExpiryPolicy, f as defaultStackPolicy, c as defineItem, a as defineRecipe, g as inventoryPlugin, _ as inventorySystemContract, l as itemContent, o as resolveCraftPlan, s as resolveCraftingOptions, p as resolveExpiry, m as resolveStack };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { inventoryError as e } from "./errors.js";
|
|
2
|
+
import { identifier as t, list as n, object as r } from "./capture.js";
|
|
3
|
+
import { defineRecipe as i } from "./crafting.js";
|
|
4
|
+
import { captureCatalogItem as a, captureItemContracts as o } from "./item.js";
|
|
5
|
+
import { inventoryPlugin as s } from "./plugin.js";
|
|
6
|
+
import { Effect as c, Layer as l } from "effect";
|
|
7
|
+
import { systemSubject as u } from "@voltro/protocol";
|
|
8
|
+
import { ensureTableRegistered as d } from "@voltro/database";
|
|
9
|
+
import { InMemoryDataStore as f } from "@voltro/runtime";
|
|
10
|
+
//#region src/inventoryPort.fixture.ts
|
|
11
|
+
function p(p) {
|
|
12
|
+
let m = r(p, [
|
|
13
|
+
"tenantId",
|
|
14
|
+
"items",
|
|
15
|
+
"content",
|
|
16
|
+
"recipes",
|
|
17
|
+
"slotSets",
|
|
18
|
+
"now"
|
|
19
|
+
]), h = t(m.tenantId), g = o(m.items), _ = Object.freeze(n(m.content, 1e4).map((e) => a(e, g)));
|
|
20
|
+
if (new Set(_.map((e) => JSON.stringify([e.id, e.schemaVersion]))).size !== _.length) throw e("invalid_input");
|
|
21
|
+
let v = Object.freeze(n(m.recipes ?? [], 256).map((e) => i(e)));
|
|
22
|
+
if (m.recipes !== void 0 && !v.length || v.some((e) => e.cost !== null)) throw e("invalid_input");
|
|
23
|
+
let y = Object.create(null);
|
|
24
|
+
for (let [i, a] of Object.entries(r(m.slotSets ?? {}))) {
|
|
25
|
+
t(i);
|
|
26
|
+
let r = Object.freeze(n(a, 64).map(t));
|
|
27
|
+
if (new Set(r).size !== r.length) throw e("invalid_input");
|
|
28
|
+
y[i] = r;
|
|
29
|
+
}
|
|
30
|
+
Object.freeze(y);
|
|
31
|
+
let b = m.now ?? Date.now;
|
|
32
|
+
if (typeof b != "function") throw e("invalid_input");
|
|
33
|
+
let x = {
|
|
34
|
+
app: {
|
|
35
|
+
name: "inventory-port-fixture",
|
|
36
|
+
type: "api",
|
|
37
|
+
voltroVersion: "0.83.1"
|
|
38
|
+
},
|
|
39
|
+
config: {},
|
|
40
|
+
env: {},
|
|
41
|
+
logger: {
|
|
42
|
+
info() {},
|
|
43
|
+
warn() {},
|
|
44
|
+
error() {},
|
|
45
|
+
debug() {}
|
|
46
|
+
}
|
|
47
|
+
}, S = c.acquireRelease(c.sync(() => s({
|
|
48
|
+
items: g,
|
|
49
|
+
slotSets: y,
|
|
50
|
+
now: b,
|
|
51
|
+
...v.length ? { crafting: { recipes: v } } : {}
|
|
52
|
+
})), (e) => c.suspend(() => e.onDeactivate(x))).pipe(c.tap((e) => c.gen(function* () {
|
|
53
|
+
e.extendSchema.tables.forEach((e) => d(e));
|
|
54
|
+
let t = new f();
|
|
55
|
+
yield* c.promise(() => Promise.resolve(e.bindDataStore(t))), yield* e.inventory.importCatalog(u("inventory.port.fixture"), {
|
|
56
|
+
tenantId: h,
|
|
57
|
+
channel: "live",
|
|
58
|
+
locale: "en",
|
|
59
|
+
revision: "fixture.r1",
|
|
60
|
+
items: _
|
|
61
|
+
});
|
|
62
|
+
}).pipe(c.orDie))), C = l.effectContext(S.pipe(c.flatMap((e) => l.build(e.services))));
|
|
63
|
+
return Object.freeze({ layer: C });
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { p as inventoryPortFixture };
|