@dappermountain/payload-plugin-achievements 0.2.6 → 0.2.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.
Files changed (74) hide show
  1. package/README.md +34 -8
  2. package/dist/catalog.d.ts +1 -0
  3. package/dist/catalog.d.ts.map +1 -1
  4. package/dist/catalog.js +1 -1
  5. package/dist/cli/reconcile.d.ts.map +1 -1
  6. package/dist/cli/reconcile.js +1 -0
  7. package/dist/collections/MetricBalances/access/index.d.ts +4 -0
  8. package/dist/collections/MetricBalances/access/index.d.ts.map +1 -0
  9. package/dist/collections/MetricBalances/access/index.js +9 -0
  10. package/dist/collections/MetricBalances/hooks/index.d.ts +3 -0
  11. package/dist/collections/MetricBalances/hooks/index.d.ts.map +1 -0
  12. package/dist/collections/MetricBalances/hooks/index.js +1 -0
  13. package/dist/collections/MetricBalances/index.d.ts +3 -0
  14. package/dist/collections/MetricBalances/index.d.ts.map +1 -0
  15. package/dist/collections/MetricBalances/index.js +48 -0
  16. package/dist/collections/Metrics/index.d.ts.map +1 -1
  17. package/dist/collections/Metrics/index.js +68 -3
  18. package/dist/collections/fields/ruleFields.d.ts.map +1 -1
  19. package/dist/collections/fields/ruleFields.js +48 -2
  20. package/dist/collections/hooks/preventCatalogDelete.d.ts.map +1 -1
  21. package/dist/collections/hooks/preventCatalogDelete.js +10 -1
  22. package/dist/collections/index.d.ts.map +1 -1
  23. package/dist/collections/index.js +2 -0
  24. package/dist/collections/slugs.d.ts.map +1 -1
  25. package/dist/collections/slugs.js +1 -0
  26. package/dist/defaults.d.ts +4 -0
  27. package/dist/defaults.d.ts.map +1 -1
  28. package/dist/defaults.js +10 -0
  29. package/dist/endpoints/index.d.ts.map +1 -1
  30. package/dist/endpoints/index.js +4 -0
  31. package/dist/endpoints/leaderboard.d.ts +6 -0
  32. package/dist/endpoints/leaderboard.d.ts.map +1 -0
  33. package/dist/endpoints/leaderboard.js +59 -0
  34. package/dist/extensions/elapsedSinceOptions.d.ts +7 -0
  35. package/dist/extensions/elapsedSinceOptions.d.ts.map +1 -0
  36. package/dist/extensions/elapsedSinceOptions.js +16 -0
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -2
  40. package/dist/plugin.d.ts.map +1 -1
  41. package/dist/plugin.js +9 -0
  42. package/dist/seed/index.d.ts +10 -0
  43. package/dist/seed/index.d.ts.map +1 -1
  44. package/dist/seed/index.js +14 -2
  45. package/dist/services/achievement/builtInRules.d.ts.map +1 -1
  46. package/dist/services/achievement/builtInRules.js +59 -21
  47. package/dist/services/achievement/duration.d.ts +6 -0
  48. package/dist/services/achievement/duration.d.ts.map +1 -0
  49. package/dist/services/achievement/duration.js +43 -0
  50. package/dist/services/achievement/getMetricLeaderboard.d.ts +30 -0
  51. package/dist/services/achievement/getMetricLeaderboard.d.ts.map +1 -0
  52. package/dist/services/achievement/getMetricLeaderboard.js +62 -0
  53. package/dist/services/achievement/index.d.ts +5 -0
  54. package/dist/services/achievement/index.d.ts.map +1 -1
  55. package/dist/services/achievement/index.js +4 -0
  56. package/dist/services/achievement/metricBalances.d.ts +42 -0
  57. package/dist/services/achievement/metricBalances.d.ts.map +1 -0
  58. package/dist/services/achievement/metricBalances.js +137 -0
  59. package/dist/services/achievement/reconcile.d.ts +1 -0
  60. package/dist/services/achievement/reconcile.d.ts.map +1 -1
  61. package/dist/services/achievement/reconcile.js +10 -0
  62. package/dist/services/achievement/recordEvent.d.ts.map +1 -1
  63. package/dist/services/achievement/recordEvent.js +22 -1
  64. package/dist/services/achievement/resolveElapsedAnchor.d.ts +15 -0
  65. package/dist/services/achievement/resolveElapsedAnchor.d.ts.map +1 -0
  66. package/dist/services/achievement/resolveElapsedAnchor.js +98 -0
  67. package/dist/services/achievement/resolveMetricValue.d.ts +32 -0
  68. package/dist/services/achievement/resolveMetricValue.d.ts.map +1 -0
  69. package/dist/services/achievement/resolveMetricValue.js +73 -0
  70. package/dist/services/achievement/validateRules.d.ts.map +1 -1
  71. package/dist/services/achievement/validateRules.js +19 -0
  72. package/dist/types.d.ts +26 -3
  73. package/dist/types.d.ts.map +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,137 @@
1
+ import { collectionOf } from '../../collections/helpers.js';
2
+ import { relationId } from './relationId.js';
3
+ import { userScopeWhere } from './where.js';
4
+ export function metricBalanceKey(userId, scopeId, metricId) {
5
+ return `${userId}:${scopeId ?? ''}:${metricId}`;
6
+ }
7
+ export async function findMetricBalance(args) {
8
+ const key = metricBalanceKey(args.userId, args.scopeId, args.metricId);
9
+ const found = await args.payload.find({
10
+ collection: collectionOf('metricBalances'),
11
+ depth: 0,
12
+ limit: 1,
13
+ overrideAccess: true,
14
+ req: args.req,
15
+ where: { key: { equals: key } },
16
+ });
17
+ const doc = found.docs[0];
18
+ const id = relationId(doc);
19
+ if (!id)
20
+ return null;
21
+ const value = typeof doc?.value === 'number' && Number.isFinite(doc.value) ? doc.value : 0;
22
+ return { id, value };
23
+ }
24
+ export async function applyMetricBalanceDelta(args) {
25
+ const existing = await findMetricBalance(args);
26
+ const nextValue = (existing?.value ?? 0) + args.change;
27
+ const key = metricBalanceKey(args.userId, args.scopeId, args.metricId);
28
+ if (existing) {
29
+ const updated = await args.payload.update({
30
+ collection: collectionOf('metricBalances'),
31
+ id: existing.id,
32
+ data: { value: nextValue },
33
+ overrideAccess: true,
34
+ req: args.req,
35
+ });
36
+ return { id: existing.id, value: typeof updated.value === 'number' ? updated.value : nextValue };
37
+ }
38
+ const created = await args.payload.create({
39
+ collection: collectionOf('metricBalances'),
40
+ data: {
41
+ user: args.userId,
42
+ scope: args.scopeId ?? undefined,
43
+ metric: args.metricId,
44
+ value: nextValue,
45
+ key,
46
+ },
47
+ overrideAccess: true,
48
+ req: args.req,
49
+ });
50
+ return {
51
+ id: String(created.id),
52
+ value: typeof created.value === 'number' ? created.value : nextValue,
53
+ };
54
+ }
55
+ /** Sum metric.delta log changes and upsert the balance projection. */
56
+ export async function rebuildMetricBalance(args) {
57
+ const scopeId = args.scopeId ?? null;
58
+ const logs = await args.payload.find({
59
+ collection: collectionOf('logs'),
60
+ depth: 0,
61
+ limit: 10_000,
62
+ pagination: false,
63
+ overrideAccess: true,
64
+ req: args.req,
65
+ where: {
66
+ and: [userScopeWhere(args.userId, scopeId), { metric: { equals: args.metricId } }],
67
+ },
68
+ });
69
+ const value = logs.docs.reduce((sum, doc) => {
70
+ const change = doc.change;
71
+ return sum + (typeof change === 'number' && Number.isFinite(change) ? change : 0);
72
+ }, 0);
73
+ const existing = await findMetricBalance({
74
+ payload: args.payload,
75
+ req: args.req,
76
+ userId: args.userId,
77
+ scopeId,
78
+ metricId: args.metricId,
79
+ });
80
+ const key = metricBalanceKey(args.userId, scopeId, args.metricId);
81
+ if (existing) {
82
+ await args.payload.update({
83
+ collection: collectionOf('metricBalances'),
84
+ id: existing.id,
85
+ data: { value },
86
+ overrideAccess: true,
87
+ req: args.req,
88
+ });
89
+ return { id: existing.id, value };
90
+ }
91
+ const created = await args.payload.create({
92
+ collection: collectionOf('metricBalances'),
93
+ data: {
94
+ user: args.userId,
95
+ scope: scopeId ?? undefined,
96
+ metric: args.metricId,
97
+ value,
98
+ key,
99
+ },
100
+ overrideAccess: true,
101
+ req: args.req,
102
+ });
103
+ return { id: String(created.id), value };
104
+ }
105
+ /** Rebuild balances for every distinct metric seen in this user’s metric.delta logs. */
106
+ export async function rebuildMetricBalancesForUser(args) {
107
+ const scopeId = args.scopeId ?? null;
108
+ const logs = await args.payload.find({
109
+ collection: collectionOf('logs'),
110
+ depth: 0,
111
+ limit: 10_000,
112
+ pagination: false,
113
+ overrideAccess: true,
114
+ req: args.req,
115
+ where: {
116
+ and: [userScopeWhere(args.userId, scopeId), { metric: { exists: true } }],
117
+ },
118
+ });
119
+ const metricIds = new Set();
120
+ for (const doc of logs.docs) {
121
+ const id = relationId(doc.metric);
122
+ if (id)
123
+ metricIds.add(id);
124
+ }
125
+ let rebuilt = 0;
126
+ for (const metricId of metricIds) {
127
+ await rebuildMetricBalance({
128
+ payload: args.payload,
129
+ req: args.req,
130
+ userId: args.userId,
131
+ scopeId,
132
+ metricId,
133
+ });
134
+ rebuilt += 1;
135
+ }
136
+ return rebuilt;
137
+ }
@@ -6,6 +6,7 @@ export type ReconcileProgressionResult = {
6
6
  achievementRequestsEnsured: number;
7
7
  tierRequestsEnsured: number;
8
8
  tierLogsWritten: number;
9
+ metricBalancesRebuilt: number;
9
10
  };
10
11
  export type ReconcileProgressionArgs = {
11
12
  payload: Payload;
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAa7D,MAAM,MAAM,0BAA0B,GAAG;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,0BAA0B,EAAE,MAAM,CAAA;IAClC,mBAAmB,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAwFD,wFAAwF;AACxF,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,0BAA0B,EAAE,cAAc,CAAC,CAAC,CAoC5D;AA2LD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,0BAA0B,CAAC,CA8CrC"}
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAc7D,MAAM,MAAM,0BAA0B,GAAG;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,0BAA0B,EAAE,MAAM,CAAA;IAClC,mBAAmB,EAAE,MAAM,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,qBAAqB,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAwFD,wFAAwF;AACxF,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,0BAA0B,EAAE,cAAc,CAAC,CAAC,CA2C5D;AA2LD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,0BAA0B,CAAC,CAgDrC"}
@@ -2,6 +2,7 @@ import { collectionOf } from '../../collections/helpers.js';
2
2
  import { syncCompositeProgression, } from './grantSideEffects.js';
3
3
  import { transitionLogData, transitionToId } from './logData.js';
4
4
  import { recordLog, syncTierChangedLog } from './recordEvent.js';
5
+ import { rebuildMetricBalancesForUser } from './metricBalances.js';
5
6
  import { relationId } from './relationId.js';
6
7
  import { resolveCatalogId } from './resolveCatalog.js';
7
8
  import { syncTierProgression } from './tierProgression.js';
@@ -88,6 +89,12 @@ export async function reconcileUserProgression(args) {
88
89
  if (!req.context)
89
90
  req.context = {};
90
91
  const grantedLogsBackfilled = await backfillGrantedLogs({ req, userId: args.userId, scopeId });
92
+ const metricBalancesRebuilt = await rebuildMetricBalancesForUser({
93
+ payload: args.payload,
94
+ req,
95
+ userId: args.userId,
96
+ scopeId,
97
+ });
91
98
  const composites = await syncCompositeProgression({
92
99
  req,
93
100
  userId: args.userId,
@@ -106,6 +113,7 @@ export async function reconcileUserProgression(args) {
106
113
  });
107
114
  return {
108
115
  grantedLogsBackfilled,
116
+ metricBalancesRebuilt,
109
117
  compositesGranted: composites.granted,
110
118
  achievementRequestsEnsured: composites.requested,
111
119
  tierRequestsEnsured: tiers.requested,
@@ -276,6 +284,7 @@ export async function reconcileProgression(args) {
276
284
  achievementRequestsEnsured: 0,
277
285
  tierRequestsEnsured: 0,
278
286
  tierLogsWritten: 0,
287
+ metricBalancesRebuilt: 0,
279
288
  };
280
289
  for (const pair of pairs) {
281
290
  const result = await reconcileUserProgression({
@@ -290,6 +299,7 @@ export async function reconcileProgression(args) {
290
299
  totals.achievementRequestsEnsured += result.achievementRequestsEnsured;
291
300
  totals.tierRequestsEnsured += result.tierRequestsEnsured;
292
301
  totals.tierLogsWritten += result.tierLogsWritten;
302
+ totals.metricBalancesRebuilt += result.metricBalancesRebuilt;
293
303
  }
294
304
  return totals;
295
305
  }
@@ -1 +1 @@
1
- {"version":3,"file":"recordEvent.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/recordEvent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAWtD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACtC,CAAA;AAgBD,6DAA6D;AAC7D,wBAAsB,SAAS,CAAC,IAAI,EAAE,cAAc,wEAuCnD;AAED,4CAA4C;AAC5C,eAAO,MAAM,WAAW,kBAAY,CAAA;AAEpC;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,wEAiCA;AAED,6FAA6F;AAC7F,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,iFA0DA"}
1
+ {"version":3,"file":"recordEvent.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/recordEvent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAatD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACtC,CAAA;AAgBD,6DAA6D;AAC7D,wBAAsB,SAAS,CAAC,IAAI,EAAE,cAAc,wEAuCnD;AAED,4CAA4C;AAC5C,eAAO,MAAM,WAAW,kBAAY,CAAA;AAEpC;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,wEAsDA;AAED,6FAA6F;AAC7F,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,iFA0DA"}
@@ -2,7 +2,9 @@ import { APIError } from 'payload';
2
2
  import { DEFAULT_ACHIEVEMENT_METRIC_SLUG } from '../../catalog.js';
3
3
  import { collectionOf } from '../../collections/helpers.js';
4
4
  import { eventTypeRequiresActor } from './eventTypeRequiresActor.js';
5
+ import { applyMetricBalanceDelta } from './metricBalances.js';
5
6
  import { transitionLogData, transitionToId } from './logData.js';
7
+ import { loadMetricDoc } from './resolveMetricValue.js';
6
8
  import { resolveCatalogId } from './resolveCatalog.js';
7
9
  import { resolveCurrentTier } from './resolveCurrentTier.js';
8
10
  import { userScopeWhere } from './where.js';
@@ -60,6 +62,17 @@ export const recordEvent = recordLog;
60
62
  export async function recordMetricChange(args) {
61
63
  const metricSlug = args.metric ?? DEFAULT_ACHIEVEMENT_METRIC_SLUG;
62
64
  const scopeId = args.scopeId ?? null;
65
+ const metricDoc = await loadMetricDoc({
66
+ payload: args.payload,
67
+ req: args.req,
68
+ metric: metricSlug,
69
+ });
70
+ if (!metricDoc) {
71
+ throw new APIError(`Unknown metric "${metricSlug}".`, 400);
72
+ }
73
+ if ((metricDoc.kind ?? 'stored') === 'computed') {
74
+ throw new APIError('Computed metrics cannot be changed with recordMetricChange.', 400);
75
+ }
63
76
  const log = await recordLog({
64
77
  payload: args.payload,
65
78
  req: args.req,
@@ -67,10 +80,18 @@ export async function recordMetricChange(args) {
67
80
  scopeId,
68
81
  type: 'metric.delta',
69
82
  actorId: args.actorId,
70
- metric: metricSlug,
83
+ metric: metricDoc.id,
71
84
  change: args.change,
72
85
  reason: args.reason,
73
86
  });
87
+ await applyMetricBalanceDelta({
88
+ payload: args.payload,
89
+ req: args.req,
90
+ userId: args.userId,
91
+ scopeId,
92
+ metricId: metricDoc.id,
93
+ change: args.change,
94
+ });
74
95
  if (args.req) {
75
96
  const { syncTierProgression } = await import('./tierProgression.js');
76
97
  await syncTierProgression({
@@ -0,0 +1,15 @@
1
+ import type { Payload, PayloadRequest } from 'payload';
2
+ /**
3
+ * Resolve the start timestamp for elapsed rules / computed metrics.
4
+ * Built-ins: `user-created-at`, `first-event`. Host keys: `extensions.metricAnchors`.
5
+ */
6
+ export declare function resolveElapsedAnchor(args: {
7
+ payload: Payload;
8
+ req?: PayloadRequest;
9
+ since?: unknown;
10
+ eventType?: unknown;
11
+ eventTypeSlug?: unknown;
12
+ userId: string;
13
+ scopeId: string | null;
14
+ }): Promise<Date | null>;
15
+ //# sourceMappingURL=resolveElapsedAnchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveElapsedAnchor.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/resolveElapsedAnchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AA8CtD;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CA0DvB"}
@@ -0,0 +1,98 @@
1
+ import { collectionOf } from '../../collections/helpers.js';
2
+ import { getAchievementOptions } from '../../options-store.js';
3
+ import { parseDate } from './duration.js';
4
+ import { relationId } from './relationId.js';
5
+ import { userScopeIncludingUnscopedWhere } from './where.js';
6
+ async function resolveEventTypeId(args) {
7
+ let eventTypeId = relationId(args.eventType);
8
+ if (eventTypeId) {
9
+ const byId = await args.payload.find({
10
+ collection: collectionOf('eventTypes'),
11
+ depth: 0,
12
+ limit: 1,
13
+ overrideAccess: true,
14
+ req: args.req,
15
+ where: { id: { equals: eventTypeId } },
16
+ });
17
+ if (byId.docs[0])
18
+ return eventTypeId;
19
+ }
20
+ const slug = typeof args.eventTypeSlug === 'string'
21
+ ? args.eventTypeSlug
22
+ : typeof args.eventType === 'string'
23
+ ? args.eventType
24
+ : '';
25
+ if (!slug)
26
+ return null;
27
+ const found = await args.payload.find({
28
+ collection: collectionOf('eventTypes'),
29
+ depth: 0,
30
+ limit: 1,
31
+ overrideAccess: true,
32
+ req: args.req,
33
+ where: { slug: { equals: slug } },
34
+ });
35
+ return relationId(found.docs[0]);
36
+ }
37
+ /**
38
+ * Resolve the start timestamp for elapsed rules / computed metrics.
39
+ * Built-ins: `user-created-at`, `first-event`. Host keys: `extensions.metricAnchors`.
40
+ */
41
+ export async function resolveElapsedAnchor(args) {
42
+ const since = String(args.since ?? 'user-created-at');
43
+ if (since === 'user-created-at') {
44
+ const usersSlug = getAchievementOptions().usersCollectionSlug ?? 'users';
45
+ try {
46
+ const user = (await args.payload.findByID({
47
+ collection: usersSlug,
48
+ id: args.userId,
49
+ depth: 0,
50
+ overrideAccess: true,
51
+ req: args.req,
52
+ }));
53
+ return parseDate(user?.createdAt);
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ if (since === 'first-event') {
60
+ const eventTypeId = await resolveEventTypeId({
61
+ payload: args.payload,
62
+ req: args.req,
63
+ eventType: args.eventType,
64
+ eventTypeSlug: args.eventTypeSlug,
65
+ });
66
+ if (!eventTypeId)
67
+ return null;
68
+ const logs = await args.payload.find({
69
+ collection: collectionOf('logs'),
70
+ depth: 0,
71
+ limit: 1,
72
+ overrideAccess: true,
73
+ req: args.req,
74
+ sort: 'createdAt',
75
+ where: {
76
+ and: [
77
+ userScopeIncludingUnscopedWhere(args.userId, args.scopeId),
78
+ { type: { equals: eventTypeId } },
79
+ ],
80
+ },
81
+ });
82
+ return parseDate(logs.docs[0]?.createdAt);
83
+ }
84
+ const hostAnchor = getAchievementOptions().extensions?.metricAnchors?.[since];
85
+ if (!hostAnchor)
86
+ return null;
87
+ try {
88
+ return parseDate(await hostAnchor({
89
+ payload: args.payload,
90
+ req: args.req,
91
+ userId: args.userId,
92
+ scopeId: args.scopeId,
93
+ }));
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
@@ -0,0 +1,32 @@
1
+ import type { Payload, PayloadRequest } from 'payload';
2
+ export type MetricDoc = {
3
+ id?: unknown;
4
+ slug?: string;
5
+ kind?: string | null;
6
+ compute?: string | null;
7
+ unit?: string | null;
8
+ since?: string | null;
9
+ eventType?: unknown;
10
+ };
11
+ export declare function loadMetricDoc(args: {
12
+ payload: Payload;
13
+ req?: PayloadRequest;
14
+ metric: string;
15
+ }): Promise<(MetricDoc & {
16
+ id: string;
17
+ }) | null>;
18
+ /**
19
+ * Current numeric value for a metric (stored balance or computed elapsed).
20
+ */
21
+ export declare function resolveMetricValue(args: {
22
+ payload: Payload;
23
+ req?: PayloadRequest;
24
+ /** Metric document id or slug, or a loaded metric doc. */
25
+ metric: string | (MetricDoc & {
26
+ id: string;
27
+ });
28
+ userId: string;
29
+ scopeId: string | null;
30
+ now?: Date;
31
+ }): Promise<number>;
32
+ //# sourceMappingURL=resolveMetricValue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveMetricValue.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/resolveMetricValue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAQtD,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,CAAC,EAAE,OAAO,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,CAAC,SAAS,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAAC,CA+B/C;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,GAAG,CAAC,SAAS,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,GAAG,CAAC,EAAE,IAAI,CAAA;CACX,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB"}
@@ -0,0 +1,73 @@
1
+ import { collectionOf } from '../../collections/helpers.js';
2
+ import { elapsedUnits } from './duration.js';
3
+ import { findMetricBalance } from './metricBalances.js';
4
+ import { relationId } from './relationId.js';
5
+ import { resolveElapsedAnchor } from './resolveElapsedAnchor.js';
6
+ export async function loadMetricDoc(args) {
7
+ const byId = relationId(args.metric);
8
+ if (byId && args.metric === byId) {
9
+ try {
10
+ const doc = (await args.payload.findByID({
11
+ collection: collectionOf('metrics'),
12
+ id: byId,
13
+ depth: 0,
14
+ overrideAccess: true,
15
+ req: args.req,
16
+ }));
17
+ if (doc)
18
+ return { ...doc, id: byId };
19
+ }
20
+ catch {
21
+ // fall through to slug lookup
22
+ }
23
+ }
24
+ const found = await args.payload.find({
25
+ collection: collectionOf('metrics'),
26
+ depth: 0,
27
+ limit: 1,
28
+ overrideAccess: true,
29
+ req: args.req,
30
+ where: {
31
+ or: [{ id: { equals: args.metric } }, { slug: { equals: args.metric } }],
32
+ },
33
+ });
34
+ const doc = found.docs[0];
35
+ const id = relationId(doc);
36
+ if (!doc || !id)
37
+ return null;
38
+ return { ...doc, id };
39
+ }
40
+ /**
41
+ * Current numeric value for a metric (stored balance or computed elapsed).
42
+ */
43
+ export async function resolveMetricValue(args) {
44
+ const doc = typeof args.metric === 'string'
45
+ ? await loadMetricDoc({ payload: args.payload, req: args.req, metric: args.metric })
46
+ : args.metric;
47
+ if (!doc)
48
+ return 0;
49
+ const kind = doc.kind ?? 'stored';
50
+ if (kind === 'computed') {
51
+ if ((doc.compute ?? 'elapsed') !== 'elapsed')
52
+ return 0;
53
+ const anchor = await resolveElapsedAnchor({
54
+ payload: args.payload,
55
+ req: args.req,
56
+ since: doc.since,
57
+ eventType: doc.eventType,
58
+ userId: args.userId,
59
+ scopeId: args.scopeId,
60
+ });
61
+ if (!anchor)
62
+ return 0;
63
+ return elapsedUnits(anchor, doc.unit ?? 'days', args.now ?? new Date()) ?? 0;
64
+ }
65
+ const balance = await findMetricBalance({
66
+ payload: args.payload,
67
+ req: args.req,
68
+ userId: args.userId,
69
+ scopeId: args.scopeId,
70
+ metricId: doc.id,
71
+ });
72
+ return balance?.value ?? 0;
73
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"validateRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/validateRules.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAuD7E;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9D,MAAM,GAAG,IAAI,CAOf;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,EAC/D,KAAK,EAAE,MAAM,GACZ,IAAI,CAKN"}
1
+ {"version":3,"file":"validateRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/validateRules.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CA0E7E;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9D,MAAM,GAAG,IAAI,CAOf;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,EAC/D,KAAK,EAAE,MAAM,GACZ,IAAI,CAKN"}
@@ -49,6 +49,25 @@ export function validateRuleNode(rule) {
49
49
  }
50
50
  return null;
51
51
  }
52
+ if (type === 'elapsed-since') {
53
+ const since = String(rule.since ?? 'user-created-at');
54
+ if (since === 'first-event') {
55
+ if (!relationId(rule.eventType) && typeof rule.eventTypeSlug !== 'string') {
56
+ return 'Elapsed since (first event) needs an event type.';
57
+ }
58
+ }
59
+ else if (since !== 'user-created-at') {
60
+ return 'Elapsed since needs a valid since source.';
61
+ }
62
+ if (typeof rule.amount !== 'number' || Number.isNaN(rule.amount) || rule.amount < 0) {
63
+ return 'Elapsed since needs a non-negative amount.';
64
+ }
65
+ const unit = rule.unit ?? 'days';
66
+ if (unit !== 'days' && unit !== 'hours' && unit !== 'minutes') {
67
+ return 'Elapsed since unit must be days, hours, or minutes.';
68
+ }
69
+ return null;
70
+ }
52
71
  if (!type) {
53
72
  return 'Each rule needs a type.';
54
73
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,11 @@
1
- import type { CollectionConfig, PayloadRequest, TypedUser } from 'payload';
1
+ import type { CollectionConfig, Payload, PayloadRequest, TypedUser } from 'payload';
2
+ /** Host-registered date anchors for computed elapsed metrics / `elapsed-since` rules. */
3
+ export type AchievementMetricAnchor = (args: {
4
+ payload: Payload;
5
+ req?: PayloadRequest;
6
+ userId: string;
7
+ scopeId: string | null;
8
+ }) => Date | null | Promise<Date | null>;
2
9
  export type AchievementScopeConfig = {
3
10
  collection: string;
4
11
  relationField?: string;
@@ -33,7 +40,7 @@ export type AchievementRuleType = {
33
40
  progressRole?: 'requirement' | 'gate';
34
41
  };
35
42
  /** Logical collection ids — map to configurable slugs via `collections`. */
36
- export type AchievementCollectionKey = 'tiers' | 'achievements' | 'grants' | 'achievementRequests' | 'tierRequests' | 'logs' | 'eventTypes' | 'metrics';
43
+ export type AchievementCollectionKey = 'tiers' | 'achievements' | 'grants' | 'achievementRequests' | 'tierRequests' | 'logs' | 'eventTypes' | 'metrics' | 'metricBalances';
37
44
  /**
38
45
  * Host overrides layered onto a plugin collection (Form Builder–style).
39
46
  * Access/admin merge shallowly; hook arrays are appended after plugin hooks.
@@ -80,6 +87,12 @@ export type AchievementEndpointsOptions = {
80
87
  * Pass `false` to skip registering the endpoint.
81
88
  */
82
89
  me?: string | false;
90
+ /**
91
+ * Root REST path for a stored-metric leaderboard (`GET`).
92
+ * Default: `'/achievements/leaderboard'`. Requires an authenticated user.
93
+ * Pass `false` to skip.
94
+ */
95
+ leaderboard?: string | false;
83
96
  /**
84
97
  * Root REST path to repair progression from existing grants (`POST`).
85
98
  * Default: `'/achievements/reconcile'`. Requires host `canReview`.
@@ -101,7 +114,17 @@ export type AchievementPluginOptions = {
101
114
  extensions?: {
102
115
  /** Custom rule evaluators merged with built-ins. */
103
116
  ruleTypes?: AchievementRuleType[];
117
+ /**
118
+ * Named date anchors for computed `elapsed` metrics and `elapsed-since` rules.
119
+ * Keys appear in Admin `since` selects; missing keys resolve to null at eval time.
120
+ */
121
+ metricAnchors?: Record<string, AchievementMetricAnchor>;
104
122
  };
123
+ /**
124
+ * When true (default), ensure system metrics + event types exist on Payload `onInit`
125
+ * (`seedAchievementCatalog`). Host product catalogs still seed via `seedAchievements`.
126
+ */
127
+ seedSystemCatalog?: boolean;
105
128
  /**
106
129
  * Host upload collection slug (e.g. `'media'`). When set, tiers gain an optional
107
130
  * `image` upload field for ladder badges. Icon keys still work without this.
@@ -117,7 +140,7 @@ export type AchievementPluginOptions = {
117
140
  users?: AchievementUsersOptions;
118
141
  usersCollectionSlug?: string;
119
142
  };
120
- export type BuiltInRuleType = 'achievement-complete' | 'event-count' | 'metric-minimum' | 'tier-at-least';
143
+ export type BuiltInRuleType = 'achievement-complete' | 'elapsed-since' | 'event-count' | 'metric-minimum' | 'tier-at-least';
121
144
  /** Engine log-type catalog slugs (seeded as system catalog rows). */
122
145
  export type BuiltInEventType = 'achievement.granted' | 'achievement.revoked' | 'metric.delta' | 'tier.changed';
123
146
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dappermountain/payload-plugin-achievements",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "A robust, Admin-configured achievements engine for Payload: composable unlock rules, derived rank progress, request/review grants, metrics, and scoped multi-tenant support.",
5
5
  "license": "MIT",
6
6
  "type": "module",