@receiz/sdk 97.0.0 → 97.2.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { buildReceizIdContinueRequest, createReceizIdIdentity, projectReceizIdentityAccount, readReceizIdentityArtifact, signReceizIdentityLoginProof, verifyReceizIdentityLoginProof, } from "./identity.js";
2
2
  export * from "./identity.js";
3
- export const RECEIZ_SDK_VERSION = "97.0.0";
3
+ export const RECEIZ_SDK_VERSION = "97.2.0";
4
4
  export const RECEIZ_DEFAULT_BASE_URL = "https://receiz.com";
5
5
  export const RECEIZ_WEBHOOK_SIGNATURE_HEADER = "x-receiz-signature";
6
6
  export const RECEIZ_WEBHOOK_TIMESTAMP_HEADER = "x-receiz-timestamp";
@@ -200,6 +200,193 @@ export function createReceizAppStateRecord(input) {
200
200
  };
201
201
  return record;
202
202
  }
203
+ export function createReceizAppStateFeed(records, options = {}) {
204
+ const feed = {
205
+ schema: options.schema ?? RECEIZ_APP_STATE_FEED_SCHEMA,
206
+ records: records.map((record) => createReceizAppStateRecord(record)),
207
+ };
208
+ if (options.namespace)
209
+ feed.namespace = options.namespace;
210
+ if (options.externalCreatorId)
211
+ feed.externalCreatorId = options.externalCreatorId;
212
+ return feed;
213
+ }
214
+ export function createReceizPublicStoreStateRecord(input) {
215
+ return createReceizAppStateRecord({
216
+ ...input,
217
+ platform: input.platform ?? "Receiz.app Commerce Cloud",
218
+ schema: input.schema ?? RECEIZ_PUBLIC_STORE_STATE_PROJECTION_SCHEMA,
219
+ });
220
+ }
221
+ export function createReceizTenantAppStateRecord(input) {
222
+ const sourceUrl = input.sourceUrl ?? receizAppStateUrlFromHost(input.tenantHost);
223
+ const data = input.data ?? {
224
+ ...input.state,
225
+ ...(input.schemaVersion !== undefined ? { schemaVersion: input.schemaVersion } : {}),
226
+ tenantHost: normalizeReceizHost(input.tenantHost),
227
+ };
228
+ const record = {
229
+ sourceUrl,
230
+ state: input.projectionState ?? (input.visibility === "private" ? "private" : "published"),
231
+ platform: input.platform ?? "Receiz App Runtime",
232
+ schema: input.schema ?? RECEIZ_PUBLIC_STORE_STATE_PROJECTION_SCHEMA,
233
+ data,
234
+ };
235
+ if (input.id)
236
+ record.id = input.id;
237
+ const externalCreatorId = input.externalCreatorId ?? input.creatorReceizId;
238
+ if (externalCreatorId)
239
+ record.externalCreatorId = externalCreatorId;
240
+ if (input.title)
241
+ record.title = input.title;
242
+ record.namespace = input.namespace ?? receizAppStateNamespaceFromUrl(sourceUrl);
243
+ record.record = input.record ?? input.state;
244
+ return createReceizAppStateRecord(record);
245
+ }
246
+ function isReceizAppStateFeed(value) {
247
+ return isRecord(value) && Array.isArray(value.records);
248
+ }
249
+ export function normalizeReceizHost(hostOrUrl) {
250
+ const trimmed = hostOrUrl.trim();
251
+ if (!trimmed)
252
+ throw new ReceizValidationError("receiz.domains.normalizeHost", ["host is required"]);
253
+ try {
254
+ return new URL(/^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`).hostname.toLowerCase();
255
+ }
256
+ catch {
257
+ return trimmed.replace(/^https?:\/\//i, "").split(/[/?#]/)[0]?.toLowerCase() ?? trimmed.toLowerCase();
258
+ }
259
+ }
260
+ function bodyWithIdempotency(body, idempotencyKey) {
261
+ if (!idempotencyKey || Object.prototype.hasOwnProperty.call(body, "idempotencyKey"))
262
+ return body;
263
+ return { ...body, idempotencyKey };
264
+ }
265
+ function headersWithIdempotency(headers, idempotencyKey) {
266
+ const next = new Headers(headers);
267
+ if (idempotencyKey)
268
+ next.set("idempotency-key", idempotencyKey);
269
+ return next;
270
+ }
271
+ function queryParams(input) {
272
+ const params = {};
273
+ for (const [key, value] of Object.entries(input)) {
274
+ if (value === null ||
275
+ value === undefined ||
276
+ typeof value === "string" ||
277
+ typeof value === "number" ||
278
+ typeof value === "boolean") {
279
+ params[key] = value;
280
+ }
281
+ }
282
+ return params;
283
+ }
284
+ function usdToCents(value) {
285
+ const trimmed = value.trim();
286
+ if (!/^\d+(\.\d{1,2})?$/.test(trimmed))
287
+ throw new ReceizValidationError("receiz.commerce.amountUsd", ["amountUsd must be a positive USD decimal"]);
288
+ const [whole = "0", fraction = ""] = trimmed.split(".");
289
+ return BigInt(whole) * 100n + BigInt((fraction + "00").slice(0, 2));
290
+ }
291
+ function centsToUsd(value) {
292
+ const sign = value < 0n ? "-" : "";
293
+ const abs = value < 0n ? -value : value;
294
+ return `${sign}${abs / 100n}.${String(abs % 100n).padStart(2, "0")}`;
295
+ }
296
+ function stringToCents(value) {
297
+ if (typeof value === "number" && Number.isFinite(value))
298
+ return BigInt(Math.max(0, Math.trunc(value)));
299
+ if (typeof value === "bigint")
300
+ return value < 0n ? 0n : value;
301
+ if (typeof value === "string" && /^\d+$/.test(value.trim()))
302
+ return BigInt(value.trim());
303
+ return 0n;
304
+ }
305
+ function sandboxProducts(count) {
306
+ return Array.from({ length: Math.max(0, count) }, (_, index) => ({
307
+ id: `sandbox_product_${index + 1}`,
308
+ title: `Sandbox Product ${index + 1}`,
309
+ amountUsd: centsToUsd(BigInt(index + 1) * 1200n),
310
+ proofObject: {
311
+ schema: "receiz.sdk.sandbox.proof_object.v1",
312
+ objectId: `sandbox_product_${index + 1}`,
313
+ },
314
+ }));
315
+ }
316
+ export async function verifyReceizWebhookRequest(request, options) {
317
+ const signature = request.headers.get(RECEIZ_WEBHOOK_SIGNATURE_HEADER) ?? "";
318
+ const timestamp = request.headers.get(RECEIZ_WEBHOOK_TIMESTAMP_HEADER) ?? "";
319
+ const body = await request.clone().text();
320
+ const verifyInput = {
321
+ secret: options.secret,
322
+ timestamp,
323
+ body,
324
+ signature,
325
+ };
326
+ if (options.toleranceSeconds !== undefined)
327
+ verifyInput.toleranceSeconds = options.toleranceSeconds;
328
+ if (options.nowMs !== undefined)
329
+ verifyInput.nowMs = options.nowMs;
330
+ return verifyReceizWebhookSignature(verifyInput);
331
+ }
332
+ function resolveReceizAppStateProjectionRecord(input) {
333
+ if (!input || !isRecord(input))
334
+ return null;
335
+ const looksLikeResponse = typeof input.ok === "boolean" &&
336
+ Object.prototype.hasOwnProperty.call(input, "record") &&
337
+ !Object.prototype.hasOwnProperty.call(input, "sourceUrl") &&
338
+ !Object.prototype.hasOwnProperty.call(input, "data");
339
+ if (looksLikeResponse) {
340
+ const response = input;
341
+ return response.record && isRecord(response.record) ? response.record : null;
342
+ }
343
+ return input;
344
+ }
345
+ function receizAppStateRestoreError(record, options) {
346
+ if (!record)
347
+ return "app_state_not_found";
348
+ if (options.schema && record.schema !== options.schema)
349
+ return "app_state_schema_mismatch";
350
+ if (options.state && record.state !== options.state)
351
+ return "app_state_state_mismatch";
352
+ if (!isRecord(record.data))
353
+ return "app_state_data_missing";
354
+ if (options.requiredDataKey && !Object.prototype.hasOwnProperty.call(record.data, options.requiredDataKey)) {
355
+ return "app_state_required_data_missing";
356
+ }
357
+ return "app_state_data_unavailable";
358
+ }
359
+ export function extractReceizAppStateData(input, options = {}) {
360
+ const record = resolveReceizAppStateProjectionRecord(input);
361
+ if (!record)
362
+ return null;
363
+ if (options.schema && record.schema !== options.schema)
364
+ return null;
365
+ if (options.state && record.state !== options.state)
366
+ return null;
367
+ if (!isRecord(record.data))
368
+ return null;
369
+ if (options.requiredDataKey && !Object.prototype.hasOwnProperty.call(record.data, options.requiredDataKey))
370
+ return null;
371
+ return record.data;
372
+ }
373
+ export function restoreReceizAppStateProjection(input, options = {}) {
374
+ const record = resolveReceizAppStateProjectionRecord(input);
375
+ const data = extractReceizAppStateData(input, options);
376
+ if (!record || !data) {
377
+ return {
378
+ ok: false,
379
+ record,
380
+ data: null,
381
+ error: receizAppStateRestoreError(record, options),
382
+ };
383
+ }
384
+ return {
385
+ ok: true,
386
+ record,
387
+ data,
388
+ };
389
+ }
203
390
  function isRecord(value) {
204
391
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
205
392
  }
@@ -367,6 +554,8 @@ export class ReceizClient {
367
554
  baseUrl;
368
555
  accessToken;
369
556
  fetchImpl;
557
+ doctor = (options = {}) => this.runDoctor(options);
558
+ capabilities = (options = {}) => this.inspectCapabilities(options);
370
559
  verification = {
371
560
  verifyArtifact: (file) => this.verifyArtifact(file),
372
561
  sealArtifact: (file, options) => this.sealArtifact(file, options),
@@ -380,13 +569,26 @@ export class ReceizClient {
380
569
  registryFeed: (feed, options = {}) => this.publishAppState(feed, options),
381
570
  };
382
571
  appState = {
383
- publish: (feed, options = {}) => this.publishAppState(feed, options),
572
+ publish: (input, options = {}) => this.publishAppStateInput(input, options),
384
573
  publishRecord: (record, options = {}) => this.publishAppStateRecord(record, options),
574
+ resolve: (input) => this.resolveAppState(input),
385
575
  byUrl: (url) => this.readAppStateByUrl(url),
386
576
  byHost: (host) => this.readAppStateByHost(host),
387
577
  byCreator: (receizId) => this.readAppStateByCreator(receizId),
388
578
  byNamespace: (namespace) => this.readAppStateByNamespace(namespace),
389
579
  byId: (id) => this.readAppStateById(id),
580
+ restoreByUrl: (url, options = {}) => this.restoreAppStateByUrl(url, options),
581
+ restoreByHost: (host, options = {}) => this.restoreAppStateByHost(host, options),
582
+ restoreById: (id, options = {}) => this.restoreAppStateById(id, options),
583
+ createRecord: createReceizAppStateRecord,
584
+ createTenantRecord: createReceizTenantAppStateRecord,
585
+ createFeed: createReceizAppStateFeed,
586
+ createPublicStoreRecord: createReceizPublicStoreStateRecord,
587
+ urlFromHost: receizAppStateUrlFromHost,
588
+ namespaceFromUrl: receizAppStateNamespaceFromUrl,
589
+ objectIdFromUrl: receizAppStateObjectIdFromUrl,
590
+ extractData: extractReceizAppStateData,
591
+ restoreProjection: restoreReceizAppStateProjection,
390
592
  };
391
593
  sports = {
392
594
  conformance: () => this.sportsConformance(),
@@ -488,12 +690,154 @@ export class ReceizClient {
488
690
  bootstrap: (username) => this.request(`/api/connect/login/bootstrap/${encodePathSegment(username)}`),
489
691
  authorizeUrl: (options) => this.authorizeUrl(options),
490
692
  };
693
+ customers = {
694
+ session: (body, options = {}) => this.delegatedWrite("/api/connect/customers/session", body, options),
695
+ portal: (body) => this.delegatedWrite("/api/connect/customers/portal", body),
696
+ orders: (query = {}) => this.delegated(appendQuery("/api/connect/customers/orders", queryParams(query))),
697
+ rewards: (query = {}) => this.delegated(appendQuery("/api/connect/customers/rewards", queryParams(query))),
698
+ assets: (query = {}) => this.delegated(appendQuery("/api/connect/customers/assets", queryParams(query))),
699
+ };
700
+ merchants = {
701
+ onboard: (body, options = {}) => this.delegatedWrite("/api/connect/merchants/onboard", body, options),
702
+ profile: (query = {}) => this.delegated(appendQuery("/api/connect/merchants/profile", query)),
703
+ capabilities: (query = {}) => this.delegated(appendQuery("/api/connect/merchants/capabilities", query)),
704
+ };
705
+ commerce = {
706
+ oneClickCheckout: (body) => this.oneClickCheckout(body),
707
+ refunds: {
708
+ create: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/refunds", body, options),
709
+ },
710
+ subscriptions: {
711
+ create: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/subscriptions", body, options),
712
+ cancel: (subscriptionId, body, options = {}) => this.delegatedWrite(`/api/connect/commerce/subscriptions/${encodePathSegment(subscriptionId)}/cancel`, body, options),
713
+ },
714
+ shipping: {
715
+ quote: (body) => this.delegatedWrite("/api/connect/commerce/shipping/quote", body),
716
+ update: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/shipping/update", body, options),
717
+ },
718
+ tax: {
719
+ quote: (body) => this.delegatedWrite("/api/connect/commerce/tax/quote", body),
720
+ },
721
+ discounts: {
722
+ create: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/discounts", body, options),
723
+ redeem: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/discounts/redeem", body, options),
724
+ },
725
+ giftCards: {
726
+ issue: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/gift-cards", body, options),
727
+ redeem: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/gift-cards/redeem", body, options),
728
+ },
729
+ accessPasses: {
730
+ issue: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/access-passes", body, options),
731
+ verify: (body) => this.delegatedWrite("/api/connect/commerce/access-passes/verify", body),
732
+ },
733
+ inventory: {
734
+ reserve: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/inventory/reserve", body, options),
735
+ adjust: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/inventory/adjust", body, options),
736
+ },
737
+ fulfillment: {
738
+ update: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/fulfillment", body, options),
739
+ },
740
+ payouts: {
741
+ create: (body, options = {}) => this.delegatedWrite("/api/connect/commerce/payouts", body, options),
742
+ status: (query = {}) => this.delegated(appendQuery("/api/connect/commerce/payouts/status", query)),
743
+ },
744
+ };
491
745
  payments = {
492
746
  embeddedCheckout: (body) => this.request("/api/payments/embed/checkout", { method: "POST", body }),
493
747
  embeddedNoteClaim: (body) => this.request("/api/payments/embed/note-claim", { method: "POST", body }),
494
748
  };
749
+ media = {
750
+ upload: (file, options = {}) => this.uploadMedia(file, options),
751
+ transform: (body, options = {}) => this.delegatedWrite("/api/connect/media/transform", body, options),
752
+ objectUrl: (query) => appendQuery(`${this.baseUrl}/api/storage/object`, query),
753
+ };
754
+ domains = {
755
+ normalizeHost: normalizeReceizHost,
756
+ tenantUrl: receizAppStateUrlFromHost,
757
+ namespace: receizAppStateNamespaceFromUrl,
758
+ objectId: receizAppStateObjectIdFromUrl,
759
+ resolveTenant: (host, options = {}) => this.restoreAppStateByHost(host, options),
760
+ reserveSubdomain: (body, options = {}) => this.delegated("/api/connect/domains/subdomains/reserve", {
761
+ method: "POST",
762
+ body: bodyWithIdempotency(body, options.idempotencyKey),
763
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
764
+ }),
765
+ verifyCustomDomain: (body, options = {}) => this.delegated("/api/connect/domains/custom/verify", {
766
+ method: "POST",
767
+ body: bodyWithIdempotency(body, options.idempotencyKey),
768
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
769
+ }),
770
+ status: (query = {}) => this.delegated(appendQuery("/api/connect/domains/status", query)),
771
+ };
772
+ events = {
773
+ subscribe: (body) => this.eventSubscribe(body),
774
+ replay: (body) => this.delegatedWrite("/api/connect/events/replay", body),
775
+ list: (query = {}) => this.delegated(appendQuery("/api/connect/events", queryParams(query))),
776
+ ack: (eventId, body = {}) => this.delegatedWrite(`/api/connect/events/${encodePathSegment(eventId)}/ack`, body),
777
+ };
778
+ proof = {
779
+ query: (query) => this.delegated("/api/connect/proof/query", { method: "POST", body: query }),
780
+ };
781
+ jobs = {
782
+ enqueue: (body) => this.delegated("/api/connect/jobs", {
783
+ method: "POST",
784
+ body: bodyWithIdempotency(body, body.idempotencyKey),
785
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
786
+ }),
787
+ status: (jobId) => this.delegated(`/api/connect/jobs/${encodePathSegment(jobId)}`),
788
+ cancel: (jobId, body = {}) => this.delegatedWrite(`/api/connect/jobs/${encodePathSegment(jobId)}/cancel`, body),
789
+ list: (query = {}) => this.delegated(appendQuery("/api/connect/jobs", queryParams(query))),
790
+ };
791
+ permissions = {
792
+ grant: (body, options = {}) => this.delegatedWrite("/api/connect/permissions/grants", body, options),
793
+ revoke: (body, options = {}) => this.delegatedWrite("/api/connect/permissions/revoke", body, options),
794
+ check: (body) => this.delegatedWrite("/api/connect/permissions/check", body),
795
+ roles: (query = {}) => this.delegated(appendQuery("/api/connect/permissions/roles", query)),
796
+ };
797
+ audit = {
798
+ append: (body, options = {}) => this.delegatedWrite("/api/connect/audit/events", body, options),
799
+ query: (query = {}) => this.delegated(appendQuery("/api/connect/audit/events", queryParams(query))),
800
+ export: (query = {}) => this.delegated(appendQuery("/api/connect/audit/export", queryParams(query))),
801
+ };
802
+ risk = {
803
+ scorePayment: (body) => this.delegatedWrite("/api/connect/risk/payment", body),
804
+ scoreAccountRecovery: (body) => this.delegatedWrite("/api/connect/risk/account-recovery", body),
805
+ velocity: (body) => this.delegatedWrite("/api/connect/risk/velocity", body),
806
+ proofActivity: (body) => this.delegatedWrite("/api/connect/risk/proof-activity", body),
807
+ };
808
+ compliance = {
809
+ exportOrders: (query) => this.delegated(appendQuery("/api/connect/compliance/orders", queryParams(query))),
810
+ exportTaxes: (query) => this.delegated(appendQuery("/api/connect/compliance/taxes", queryParams(query))),
811
+ exportPayouts: (query) => this.delegated(appendQuery("/api/connect/compliance/payouts", queryParams(query))),
812
+ exportCustomers: (query) => this.delegated(appendQuery("/api/connect/compliance/customers", queryParams(query))),
813
+ exportAudit: (query) => this.delegated(appendQuery("/api/connect/compliance/audit", queryParams(query))),
814
+ };
815
+ portability = {
816
+ exportStore: (query) => this.delegated(appendQuery("/api/connect/portability/store/export", query)),
817
+ importStore: (body, options = {}) => this.delegatedWrite("/api/connect/portability/store/import", body, options),
818
+ };
819
+ search = {
820
+ query: (body) => this.delegatedWrite("/api/connect/search/query", body),
821
+ products: (body) => this.delegatedWrite("/api/connect/search/products", body),
822
+ pages: (body) => this.delegatedWrite("/api/connect/search/pages", body),
823
+ blog: (body) => this.delegatedWrite("/api/connect/search/blog", body),
824
+ orders: (body) => this.delegatedWrite("/api/connect/search/orders", body),
825
+ customers: (body) => this.delegatedWrite("/api/connect/search/customers", body),
826
+ proofObjects: (body) => this.delegatedWrite("/api/connect/search/proof-objects", body),
827
+ };
828
+ notifications = {
829
+ send: (body, options = {}) => this.delegatedWrite("/api/connect/notifications/send", body, options),
830
+ subscribe: (body, options = {}) => this.delegatedWrite("/api/connect/notifications/subscribe", body, options),
831
+ templates: (query = {}) => this.delegated(appendQuery("/api/connect/notifications/templates", query)),
832
+ };
833
+ releases = {
834
+ pin: (body, options = {}) => this.delegatedWrite("/api/connect/releases/pin", body, options),
835
+ check: (query = {}) => this.delegated(appendQuery("/api/connect/releases/check", query)),
836
+ supported: () => this.request("/api/releases/rails/supported"),
837
+ };
495
838
  webhooks = {
496
839
  sign: createReceizWebhookSignature,
840
+ verify: verifyReceizWebhookRequest,
497
841
  verifySignature: verifyReceizWebhookSignature,
498
842
  signaturePayload: buildReceizWebhookSignaturePayload,
499
843
  assertEvent: assertReceizWebhookEvent,
@@ -513,6 +857,33 @@ export class ReceizClient {
513
857
  assetManifest: projectReceizAssetManifest,
514
858
  sportsCardManifest: projectReceizSportsCardManifest,
515
859
  };
860
+ sandbox = {
861
+ seedStore: (options) => {
862
+ const tenantHost = normalizeReceizHost(options.tenantHost);
863
+ return {
864
+ ok: true,
865
+ schema: "receiz.sdk.sandbox.store.v1",
866
+ tenantHost,
867
+ state: {
868
+ brand: tenantHost,
869
+ products: sandboxProducts(options.products ?? 3),
870
+ rewards: [{ id: "sandbox_reward_1", title: "Sandbox reward", type: "credit" }],
871
+ },
872
+ };
873
+ },
874
+ wallet: (options = {}) => ({
875
+ ok: true,
876
+ userId: "sandbox_customer",
877
+ balanceUsdCents: options.balanceUsdCents ?? "10000",
878
+ balancePhiMicro: options.balancePhiMicro ?? "100000000",
879
+ }),
880
+ checkout: (options) => ({
881
+ ok: options.outcome !== "failure",
882
+ checkoutSessionId: "sandbox_checkout",
883
+ status: options.outcome === "failure" ? "failed" : "succeeded",
884
+ amountUsd: options.amountUsd,
885
+ }),
886
+ };
516
887
  proofMemory = {
517
888
  createRegister: createReceizProofRegister,
518
889
  createMemory: createReceizProofMemory,
@@ -521,6 +892,12 @@ export class ReceizClient {
521
892
  assertSnapshot: assertReceizProofRegisterSnapshot,
522
893
  additionsQuery: receizProofMemoryAdditionsQuery,
523
894
  };
895
+ offline = {
896
+ createQueue: createReceizOfflineProofQueue,
897
+ createInMemoryStorage: createReceizInMemoryOfflineProofQueueStorage,
898
+ createLocalStorage: createReceizLocalStorageOfflineProofQueueStorage,
899
+ assertSnapshot: assertReceizOfflineProofQueueSnapshot,
900
+ };
524
901
  constructor(options = {}) {
525
902
  this.baseUrl = trimTrailingSlash(options.baseUrl ?? RECEIZ_DEFAULT_BASE_URL);
526
903
  this.accessToken = options.accessToken;
@@ -572,22 +949,131 @@ export class ReceizClient {
572
949
  async readPublicProofByUrl(url) {
573
950
  return this.request(appendQuery("/api/public-proof/by-url", { url }));
574
951
  }
952
+ async inspectCapabilities(options = {}) {
953
+ const tenantHost = options.tenantHost ? normalizeReceizHost(options.tenantHost) : undefined;
954
+ const scopes = options.scopes ?? [];
955
+ const delegatedAvailable = Boolean(this.accessToken);
956
+ const configured = (available, reason) => ({
957
+ available,
958
+ status: available ? "available" : "missing",
959
+ ...(reason ? { reason } : {}),
960
+ ...(!available ? { fixUrl: `${this.baseUrl}/developers` } : {}),
961
+ });
962
+ return {
963
+ ok: true,
964
+ schema: "receiz.sdk.capabilities.v1",
965
+ baseUrl: this.baseUrl,
966
+ ...(tenantHost ? { tenantHost } : {}),
967
+ scopes,
968
+ capabilities: {
969
+ identity: configured(true),
970
+ wallet: configured(delegatedAvailable, "Connect bearer token required for wallet reads."),
971
+ payments: configured(delegatedAvailable, "Connect bearer token required for checkout/session routes."),
972
+ proofStore: configured(true),
973
+ media: configured(delegatedAvailable, "Connect bearer token required for media uploads."),
974
+ domains: configured(Boolean(tenantHost), "tenantHost is required for tenant domain helpers."),
975
+ twin: configured(delegatedAvailable, "Connect bearer token required for Twin routes."),
976
+ commerce: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for commerce helpers."),
977
+ rewards: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for reward routes."),
978
+ events: configured(delegatedAvailable, "Connect bearer token required for event subscriptions."),
979
+ search: configured(delegatedAvailable, "Connect bearer token required for private proof search."),
980
+ permissions: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for tenant permissions."),
981
+ jobs: configured(delegatedAvailable, "Connect bearer token required for durable jobs."),
982
+ audit: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for sealed tenant audit events."),
983
+ risk: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for risk signals."),
984
+ compliance: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for compliance exports."),
985
+ portability: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for store portability."),
986
+ notifications: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for notifications."),
987
+ offline: configured(true),
988
+ releases: configured(delegatedAvailable && Boolean(tenantHost), "Connect bearer token and tenantHost are required for release rail pinning."),
989
+ },
990
+ };
991
+ }
992
+ async runDoctor(options = {}) {
993
+ const capabilities = await this.inspectCapabilities(options);
994
+ const missing = [];
995
+ const warnings = [];
996
+ const fixes = [];
997
+ if (!this.accessToken) {
998
+ missing.push({
999
+ code: "missing_access_token",
1000
+ message: "Create the Receiz client with a delegated Connect/OIDC bearer token before using wallet, media, commerce, events, domains, or jobs.",
1001
+ fixUrl: `${this.baseUrl}/developers/connect`,
1002
+ });
1003
+ }
1004
+ if (!options.tenantHost) {
1005
+ warnings.push({
1006
+ code: "missing_tenant_host",
1007
+ message: "Pass tenantHost so app-state, commerce, domain, permission, and customer-session helpers can scope correctly.",
1008
+ fixUrl: `${this.baseUrl}/developers/app-state`,
1009
+ });
1010
+ }
1011
+ if (!options.callbackUrl) {
1012
+ warnings.push({
1013
+ code: "missing_callback_url",
1014
+ message: "Pass callbackUrl to verify your Connect/OIDC redirect configuration before users hit the authorize screen.",
1015
+ fixUrl: `${this.baseUrl}/developers/connect`,
1016
+ });
1017
+ }
1018
+ const scopes = new Set(options.scopes ?? []);
1019
+ for (const requiredScope of ["openid", "profile", "receiz:record"]) {
1020
+ if (!scopes.has(requiredScope)) {
1021
+ warnings.push({
1022
+ code: "missing_scope",
1023
+ message: `Recommended scope missing: ${requiredScope}.`,
1024
+ fixUrl: `${this.baseUrl}/developers/apps`,
1025
+ });
1026
+ }
1027
+ }
1028
+ fixes.push(...missing, ...warnings);
1029
+ return {
1030
+ ok: missing.length === 0,
1031
+ schema: "receiz.sdk.doctor.v1",
1032
+ baseUrl: this.baseUrl,
1033
+ ...(capabilities.tenantHost ? { tenantHost: capabilities.tenantHost } : {}),
1034
+ capabilities: capabilities.capabilities,
1035
+ missing,
1036
+ warnings,
1037
+ fixes,
1038
+ };
1039
+ }
575
1040
  async publishAppState(feed, options = {}) {
576
- const requestOptions = { method: "POST", body: feed };
577
- if (options.signature)
578
- requestOptions.headers = { "x-public-proof-feed-signature": options.signature };
1041
+ const requestOptions = {
1042
+ method: "POST",
1043
+ body: feed,
1044
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
1045
+ };
1046
+ if (options.signature) {
1047
+ const headers = headersWithIdempotency(requestOptions.headers, options.idempotencyKey);
1048
+ headers.set("x-public-proof-feed-signature", options.signature);
1049
+ requestOptions.headers = headers;
1050
+ }
579
1051
  return this.request("/api/public-proof/registry/feed", requestOptions);
580
1052
  }
1053
+ async publishAppStateInput(input, options = {}) {
1054
+ if (isReceizAppStateFeed(input)) {
1055
+ return this.publishAppState(input, options);
1056
+ }
1057
+ const record = createReceizTenantAppStateRecord(input);
1058
+ const publishOptions = {};
1059
+ if (options.signature)
1060
+ publishOptions.signature = options.signature;
1061
+ const idempotencyKey = options.idempotencyKey ?? input.idempotencyKey;
1062
+ if (idempotencyKey)
1063
+ publishOptions.idempotencyKey = idempotencyKey;
1064
+ const feedOptions = { schema: RECEIZ_PUBLIC_STORE_STATE_FEED_SCHEMA };
1065
+ if (record.namespace)
1066
+ feedOptions.namespace = record.namespace;
1067
+ if (record.externalCreatorId)
1068
+ feedOptions.externalCreatorId = record.externalCreatorId;
1069
+ return this.publishAppState(createReceizAppStateFeed([record], feedOptions), publishOptions);
1070
+ }
581
1071
  async publishAppStateRecord(record, options = {}) {
582
- const appStateRecord = createReceizAppStateRecord(record);
583
- const feed = {
1072
+ const feed = createReceizAppStateFeed([record], {
584
1073
  schema: options.feedSchema ?? RECEIZ_APP_STATE_FEED_SCHEMA,
585
- records: [appStateRecord],
586
- };
587
- if (options.namespace)
588
- feed.namespace = options.namespace;
589
- if (options.externalCreatorId)
590
- feed.externalCreatorId = options.externalCreatorId;
1074
+ ...(options.namespace ? { namespace: options.namespace } : {}),
1075
+ ...(options.externalCreatorId ? { externalCreatorId: options.externalCreatorId } : {}),
1076
+ });
591
1077
  return this.publishAppState(feed, options.signature ? { signature: options.signature } : {});
592
1078
  }
593
1079
  async readAppStateByUrl(url) {
@@ -605,6 +1091,156 @@ export class ReceizClient {
605
1091
  async readAppStateById(id) {
606
1092
  return this.request(`/api/public-proof/${encodePathSegment(id)}`);
607
1093
  }
1094
+ async restoreAppStateByUrl(url, options = {}) {
1095
+ const response = await this.readAppStateByUrl(url);
1096
+ return restoreReceizAppStateProjection(response, options);
1097
+ }
1098
+ async restoreAppStateByHost(host, options = {}) {
1099
+ return this.restoreAppStateByUrl(receizAppStateUrlFromHost(host), options);
1100
+ }
1101
+ async restoreAppStateById(id, options = {}) {
1102
+ try {
1103
+ const response = await this.readAppStateById(id);
1104
+ return restoreReceizAppStateProjection(response, options);
1105
+ }
1106
+ catch (error) {
1107
+ if (error instanceof ReceizHttpError && error.status === 404) {
1108
+ return {
1109
+ ok: false,
1110
+ record: null,
1111
+ data: null,
1112
+ error: "app_state_not_found",
1113
+ };
1114
+ }
1115
+ throw error;
1116
+ }
1117
+ }
1118
+ async resolveAppState(input) {
1119
+ const options = {
1120
+ ...(input.schema ? { schema: input.schema } : {}),
1121
+ ...(input.state ? { state: input.state } : {}),
1122
+ ...(input.requiredDataKey ? { requiredDataKey: input.requiredDataKey } : {}),
1123
+ };
1124
+ if (input.id)
1125
+ return this.restoreAppStateById(input.id, options);
1126
+ if (input.url)
1127
+ return this.restoreAppStateByUrl(input.url, options);
1128
+ if (input.host || input.tenantHost)
1129
+ return this.restoreAppStateByHost(input.host ?? input.tenantHost ?? "", options);
1130
+ if (input.namespace) {
1131
+ const response = await this.readAppStateByNamespace(input.namespace);
1132
+ const records = response.records;
1133
+ return { ok: response.ok, record: records[0] ?? null, data: null, records };
1134
+ }
1135
+ const creator = input.creatorReceizId ?? input.receizId;
1136
+ if (creator) {
1137
+ const response = await this.readAppStateByCreator(creator);
1138
+ const records = response.records;
1139
+ return { ok: response.ok, record: records[0] ?? null, data: null, records };
1140
+ }
1141
+ throw new ReceizValidationError("receiz.appState.resolve", ["id, url, host, tenantHost, namespace, creatorReceizId, or receizId is required"]);
1142
+ }
1143
+ async oneClickCheckout(body) {
1144
+ const tenantHost = normalizeReceizHost(body.tenantHost);
1145
+ const totalUsdCents = usdToCents(body.amountUsd);
1146
+ const walletFirst = body.walletFirst !== false;
1147
+ const cardFallback = body.cardFallback !== false;
1148
+ const wallet = walletFirst ? await this.delegated("/api/connect/wallet/me") : null;
1149
+ const walletAvailable = walletFirst ? stringToCents(wallet?.balanceUsdCents) : 0n;
1150
+ const walletApplied = walletFirst ? (walletAvailable > totalUsdCents ? totalUsdCents : walletAvailable) : 0n;
1151
+ const cardDelta = totalUsdCents - walletApplied;
1152
+ let checkoutSession = null;
1153
+ if (cardDelta > 0n) {
1154
+ if (!cardFallback) {
1155
+ const response = {
1156
+ ok: false,
1157
+ tenantHost,
1158
+ funding: {
1159
+ totalUsdCents: String(totalUsdCents),
1160
+ walletAppliedUsdCents: String(walletApplied),
1161
+ cardDeltaUsdCents: String(cardDelta),
1162
+ walletFirst,
1163
+ cardFallback,
1164
+ },
1165
+ wallet,
1166
+ checkoutSession: null,
1167
+ proofObject: null,
1168
+ events: [{ type: "checkout.card_delta_required", cardDeltaUsdCents: String(cardDelta) }],
1169
+ error: "card_delta_required",
1170
+ };
1171
+ if (body.orderId)
1172
+ response.orderId = body.orderId;
1173
+ return response;
1174
+ }
1175
+ const checkoutBody = {
1176
+ amountUsd: centsToUsd(cardDelta),
1177
+ currency: body.currency ?? "usd",
1178
+ uiMode: "embedded",
1179
+ description: `Receiz one-click checkout for ${tenantHost}`,
1180
+ tenantHost,
1181
+ };
1182
+ if (body.orderId)
1183
+ checkoutBody.referenceId = body.orderId;
1184
+ if (body.customerEmail)
1185
+ checkoutBody.customerEmail = body.customerEmail;
1186
+ if (body.successUrl)
1187
+ checkoutBody.successUrl = body.successUrl;
1188
+ if (body.cancelUrl)
1189
+ checkoutBody.cancelUrl = body.cancelUrl;
1190
+ if (body.cart)
1191
+ checkoutBody.cart = body.cart;
1192
+ checkoutSession = await this.delegated("/api/connect/payments/checkout", {
1193
+ method: "POST",
1194
+ body: bodyWithIdempotency(checkoutBody, body.idempotencyKey),
1195
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
1196
+ });
1197
+ }
1198
+ const response = {
1199
+ ok: true,
1200
+ tenantHost,
1201
+ funding: {
1202
+ totalUsdCents: String(totalUsdCents),
1203
+ walletAppliedUsdCents: String(walletApplied),
1204
+ cardDeltaUsdCents: String(cardDelta),
1205
+ walletFirst,
1206
+ cardFallback,
1207
+ },
1208
+ wallet,
1209
+ checkoutSession,
1210
+ proofObject: checkoutSession?.proofBundle ?? null,
1211
+ events: [
1212
+ { type: "wallet.summary.read", walletAppliedUsdCents: String(walletApplied) },
1213
+ ...(checkoutSession ? [{ type: "checkout.session.created", checkoutSessionId: checkoutSession.checkoutSessionId ?? null }] : []),
1214
+ ],
1215
+ };
1216
+ if (body.orderId)
1217
+ response.orderId = body.orderId;
1218
+ return response;
1219
+ }
1220
+ async uploadMedia(file, options = {}) {
1221
+ const form = new FormData();
1222
+ form.set("file", file, options.filename ?? "receiz-media");
1223
+ if (options.tenantHost)
1224
+ form.set("tenantHost", normalizeReceizHost(options.tenantHost));
1225
+ if (options.purpose)
1226
+ form.set("purpose", options.purpose);
1227
+ if (options.metadata)
1228
+ form.set("metadata", JSON.stringify(options.metadata));
1229
+ if (options.idempotencyKey)
1230
+ form.set("idempotencyKey", options.idempotencyKey);
1231
+ return this.delegated("/api/connect/media/upload", {
1232
+ method: "POST",
1233
+ body: form,
1234
+ headers: headersWithIdempotency(undefined, options.idempotencyKey),
1235
+ });
1236
+ }
1237
+ async eventSubscribe(body) {
1238
+ return this.delegated("/api/connect/events/subscribe", {
1239
+ method: "POST",
1240
+ body: bodyWithIdempotency(body, body.idempotencyKey),
1241
+ headers: headersWithIdempotency(undefined, body.idempotencyKey),
1242
+ });
1243
+ }
608
1244
  async sportsConformance() {
609
1245
  return this.request("/api/game/sports/conformance");
610
1246
  }
@@ -616,6 +1252,14 @@ export class ReceizClient {
616
1252
  throw new Error("receiz_access_token_required");
617
1253
  return this.request(path, { ...options, bearerToken: this.accessToken });
618
1254
  }
1255
+ async delegatedWrite(path, body = {}, options = {}) {
1256
+ const idempotencyKey = options.idempotencyKey ?? (typeof body.idempotencyKey === "string" ? body.idempotencyKey : undefined);
1257
+ return this.delegated(path, {
1258
+ method: "POST",
1259
+ body: bodyWithIdempotency(body, idempotencyKey),
1260
+ headers: headersWithIdempotency(undefined, idempotencyKey),
1261
+ });
1262
+ }
619
1263
  async delegatedBlob(path, options = {}) {
620
1264
  if (!this.accessToken)
621
1265
  throw new Error("receiz_access_token_required");
@@ -1450,6 +2094,213 @@ export function receizProofMemoryAdditionsQuery(value, limit) {
1450
2094
  ...(limit === undefined ? {} : { limit }),
1451
2095
  };
1452
2096
  }
2097
+ function normalizeOfflineQueueItem(value) {
2098
+ const record = ensureRecord(value, "ReceizOfflineProofQueueItem");
2099
+ const issues = [];
2100
+ const id = ensureString(record, "id", issues);
2101
+ const kind = ensureString(record, "kind", issues);
2102
+ if (!isRecord(record.payload))
2103
+ issues.push("payload must be an object");
2104
+ const attempts = record.attempts;
2105
+ if (attempts !== undefined && (typeof attempts !== "number" || !Number.isInteger(attempts) || attempts < 0)) {
2106
+ issues.push("attempts must be a non-negative integer when present");
2107
+ }
2108
+ const item = {
2109
+ id: id ?? "",
2110
+ kind: (kind ?? ""),
2111
+ payload: isRecord(record.payload) ? record.payload : {},
2112
+ createdAt: typeof record.createdAt === "string" && record.createdAt.trim() ? record.createdAt : new Date().toISOString(),
2113
+ attempts: typeof attempts === "number" ? attempts : 0,
2114
+ };
2115
+ if (typeof record.idempotencyKey === "string" && record.idempotencyKey.trim())
2116
+ item.idempotencyKey = record.idempotencyKey;
2117
+ if (record.lastError === null || isRecord(record.lastError))
2118
+ item.lastError = record.lastError;
2119
+ failIfIssues("ReceizOfflineProofQueueItem", issues);
2120
+ return item;
2121
+ }
2122
+ export function assertReceizOfflineProofQueueSnapshot(value) {
2123
+ const record = ensureRecord(value, "ReceizOfflineProofQueueSnapshot");
2124
+ const issues = [];
2125
+ if (record.schema !== "receiz.sdk.offline_proof_queue.v1")
2126
+ issues.push("schema must be receiz.sdk.offline_proof_queue.v1");
2127
+ const ownerId = record.ownerId;
2128
+ if (ownerId !== null && ownerId !== undefined && typeof ownerId !== "string") {
2129
+ issues.push("ownerId must be a string or null when present");
2130
+ }
2131
+ const createdAt = ensureString(record, "createdAt", issues);
2132
+ const updatedAt = ensureString(record, "updatedAt", issues);
2133
+ if (!Array.isArray(record.pending))
2134
+ issues.push("pending must be an array");
2135
+ if (!Array.isArray(record.settled))
2136
+ issues.push("settled must be an array");
2137
+ if (!Array.isArray(record.failed))
2138
+ issues.push("failed must be an array");
2139
+ failIfIssues("ReceizOfflineProofQueueSnapshot", issues);
2140
+ return {
2141
+ schema: "receiz.sdk.offline_proof_queue.v1",
2142
+ ownerId: typeof ownerId === "string" || ownerId === null ? ownerId : null,
2143
+ createdAt: createdAt ?? "",
2144
+ updatedAt: updatedAt ?? "",
2145
+ pending: Array.isArray(record.pending) ? record.pending.map(normalizeOfflineQueueItem) : [],
2146
+ settled: Array.isArray(record.settled) ? record.settled.map(normalizeOfflineQueueItem) : [],
2147
+ failed: Array.isArray(record.failed) ? record.failed.map(normalizeOfflineQueueItem) : [],
2148
+ };
2149
+ }
2150
+ function parseOfflineProofQueueStorageValue(value) {
2151
+ if (value === null || value === undefined)
2152
+ return null;
2153
+ if (typeof value === "string") {
2154
+ const trimmed = value.trim();
2155
+ if (!trimmed)
2156
+ return null;
2157
+ return assertReceizOfflineProofQueueSnapshot(JSON.parse(trimmed));
2158
+ }
2159
+ return assertReceizOfflineProofQueueSnapshot(value);
2160
+ }
2161
+ export class ReceizOfflineProofQueue {
2162
+ ownerId;
2163
+ createdAt;
2164
+ pendingItems;
2165
+ settledItems;
2166
+ failedItems;
2167
+ storage;
2168
+ constructor(options = {}) {
2169
+ const snapshot = options.snapshot ? assertReceizOfflineProofQueueSnapshot(options.snapshot) : null;
2170
+ this.ownerId = options.ownerId ?? snapshot?.ownerId ?? null;
2171
+ this.createdAt = snapshot?.createdAt ?? new Date().toISOString();
2172
+ this.pendingItems = snapshot?.pending ? [...snapshot.pending] : [];
2173
+ this.settledItems = snapshot?.settled ? [...snapshot.settled] : [];
2174
+ this.failedItems = snapshot?.failed ? [...snapshot.failed] : [];
2175
+ this.storage = options.storage ?? null;
2176
+ }
2177
+ enqueue(item) {
2178
+ const normalized = normalizeOfflineQueueItem(item);
2179
+ if (this.pendingItems.some((pending) => pending.id === normalized.id))
2180
+ return this;
2181
+ if (this.settledItems.some((settled) => settled.id === normalized.id))
2182
+ return this;
2183
+ this.pendingItems.push(normalized);
2184
+ return this;
2185
+ }
2186
+ snapshot() {
2187
+ return {
2188
+ schema: "receiz.sdk.offline_proof_queue.v1",
2189
+ ownerId: this.ownerId,
2190
+ createdAt: this.createdAt,
2191
+ updatedAt: new Date().toISOString(),
2192
+ pending: [...this.pendingItems],
2193
+ settled: [...this.settledItems],
2194
+ failed: [...this.failedItems],
2195
+ };
2196
+ }
2197
+ async flush() {
2198
+ const snapshot = this.snapshot();
2199
+ if (this.storage)
2200
+ await this.storage.write(snapshot);
2201
+ return snapshot;
2202
+ }
2203
+ async clear() {
2204
+ this.pendingItems = [];
2205
+ this.settledItems = [];
2206
+ this.failedItems = [];
2207
+ if (this.storage?.remove)
2208
+ await this.storage.remove();
2209
+ }
2210
+ async replay(client) {
2211
+ const originalPending = [...this.pendingItems];
2212
+ let settled = 0;
2213
+ let failed = 0;
2214
+ this.pendingItems = [];
2215
+ for (const item of originalPending) {
2216
+ try {
2217
+ await replayOfflineProofQueueItem(client, item);
2218
+ this.settledItems.push({ ...item, attempts: (item.attempts ?? 0) + 1, lastError: null });
2219
+ settled += 1;
2220
+ }
2221
+ catch (error) {
2222
+ const failedItem = {
2223
+ ...item,
2224
+ attempts: (item.attempts ?? 0) + 1,
2225
+ lastError: errorDetail(error),
2226
+ };
2227
+ this.failedItems.push(failedItem);
2228
+ failed += 1;
2229
+ }
2230
+ }
2231
+ await this.flush();
2232
+ return { ok: failed === 0, settled, failed };
2233
+ }
2234
+ toJSON() {
2235
+ return this.snapshot();
2236
+ }
2237
+ }
2238
+ async function replayOfflineProofQueueItem(client, item) {
2239
+ if (item.kind === "app_state.publish") {
2240
+ return client.appState.publish(item.payload, item.idempotencyKey ? { idempotencyKey: item.idempotencyKey } : {});
2241
+ }
2242
+ if (item.kind === "connect.record") {
2243
+ return client.request("/api/connect/record", {
2244
+ method: "POST",
2245
+ body: bodyWithIdempotency(item.payload, item.idempotencyKey),
2246
+ headers: headersWithIdempotency(undefined, item.idempotencyKey),
2247
+ });
2248
+ }
2249
+ if (item.kind === "webhook.event") {
2250
+ return client.request("/api/connect/webhooks/replay", {
2251
+ method: "POST",
2252
+ body: bodyWithIdempotency(item.payload, item.idempotencyKey),
2253
+ headers: headersWithIdempotency(undefined, item.idempotencyKey),
2254
+ });
2255
+ }
2256
+ return client.request("/api/connect/offline/replay", {
2257
+ method: "POST",
2258
+ body: bodyWithIdempotency({ kind: item.kind, payload: item.payload }, item.idempotencyKey),
2259
+ headers: headersWithIdempotency(undefined, item.idempotencyKey),
2260
+ });
2261
+ }
2262
+ export async function createReceizOfflineProofQueue(options = {}) {
2263
+ const storedSnapshot = options.storage ? parseOfflineProofQueueStorageValue(await options.storage.read()) : null;
2264
+ const queueOptions = {
2265
+ ownerId: options.ownerId ?? storedSnapshot?.ownerId ?? null,
2266
+ };
2267
+ if (options.storage)
2268
+ queueOptions.storage = options.storage;
2269
+ const snapshot = options.snapshot ?? storedSnapshot;
2270
+ if (snapshot)
2271
+ queueOptions.snapshot = snapshot;
2272
+ return new ReceizOfflineProofQueue(queueOptions);
2273
+ }
2274
+ export function createReceizInMemoryOfflineProofQueueStorage(initialValue = null) {
2275
+ let storedText = typeof initialValue === "string"
2276
+ ? initialValue
2277
+ : initialValue
2278
+ ? JSON.stringify(assertReceizOfflineProofQueueSnapshot(initialValue))
2279
+ : null;
2280
+ return {
2281
+ read: () => storedText,
2282
+ write: (snapshot) => {
2283
+ storedText = JSON.stringify(assertReceizOfflineProofQueueSnapshot(snapshot));
2284
+ },
2285
+ remove: () => {
2286
+ storedText = null;
2287
+ },
2288
+ readText: () => storedText,
2289
+ };
2290
+ }
2291
+ export function createReceizLocalStorageOfflineProofQueueStorage(key = "receiz:offline-proof-queue:v1", storage = globalThis.localStorage) {
2292
+ if (!storage)
2293
+ throw new Error("receiz_local_storage_unavailable");
2294
+ return {
2295
+ read: () => storage.getItem(key),
2296
+ write: (snapshot) => {
2297
+ storage.setItem(key, JSON.stringify(assertReceizOfflineProofQueueSnapshot(snapshot)));
2298
+ },
2299
+ remove: () => {
2300
+ storage.removeItem(key);
2301
+ },
2302
+ };
2303
+ }
1453
2304
  function bodyToString(body) {
1454
2305
  if (typeof body === "string")
1455
2306
  return body;