@nostr-games/inventory 0.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,744 @@
1
+ /**
2
+ * Minimal structural type compatible with a signed Nostr event.
3
+ *
4
+ * We intentionally define this locally instead of depending on a full Nostr
5
+ * library. Any object that structurally matches this shape (for example, the
6
+ * event type from `nostr-tools`) is accepted.
7
+ */
8
+ interface NostrEvent {
9
+ pubkey: string;
10
+ created_at: number;
11
+ kind: number;
12
+ tags: string[][];
13
+ content: string;
14
+ sig?: string;
15
+ id?: string;
16
+ }
17
+ /**
18
+ * An unsigned event template as produced by the builders in this library.
19
+ *
20
+ * Builders never create `id` or `sig` and never sign anything. The template is
21
+ * intended to be passed to a signer / relay client by the consuming
22
+ * application.
23
+ *
24
+ * `pubkey` and `created_at` are intentionally omitted: they are added by the
25
+ * signing/publishing layer, which is out of scope for this library.
26
+ */
27
+ interface UnsignedEventTemplate<K extends number = number> {
28
+ kind: K;
29
+ content: string;
30
+ tags: string[][];
31
+ }
32
+
33
+ /**
34
+ * Kind numbers defined by this library.
35
+ *
36
+ * - {@link KIND_GAME_ITEM_DEFINITION} (31632): Game Item Definition.
37
+ * - {@link KIND_GAME_INVENTORY} (31633): Game Inventory.
38
+ */
39
+ declare const KIND_GAME_ITEM_DEFINITION: 31632;
40
+ declare const KIND_GAME_INVENTORY: 31633;
41
+ type KindGameItemDefinition = typeof KIND_GAME_ITEM_DEFINITION;
42
+ type KindGameInventory = typeof KIND_GAME_INVENTORY;
43
+
44
+ /**
45
+ * Parsing modes shared across the library.
46
+ *
47
+ * - `permissive` (default): follow the "SHOULD tolerate" rules of the specs.
48
+ * Invalid tags are ignored, invalid JSON content does not block tag parsing,
49
+ * duplicate inventory items resolve using the recommended default strategy.
50
+ * - `strict`: reject the event when the specs allow rejection (for example
51
+ * duplicate inventory item references, or invalid JSON content when JSON is
52
+ * required).
53
+ *
54
+ * "MUST reject" rules (wrong kind, missing/empty `d`, missing required item
55
+ * definition tags) apply in both modes.
56
+ */
57
+ type ParseMode = "permissive" | "strict";
58
+ /**
59
+ * A recoverable, non-fatal issue detected while parsing.
60
+ *
61
+ * Warnings never prevent parsing in permissive mode. They are surfaced so
62
+ * callers can audit what was ignored or coerced.
63
+ */
64
+ interface ParseWarning {
65
+ /** Machine-readable warning code. */
66
+ code: ParseWarningCode;
67
+ /** Human-readable explanation. */
68
+ message: string;
69
+ /** The offending tag, when the warning relates to a specific tag. */
70
+ tag?: string[];
71
+ }
72
+ type ParseWarningCode = "invalid-json-content" | "invalid-item-tag" | "invalid-quantity" | "wrong-referenced-kind" | "malformed-address" | "duplicate-item" | "invalid-grant-tag" | "empty-required-value";
73
+ /**
74
+ * Structured parse result.
75
+ *
76
+ * `ok: true` means a usable value was produced (possibly with warnings about
77
+ * ignored tags). `ok: false` means the event was rejected as invalid for this
78
+ * kind.
79
+ */
80
+ type ParseResult<T> = {
81
+ ok: true;
82
+ value: T;
83
+ warnings: ParseWarning[];
84
+ } | {
85
+ ok: false;
86
+ error: string;
87
+ warnings: ParseWarning[];
88
+ };
89
+
90
+ /**
91
+ * Helpers for NIP-01 addressable event coordinates of the form:
92
+ *
93
+ * ```text
94
+ * <kind>:<pubkey>:<d-tag>
95
+ * ```
96
+ *
97
+ * IMPORTANT: the `d-tag` may itself contain `:` characters. The recommended
98
+ * `d` format in kind:31632 is `<namespace>:<category>:<slug>` (for example
99
+ * `blobbi:food:carrot`), which means a full address such as
100
+ * `31632:pubkey123:blobbi:food:carrot` has more than three colon-separated
101
+ * segments. Address parsing therefore splits only on the first two colons and
102
+ * treats everything after the second colon as the `d-tag`.
103
+ */
104
+ interface AddressableEventAddress {
105
+ kind: number;
106
+ pubkey: string;
107
+ identifier: string;
108
+ }
109
+ /**
110
+
111
+ * Options controlling how strict pubkey validation is.
112
+ *
113
+ * Placeholder pubkeys are accepted by default to support fixtures, drafts,
114
+ * and permissive parsing. Consumers may enable canonical 64-character hex
115
+ * validation.
116
+ *
117
+ * - By default, only a non-empty pubkey segment is required.
118
+ * - When `requireHexPubkey` is `true`, the pubkey must be a 64-character
119
+ * lowercase hexadecimal string, which is the canonical Nostr pubkey form.
120
+ */
121
+ interface AddressParseOptions {
122
+ requireHexPubkey?: boolean;
123
+ }
124
+ /**
125
+ * Build an addressable event coordinate string.
126
+ *
127
+ * @throws if any component is invalid (empty pubkey/identifier or non-integer
128
+ * kind). Builders in this library validate their inputs before calling this,
129
+ * so a throw here indicates a programming error rather than bad user data.
130
+ */
131
+ declare function buildAddressableEventAddress(kind: number, pubkey: string, identifier: string): string;
132
+ /**
133
+ * Parse an addressable event coordinate string.
134
+ *
135
+ * Returns `null` when the string is not a well-formed `<kind>:<pubkey>:<d>`
136
+ * coordinate. Everything after the second colon is treated as the identifier
137
+ * (`d` tag), preserving any colons it contains.
138
+ */
139
+ declare function parseAddressableEventAddress(address: string, options?: AddressParseOptions): AddressableEventAddress | null;
140
+
141
+ /**
142
+ * Return the value of the first tag with the given name, or `undefined` if no
143
+ * such tag exists or its value slot is missing.
144
+ */
145
+ declare function getTagValue(tags: string[][], name: string): string | undefined;
146
+ /**
147
+ * Return all values of tags with the given name (index 1), skipping tags whose
148
+ * value slot is missing.
149
+ */
150
+ declare function getTagValues(tags: string[][], name: string): string[];
151
+ /**
152
+ * Return the `d` tag value of an event or its raw tags.
153
+ *
154
+ * Returns `undefined` if there is no `d` tag or its value is missing. Note that
155
+ * an empty-string `d` value is returned as `""` (the caller decides whether an
156
+ * empty `d` is acceptable; per both specs it is not).
157
+ */
158
+ declare function getDTag(source: NostrEvent | string[][]): string | undefined;
159
+
160
+ /**
161
+ * Result of attempting to parse a `content` string as JSON.
162
+ */
163
+ type ContentParseResult = {
164
+ kind: "empty";
165
+ } | {
166
+ kind: "json";
167
+ value: unknown;
168
+ } | {
169
+ kind: "invalid";
170
+ raw: string;
171
+ };
172
+ /**
173
+ * Attempt to parse an event `content` string as JSON without throwing.
174
+ *
175
+ * An empty string is treated as {@link ContentParseResult} `empty`, matching
176
+ * both specifications which allow `content` to be an empty string.
177
+ *
178
+ * This never throws: invalid JSON is reported as `invalid` so callers can
179
+ * decide how to react based on their parse mode.
180
+ */
181
+ declare function parseContentJson(content: string): ContentParseResult;
182
+ /**
183
+ * Normalize a `content` input that may be either a preserialized string or a
184
+ * value to be JSON-serialized.
185
+ *
186
+ * - `undefined` -> `""` (the spec default for both kinds).
187
+ * - `string` -> returned as-is (the caller is responsible for its validity).
188
+ * - anything else -> `JSON.stringify`.
189
+ *
190
+ * This function always either returns a valid string or throws; it never
191
+ * returns `undefined`. `JSON.stringify` returns `undefined` for values that
192
+ * are not JSON-serializable (for example `function`, `symbol`, or a top-level
193
+ * `undefined` reached via another path), and throws on circular references. In
194
+ * both cases a clear error is raised.
195
+ *
196
+ * @throws {Error} when the value is not JSON-serializable (including circular
197
+ * references).
198
+ */
199
+ declare function serializeContent(content: unknown): string;
200
+
201
+ /**
202
+ * A derivation reference declared with an `a` tag marked `based_on`.
203
+ */
204
+ interface GameItemBasedOnReference {
205
+ /** The referenced `31632:<pubkey>:<d>` address (as written in the tag). */
206
+ address: string;
207
+ /** Relay URL hint, or `""` when unknown. */
208
+ relay: string;
209
+ }
210
+ /**
211
+ * A parsed kind:31632 Game Item Definition.
212
+ *
213
+ * Tags are the source of truth. `content` holds the raw content string; parsed
214
+ * JSON is exposed separately via {@link GameItemDefinition.contentJson}.
215
+ */
216
+ interface GameItemDefinition {
217
+ /** The `d` tag value (item identifier). */
218
+ id: string;
219
+ /** The full addressable coordinate `31632:<pubkey>:<d>`. */
220
+ address: string;
221
+ /** The event author / item issuer. */
222
+ issuer: string;
223
+ kind: KindGameItemDefinition;
224
+ /** Required `name` tag. */
225
+ name: string;
226
+ /** Required `type` tag. */
227
+ type: string;
228
+ /** Optional `category` tag. */
229
+ category?: string;
230
+ /** Optional `image` tag. */
231
+ image?: string;
232
+ /** Optional `model_3d` tag. */
233
+ model3d?: string;
234
+ /** Optional `audio` tag. */
235
+ audio?: string;
236
+ /** Optional `symbol` tag. */
237
+ symbol?: string;
238
+ /** Optional `rarity` tag. */
239
+ rarity?: string;
240
+ /** Optional `max_stack` tag, kept as the raw string per the spec. */
241
+ maxStack?: string;
242
+ /** Optional `version` tag. */
243
+ version?: string;
244
+ /** Optional `alt` tag. */
245
+ alt?: string;
246
+ /** All `context` tag values (repeatable). */
247
+ contexts: string[];
248
+ /** All `t` topic tag values (repeatable). */
249
+ topics: string[];
250
+ /** All `based_on` derivation references (repeatable). */
251
+ basedOn: GameItemBasedOnReference[];
252
+ /** Raw `content` string, preserved exactly as received. */
253
+ content: string;
254
+ /**
255
+ * Parsed content JSON, when `content` was non-empty valid JSON. `undefined`
256
+ * when content is empty or (in permissive mode) invalid JSON.
257
+ */
258
+ contentJson?: unknown;
259
+ /** The original event this definition was parsed from. */
260
+ event: NostrEvent;
261
+ }
262
+ /**
263
+ * Input for building a kind:31632 event template.
264
+ *
265
+ * `content` accepts either a preserialized string or any JSON-serializable
266
+ * value. Tag-owned fields (name, type, etc.) MUST NOT be duplicated in content
267
+ * by the library; that is left to the caller's discretion per the spec.
268
+ */
269
+ interface BuildGameItemDefinitionInput {
270
+ /** The `d` item identifier. Required, non-empty. */
271
+ id: string;
272
+ /** The `name` tag. Required, non-empty. */
273
+ name: string;
274
+ /** The `type` tag. Required, non-empty. */
275
+ type: string;
276
+ category?: string;
277
+ image?: string;
278
+ model3d?: string;
279
+ audio?: string;
280
+ symbol?: string;
281
+ rarity?: string;
282
+ /** Suggested max stack; coerced to a string if provided as a number. */
283
+ maxStack?: string | number;
284
+ version?: string | number;
285
+ /**
286
+ * Human-readable fallback. If omitted, the builder does NOT auto-generate an
287
+ * `alt` tag (it is only RECOMMENDED by the spec). Pass a string to include it.
288
+ */
289
+ alt?: string;
290
+ contexts?: string[];
291
+ topics?: string[];
292
+ basedOn?: GameItemBasedOnReference[];
293
+ /** Optional content; string or JSON-serializable value. Defaults to `""`. */
294
+ content?: unknown;
295
+ /**
296
+ * Extra tags to append verbatim after the managed tags. Use for
297
+ * forward-compatible or game-specific tags.
298
+ *
299
+ * Tags that conflict with builder-managed tags are REJECTED (the builder
300
+ * throws), not silently ignored or duplicated. Managed tags are: `d`,
301
+ * `name`, `type`, `category`, `image`, `model_3d`, `audio`, `symbol`,
302
+ * `rarity`, `max_stack`, `version`, `context`, `t`, `alt`. An `a` tag only
303
+ * conflicts when it carries the `based_on` marker (pass such references via
304
+ * `basedOn` instead); other `a` usages remain allowed.
305
+ */
306
+ extraTags?: string[][];
307
+ }
308
+
309
+ /**
310
+ * The marker used on an `a` tag to indicate a derivation reference.
311
+ */
312
+ declare const BASED_ON_MARKER: "based_on";
313
+ interface GameItemAddress {
314
+ kind: KindGameItemDefinition;
315
+ pubkey: string;
316
+ /** The item `d` tag / identifier. */
317
+ itemId: string;
318
+ }
319
+ /**
320
+ * Build the addressable coordinate for a game item definition:
321
+ * `31632:<pubkey>:<item-d-tag>`.
322
+ */
323
+ declare function buildGameItemAddress(pubkey: string, itemId: string): string;
324
+ /**
325
+ * Parse a `31632:<pubkey>:<item-d-tag>` coordinate.
326
+ *
327
+ * Returns `null` if the string is malformed or does not reference kind 31632.
328
+ */
329
+ declare function parseGameItemAddress(address: string, options?: AddressParseOptions): GameItemAddress | null;
330
+
331
+ /**
332
+ * A structured validation issue for a kind:31632 event.
333
+ */
334
+ interface ItemDefinitionValidationIssue {
335
+ code: "wrong-kind" | "missing-d" | "empty-d" | "missing-name" | "empty-name" | "missing-type" | "empty-type" | "invalid-json-content";
336
+ message: string;
337
+ }
338
+ interface ItemDefinitionValidationOptions {
339
+ /**
340
+ * When `true`, `content` must be empty or valid JSON. This mirrors the spec:
341
+ * an item definition is rejected for invalid JSON only when the client
342
+ * "requires strict JSON content". Defaults to `false`.
343
+ */
344
+ requireJsonContent?: boolean;
345
+ }
346
+ interface ItemDefinitionValidationResult {
347
+ valid: boolean;
348
+ issues: ItemDefinitionValidationIssue[];
349
+ }
350
+ /**
351
+ * Validate that an event is a well-formed kind:31632 Game Item Definition.
352
+ *
353
+ * Per the spec, an event is rejected as an item definition if:
354
+ * - `kind` is not 31632
355
+ * - the `d` tag is missing or empty
356
+ * - the `name` tag is missing or empty
357
+ * - the `type` tag is missing or empty
358
+ * - (only when {@link ItemDefinitionValidationOptions.requireJsonContent})
359
+ * `content` is not valid JSON
360
+ *
361
+ * A required value that contains only whitespace is treated as empty. Values
362
+ * are never trimmed or otherwise mutated.
363
+ */
364
+ declare function validateGameItemDefinition(event: NostrEvent, options?: ItemDefinitionValidationOptions): ItemDefinitionValidationResult;
365
+
366
+ interface ParseGameItemDefinitionOptions {
367
+ /**
368
+ * `permissive` (default) tolerates invalid JSON content and unknown tags.
369
+ * `strict` additionally rejects the event when `content` is non-empty and
370
+ * not valid JSON (i.e. it implies `requireJsonContent`).
371
+ */
372
+ mode?: ParseMode;
373
+ /**
374
+ * Explicitly require valid JSON content, independent of mode. When left
375
+ * `undefined`, this defaults to `true` in strict mode and `false` in
376
+ * permissive mode.
377
+ */
378
+ requireJsonContent?: boolean;
379
+ }
380
+ /**
381
+ * Parse a kind:31632 event into a {@link GameItemDefinition}, returning a
382
+ * structured {@link ParseResult}.
383
+ *
384
+ * The event is rejected (`ok: false`) only for the MUST-reject conditions
385
+ * (wrong kind, missing/empty `d`/`name`/`type`, and invalid JSON when
386
+ * required). Unknown tags are preserved on the event and tolerated. Invalid
387
+ * JSON content in permissive mode produces a warning, not a rejection. Invalid
388
+ * `based_on` references (malformed address or wrong referenced kind) are
389
+ * ignored and reported as warnings.
390
+ */
391
+ declare function parseGameItemDefinitionResult(event: NostrEvent, options?: ParseGameItemDefinitionOptions): ParseResult<GameItemDefinition>;
392
+ /**
393
+ * Convenience wrapper returning the parsed definition or `null`.
394
+ *
395
+ * Prefer {@link parseGameItemDefinitionResult} when you need warnings or the
396
+ * rejection reason.
397
+ */
398
+ declare function parseGameItemDefinition(event: NostrEvent, options?: ParseGameItemDefinitionOptions): GameItemDefinition | null;
399
+
400
+ /**
401
+ * Build an unsigned kind:31632 event template.
402
+ *
403
+ * The builder:
404
+ * - validates required input (`id`, `name`, `type` must be non-empty and not
405
+ * whitespace-only);
406
+ * - validates `maxStack` (positive decimal integer) and numeric `version`
407
+ * (finite);
408
+ * - validates every `basedOn` reference (well-formed kind:31632 coordinate);
409
+ * - never creates `id` or `sig`;
410
+ * - emits tags in a stable, deterministic order to simplify testing;
411
+ * - rejects `extraTags` that conflict with builder-managed tags (see
412
+ * {@link MANAGED_TAG_NAMES}); an `a` tag is only treated as conflicting when
413
+ * it carries the `based_on` marker, so other future `a` usages remain
414
+ * possible;
415
+ * - appends the remaining `extraTags` verbatim after the managed tags.
416
+ *
417
+ * Valid values are never trimmed or otherwise mutated.
418
+ *
419
+ * @throws {Error} if a required field is missing/blank, an optional numeric
420
+ * field is invalid, a `basedOn` reference is invalid, or an `extraTags` entry
421
+ * conflicts with a builder-managed tag.
422
+ */
423
+ declare function buildGameItemDefinitionEvent(input: BuildGameItemDefinitionInput): UnsignedEventTemplate<KindGameItemDefinition>;
424
+
425
+ /**
426
+ * A single item reference inside an inventory.
427
+ */
428
+ interface GameInventoryItem {
429
+ /** The referenced `31632:<issuer-pubkey>:<item-d-tag>` address. */
430
+ address: string;
431
+ /** Relay URL hint, or `""` when unknown. */
432
+ relay: string;
433
+ /** Positive integer quantity. */
434
+ quantity: number;
435
+ }
436
+ /**
437
+ * A grant / receipt reference declared with an `e` tag marked `grant`.
438
+ */
439
+ interface GameInventoryGrantReference {
440
+ /** The referenced grant event id. */
441
+ eventId: string;
442
+ /** Relay URL hint, or `""` when unknown. */
443
+ relay: string;
444
+ }
445
+ /**
446
+ * A parsed kind:31633 Game Inventory.
447
+ *
448
+ * Tags are the source of truth. `content` holds the raw content string; parsed
449
+ * JSON is exposed separately via {@link GameInventory.contentJson}.
450
+ */
451
+ interface GameInventory {
452
+ /** The `d` tag value (inventory context identifier). */
453
+ id: string;
454
+ /** The full addressable coordinate `31633:<owner>:<d>`. */
455
+ address: string;
456
+ /** The event author / inventory owner. */
457
+ owner: string;
458
+ kind: KindGameInventory;
459
+ /** All `context` tag values (repeatable). */
460
+ contexts: string[];
461
+ /** Optional `name` tag. */
462
+ name?: string;
463
+ /** Optional `alt` tag. */
464
+ alt?: string;
465
+ /** Valid item references, in tag order (after duplicate resolution). */
466
+ items: GameInventoryItem[];
467
+ /** Grant references from `e` tags marked `grant`. */
468
+ grants: GameInventoryGrantReference[];
469
+ /** Convenience: grant event ids only. */
470
+ grantEventIds: string[];
471
+ /** Raw `content` string, preserved exactly as received. */
472
+ content: string;
473
+ /** Parsed content JSON, when content was non-empty valid JSON. */
474
+ contentJson?: unknown;
475
+ /** The original event this inventory was parsed from. */
476
+ event: NostrEvent;
477
+ }
478
+ /**
479
+ * Input for a single item when building an inventory event.
480
+ *
481
+ * `quantity` must be a non-negative safe integer. `0` omits the item; negative,
482
+ * decimal, NaN, Infinity, and unsafe-integer values throw. Quantities are never
483
+ * floored, clamped, or silently dropped.
484
+ */
485
+ interface BuildGameInventoryItemInput {
486
+ address: string;
487
+ relay?: string;
488
+ quantity: number;
489
+ }
490
+ interface BuildGameInventoryGrantInput {
491
+ eventId: string;
492
+ relay?: string;
493
+ }
494
+ /**
495
+ * Strategy for handling duplicate item addresses when building or parsing.
496
+ *
497
+ * - `last` (spec default): keep the last valid quantity.
498
+ * - `sum`: sum all valid quantities for the address.
499
+ * - `strict`: reject (build throws / parse returns error).
500
+ */
501
+ type DuplicateStrategy = "last" | "sum" | "strict";
502
+ /**
503
+ * Input for building a kind:31633 event template.
504
+ */
505
+ interface BuildGameInventoryInput {
506
+ /** The `d` inventory identifier. Required, non-empty. */
507
+ id: string;
508
+ items?: BuildGameInventoryItemInput[];
509
+ contexts?: string[];
510
+ name?: string;
511
+ alt?: string;
512
+ grants?: BuildGameInventoryGrantInput[];
513
+ /** Optional content; string or JSON-serializable value. Defaults to `""`. */
514
+ content?: unknown;
515
+ /**
516
+ * How to handle duplicate item addresses in the input. Defaults to `last`
517
+ * (the spec's recommended default). `strict` throws on duplicates. `sum`
518
+ * throws if the summed quantity exceeds Number.MAX_SAFE_INTEGER.
519
+ */
520
+ duplicateStrategy?: DuplicateStrategy;
521
+ /**
522
+ * Extra tags appended verbatim after managed tags.
523
+ *
524
+ * Tags that conflict with builder-managed tags are REJECTED (the builder
525
+ * throws), not silently duplicated. Rejected: `d`, `context`, `name`, `alt`;
526
+ * every `a` tag (all `a` tags represent inventory items in kind:31633 — pass
527
+ * items via `items`); and `e` tags carrying the `grant` marker (pass grants
528
+ * via `grants`). Unrelated forward-compatible tags, including non-grant `e`
529
+ * tags, are allowed.
530
+ */
531
+ extraTags?: string[][];
532
+ }
533
+
534
+ /**
535
+ * The marker used on an `e` tag to indicate a grant / receipt reference.
536
+ */
537
+ declare const GRANT_MARKER: "grant";
538
+ interface GameInventoryAddress {
539
+ kind: KindGameInventory;
540
+ pubkey: string;
541
+ /** The inventory `d` tag / context identifier. */
542
+ inventoryId: string;
543
+ }
544
+ /**
545
+ * Build the addressable coordinate for a game inventory:
546
+ * `31633:<owner-pubkey>:<inventory-d-tag>`.
547
+ */
548
+ declare function buildGameInventoryAddress(ownerPubkey: string, inventoryId: string): string;
549
+ /**
550
+ * Parse a `31633:<owner-pubkey>:<inventory-d-tag>` coordinate.
551
+ *
552
+ * Returns `null` if the string is malformed or does not reference kind 31633.
553
+ */
554
+ declare function parseGameInventoryAddress(address: string, options?: AddressParseOptions): GameInventoryAddress | null;
555
+
556
+ /**
557
+ * Public quantity helpers for kind:31633 inventory `a` tags.
558
+ *
559
+ * Per the spec, quantity MUST be encoded as a decimal integer string. Parsers
560
+ * SHOULD ignore item references whose quantity is missing, `0`, negative,
561
+ * decimal, or non-numeric. A "valid" stored inventory quantity is therefore a
562
+ * positive integer.
563
+ *
564
+ * - {@link parseInventoryQuantity} parses an untrusted tag string, tolerantly
565
+ * returning `null` for anything that is not a canonical positive decimal
566
+ * integer string.
567
+ * - {@link encodeInventoryQuantity} encodes a positive integer back into its
568
+ * canonical decimal string form.
569
+ *
570
+ * Strict validation of public numeric inputs (used by the builder and the
571
+ * inventory helpers) lives in `quantity.internal.ts` and is not part of the
572
+ * public API.
573
+ */
574
+ /**
575
+ * Parse an inventory quantity *string* into a positive integer.
576
+ *
577
+ * Returns `null` for any value that is not a strictly positive canonical
578
+ * decimal integer string. This intentionally rejects:
579
+ * - `undefined` / `null` / missing
580
+ * - `"0"` and `"-1"` (not positive)
581
+ * - `"1.5"` (decimal)
582
+ * - `"abc"`, `"1e3"`, `"0x1"`, `" 1 "`, `"+1"` (not a plain decimal integer)
583
+ * - `"03"` (leading zeros; a well-formed publisher never emits them)
584
+ */
585
+ declare function parseInventoryQuantity(raw: string | undefined | null): number | null;
586
+ /**
587
+ * Encode a positive integer quantity as its canonical decimal string.
588
+ *
589
+ * @throws if the value is not a positive safe integer.
590
+ */
591
+ declare function encodeInventoryQuantity(value: number): string;
592
+
593
+ interface InventoryValidationIssue {
594
+ code: "wrong-kind" | "missing-d" | "empty-d" | "invalid-json-content";
595
+ message: string;
596
+ }
597
+ interface InventoryValidationOptions {
598
+ /**
599
+ * When `true`, non-empty invalid JSON content is treated as invalid. The
600
+ * spec only allows rejecting invalid JSON when NOT in permissive mode, so
601
+ * this defaults to `false`.
602
+ */
603
+ requireJsonContent?: boolean;
604
+ }
605
+ interface InventoryValidationResult {
606
+ valid: boolean;
607
+ issues: InventoryValidationIssue[];
608
+ }
609
+ /**
610
+ * Validate the MUST-reject conditions for a kind:31633 inventory.
611
+ *
612
+ * An event MUST be rejected if:
613
+ * - `kind` is not 31633
614
+ * - the `d` tag is missing
615
+ * - the `d` tag is empty or contains only whitespace
616
+ *
617
+ * A whitespace-only `d` value is treated as empty; values are never trimmed.
618
+ * Item-level problems (bad quantity, wrong referenced kind, malformed address)
619
+ * do NOT invalidate the inventory; those items are ignored during parsing.
620
+ */
621
+ declare function validateGameInventory(event: NostrEvent, options?: InventoryValidationOptions): InventoryValidationResult;
622
+
623
+ interface ParseGameInventoryOptions {
624
+ /**
625
+ * `permissive` (default) tolerates invalid JSON content and ignores invalid
626
+ * item tags. `strict` additionally:
627
+ * - rejects the event when `content` is non-empty and not valid JSON;
628
+ * - defaults the duplicate strategy to `strict` (reject on duplicates).
629
+ */
630
+ mode?: ParseMode;
631
+ /**
632
+ * Duplicate item address handling. When omitted, defaults to `strict` in
633
+ * strict mode and `last` in permissive mode (the spec's recommended
634
+ * default).
635
+ */
636
+ duplicateStrategy?: DuplicateStrategy;
637
+ /**
638
+ * Require valid JSON content independent of mode. Defaults to `true` in
639
+ * strict mode, `false` in permissive mode.
640
+ */
641
+ requireJsonContent?: boolean;
642
+ /**
643
+ * Require item addresses to use 64-char hex pubkeys. Defaults to `false`
644
+ * (the spec examples use placeholder pubkeys).
645
+ */
646
+ requireHexPubkey?: boolean;
647
+ /**
648
+ * Require grant `e` tag event ids to be canonical 64-char lowercase hex.
649
+ * Defaults to `false` so fixture placeholders remain supported.
650
+ */
651
+ requireHexEventId?: boolean;
652
+ }
653
+ /**
654
+ * Parse a kind:31633 event into a {@link GameInventory}, returning a structured
655
+ * {@link ParseResult}.
656
+ *
657
+ * The event is rejected (`ok: false`) only when: kind is wrong, `d` is
658
+ * missing/empty, JSON content is required but invalid, duplicate item
659
+ * references are present under the `strict` strategy, or a `sum` duplicate
660
+ * resolution would overflow Number.MAX_SAFE_INTEGER. Item references that are
661
+ * malformed, reference a non-31632 kind, or carry an invalid quantity are
662
+ * ignored and reported as warnings. Malformed grant tags are ignored with an
663
+ * `invalid-grant-tag` warning.
664
+ */
665
+ declare function parseGameInventoryResult(event: NostrEvent, options?: ParseGameInventoryOptions): ParseResult<GameInventory>;
666
+ /**
667
+ * Convenience wrapper returning the parsed inventory or `null`.
668
+ */
669
+ declare function parseGameInventory(event: NostrEvent, options?: ParseGameInventoryOptions): GameInventory | null;
670
+
671
+ /**
672
+ * Build an unsigned kind:31633 event template.
673
+ *
674
+ * The builder:
675
+ * - validates the required `d` id (non-empty, not whitespace-only);
676
+ * - validates each item address (must be a kind:31632 coordinate);
677
+ * - requires each item quantity to be a non-negative safe integer; `0` is
678
+ * omitted, and negatives/decimals/NaN/Infinity/unsafe integers throw (they
679
+ * are never floored, clamped, or silently dropped);
680
+ * - resolves duplicate addresses per `duplicateStrategy` (default `last`), and
681
+ * throws if a `sum` overflows Number.MAX_SAFE_INTEGER;
682
+ * - rejects grants with an empty/whitespace-only event id;
683
+ * - emits tags in a stable, deterministic order;
684
+ * - never creates `id` or `sig`;
685
+ * - rejects `extraTags` that conflict with builder-managed tags and appends the
686
+ * rest verbatim after the managed tags.
687
+ *
688
+ * Non-empty display values are never trimmed or otherwise normalized.
689
+ *
690
+ * @throws {Error} for a blank `id`, invalid item address, invalid quantity,
691
+ * duplicate under the `strict` strategy, `sum` overflow, a blank grant event
692
+ * id, or an `extraTags` entry that conflicts with a managed tag.
693
+ */
694
+ declare function buildGameInventoryEvent(input: BuildGameInventoryInput): UnsignedEventTemplate<KindGameInventory>;
695
+
696
+ /**
697
+ * Read the quantity of an item in an inventory.
698
+ *
699
+ * Returns `0` when the item is not present. This mirrors inventory semantics:
700
+ * a missing item is equivalent to holding zero of it.
701
+ */
702
+ declare function getInventoryItemQuantity(inventory: GameInventory, itemAddress: string): number;
703
+ /**
704
+ * Return a new inventory with the given item's quantity set to an exact value.
705
+ *
706
+ * - `itemAddress` must be a valid kind:31632 coordinate (throws otherwise).
707
+ * - `quantity` must be a non-negative safe integer (throws otherwise); it is
708
+ * never floored, clamped, or silently dropped.
709
+ * - Setting a quantity of `0` removes the item.
710
+ * - The input inventory is never mutated (pure / immutable). When the
711
+ * operation is rejected it throws before producing any value, so the input
712
+ * is untouched.
713
+ */
714
+ declare function setInventoryItemQuantity(inventory: GameInventory, itemAddress: string, quantity: number, relay?: string): GameInventory;
715
+ /**
716
+ * Return a new inventory with `amount` added to the item's quantity.
717
+ *
718
+ * - `itemAddress` must be a valid kind:31632 coordinate (throws otherwise).
719
+ * - `amount` must be a non-negative safe integer (throws otherwise). An amount
720
+ * of `0` is a no-op that still returns a fresh inventory object.
721
+ * - Throws if the resulting quantity would exceed Number.MAX_SAFE_INTEGER.
722
+ * - The input inventory is never mutated.
723
+ */
724
+ declare function addInventoryItemQuantity(inventory: GameInventory, itemAddress: string, amount: number, relay?: string): GameInventory;
725
+ /**
726
+ * Return a new inventory with `amount` removed from the item's quantity.
727
+ *
728
+ * - `itemAddress` must be a valid kind:31632 coordinate (throws otherwise).
729
+ * - `amount` must be a non-negative safe integer (throws otherwise).
730
+ * - The result is clamped at zero; reaching zero removes the item. (Clamping a
731
+ * removal *result* to zero is allowed; the requested `amount` itself must be
732
+ * valid and is never silently corrected.)
733
+ * - The input inventory is never mutated.
734
+ */
735
+ declare function removeInventoryItemQuantity(inventory: GameInventory, itemAddress: string, amount: number): GameInventory;
736
+ /**
737
+ * Return a shallow copy of the inventory's items array.
738
+ *
739
+ * Useful for callers that want the item list without exposing the internal
740
+ * reference.
741
+ */
742
+ declare function getInventoryItems(inventory: GameInventory): GameInventoryItem[];
743
+
744
+ export { type AddressParseOptions, type AddressableEventAddress, BASED_ON_MARKER, type BuildGameInventoryGrantInput, type BuildGameInventoryInput, type BuildGameInventoryItemInput, type BuildGameItemDefinitionInput, type ContentParseResult, type DuplicateStrategy, GRANT_MARKER, type GameInventory, type GameInventoryAddress, type GameInventoryGrantReference, type GameInventoryItem, type GameItemAddress, type GameItemBasedOnReference, type GameItemDefinition, type InventoryValidationIssue, type InventoryValidationOptions, type InventoryValidationResult, type ItemDefinitionValidationIssue, type ItemDefinitionValidationOptions, type ItemDefinitionValidationResult, KIND_GAME_INVENTORY, KIND_GAME_ITEM_DEFINITION, type KindGameInventory, type KindGameItemDefinition, type NostrEvent, type ParseGameInventoryOptions, type ParseGameItemDefinitionOptions, type ParseMode, type ParseResult, type ParseWarning, type ParseWarningCode, type UnsignedEventTemplate, addInventoryItemQuantity, buildAddressableEventAddress, buildGameInventoryAddress, buildGameInventoryEvent, buildGameItemAddress, buildGameItemDefinitionEvent, encodeInventoryQuantity, getDTag, getInventoryItemQuantity, getInventoryItems, getTagValue, getTagValues, parseAddressableEventAddress, parseContentJson, parseGameInventory, parseGameInventoryAddress, parseGameInventoryResult, parseGameItemAddress, parseGameItemDefinition, parseGameItemDefinitionResult, parseInventoryQuantity, removeInventoryItemQuantity, serializeContent, setInventoryItemQuantity, validateGameInventory, validateGameItemDefinition };