/** * What a purchase DOES. `item_grant` / `non_consumable` hand the player a * UniversalItem (non_consumable additionally caps ownership via maxPerUser); * `consumable` grants nothing durable — the world applies the effect itself. */ export declare const WORLD_PRODUCT_TYPES: readonly ["consumable", "non_consumable", "item_grant"]; export type WorldProductType = (typeof WORLD_PRODUCT_TYPES)[number]; /** Up to 64 chars, lowercase letters/digits/`_`/`-`, starting alphanumeric. */ export declare const PRODUCT_KEY_PATTERN: RegExp; export declare const PRODUCT_KEY_MAX = 64; export declare const PRODUCT_TITLE_MAX = 120; export declare const PRODUCT_DESCRIPTION_MAX = 2000; export declare const isProductKey: (value: unknown) => value is string; /** The two types that must name the UniversalItem they hand over. */ export declare const productTypeGrantsItem: (type: WorldProductType) => boolean; export type ProductGrantDisplay = 'wallet' | 'uses'; export type ProductGrant = { kind: 'item'; itemId: string; quantity?: number; } | { kind: 'pass'; passKey?: string; durationSeconds?: number; } | { kind: 'currency'; code: string; amount: number; display?: ProductGrantDisplay; }; export declare const MAX_GRANT_EFFECTS = 8; export declare const MAX_GRANT_ITEM_QUANTITY = 100; export declare const MAX_GRANT_CURRENCY_AMOUNT = 1000000000; export declare const MAX_PASS_DURATION_SECONDS: number; /** Currency codes and pass keys share the product-key charset — world code spells all three. */ export declare const GRANT_CODE_PATTERN: RegExp; export declare const GRANT_CODE_MAX = 64; export type ProductRegistrationInput = { key?: string; title: string; description?: string; priceLix: number; /** Legacy authoring: required only when `grants` is absent, refused alongside it. */ type?: string; itemId?: string; /** The authored effect list — replaces type/itemId, and is immutable once registered. */ grants?: ProductGrant[]; maxPerUser?: number; }; export type ProductRegistrationFields = { key?: string; title: string; description?: string; priceLix: number; type?: WorldProductType; itemId?: string; grants?: ProductGrant[]; maxPerUser?: number; }; export type ProductRegistrationPlan = { fields: ProductRegistrationFields; warnings: string[]; }; /** * Build the JSON body, applying every local rule first. Keys the caller did not * set are OMITTED rather than sent empty — an empty description and no * description are the same row, and sending "" would look like intent. */ export declare function buildProductRegistration(input: ProductRegistrationInput): ProductRegistrationPlan; export type ProductUpdateInput = { title?: string; description?: string; /** null clears the cap. */ maxPerUser?: number | null; priceLix?: number; active?: boolean; /** Not patchable — carried only so an attempted rename fails HERE, with a reason. */ key?: string; /** Not patchable either — carried only so an attempted re-grant fails HERE, with a reason. */ grants?: ProductGrant[]; }; export type ProductPatch = Omit; /** Validate a PATCH body locally and refuse an empty one (a no-op round trip that reads as success). */ export declare function buildProductUpdate(input: ProductUpdateInput): ProductPatch;