@voltro/plugin-economy 0.0.0-stage → 0.88.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.
Files changed (177) hide show
  1. package/CHANGELOG.md +24492 -0
  2. package/LICENSE +61 -0
  3. package/README.md +2719 -2
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +9841 -0
  6. package/dist/amount.js +20 -0
  7. package/dist/appleIap.server.js +262 -0
  8. package/dist/appleIapNotifications.server.js +162 -0
  9. package/dist/appleIapTrust.server.js +30 -0
  10. package/dist/bundleValue.server.js +112 -0
  11. package/dist/canonicalDataJson.js +10 -0
  12. package/dist/capture.js +53 -0
  13. package/dist/codePort.fixture.js +123 -0
  14. package/dist/codeStore.server.js +439 -0
  15. package/dist/codeTables.entity.js +43 -0
  16. package/dist/codes.d.ts +150 -0
  17. package/dist/codes.js +82 -0
  18. package/dist/components.d.ts +397 -0
  19. package/dist/components.js +283 -0
  20. package/dist/consistency.server.js +115 -0
  21. package/dist/containerStore.server.js +687 -0
  22. package/dist/containerStore.testFixtures.js +75 -0
  23. package/dist/containerTables.entity.js +45 -0
  24. package/dist/containers.d.ts +311 -0
  25. package/dist/containers.fixture.js +128 -0
  26. package/dist/containers.js +214 -0
  27. package/dist/containers.server.js +39 -0
  28. package/dist/containersPort.fixture.js +74 -0
  29. package/dist/currency.js +32 -0
  30. package/dist/declarationFixtures.js +61 -0
  31. package/dist/economy-app/events/economy.refundReversed.event.ts +1 -0
  32. package/dist/economy-app/events/economy.refunded.event.ts +1 -0
  33. package/dist/economyPort.fixture.js +124 -0
  34. package/dist/errors.d.ts +14 -0
  35. package/dist/errors.js +21 -0
  36. package/dist/events.d.ts +46 -0
  37. package/dist/events.js +1 -0
  38. package/dist/googleIap.server.js +391 -0
  39. package/dist/googleIapNotifications.server.js +130 -0
  40. package/dist/googleIapSubscription.server.js +183 -0
  41. package/dist/iap.d.ts +309 -0
  42. package/dist/iap.js +303 -0
  43. package/dist/iap.testFixtures.js +144 -0
  44. package/dist/iapAccountTables.entity.js +39 -0
  45. package/dist/iapAccounts.server.js +200 -0
  46. package/dist/iapBinding.server.js +523 -0
  47. package/dist/iapCheckout.server.js +97 -0
  48. package/dist/iapCompletion.server.js +55 -0
  49. package/dist/iapCurrentState.server.js +107 -0
  50. package/dist/iapCurrentState.testFixtures.js +22 -0
  51. package/dist/iapCurrentStatePort.fixture.js +63 -0
  52. package/dist/iapEntitlements.d.ts +79 -0
  53. package/dist/iapEntitlements.js +57 -0
  54. package/dist/iapEntitlements.server.js +48 -0
  55. package/dist/iapEntitlementsBinding.server.js +105 -0
  56. package/dist/iapEntitlementsPort.fixture.js +80 -0
  57. package/dist/iapInstalledEntitlements.testFixtures.js +75 -0
  58. package/dist/iapInstalledNotifications.testFixtures.js +93 -0
  59. package/dist/iapInventoryRefunds.server.js +261 -0
  60. package/dist/iapNotificationCustodyPort.fixture.js +67 -0
  61. package/dist/iapNotificationPort.fixture.js +73 -0
  62. package/dist/iapNotificationProcessor.server.js +134 -0
  63. package/dist/iapNotificationProcessor.testFixtures.js +63 -0
  64. package/dist/iapNotificationProcessorPort.fixture.js +55 -0
  65. package/dist/iapNotificationSchedule.server.js +69 -0
  66. package/dist/iapNotificationStore.server.js +352 -0
  67. package/dist/iapNotificationStore.testFixtures.js +31 -0
  68. package/dist/iapNotificationTables.entity.js +48 -0
  69. package/dist/iapNotificationTarget.server.js +36 -0
  70. package/dist/iapNotificationTarget.testFixtures.js +29 -0
  71. package/dist/iapNotifications.d.ts +644 -0
  72. package/dist/iapNotifications.js +6 -0
  73. package/dist/iapNotifications.server.js +95 -0
  74. package/dist/iapNotificationsBinding.server.js +232 -0
  75. package/dist/iapNotificationsSystemPort.fixture.js +50 -0
  76. package/dist/iapPlugin.testFixtures.js +59 -0
  77. package/dist/iapPort.fixture.js +74 -0
  78. package/dist/iapProviderCapture.server.js +17 -0
  79. package/dist/iapRecovery.d.ts +146 -0
  80. package/dist/iapRecovery.js +89 -0
  81. package/dist/iapRefundEventDelivery.server.js +82 -0
  82. package/dist/iapRefundEvents.js +44 -0
  83. package/dist/iapRefundEventsPort.fixture.js +59 -0
  84. package/dist/iapRefundPort.fixture.js +54 -0
  85. package/dist/iapRefundTables.entity.js +55 -0
  86. package/dist/iapRefunds.d.ts +755 -0
  87. package/dist/iapRefunds.js +6 -0
  88. package/dist/iapRefunds.server.js +553 -0
  89. package/dist/iapRefunds.testFixtures.js +64 -0
  90. package/dist/iapSpendAdmission.server.js +98 -0
  91. package/dist/iapStore.server.js +740 -0
  92. package/dist/iapStore.testFixtures.js +127 -0
  93. package/dist/iapTables.entity.js +80 -0
  94. package/dist/iapVerification.server.js +120 -0
  95. package/dist/index.d.ts +2984 -0
  96. package/dist/index.js +15 -0
  97. package/dist/intentionRecovery.js +133 -0
  98. package/dist/ledger.js +77 -0
  99. package/dist/loyalty.d.ts +274 -0
  100. package/dist/loyalty.fixture.js +92 -0
  101. package/dist/loyalty.js +311 -0
  102. package/dist/loyaltyBinding.server.js +203 -0
  103. package/dist/loyaltyPort.fixture.js +86 -0
  104. package/dist/loyaltyShopSource.server.js +90 -0
  105. package/dist/loyaltyStore.server.js +552 -0
  106. package/dist/loyaltyStore.testFixtures.js +82 -0
  107. package/dist/loyaltyTables.entity.js +49 -0
  108. package/dist/offerMerchandising.fixture.js +114 -0
  109. package/dist/offerMerchandising.js +282 -0
  110. package/dist/offerPolicies.d.ts +441 -0
  111. package/dist/offerPolicies.fixture.js +53 -0
  112. package/dist/offerPolicies.js +267 -0
  113. package/dist/piggyBanks.d.ts +272 -0
  114. package/dist/piggyBanks.fixture.js +70 -0
  115. package/dist/piggyBanks.js +200 -0
  116. package/dist/piggyBinding.server.js +204 -0
  117. package/dist/piggyPort.fixture.js +72 -0
  118. package/dist/piggyStore.server.js +610 -0
  119. package/dist/piggyStore.testFixtures.js +97 -0
  120. package/dist/piggyTables.entity.js +40 -0
  121. package/dist/plugin.js +1047 -0
  122. package/dist/port.js +12 -0
  123. package/dist/price.d.ts +99 -0
  124. package/dist/price.js +143 -0
  125. package/dist/purchaseRecovery.js +55 -0
  126. package/dist/reports.server.js +114 -0
  127. package/dist/rpc.d.ts +3201 -0
  128. package/dist/rpc.js +1200 -0
  129. package/dist/sanctionsTradeAuthority.server.js +79 -0
  130. package/dist/server.d.ts +3856 -0
  131. package/dist/server.js +43 -0
  132. package/dist/shop.d.ts +279 -0
  133. package/dist/shop.js +195 -0
  134. package/dist/shop.testFixtures.js +113 -0
  135. package/dist/shopPort.fixture.js +71 -0
  136. package/dist/shopPresentation.js +67 -0
  137. package/dist/shopStore.server.js +623 -0
  138. package/dist/shopTables.entity.js +62 -0
  139. package/dist/shopUiCopy.js +56 -0
  140. package/dist/spendLimits.d.ts +140 -0
  141. package/dist/spendLimits.fixture.js +52 -0
  142. package/dist/spendLimits.js +154 -0
  143. package/dist/spendLimitsPort.fixture.js +119 -0
  144. package/dist/subscriptionBinding.server.js +210 -0
  145. package/dist/subscriptionStore.server.js +642 -0
  146. package/dist/subscriptionStore.testFixtures.js +71 -0
  147. package/dist/subscriptionTables.entity.js +36 -0
  148. package/dist/subscriptions.d.ts +394 -0
  149. package/dist/subscriptions.fixture.js +96 -0
  150. package/dist/subscriptions.js +212 -0
  151. package/dist/subscriptionsPort.fixture.js +75 -0
  152. package/dist/systemFixtures.js +88 -0
  153. package/dist/tables.entity.js +82 -0
  154. package/dist/testing.d.ts +4615 -0
  155. package/dist/testing.js +29 -0
  156. package/dist/tradeAuthority.server.js +171 -0
  157. package/dist/tradeBinding.server.js +200 -0
  158. package/dist/tradeComponents.js +341 -0
  159. package/dist/tradeEscrow.server.js +231 -0
  160. package/dist/tradeEscrow.testFixtures.js +132 -0
  161. package/dist/tradeExpiry.server.js +42 -0
  162. package/dist/tradeRecovery.js +47 -0
  163. package/dist/tradeStore.server.js +749 -0
  164. package/dist/tradeStore.testFixtures.js +72 -0
  165. package/dist/tradeTables.entity.js +65 -0
  166. package/dist/tradeUiCopy.js +78 -0
  167. package/dist/trades.d.ts +246 -0
  168. package/dist/trades.fixture.js +102 -0
  169. package/dist/trades.js +288 -0
  170. package/dist/tradesPort.fixture.js +93 -0
  171. package/dist/walletEscrow.js +105 -0
  172. package/dist/walletEscrow.server.js +375 -0
  173. package/dist/walletKeys.server.js +4 -0
  174. package/dist/walletStore.js +570 -0
  175. package/dist/web.d.ts +2257 -0
  176. package/dist/web.js +8 -0
  177. package/package.json +161 -4
@@ -0,0 +1,63 @@
1
+ import { economyError as e } from "./errors.js";
2
+ import { iapNotificationTables as t } from "./iapNotificationTables.entity.js";
3
+ import { iapCurrentStatePort as n } from "./iapCurrentState.server.js";
4
+ import { dataStoreIapNotificationCustody as r } from "./iapNotificationStore.server.js";
5
+ import { iapNotificationProcessor as i } from "./iapNotificationProcessor.server.js";
6
+ import { iapRefundsFixture as a } from "./iapRefunds.testFixtures.js";
7
+ import { Effect as o } from "effect";
8
+ import { randomBytes as s } from "node:crypto";
9
+ import { ensureTableRegistered as c } from "@voltro/database";
10
+ import { makeFieldCipher as l } from "@voltro/runtime";
11
+ // src/iapNotificationProcessor.testFixtures.ts
12
+ var u = async (u, d = "test.notice.processor") => {
13
+ t.forEach(c);
14
+ let f = await a(u, d), p = l(s(32).toString("base64url")), m = f.f.at + 1, h = 0, g = !1, _ = r(u, {
15
+ namespace: d,
16
+ deployment: f.n.options,
17
+ orders: f.f.iap,
18
+ bodyRetentionMs: 1e3,
19
+ cipher: { read: () => p },
20
+ now: () => m
21
+ }), v = n({
22
+ ...f.n.options,
23
+ accounts: f.f.accounts,
24
+ now: () => m
25
+ }, {
26
+ initialize: () => o.void,
27
+ read: (t) => (h++, g ? o.fail(e("unavailable")) : o.succeed({
28
+ orderId: t.orderId,
29
+ channel: "ios",
30
+ state: "revoked",
31
+ refundNumerator: 1,
32
+ refundDenominator: 1,
33
+ observedAt: t.at,
34
+ revokedAt: null
35
+ }))
36
+ }), y = {
37
+ deployment: f.n.options,
38
+ notifications: {
39
+ initialize: () => o.void,
40
+ verify: (t) => t.body === "authenticated-notice-fixture" ? o.succeed(f.n.evidence) : o.fail(e("forbidden"))
41
+ },
42
+ current: v,
43
+ custody: _,
44
+ refunds: f.refunds,
45
+ orders: f.f.iap
46
+ };
47
+ return {
48
+ ...f,
49
+ custody: _,
50
+ current: v,
51
+ options: y,
52
+ processor: i(y),
53
+ calls: () => h,
54
+ failProvider: (e) => {
55
+ g = e;
56
+ },
57
+ advance: (e) => {
58
+ m += e;
59
+ },
60
+ body: "authenticated-notice-fixture"
61
+ };
62
+ };
63
+ export { u as iapNotificationProcessorFixture };
@@ -0,0 +1,55 @@
1
+ import { economyError as e } from "./errors.js";
2
+ import { IapNotificationProcessorPort as t } from "./iapNotificationProcessor.server.js";
3
+ import { iapNotificationProcessorFixture as n } from "./iapNotificationProcessor.testFixtures.js";
4
+ import { Effect as r, Layer as i } from "effect";
5
+ import { InMemoryDataStore as a } from "@voltro/runtime";
6
+ // src/iapNotificationProcessorPort.fixture.ts
7
+ var o = /* @__PURE__ */ new WeakMap(), s = r.tryPromise({
8
+ try: () => n(new a()),
9
+ catch: () => e("unavailable")
10
+ }).pipe(r.map((t) => {
11
+ let n = !1, i = (t) => r.suspend(() => n ? r.fail(e("unavailable")) : t()), a = Object.freeze({
12
+ initialize: () => i(t.processor.initialize),
13
+ ingest: (e) => i(() => t.processor.ingest(e)),
14
+ process: (e) => i(() => t.processor.process(e)),
15
+ pending: () => i(t.processor.pending),
16
+ prune: () => i(t.processor.prune),
17
+ exportSubject: (e) => i(() => t.processor.exportSubject(e)),
18
+ eraseSubject: (e) => i(() => t.processor.eraseSubject(e))
19
+ });
20
+ o.set(a, t);
21
+ let s = r.sync(() => {
22
+ n = !0;
23
+ }).pipe(r.flatMap(() => t.f.accounts.eraseSubject(t.privacy)), r.flatMap(() => t.custody.eraseSubject(t.privacy)));
24
+ return Object.freeze({
25
+ service: a,
26
+ body: t.body,
27
+ privacy: t.privacy,
28
+ close: s
29
+ });
30
+ })), c = i.effect(t, r.acquireRelease(s, (e) => e.close.pipe(r.orDie)).pipe(r.map((e) => e.service))), l = Object.freeze({
31
+ kind: "port",
32
+ tag: t,
33
+ fakeLayer: c,
34
+ probe: {
35
+ run: () => r.scoped(r.gen(function* () {
36
+ let n = yield* t, i = o.get(n);
37
+ if (!i) return yield* r.fail(e("unavailable"));
38
+ yield* n.initialize();
39
+ let a = yield* n.ingest({ body: i.body }), s = yield* n.ingest({ body: i.body }), c = yield* n.pending();
40
+ return {
41
+ processed: a?.state === "processed",
42
+ replay: s !== null && s.id === a?.id,
43
+ pending: c.length,
44
+ providerCalls: i.calls()
45
+ };
46
+ }).pipe(r.provide(c))),
47
+ expected: {
48
+ processed: !0,
49
+ replay: !0,
50
+ pending: 0,
51
+ providerCalls: 1
52
+ }
53
+ }
54
+ });
55
+ export { c as iapNotificationProcessorFakeLayer, s as iapNotificationProcessorPortFixture, l as iapNotificationProcessorPortSystemFixture };
@@ -0,0 +1,69 @@
1
+ import { economyError as e } from "./errors.js";
2
+ import { captureBoundMethods as t, dataObject as n, identifier as r } from "./capture.js";
3
+ import { shopInteger as i } from "./shop.js";
4
+ import { iapChannel as a } from "./iap.js";
5
+ import { captureIapNotificationProcessorService as o } from "./iapNotificationProcessor.server.js";
6
+ import { Effect as s } from "effect";
7
+ import { defineSchedule as c } from "@voltro/runtime";
8
+ // src/iapNotificationSchedule.server.ts
9
+ var l = (t) => {
10
+ let r = n(t, ["processor", "maxNotices"]), a = o(r.processor), c = i(r.maxNotices ?? 1e3, 1, 1e5);
11
+ return s.gen(function* () {
12
+ let t = yield* a.prune(), n = yield* a.pending();
13
+ if (!Number.isSafeInteger(t) || t < 0 || t > 1e5 || n.length > c) return yield* s.fail(e("limit_exceeded"));
14
+ if (new Set(n.map((e) => e.id)).size !== n.length || n.some((e) => e.state !== "pending")) return yield* s.fail(e("invalid_record"));
15
+ for (let t of n) {
16
+ let n = yield* a.process(t.id);
17
+ if (n.id !== t.id || n.orderId !== t.orderId || n.state !== "processed") return yield* s.fail(e("invalid_record"));
18
+ }
19
+ return Object.freeze({
20
+ prunedBodies: t,
21
+ scannedRows: n.length,
22
+ processedNotices: n.length
23
+ });
24
+ }).pipe(s.catchDefect(() => s.fail(e("unavailable"))));
25
+ }, u = (t) => {
26
+ let a = n(t, [
27
+ "processor",
28
+ "maxNotices",
29
+ "name",
30
+ "cron",
31
+ "timezone"
32
+ ]), u = r(a.name), d = a.cron, f = a.timezone ?? "UTC";
33
+ if (typeof d != "string" || !d.length || d.length > 120 || typeof f != "string" || !f.length || f.length > 120) throw e("invalid_input");
34
+ let p = o(a.processor), m = i(a.maxNotices ?? 1e3, 1, 1e5);
35
+ return c({
36
+ name: u,
37
+ cron: d,
38
+ timezone: f,
39
+ handler: () => l({
40
+ processor: p,
41
+ maxNotices: m
42
+ }).pipe(s.asVoid)
43
+ });
44
+ }, d = (o) => {
45
+ let u = n(o, [
46
+ "system",
47
+ "channel",
48
+ "name",
49
+ "cron",
50
+ "timezone",
51
+ "maxNotices"
52
+ ]), d = t(u.system, [
53
+ "initialize",
54
+ "processor",
55
+ "exportSubject",
56
+ "eraseSubject"
57
+ ]), f = a(u.channel), p = r(u.name), m = u.cron, h = u.timezone ?? "UTC", g = i(u.maxNotices ?? 1e3, 1, 1e5);
58
+ if (typeof m != "string" || !m.length || m.length > 120 || typeof h != "string" || !h.length || h.length > 120) throw e("invalid_input");
59
+ return c({
60
+ name: p,
61
+ cron: m,
62
+ timezone: h,
63
+ handler: () => d.processor(f).pipe(s.flatMap((e) => l({
64
+ processor: e,
65
+ maxNotices: g
66
+ })), s.asVoid)
67
+ });
68
+ };
69
+ export { u as iapNotificationSchedule, d as installedIapNotificationSchedule, l as runIapNotificationWork };
@@ -0,0 +1,352 @@
1
+ import { EconomyError as e, economyError as t } from "./errors.js";
2
+ import { captureBoundMethods as n, dataObject as r, identifier as i } from "./capture.js";
3
+ import { shopInteger as a } from "./shop.js";
4
+ import { iapTimestamp as o } from "./iap.js";
5
+ import { canonicalDataJson as s } from "./canonicalDataJson.js";
6
+ import { issueVerifiedIapNotification as c, readVerifiedIapNotification as l } from "./iapNotifications.server.js";
7
+ import { captureIapNotificationTargetOptions as u, readAcceptedIapNotificationTarget as d } from "./iapNotificationTarget.server.js";
8
+ import { iapNotificationNamespacesTable as f, iapNotificationSubjectsTable as p, iapNotificationsTable as m } from "./iapNotificationTables.entity.js";
9
+ import { readAcceptedIapRefund as h } from "./iapRefunds.server.js";
10
+ import { Context as g, Effect as _, Result as v } from "effect";
11
+ import { Buffer as y } from "node:buffer";
12
+ import { createHash as b } from "node:crypto";
13
+ import { decodeFieldValue as x, encodeFieldValue as ee, eq as S, fieldBinding as C, queryFor as w } from "@voltro/database";
14
+ import { executeSqlMutations as T, prepareSqlMutation as E, prepareSqlMutationGroup as te } from "@voltro/database/sql";
15
+ import { getFieldCipher as ne } from "@voltro/runtime";
16
+ // src/iapNotificationStore.server.ts
17
+ var D = class extends g.Service()("@voltro/IapNotificationCustodyPort") {}, O = (e) => n(e, [
18
+ "initialize",
19
+ "retain",
20
+ "restore",
21
+ "markProcessed",
22
+ "pending",
23
+ "prune",
24
+ "exportSubject",
25
+ "eraseSubject"
26
+ ]), k = (e) => b("sha256").update(s(e)).digest("hex"), A = (e, ...t) => e + k(t).slice(0, 56), j = (n) => n instanceof e ? n : t("unavailable"), M = (e) => _.tryPromise({
27
+ try: e,
28
+ catch: j
29
+ }), N = (e) => {
30
+ try {
31
+ return e();
32
+ } catch (e) {
33
+ return _.fail(j(e));
34
+ }
35
+ }, P = async (e) => {
36
+ let t = await _.runPromise(e.pipe(_.result));
37
+ if (v.isFailure(t)) throw t.failure;
38
+ return t.success;
39
+ }, F = (e) => o(e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : NaN), I = (g, v) => {
40
+ let b = r(v, [
41
+ "namespace",
42
+ "deployment",
43
+ "orders",
44
+ "bodyRetentionMs",
45
+ "maxRows",
46
+ "maxBodyBytes",
47
+ "now",
48
+ "cipher"
49
+ ]), D = i(b.namespace), O = u(b.deployment), I = n(b.orders, ["prepareNotification"]), re = a(b.bodyRetentionMs, 1, 31536e6), L = a(b.maxRows ?? 1e4, 1, 1e5), R = a(b.maxBodyBytes ?? 262144, 1024, 1048576), z = b.now ?? Date.now;
50
+ if (typeof z != "function") throw t("invalid_input");
51
+ let ie = b.cipher === void 0 ? { read: ne } : n(b.cipher, ["read"]), B = () => {
52
+ let e = ie.read();
53
+ if (!e) throw t("unavailable");
54
+ return n(e, ["encrypt", "decrypt"]);
55
+ }, V = k(O), H = () => o(z()), U = A("enn_", D), W = (e, t) => A("ens_", D, e, t), G = (e) => "eni_" + e.slice(0, 56), K = (e) => {
56
+ let n = r(e, [
57
+ "tenantId",
58
+ "subjectId",
59
+ "kind"
60
+ ]);
61
+ if (n.kind !== "user" && n.kind !== "guest") throw t("invalid_input");
62
+ return Object.freeze({
63
+ tenantId: i(n.tenantId),
64
+ ownerId: i(n.subjectId)
65
+ });
66
+ }, q = async (e, t) => {
67
+ await g.insertIgnore(f.tableName, {
68
+ id: U,
69
+ namespace: D,
70
+ configurationDigest: V,
71
+ admission: 0,
72
+ rowsCount: 0
73
+ }, { conflictColumns: ["id"] }), await g.insertIgnore(p.tableName, {
74
+ id: W(e, t),
75
+ namespace: D,
76
+ tenantId: e,
77
+ ownerId: t,
78
+ erased: !1,
79
+ admission: 0,
80
+ rowsCount: 0
81
+ }, { conflictColumns: ["id"] });
82
+ }, J = async (e, n, r, i = !1) => {
83
+ let o = (await e.query(w(p).where(S("id", W(n, r))).limit(1).descriptor))[0];
84
+ if (o) {
85
+ if (o.namespace !== D || o.tenantId !== n || o.admission !== 0 || o.erased !== !0 && o.erased !== !1 || (o.erased ? o.ownerId !== null : o.ownerId !== r)) throw t("invalid_record");
86
+ if (a(o.rowsCount, 0, L), o.erased && !i) throw t("erased");
87
+ return o;
88
+ }
89
+ }, Y = async (e, n, r, i = !1) => {
90
+ if (await e.updateMany(f.tableName, { admission: 0 }, { where: S("id", U) }) !== 1 || await e.updateMany(p.tableName, { admission: 0 }, { where: S("id", W(n, r)) }) !== 1) throw t("unavailable");
91
+ let o = (await e.query(w(f).where(S("id", U)).limit(1).descriptor))[0], s = await J(e, n, r, i);
92
+ if (!o || o.namespace !== D || o.admission !== 0 || !s) throw t("invalid_record");
93
+ if (!i && o.configurationDigest !== V) throw t("forbidden");
94
+ return a(o.rowsCount, 0, L), {
95
+ header: o,
96
+ person: s
97
+ };
98
+ }, X = (e, n = !1) => {
99
+ if (e.namespace !== D || typeof e.identity != "string" || !/^[a-f0-9]{64}$/.test(e.identity) || e.id !== G(e.identity) || typeof e.fingerprint != "string" || !/^[a-f0-9]{64}$/.test(e.fingerprint) || typeof e.ownerKey != "string" || !/^ens_[a-f0-9]{56}$/.test(e.ownerKey) || ![
100
+ "pending",
101
+ "processed",
102
+ "expired",
103
+ "erased"
104
+ ].includes(String(e.state))) throw t("invalid_record");
105
+ let r = F(e.createdAt), a = F(e.expiresAt);
106
+ if (!n && typeof e.sealedBody == "string" && e.sealedBody.length > R * 4 + 512) throw t("limit_exceeded");
107
+ if (a <= r || !n && (e.state === "pending" ? typeof e.sealedBody != "string" || !e.sealedBody.startsWith("enc:v2:r:") : e.state === "processed" ? e.sealedBody !== null && (typeof e.sealedBody != "string" || !e.sealedBody.startsWith("enc:v2:r:")) : e.sealedBody !== null) || (i(e.tenantId), e.state !== "erased" && e.ownerKey !== W(i(e.tenantId), i(e.ownerId))) || e.state === "erased" && e.ownerId !== null) throw t("invalid_record");
108
+ return Object.freeze({
109
+ id: i(e.id),
110
+ orderId: i(e.orderId),
111
+ state: e.state
112
+ });
113
+ }, Z = async (e, n = !1) => {
114
+ let r = (await e.query(w(f).where(S("id", U)).limit(1).descriptor))[0];
115
+ if (r && (r.namespace !== D || r.admission !== 0 || typeof r.configurationDigest != "string" || !/^[a-f0-9]{64}$/.test(r.configurationDigest))) throw t("invalid_record");
116
+ if (r && !n && r.configurationDigest !== V) throw t("forbidden");
117
+ let i = await e.query(w(m).where(S("namespace", D)).limit(L + 1).descriptor);
118
+ if (i.length > L) throw t("limit_exceeded");
119
+ if (i.length !== (r ? a(r.rowsCount, 0, L) : 0)) throw t("invalid_record");
120
+ return i.forEach((e) => X(e, n)), i;
121
+ }, Q = (e) => {
122
+ if (X(e), e.state === "erased") throw t("erased");
123
+ if (e.state !== "pending" || typeof e.sealedBody != "string") throw t("unavailable");
124
+ let n = x(B(), e.sealedBody, void 0, C(m.tableName, "sealedBody", e.id));
125
+ if (typeof n != "string") throw t("invalid_record");
126
+ if (y.byteLength(n, "utf8") > R) throw t("limit_exceeded");
127
+ let a = r(JSON.parse(n), [
128
+ "namespace",
129
+ "deployment",
130
+ "scope",
131
+ "orderId",
132
+ "notice",
133
+ "material",
134
+ "fingerprint",
135
+ "createdAt",
136
+ "expiresAt"
137
+ ], "invalid_record"), o = r(a.scope, ["tenantId", "ownerId"], "invalid_record");
138
+ if (a.namespace !== D || s(a.deployment) !== s(O) || o.tenantId !== e.tenantId || o.ownerId !== e.ownerId || e.ownerKey !== W(i(o.tenantId), i(o.ownerId)) || a.orderId !== e.orderId || a.fingerprint !== e.fingerprint || a.createdAt !== F(e.createdAt) || a.expiresAt !== F(e.expiresAt)) throw t("invalid_record");
139
+ let u = c(a.notice, r(a.material, Object.keys(a.material), "invalid_record")), d = l(u);
140
+ if (d.fingerprint !== e.fingerprint || e.identity !== k([
141
+ O.channel,
142
+ O.environment,
143
+ O.applicationId,
144
+ d.notice.noticeId
145
+ ])) throw t("invalid_record");
146
+ return {
147
+ evidence: u,
148
+ owner: {
149
+ tenantId: i(o.tenantId),
150
+ ownerId: i(o.ownerId)
151
+ }
152
+ };
153
+ }, ae = (n) => N(() => {
154
+ let r = l(n);
155
+ return M(async () => {
156
+ let i = await P(I.prepareNotification(O, n)), a = await P(d(i)), c = H(), l = o(c + re);
157
+ if (a.fingerprint !== r.fingerprint) throw t("forbidden");
158
+ let u = k([
159
+ O.channel,
160
+ O.environment,
161
+ O.applicationId,
162
+ r.notice.noticeId
163
+ ]), h = G(u), _ = a.scope, v = s({
164
+ namespace: D,
165
+ deployment: O,
166
+ scope: _,
167
+ orderId: a.orderId,
168
+ notice: r.notice,
169
+ material: r.material,
170
+ fingerprint: r.fingerprint,
171
+ createdAt: c,
172
+ expiresAt: l
173
+ });
174
+ if (y.byteLength(v, "utf8") > R) throw t("limit_exceeded");
175
+ let b = ee(B(), v, C(m.tableName, "sealedBody", h));
176
+ if (!b.startsWith("enc:v2:r:")) throw t("unavailable");
177
+ await q(_.tenantId, _.ownerId);
178
+ let x = await g.transactional(async (e) => {
179
+ let { header: n, person: i } = await Y(e, _.tenantId, _.ownerId), o = await Z(e), s = o.filter((e) => e.ownerKey === W(_.tenantId, _.ownerId));
180
+ if (o.length !== n.rowsCount || s.length !== i.rowsCount) throw t("invalid_record");
181
+ let d = o.find((e) => e.id === h);
182
+ if (d) {
183
+ if (d.identity !== u || d.fingerprint !== r.fingerprint || d.tenantId !== _.tenantId || d.ownerKey !== W(_.tenantId, _.ownerId) || d.orderId !== a.orderId) throw t("conflict");
184
+ return d.state === "pending" && Q(d), X(d);
185
+ }
186
+ if (o.length >= L) throw t("limit_exceeded");
187
+ let g = {
188
+ id: h,
189
+ namespace: D,
190
+ tenantId: _.tenantId,
191
+ ownerId: _.ownerId,
192
+ ownerKey: W(_.tenantId, _.ownerId),
193
+ orderId: a.orderId,
194
+ identity: u,
195
+ fingerprint: r.fingerprint,
196
+ state: "pending",
197
+ sealedBody: b,
198
+ createdAt: new Date(c),
199
+ expiresAt: new Date(l)
200
+ };
201
+ return await e.insert(m.tableName, g), await e.update(f.tableName, U, { rowsCount: o.length + 1 }), await e.update(p.tableName, W(_.tenantId, _.ownerId), { rowsCount: s.length + 1 }), X(g);
202
+ });
203
+ try {
204
+ return await P(d(i)), await g.transactional(async (e) => {
205
+ if (!await J(e, _.tenantId, _.ownerId)) throw t("invalid_record");
206
+ let n = (await e.query(w(m).where(S("id", x.id)).limit(1).descriptor))[0];
207
+ if (!n) throw t("invalid_record");
208
+ return X(n);
209
+ });
210
+ } catch (t) {
211
+ throw t instanceof e && t.code === "erased" && await P($({
212
+ tenantId: _.tenantId,
213
+ subjectId: _.ownerId,
214
+ kind: "user"
215
+ })), t;
216
+ }
217
+ });
218
+ }), oe = (n) => N(() => {
219
+ let r = i(n);
220
+ return M(async () => {
221
+ let n = H(), a = await g.transactional(async (e) => {
222
+ let a = (await e.query(w(m).where(S("id", r)).limit(1).descriptor))[0];
223
+ if (!a) throw t("not_found");
224
+ if (a.namespace !== D) throw t("forbidden");
225
+ let o = Q(a);
226
+ if (F(a.expiresAt) <= n) throw t("unavailable");
227
+ if (!await J(e, o.owner.tenantId, o.owner.ownerId)) throw t("invalid_record");
228
+ return {
229
+ ...o,
230
+ orderId: i(a.orderId),
231
+ expiresAt: F(a.expiresAt)
232
+ };
233
+ });
234
+ try {
235
+ let e = await P(I.prepareNotification(O, a.evidence)), n = await P(d(e));
236
+ if (n.orderId !== a.orderId || s(n.scope) !== s(a.owner)) throw t("conflict");
237
+ await g.transactional(async (e) => {
238
+ if (!await J(e, a.owner.tenantId, a.owner.ownerId)) throw t("invalid_record");
239
+ if (H() >= a.expiresAt) throw t("unavailable");
240
+ });
241
+ let i = l(a.evidence);
242
+ return c(i.notice, i.material, () => M(() => g.transactional(async (e) => {
243
+ let n = (await e.query(w(m).where(S("id", r)).limit(1).descriptor))[0];
244
+ if (!n || n.namespace !== D) throw t("not_found");
245
+ let o = Q(n);
246
+ if (n.fingerprint !== i.fingerprint || n.orderId !== a.orderId || s(o.owner) !== s(a.owner)) throw t("conflict");
247
+ if (!await J(e, o.owner.tenantId, o.owner.ownerId)) throw t("invalid_record");
248
+ if (H() >= F(n.expiresAt)) throw t("unavailable");
249
+ })), () => _.succeed(E(g, async (e) => {
250
+ await Y(e, a.owner.tenantId, a.owner.ownerId);
251
+ let n = (await e.query(w(m).where(S("id", r)).limit(1).descriptor))[0];
252
+ if (!n) throw t("not_found");
253
+ let o = Q(n);
254
+ if (n.fingerprint !== i.fingerprint || n.orderId !== a.orderId || s(o.owner) !== s(a.owner)) throw t("conflict");
255
+ if (H() >= F(n.expiresAt)) throw t("unavailable");
256
+ return null;
257
+ })));
258
+ } catch (t) {
259
+ throw t instanceof e && t.code === "erased" && await P($({
260
+ tenantId: a.owner.tenantId,
261
+ subjectId: a.owner.ownerId,
262
+ kind: "user"
263
+ })), t;
264
+ }
265
+ });
266
+ }), se = (e, n) => N(() => {
267
+ let r = i(e), a = h(n);
268
+ return M(async () => {
269
+ let e = await g.transactional(async (e) => {
270
+ let n = (await e.query(w(m).where(S("id", r)).limit(1).descriptor))[0];
271
+ if (!n || n.namespace !== D || n.orderId !== a.orderId || n.fingerprint !== a.fingerprint || n.ownerKey !== W(a.scope.tenantId, a.scope.ownerId)) throw t("forbidden");
272
+ if (!await J(e, a.scope.tenantId, a.scope.ownerId)) throw t("invalid_record");
273
+ return n.state === "processed" ? X(n) : null;
274
+ });
275
+ if (e) return e;
276
+ let n = await P(a.prepare()), i = E(g, async (e) => {
277
+ await Y(e, a.scope.tenantId, a.scope.ownerId);
278
+ let n = (await e.query(w(m).where(S("id", r)).limit(1).descriptor))[0];
279
+ if (!n) throw t("not_found");
280
+ if (n.state === "processed") return X(n);
281
+ let i = Q(n), o = l(i.evidence);
282
+ if (n.orderId !== a.orderId || o.fingerprint !== a.fingerprint || o.notice.noticeId !== a.noticeId || s(i.owner) !== s(a.scope)) throw t("conflict");
283
+ if (H() >= F(n.expiresAt)) throw t("unavailable");
284
+ return await e.update(m.tableName, r, { state: "processed" }), X({
285
+ ...n,
286
+ state: "processed"
287
+ });
288
+ }), o = te(g, [n, i], async (e, t) => t[1]);
289
+ return (await T(g, [o]))[0];
290
+ });
291
+ }), ce = () => M(() => g.transactional(async (e) => {
292
+ let t = H(), n = await Z(e);
293
+ return Object.freeze(n.filter((e) => e.state === "pending" && F(e.expiresAt) > t).map((e) => X(e)));
294
+ })), le = () => M(async () => {
295
+ let e = H();
296
+ return g.transactional(async (n) => {
297
+ if (await n.updateMany(f.tableName, { admission: 0 }, { where: S("id", U) }) !== 1) return 0;
298
+ let r = (await n.query(w(f).where(S("id", U)).limit(1).descriptor))[0], i = await Z(n);
299
+ if (!r || r.namespace !== D || r.admission !== 0 || i.length !== r.rowsCount) throw t("invalid_record");
300
+ let a = 0;
301
+ for (let t of i) (t.state === "pending" || t.state === "processed") && t.sealedBody !== null && F(t.expiresAt) <= e && (await n.update(m.tableName, String(t.id), {
302
+ state: t.state === "processed" ? "processed" : "expired",
303
+ sealedBody: null
304
+ }), a++);
305
+ return a;
306
+ });
307
+ }), ue = (e) => N(() => {
308
+ let n = K(e);
309
+ return M(() => g.transactional(async (e) => {
310
+ let r = await J(e, n.tenantId, n.ownerId, !0), i = (await Z(e, !0)).filter((e) => e.tenantId === n.tenantId && e.ownerKey === W(n.tenantId, n.ownerId));
311
+ if (i.length !== (r ? r.rowsCount : 0)) throw t("invalid_record");
312
+ return { notifications: r?.erased ? [] : i.map((e) => ({
313
+ ...X(e),
314
+ createdAt: F(e.createdAt),
315
+ expiresAt: F(e.expiresAt)
316
+ })) };
317
+ }));
318
+ }), $ = (e) => N(() => {
319
+ let n = K(e);
320
+ return M(async () => {
321
+ await q(n.tenantId, n.ownerId), await g.transactional(async (e) => {
322
+ let { header: r, person: i } = await Y(e, n.tenantId, n.ownerId, !0), a = await Z(e, !0), o = a.filter((e) => e.tenantId === n.tenantId && e.ownerKey === W(n.tenantId, n.ownerId));
323
+ if (a.length !== r.rowsCount || o.length !== i.rowsCount) throw t("invalid_record");
324
+ for (let t of o) await e.update(m.tableName, String(t.id), {
325
+ ownerId: null,
326
+ sealedBody: null,
327
+ state: "erased"
328
+ });
329
+ await e.update(p.tableName, W(n.tenantId, n.ownerId), {
330
+ ownerId: null,
331
+ erased: !0
332
+ });
333
+ });
334
+ });
335
+ });
336
+ return Object.freeze({
337
+ initialize: () => _.try({
338
+ try: () => {
339
+ B();
340
+ },
341
+ catch: j
342
+ }),
343
+ retain: ae,
344
+ restore: oe,
345
+ markProcessed: se,
346
+ pending: ce,
347
+ prune: le,
348
+ exportSubject: ue,
349
+ eraseSubject: $
350
+ });
351
+ };
352
+ export { D as IapNotificationCustodyPort, O as captureIapNotificationCustodyService, I as dataStoreIapNotificationCustody };
@@ -0,0 +1,31 @@
1
+ import { iapNotificationTables as e } from "./iapNotificationTables.entity.js";
2
+ import { dataStoreIapNotificationCustody as t } from "./iapNotificationStore.server.js";
3
+ import { iapStoreFixture as n } from "./iapStore.testFixtures.js";
4
+ import { iapSqlNoticeFixture as r } from "./iapNotificationTarget.testFixtures.js";
5
+ import { Effect as i } from "effect";
6
+ import { ensureTableRegistered as a } from "@voltro/database";
7
+ // src/iapNotificationStore.testFixtures.ts
8
+ var o = async (o, s = "test.tenant", c = {}) => {
9
+ e.forEach(a);
10
+ let l = await n(o, s), u = await i.runPromise(l.iap.redeem(l.user(), l.intent)), d = r(l), f = {
11
+ namespace: s,
12
+ deployment: d.options,
13
+ orders: l.iap,
14
+ bodyRetentionMs: 1e3,
15
+ now: () => l.at,
16
+ ...c
17
+ };
18
+ return {
19
+ f: l,
20
+ n: d,
21
+ original: u,
22
+ options: f,
23
+ custody: t(o, f),
24
+ privacy: {
25
+ tenantId: s,
26
+ subjectId: l.scope.ownerId,
27
+ kind: "guest"
28
+ }
29
+ };
30
+ };
31
+ export { o as iapNotificationStoreFixture };
@@ -0,0 +1,48 @@
1
+ import { boolean as e, id as t, integer as n, table as r, text as i, timestamp as a } from "@voltro/database";
2
+ // src/iapNotificationTables.entity.ts
3
+ var o = () => i().maxLength(120).serverOnly(), s = () => i().maxLength(120).externalId().sensitive("pii"), c = r("_voltro_eco_iap_notice_namespaces", {
4
+ id: t({ prefix: "enn" }),
5
+ namespace: o(),
6
+ configurationDigest: i().maxLength(64).serverOnly(),
7
+ admission: n().serverOnly(),
8
+ rowsCount: n().serverOnly()
9
+ }), l = r("_voltro_eco_iap_notice_subjects", {
10
+ id: t({ prefix: "ens" }),
11
+ namespace: o(),
12
+ tenantId: s(),
13
+ ownerId: i().maxLength(120).nullable().externalId().sensitive("pii"),
14
+ erased: e().serverOnly(),
15
+ admission: n().serverOnly(),
16
+ rowsCount: n().serverOnly()
17
+ }).index([
18
+ "namespace",
19
+ "tenantId",
20
+ "ownerId"
21
+ ]), u = r("_voltro_eco_iap_notices", {
22
+ id: t({ prefix: "eni" }),
23
+ namespace: o(),
24
+ tenantId: s(),
25
+ ownerId: i().maxLength(120).nullable().externalId().sensitive("pii"),
26
+ ownerKey: i().maxLength(64).serverOnly(),
27
+ orderId: i().maxLength(64).serverOnly(),
28
+ identity: i().maxLength(64).serverOnly(),
29
+ fingerprint: i().maxLength(64).serverOnly(),
30
+ state: i().maxLength(16).oneOf([
31
+ "pending",
32
+ "processed",
33
+ "expired",
34
+ "erased"
35
+ ]).serverOnly(),
36
+ sealedBody: i().nullable().serverOnly(),
37
+ createdAt: a().serverOnly(),
38
+ expiresAt: a().serverOnly()
39
+ }).index(["namespace"]).index([
40
+ "namespace",
41
+ "tenantId",
42
+ "ownerKey"
43
+ ]), d = [
44
+ c,
45
+ l,
46
+ u
47
+ ];
48
+ export { c as iapNotificationNamespacesTable, l as iapNotificationSubjectsTable, d as iapNotificationTables, u as iapNotificationsTable };
@@ -0,0 +1,36 @@
1
+ import { economyError as e } from "./errors.js";
2
+ import { dataObject as t, identifier as n } from "./capture.js";
3
+ import { iapEnvironment as r } from "./iap.js";
4
+ import { isIapRefundChannel as i } from "./iapNotifications.server.js";
5
+ import { Effect as a } from "effect";
6
+ // src/iapNotificationTarget.server.ts
7
+ var o = /* @__PURE__ */ new WeakMap(), s = (e, t, r) => {
8
+ let i = Object.freeze({
9
+ orderId: n(e),
10
+ noticeId: t
11
+ });
12
+ return o.set(i, r), i;
13
+ }, c = (t) => {
14
+ let n = typeof t == "object" && t ? o.get(t) : void 0;
15
+ return n ? a.suspend(n) : a.fail(e("forbidden"));
16
+ }, l = (e) => c(e).pipe(a.map((e) => Object.freeze({
17
+ orderId: e.orderId,
18
+ notice: e.notice,
19
+ receipt: e.receipt
20
+ }))), u = (a) => {
21
+ let o = t(a, [
22
+ "realMoney",
23
+ "channel",
24
+ "applicationId",
25
+ "environment"
26
+ ]);
27
+ if (o.realMoney !== !0) throw e(o.realMoney === !1 ? "real_money_disabled" : "invalid_input");
28
+ if (!i(o.channel)) throw e("invalid_input");
29
+ return Object.freeze({
30
+ realMoney: !0,
31
+ channel: o.channel,
32
+ applicationId: n(o.applicationId),
33
+ environment: r(o.environment)
34
+ });
35
+ };
36
+ export { u as captureIapNotificationTargetOptions, s as issueAcceptedIapNotificationTarget, l as observeAcceptedIapNotificationTarget, c as readAcceptedIapNotificationTarget };