dgc-core 2.0.5 → 2.0.8

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.
@@ -9,6 +9,7 @@ const node_crypto_1 = require("node:crypto");
9
9
  const mongodb_1 = require("mongodb");
10
10
  const entry_id_1 = require("./entry-id");
11
11
  const api_pagination_1 = require("./api-pagination");
12
+ const eligibility_1 = require("./eligibility");
12
13
  const domain_1 = require("./domain");
13
14
  const schemas_1 = require("./schemas");
14
15
  const projection = { entryVersion: 0 };
@@ -129,9 +130,10 @@ function filterCategory(filter, params) {
129
130
  }
130
131
  /** Each method reads only its own records. Transactions contain related writes only. */
131
132
  class GiveawayRepository {
132
- constructor(db, client) {
133
+ constructor(db, client, options = {}) {
133
134
  this.db = db;
134
135
  this.client = client;
136
+ this.options = options;
135
137
  }
136
138
  get giveaways() {
137
139
  return this.db.collection('giveaways');
@@ -151,6 +153,111 @@ class GiveawayRepository {
151
153
  get activity() {
152
154
  return this.db.collection('audit');
153
155
  }
156
+ async checkCreditCandidate(entry) {
157
+ const check = this.options.checkCreditEligibility;
158
+ if (!check)
159
+ throw new domain_1.DomainError('RevenueCat entitlement verification is not configured', 503);
160
+ if (typeof entry.userId !== 'string' ||
161
+ !entry.userId ||
162
+ entry.userId.length > 128 ||
163
+ /[.#$[\]/]/.test(entry.userId))
164
+ throw new domain_1.DomainError('The entrant has no valid DGC account identity', 503);
165
+ let evidence;
166
+ try {
167
+ evidence = await check(entry.userId);
168
+ }
169
+ catch {
170
+ throw new domain_1.DomainError('Unable to verify active membership. Try again.', 503);
171
+ }
172
+ if (evidence === null)
173
+ return null;
174
+ if (!evidence ||
175
+ evidence.userId !== entry.userId ||
176
+ typeof evidence.entitlementId !== 'string' ||
177
+ !evidence.entitlementId.trim() ||
178
+ evidence.entitlementId.length > 255 ||
179
+ !(0, eligibility_1.isGiveawayMemberEligible)(giveaway_2.GiveawayCategory.Credits, evidence.collectorType) ||
180
+ typeof evidence.checkedAt !== 'string' ||
181
+ !Number.isFinite(Date.parse(evidence.checkedAt)) ||
182
+ Date.parse(evidence.checkedAt) > Date.now() + 5000 ||
183
+ (evidence.expiresAt !== null &&
184
+ (typeof evidence.expiresAt !== 'number' || !Number.isFinite(evidence.expiresAt))))
185
+ throw new domain_1.DomainError('Unable to verify active membership. Try again.', 503);
186
+ this.assertFreshCreditEvidence(evidence);
187
+ return {
188
+ userId: evidence.userId,
189
+ checkedAt: evidence.checkedAt,
190
+ entitlementId: evidence.entitlementId,
191
+ collectorType: evidence.collectorType,
192
+ expiresAt: evidence.expiresAt
193
+ };
194
+ }
195
+ assertFreshCreditEvidence(evidence) {
196
+ if (Date.now() - Date.parse(evidence.checkedAt) > 60000 ||
197
+ (evidence.expiresAt !== null && evidence.expiresAt <= Date.now()))
198
+ throw new domain_1.DomainError('Entitlement verification expired. Review and try again.', 409);
199
+ }
200
+ assertVerifiedCreditCandidate(g, reviewed, entry, verified) {
201
+ if (g.category !== reviewed.category || g.revision !== reviewed.revision)
202
+ throw new domain_1.DomainError('Giveaway changed. Review the selection again.', 409);
203
+ if (g.category !== giveaway_2.GiveawayCategory.Credits)
204
+ return;
205
+ if (!verified || verified.entryId !== entry._id || verified.evidence.userId !== entry.userId)
206
+ throw new domain_1.DomainError('Active membership must be verified before awarding credits', 503);
207
+ this.assertFreshCreditEvidence(verified.evidence);
208
+ }
209
+ async verifiedCreditSelection(g, input) {
210
+ if ((0, domain_1.effectiveStatus)(g) !== giveaway_2.GiveawayStatus.Closed)
211
+ throw new domain_1.DomainError('Close entries before selecting a winner', 409);
212
+ if (g.revision !== input.revision)
213
+ throw new domain_1.DomainError('Giveaway changed. Refresh before selecting.', 409);
214
+ if (g.selection ||
215
+ (await this.winners.findOne({ giveawayId: g._id, replacedAt: null }, { projection: { _id: 1 } })))
216
+ throw new domain_1.DomainError('A winner is already selected. Explicitly reset the selection to change it.', 409);
217
+ const maxAttempts = this.options.creditWinnerMaxAttempts ?? 5;
218
+ if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 100)
219
+ throw new domain_1.DomainError('Credit winner attempts must be an integer between 1 and 100', 503);
220
+ const tiers = (0, eligibility_1.giveawayEligibleCollectorTypes)(giveaway_2.GiveawayCategory.Credits);
221
+ const rejected = [];
222
+ const deadline = Date.now() + 30000;
223
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
224
+ if (Date.now() >= deadline)
225
+ throw new domain_1.DomainError('Membership verification is taking too long. Try the draw again.', 503);
226
+ const eligible = {
227
+ giveawayId: g._id,
228
+ _collectorType: { $in: [...tiers] },
229
+ ...(rejected.length > 0 && { _id: { $nin: rejected } })
230
+ };
231
+ const count = await this.entries.countDocuments(eligible, {});
232
+ if (!count)
233
+ throw new domain_1.DomainError('This giveaway has no entries with an active eligible entitlement', 409);
234
+ const entry = input.method === giveaway_2.GiveawaySelectionMethod.Manual
235
+ ? await this.entries.findOne({ ...eligible, _id: input.entryId }, {})
236
+ : await this.entries
237
+ .find(eligible, {
238
+ projection: { _id: 1, userId: 1, giveawayId: 1, _collectorType: 1 }
239
+ })
240
+ .sort({ _id: 1 })
241
+ .skip((0, node_crypto_1.randomInt)(count))
242
+ .limit(1)
243
+ .next();
244
+ if (!entry)
245
+ throw new domain_1.DomainError('Select a valid entry in this giveaway', 422);
246
+ if (!(0, eligibility_1.isGiveawayMemberEligible)(g.category, entry._collectorType))
247
+ throw new domain_1.DomainError('This entry is not eligible for the giveaway prize', 403);
248
+ const evidence = await this.checkCreditCandidate(entry);
249
+ if (Date.now() >= deadline)
250
+ throw new domain_1.DomainError('Membership verification is taking too long. Try the draw again.', 503);
251
+ if (evidence)
252
+ return { entryId: entry._id, evidence, skipped: rejected.length };
253
+ if (input.method === giveaway_2.GiveawaySelectionMethod.Manual)
254
+ throw new domain_1.DomainError('This entrant has no active Rookie, Collector or Elite entitlement', 422);
255
+ rejected.push(entry._id);
256
+ if (count === 1)
257
+ throw new domain_1.DomainError('This giveaway has no entries with an active eligible entitlement', 409);
258
+ }
259
+ throw new domain_1.DomainError(`No verified winner found after checking ${maxAttempts} candidate${maxAttempts === 1 ? '' : 's'}. Try the draw again.`, 503);
260
+ }
154
261
  async transaction(operation) {
155
262
  const session = this.client.startSession();
156
263
  try {
@@ -579,6 +686,49 @@ class GiveawayRepository {
579
686
  { $sort: { _id: -1 } },
580
687
  { $skip: skip },
581
688
  { $limit: limit },
689
+ {
690
+ $lookup: {
691
+ from: 'notification_deliveries',
692
+ let: { notification: { $toString: '$_id' } },
693
+ pipeline: [
694
+ { $match: { $expr: { $eq: ['$notificationId', '$$notification'] } } },
695
+ {
696
+ $group: {
697
+ _id: null,
698
+ queued: {
699
+ $sum: {
700
+ $cond: [
701
+ { $eq: ['$status', giveaway_2.GiveawayNotificationDeliveryStatus.Queued] },
702
+ 1,
703
+ 0
704
+ ]
705
+ }
706
+ },
707
+ skipped: {
708
+ $sum: {
709
+ $cond: [
710
+ { $eq: ['$status', giveaway_2.GiveawayNotificationDeliveryStatus.Skipped] },
711
+ 1,
712
+ 0
713
+ ]
714
+ }
715
+ },
716
+ pending: {
717
+ $sum: {
718
+ $cond: [
719
+ { $eq: ['$status', giveaway_2.GiveawayNotificationDeliveryStatus.Pending] },
720
+ 1,
721
+ 0
722
+ ]
723
+ }
724
+ }
725
+ }
726
+ },
727
+ { $project: { _id: 0, queued: 1, skipped: 1, pending: 1 } }
728
+ ],
729
+ as: 'deliveryTotals'
730
+ }
731
+ },
582
732
  {
583
733
  $lookup: {
584
734
  from: 'giveaways',
@@ -594,10 +744,13 @@ class GiveawayRepository {
594
744
  $set: {
595
745
  giveawayTitle: {
596
746
  $ifNull: [{ $first: '$campaign.title' }, '$giveawayId']
747
+ },
748
+ deliveryCounts: {
749
+ $ifNull: [{ $first: '$deliveryTotals' }, { queued: 0, skipped: 0, pending: 0 }]
597
750
  }
598
751
  }
599
752
  },
600
- { $unset: ['campaign', 'claimToken'] },
753
+ { $unset: ['campaign', 'claimToken', 'deliveryTotals'] },
601
754
  stringId
602
755
  ])
603
756
  .toArray(),
@@ -741,6 +894,20 @@ class GiveawayRepository {
741
894
  }
742
895
  async confirmWinners(actor, id, raw) {
743
896
  const input = schemas_1.assignmentsSchema.parse(raw);
897
+ const reviewed = await this.getGiveaway(id);
898
+ let verified;
899
+ if (reviewed.category === giveaway_2.GiveawayCategory.Credits) {
900
+ const rows = await this.entries
901
+ .find({ giveawayId: id, _id: { $in: input.assignments.map(a => a.entryId) } }, { projection: { _id: 1, userId: 1, giveawayId: 1, _collectorType: 1 } })
902
+ .limit(1)
903
+ .toArray();
904
+ const existing = await this.winners.findOne({ giveawayId: id, replacedAt: null }, { projection: { _id: 1 } });
905
+ (0, domain_1.confirmWinners)(reviewed, rows, !!existing, actor, input);
906
+ const evidence = await this.checkCreditCandidate(rows[0]);
907
+ if (!evidence)
908
+ throw new domain_1.DomainError('This entrant has no active Rookie, Collector or Elite entitlement', 422);
909
+ verified = { entryId: rows[0]._id, evidence, skipped: 0 };
910
+ }
744
911
  return this.transaction(async (session) => {
745
912
  const g = await this.getGiveaway(id, session);
746
913
  const rows = await this.entries
@@ -749,17 +916,25 @@ class GiveawayRepository {
749
916
  _id: { $in: input.assignments.map(a => a.entryId) }
750
917
  }, {
751
918
  session,
752
- projection: { _id: 1, userId: 1, giveawayId: 1 }
919
+ projection: { _id: 1, userId: 1, giveawayId: 1, _collectorType: 1 }
753
920
  })
754
921
  .limit(1000)
755
922
  .toArray();
756
923
  const entries = rows;
757
924
  const existing = await this.winners.findOne({ giveawayId: id, replacedAt: null }, { session, projection: { _id: 1 } });
758
925
  const winners = (0, domain_1.confirmWinners)(g, entries, !!existing, actor, input);
926
+ this.assertVerifiedCreditCandidate(g, reviewed, entries[0], verified);
927
+ if (verified)
928
+ winners[0].creditEligibility = verified.evidence;
759
929
  await this.updateGiveaway({ ...g, status: giveaway_2.GiveawayStatus.AwaitingClaim, revision: g.revision + 1 }, g.revision, session);
760
930
  const result = await this.winners.insertMany(winners.map(winner => ({ ...winner })), { session });
761
- await this.enqueueEvent(g, giveaway_2.GiveawayNotificationKind.Results, 'results:' + result.insertedIds[0].toHexString(), session);
762
- await this.audit(actor, 'Winners confirmed', id, winners.length + ' prize assignments', session);
931
+ await this.enqueueEvent(g, giveaway_2.GiveawayNotificationKind.Results, 'results:' + result.insertedIds[0].toHexString(), session, winners[0].claimDeadline);
932
+ await this.audit(actor, 'Winners confirmed', id, verified
933
+ ? JSON.stringify({
934
+ prizeAssignments: winners.length,
935
+ creditEligibility: verified.evidence
936
+ })
937
+ : winners.length + ' prize assignments', session);
763
938
  return winners.map((winner, index) => ({
764
939
  ...winner,
765
940
  _id: result.insertedIds[index].toHexString()
@@ -767,22 +942,52 @@ class GiveawayRepository {
767
942
  });
768
943
  }
769
944
  async replaceWinner(actor, id, winnerId, replacementEntryId, reason) {
945
+ const reviewed = await this.getGiveaway(id);
946
+ let verified;
947
+ if (reviewed.category === giveaway_2.GiveawayCategory.Credits) {
948
+ const previous = await this.winners.findOne({
949
+ _id: objectId(winnerId),
950
+ giveawayId: id,
951
+ replacedAt: null
952
+ });
953
+ const entry = await this.entries.findOne({ _id: schemas_1.entryIdSchema.parse(replacementEntryId), giveawayId: id }, { projection: { _id: 1, userId: 1, giveawayId: 1, _collectorType: 1 } });
954
+ if (!previous || !entry)
955
+ throw new domain_1.DomainError('Winner or entrant not found', 404);
956
+ const existing = await this.winners.findOne({ giveawayId: id, entryId: replacementEntryId, replacedAt: null }, { projection: { _id: 1 } });
957
+ (0, domain_1.replaceWinner)(reviewed, serialize(previous), entry, !!existing, actor, reason);
958
+ const evidence = await this.checkCreditCandidate(entry);
959
+ if (!evidence)
960
+ throw new domain_1.DomainError('This entrant has no active Rookie, Collector or Elite entitlement', 422);
961
+ verified = { entryId: entry._id, evidence, skipped: 0 };
962
+ }
770
963
  return this.transaction(async (session) => {
771
964
  const g = await this.getGiveaway(id, session);
772
965
  const previousWinner = await this.winners.findOne({ _id: objectId(winnerId), giveawayId: id, replacedAt: null }, { session });
773
- const entrant = await this.entries.findOne({ _id: schemas_1.entryIdSchema.parse(replacementEntryId), giveawayId: id }, { session, projection: { _id: 1, giveawayId: 1 } });
966
+ const entrant = await this.entries.findOne({ _id: schemas_1.entryIdSchema.parse(replacementEntryId), giveawayId: id }, { session, projection: { _id: 1, userId: 1, giveawayId: 1, _collectorType: 1 } });
774
967
  if (!previousWinner || !entrant)
775
968
  throw new domain_1.DomainError('Winner or entrant not found', 404);
776
969
  const old = serialize(previousWinner);
777
970
  const entry = entrant;
778
971
  const existing = await this.winners.findOne({ giveawayId: id, entryId: replacementEntryId, replacedAt: null }, { session, projection: { _id: 1 } });
779
972
  const { previous, replacement } = (0, domain_1.replaceWinner)(g, old, entry, !!existing, actor, reason);
973
+ this.assertVerifiedCreditCandidate(g, reviewed, entry, verified);
974
+ replacement.userId = entry.userId;
975
+ if (verified) {
976
+ replacement.creditEligibility = verified.evidence;
977
+ replacement.selection.creditEligibility = verified.evidence;
978
+ }
780
979
  await this.updateGiveaway({ ...g, status: giveaway_2.GiveawayStatus.AwaitingClaim, revision: g.revision + 1 }, g.revision, session);
781
980
  const { _id, ...previousFields } = previous;
782
981
  await this.winners.updateOne({ _id: objectId(_id), replacedAt: null }, { $set: previousFields }, { session });
783
982
  const result = await this.winners.insertOne({ ...replacement }, { session });
784
- await this.enqueueEvent(g, giveaway_2.GiveawayNotificationKind.Results, 'results:' + result.insertedId.toHexString(), session);
785
- await this.audit(actor, 'Winner replaced', id, old._id + ': ' + reason.trim(), session);
983
+ await this.enqueueEvent(g, giveaway_2.GiveawayNotificationKind.Results, 'results:' + result.insertedId.toHexString(), session, replacement.claimDeadline);
984
+ await this.audit(actor, 'Winner replaced', id, verified
985
+ ? JSON.stringify({
986
+ previousWinnerId: old._id,
987
+ reason: reason.trim(),
988
+ creditEligibility: verified.evidence
989
+ })
990
+ : old._id + ': ' + reason.trim(), session);
786
991
  return { ...replacement, _id: result.insertedId.toHexString() };
787
992
  });
788
993
  }
@@ -818,7 +1023,7 @@ class GiveawayRepository {
818
1023
  return winner;
819
1024
  });
820
1025
  }
821
- async enqueueEvent(g, kind, eventKey, session) {
1026
+ async enqueueEvent(g, kind, eventKey, session, expiresAt) {
822
1027
  const copy = g.reminders?.[kind];
823
1028
  if (!copy?.enabled)
824
1029
  return;
@@ -828,6 +1033,11 @@ class GiveawayRepository {
828
1033
  eventKey,
829
1034
  kind,
830
1035
  audience: giveaway_2.GiveawayNotificationAudience.Entrants,
1036
+ category: g.category,
1037
+ expiresAt: kind === giveaway_2.GiveawayNotificationKind.Closing
1038
+ ? g.closesAt
1039
+ : expiresAt ||
1040
+ new Date(Date.now() + (g.claimWindowDays || 14) * 86400000).toISOString(),
831
1041
  title: copy.title,
832
1042
  message: copy.message,
833
1043
  status: giveaway_2.GiveawayNotificationStatus.Pending,
@@ -837,6 +1047,10 @@ class GiveawayRepository {
837
1047
  }
838
1048
  async selectWinner(actor, id, raw) {
839
1049
  const input = schemas_1.selectionSchema.parse(raw);
1050
+ const reviewed = await this.getGiveaway(id);
1051
+ const verified = reviewed.category === giveaway_2.GiveawayCategory.Credits
1052
+ ? await this.verifiedCreditSelection(reviewed, input)
1053
+ : undefined;
840
1054
  return this.transaction(async (session) => {
841
1055
  const g = await this.getGiveaway(id, session);
842
1056
  if ((0, domain_1.effectiveStatus)(g) !== giveaway_2.GiveawayStatus.Closed)
@@ -846,29 +1060,47 @@ class GiveawayRepository {
846
1060
  if (g.selection ||
847
1061
  (await this.winners.findOne({ giveawayId: id, replacedAt: null }, { session })))
848
1062
  throw new domain_1.DomainError('A winner is already selected. Explicitly reset the selection to change it.', 409);
849
- const count = await this.entries.countDocuments({ giveawayId: id }, { session });
1063
+ const collectorTypes = (0, eligibility_1.giveawayEligibleCollectorTypes)(g.category);
1064
+ const eligible = {
1065
+ giveawayId: id,
1066
+ ...(collectorTypes !== null && { _collectorType: { $in: [...collectorTypes] } })
1067
+ };
1068
+ const count = verified ? 1 : await this.entries.countDocuments(eligible, { session });
850
1069
  if (!count)
851
1070
  throw new domain_1.DomainError('This giveaway has no eligible entries', 409);
852
- const entry = input.method === giveaway_2.GiveawaySelectionMethod.Manual
853
- ? await this.entries.findOne({ giveawayId: id, _id: input.entryId }, { session })
1071
+ const candidateId = verified?.entryId ||
1072
+ (input.method === giveaway_2.GiveawaySelectionMethod.Manual ? input.entryId : undefined);
1073
+ const entry = candidateId
1074
+ ? await this.entries.findOne({ ...eligible, _id: candidateId }, { session })
854
1075
  : await this.entries
855
- .find({ giveawayId: id }, { session })
1076
+ .find(eligible, { session })
856
1077
  .sort({ _id: 1 })
857
1078
  .skip((0, node_crypto_1.randomInt)(count))
858
1079
  .limit(1)
859
1080
  .next();
860
1081
  if (!entry)
861
1082
  throw new domain_1.DomainError('Select a valid entry in this giveaway', 422);
1083
+ if (!(0, eligibility_1.isGiveawayMemberEligible)(g.category, entry._collectorType))
1084
+ throw new domain_1.DomainError('This entry is not eligible for the giveaway prize', 403);
1085
+ this.assertVerifiedCreditCandidate(g, reviewed, entry, verified);
862
1086
  const selection = {
863
1087
  entryId: entry._id,
864
1088
  method: input.method,
865
1089
  reason: input.method === giveaway_2.GiveawaySelectionMethod.Manual ? input.reason : '',
866
1090
  selectedBy: actor,
867
- selectedAt: new Date().toISOString()
1091
+ selectedAt: new Date().toISOString(),
1092
+ ...(verified && { creditEligibility: verified.evidence })
868
1093
  };
869
1094
  const updated = { ...g, selection, revision: g.revision + 1 };
870
1095
  await this.updateGiveaway(updated, g.revision, session);
871
- await this.audit(actor, 'Winner selected: ' + input.method, id, entry._id + (selection.reason ? ': ' + selection.reason : ''), session);
1096
+ await this.audit(actor, 'Winner selected: ' + input.method, id, verified
1097
+ ? JSON.stringify({
1098
+ entryId: entry._id,
1099
+ reason: selection.reason,
1100
+ creditEligibility: verified.evidence,
1101
+ skippedIneligibleEntries: verified.skipped
1102
+ })
1103
+ : entry._id + (selection.reason ? ': ' + selection.reason : ''), session);
872
1104
  return { giveaway: updated, entry: normalizeEntry(entry) };
873
1105
  });
874
1106
  }
@@ -930,7 +1162,7 @@ class GiveawayRepository {
930
1162
  category: giveaway_2.GiveawayCategory.Credits,
931
1163
  creditAmount: amount,
932
1164
  countries: [],
933
- rules: 'One ticket per DGC account. All members have equal chances. One winner. The winner must claim within the stated claim window.',
1165
+ rules: 'One ticket per eligible DGC account. Eligible members have equal chances. One winner. The winner must claim within the stated claim window.',
934
1166
  deliveryInformation: '',
935
1167
  revision: 1,
936
1168
  createdAt: now,
@@ -1263,7 +1495,11 @@ class GiveawayRepository {
1263
1495
  const now = new Date().toISOString();
1264
1496
  const updated = await this.notifications.updateOne(filter, successful
1265
1497
  ? {
1266
- $set: { status: giveaway_2.GiveawayNotificationStatus.Sent, sentAt: now },
1498
+ $set: {
1499
+ status: giveaway_2.GiveawayNotificationStatus.Sent,
1500
+ sentAt: now,
1501
+ dispatchCompletedAt: now
1502
+ },
1267
1503
  $unset: { error: '', claimToken: '' }
1268
1504
  }
1269
1505
  : {
@@ -2,6 +2,7 @@
2
2
  export * from './domain';
3
3
  export * from './api-pagination';
4
4
  export * from './entry-id';
5
+ export * from './eligibility';
5
6
  export * from './giveaway-repository';
6
7
  export * from './schemas';
7
8
  export * from './credit-award';
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  __exportStar(require("./domain"), exports);
19
19
  __exportStar(require("./api-pagination"), exports);
20
20
  __exportStar(require("./entry-id"), exports);
21
+ __exportStar(require("./eligibility"), exports);
21
22
  __exportStar(require("./giveaway-repository"), exports);
22
23
  __exportStar(require("./schemas"), exports);
23
24
  __exportStar(require("./credit-award"), exports);
package/dist/index.d.ts CHANGED
@@ -2,8 +2,6 @@ export * from './types';
2
2
  export * from './db/mongo';
3
3
  export * from './db/mongo2';
4
4
  export * from './db/mongo3';
5
- export * from './tw/utils';
6
- export * from './tw/wallet';
7
5
  export * from './auth';
8
6
  export * from './chunk';
9
7
  export * from './env';
@@ -20,3 +18,5 @@ export * from './validator';
20
18
  export * from './id';
21
19
  export * from './http-error';
22
20
  export * from './giveaways';
21
+ export * from './constants/revenuecat';
22
+ export * from './revenuecat';
package/dist/index.js CHANGED
@@ -18,8 +18,6 @@ __exportStar(require("./types"), exports);
18
18
  __exportStar(require("./db/mongo"), exports);
19
19
  __exportStar(require("./db/mongo2"), exports);
20
20
  __exportStar(require("./db/mongo3"), exports);
21
- __exportStar(require("./tw/utils"), exports);
22
- __exportStar(require("./tw/wallet"), exports);
23
21
  __exportStar(require("./auth"), exports);
24
22
  __exportStar(require("./chunk"), exports);
25
23
  __exportStar(require("./env"), exports);
@@ -36,3 +34,5 @@ __exportStar(require("./validator"), exports);
36
34
  __exportStar(require("./id"), exports);
37
35
  __exportStar(require("./http-error"), exports);
38
36
  __exportStar(require("./giveaways"), exports);
37
+ __exportStar(require("./constants/revenuecat"), exports);
38
+ __exportStar(require("./revenuecat"), exports);
package/dist/pub.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.publishMessage = publishMessage;
4
4
  // src/pub.ts
5
5
  const pubsub_1 = require("@google-cloud/pubsub");
6
+ const logger_1 = require("./logger");
6
7
  let cachedPubSub = null;
7
8
  function getPubSub(projectId) {
8
9
  if (!projectId)
@@ -26,11 +27,11 @@ async function publishMessage(projectId, topicName, envelope) {
26
27
  const messageId = await topic.publishMessage({
27
28
  data
28
29
  });
29
- console.info(`INFO: Pub/Sub publish topic=${topicName} msg_id=${messageId}`);
30
+ (0, logger_1.log)(logger_1.LogLevel.INFO, 'Pub/Sub publish', { topicName, messageId });
30
31
  return messageId;
31
32
  }
32
33
  catch (err) {
33
- console.error('ERROR: publish failed:', err);
34
+ (0, logger_1.log)(logger_1.LogLevel.ERROR, 'publish failed', { error: err });
34
35
  throw err;
35
36
  }
36
37
  }
@@ -0,0 +1,16 @@
1
+ import { HttpError } from './http-error';
2
+ import type { GiveawayCreditEligibilityEvidence, RevenueCatClient, RevenueCatEntitlementIds } from './types/revenuecat';
3
+ export type RevenueCatClientOptions = {
4
+ apiKey: string;
5
+ projectId: string;
6
+ timeoutMs?: number;
7
+ totalTimeoutMs?: number;
8
+ maxPages?: number;
9
+ };
10
+ /** Deliberately excludes the original HTTP error, which can contain the API key. */
11
+ export declare class RevenueCatLookupError extends HttpError {
12
+ constructor();
13
+ }
14
+ export declare function createRevenueCatClient(options: RevenueCatClientOptions): RevenueCatClient;
15
+ /** Resolve any eligible entitlement from one customer lookup; never infer Free from API failures. */
16
+ export declare function verifyCreditGiveawayEntitlement(userId: string, client: RevenueCatClient, entitlementIds: RevenueCatEntitlementIds): Promise<GiveawayCreditEligibilityEvidence | null>;