@dappermountain/payload-plugin-achievements 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +20 -6
  2. package/dist/admin/ReconcileProgressionButton.d.ts +11 -0
  3. package/dist/admin/ReconcileProgressionButton.d.ts.map +1 -0
  4. package/dist/admin/ReconcileProgressionButton.js +41 -0
  5. package/dist/catalog.d.ts +3 -0
  6. package/dist/catalog.d.ts.map +1 -1
  7. package/dist/catalog.js +1 -0
  8. package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.d.ts.map +1 -1
  9. package/dist/collections/AchievementRequests/hooks/prepareAchievementRequest.js +35 -15
  10. package/dist/collections/Grants/hooks/afterGrantMutation.d.ts +4 -0
  11. package/dist/collections/Grants/hooks/afterGrantMutation.d.ts.map +1 -0
  12. package/dist/collections/Grants/hooks/afterGrantMutation.js +56 -0
  13. package/dist/collections/Grants/hooks/index.d.ts.map +1 -1
  14. package/dist/collections/Grants/hooks/index.js +3 -0
  15. package/dist/collections/Grants/index.d.ts.map +1 -1
  16. package/dist/collections/Grants/index.js +18 -1
  17. package/dist/collections/Logs/index.js +1 -1
  18. package/dist/collections/TierRequests/access/index.d.ts +3 -0
  19. package/dist/collections/TierRequests/access/index.d.ts.map +1 -0
  20. package/dist/collections/TierRequests/access/index.js +8 -0
  21. package/dist/collections/TierRequests/hooks/index.d.ts +3 -0
  22. package/dist/collections/TierRequests/hooks/index.d.ts.map +1 -0
  23. package/dist/collections/TierRequests/hooks/index.js +5 -0
  24. package/dist/collections/TierRequests/hooks/prepareTierRequest.d.ts +8 -0
  25. package/dist/collections/TierRequests/hooks/prepareTierRequest.d.ts.map +1 -0
  26. package/dist/collections/TierRequests/hooks/prepareTierRequest.js +67 -0
  27. package/dist/collections/TierRequests/index.d.ts +3 -0
  28. package/dist/collections/TierRequests/index.d.ts.map +1 -0
  29. package/dist/collections/TierRequests/index.js +47 -0
  30. package/dist/collections/Tiers/index.d.ts.map +1 -1
  31. package/dist/collections/Tiers/index.js +11 -3
  32. package/dist/collections/index.d.ts.map +1 -1
  33. package/dist/collections/index.js +2 -0
  34. package/dist/collections/slugs.d.ts.map +1 -1
  35. package/dist/collections/slugs.js +1 -0
  36. package/dist/defaults.d.ts +3 -0
  37. package/dist/defaults.d.ts.map +1 -1
  38. package/dist/defaults.js +12 -3
  39. package/dist/endpoints/index.d.ts.map +1 -1
  40. package/dist/endpoints/index.js +9 -3
  41. package/dist/endpoints/reconcile.d.ts +8 -0
  42. package/dist/endpoints/reconcile.d.ts.map +1 -0
  43. package/dist/endpoints/reconcile.js +70 -0
  44. package/dist/exports/client.d.ts +1 -0
  45. package/dist/exports/client.d.ts.map +1 -1
  46. package/dist/exports/client.js +1 -0
  47. package/dist/index.d.ts +3 -3
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +2 -2
  50. package/dist/seed/index.d.ts +2 -0
  51. package/dist/seed/index.d.ts.map +1 -1
  52. package/dist/seed/index.js +1 -0
  53. package/dist/services/achievement/contextFlags.d.ts +5 -0
  54. package/dist/services/achievement/contextFlags.d.ts.map +1 -0
  55. package/dist/services/achievement/contextFlags.js +4 -0
  56. package/dist/services/achievement/grantSideEffects.d.ts +54 -0
  57. package/dist/services/achievement/grantSideEffects.d.ts.map +1 -0
  58. package/dist/services/achievement/grantSideEffects.js +269 -0
  59. package/dist/services/achievement/index.d.ts +4 -0
  60. package/dist/services/achievement/index.d.ts.map +1 -1
  61. package/dist/services/achievement/index.js +3 -0
  62. package/dist/services/achievement/reconcile.d.ts +24 -0
  63. package/dist/services/achievement/reconcile.d.ts.map +1 -0
  64. package/dist/services/achievement/reconcile.js +170 -0
  65. package/dist/services/achievement/recordEvent.d.ts.map +1 -1
  66. package/dist/services/achievement/recordEvent.js +8 -0
  67. package/dist/services/achievement/resolveCurrentTier.d.ts +1 -1
  68. package/dist/services/achievement/resolveCurrentTier.d.ts.map +1 -1
  69. package/dist/services/achievement/resolveCurrentTier.js +12 -2
  70. package/dist/services/achievement/submitAchievementRequest.d.ts +4 -2
  71. package/dist/services/achievement/submitAchievementRequest.d.ts.map +1 -1
  72. package/dist/services/achievement/submitAchievementRequest.js +8 -101
  73. package/dist/services/achievement/tierApproval.d.ts +8 -0
  74. package/dist/services/achievement/tierApproval.d.ts.map +1 -0
  75. package/dist/services/achievement/tierApproval.js +21 -0
  76. package/dist/services/achievement/tierProgression.d.ts +22 -0
  77. package/dist/services/achievement/tierProgression.d.ts.map +1 -0
  78. package/dist/services/achievement/tierProgression.js +100 -0
  79. package/dist/types.d.ts +8 -2
  80. package/dist/types.d.ts.map +1 -1
  81. package/package.json +6 -6
@@ -0,0 +1,24 @@
1
+ import type { Payload, PayloadRequest } from 'payload';
2
+ export type ReconcileProgressionResult = {
3
+ usersScanned: number;
4
+ grantedLogsBackfilled: number;
5
+ compositesGranted: number;
6
+ achievementRequestsEnsured: number;
7
+ tierRequestsEnsured: number;
8
+ tierLogsWritten: number;
9
+ };
10
+ /** Repair progression for one user (+ optional scope) from current grants + catalog. */
11
+ export declare function reconcileUserProgression(args: {
12
+ payload: Payload;
13
+ req?: PayloadRequest;
14
+ userId: string;
15
+ scopeId?: string | null;
16
+ }): Promise<Omit<ReconcileProgressionResult, 'usersScanned'>>;
17
+ /** Scan grants and reconcile each distinct (user, scope) pair. */
18
+ export declare function reconcileProgression(args: {
19
+ payload: Payload;
20
+ req?: PayloadRequest;
21
+ scopeId?: string | null;
22
+ limit?: number;
23
+ }): Promise<ReconcileProgressionResult>;
24
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +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;AAY7D,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;AAoFD,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;AAOD,kEAAkE;AAClE,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAyDtC"}
@@ -0,0 +1,170 @@
1
+ import { collectionOf } from '../../collections/helpers';
2
+ import { syncCompositeProgression, } from './grantSideEffects';
3
+ import { recordLog, syncTierChangedLog } from './recordEvent';
4
+ import { relationId } from './relationId';
5
+ import { resolveCatalogId } from './resolveCatalog';
6
+ import { syncTierProgression } from './tierProgression';
7
+ import { userScopeWhere } from './where';
8
+ async function backfillGrantedLogs(args) {
9
+ const typeId = await resolveCatalogId({
10
+ payload: args.req.payload,
11
+ req: args.req,
12
+ key: 'eventTypes',
13
+ slugOrId: 'achievement.granted',
14
+ });
15
+ const grants = await args.req.payload.find({
16
+ collection: collectionOf('grants'),
17
+ depth: 0,
18
+ limit: 500,
19
+ pagination: false,
20
+ overrideAccess: true,
21
+ req: args.req,
22
+ where: userScopeWhere(args.userId, args.scopeId),
23
+ });
24
+ const logs = await args.req.payload.find({
25
+ collection: collectionOf('logs'),
26
+ depth: 0,
27
+ limit: 500,
28
+ pagination: false,
29
+ overrideAccess: true,
30
+ req: args.req,
31
+ where: {
32
+ and: [userScopeWhere(args.userId, args.scopeId), { type: { equals: typeId } }],
33
+ },
34
+ });
35
+ const logged = new Set(logs.docs
36
+ .map((log) => relationId(log.data?.achievement))
37
+ .filter((id) => Boolean(id)));
38
+ let written = 0;
39
+ for (const grant of grants.docs) {
40
+ const achievementId = relationId(grant.achievement);
41
+ if (!achievementId || logged.has(achievementId))
42
+ continue;
43
+ let slug;
44
+ try {
45
+ const def = (await args.req.payload.findByID({
46
+ collection: collectionOf('achievements'),
47
+ id: achievementId,
48
+ depth: 0,
49
+ overrideAccess: true,
50
+ req: args.req,
51
+ select: { slug: true },
52
+ }));
53
+ slug = def?.slug;
54
+ }
55
+ catch {
56
+ // ignore
57
+ }
58
+ const completedAt = grant.completedAt;
59
+ await recordLog({
60
+ payload: args.req.payload,
61
+ req: args.req,
62
+ userId: args.userId,
63
+ scopeId: args.scopeId,
64
+ type: 'achievement.granted',
65
+ data: {
66
+ achievement: achievementId,
67
+ ...(slug ? { achievementSlug: slug } : {}),
68
+ backfilled: true,
69
+ ...(completedAt ? { completedAt } : {}),
70
+ },
71
+ });
72
+ logged.add(achievementId);
73
+ written += 1;
74
+ }
75
+ return written;
76
+ }
77
+ /** Repair progression for one user (+ optional scope) from current grants + catalog. */
78
+ export async function reconcileUserProgression(args) {
79
+ const scopeId = args.scopeId ?? null;
80
+ const req = args.req ??
81
+ {
82
+ payload: args.payload,
83
+ context: {},
84
+ };
85
+ if (!req.context)
86
+ req.context = {};
87
+ const grantedLogsBackfilled = await backfillGrantedLogs({ req, userId: args.userId, scopeId });
88
+ const composites = await syncCompositeProgression({
89
+ req,
90
+ userId: args.userId,
91
+ scopeId,
92
+ });
93
+ const tiers = await syncTierProgression({
94
+ req,
95
+ userId: args.userId,
96
+ scopeId,
97
+ });
98
+ const tierLog = await syncTierChangedLog({
99
+ payload: args.payload,
100
+ req,
101
+ userId: args.userId,
102
+ scopeId,
103
+ });
104
+ return {
105
+ grantedLogsBackfilled,
106
+ compositesGranted: composites.granted,
107
+ achievementRequestsEnsured: composites.requested,
108
+ tierRequestsEnsured: tiers.requested,
109
+ tierLogsWritten: tierLog ? 1 : 0,
110
+ };
111
+ }
112
+ function scopeKey(scope) {
113
+ const id = relationId(scope);
114
+ return id ?? '';
115
+ }
116
+ /** Scan grants and reconcile each distinct (user, scope) pair. */
117
+ export async function reconcileProgression(args) {
118
+ const req = args.req ??
119
+ {
120
+ payload: args.payload,
121
+ context: {},
122
+ };
123
+ if (!req.context)
124
+ req.context = {};
125
+ const where = args.scopeId
126
+ ? { scope: { equals: args.scopeId } }
127
+ : undefined;
128
+ const grants = await args.payload.find({
129
+ collection: collectionOf('grants'),
130
+ depth: 0,
131
+ limit: args.limit ?? 5000,
132
+ pagination: false,
133
+ overrideAccess: true,
134
+ req,
135
+ ...(where ? { where } : {}),
136
+ });
137
+ const pairs = new Map();
138
+ for (const grant of grants.docs) {
139
+ const userId = relationId(grant.user);
140
+ if (!userId)
141
+ continue;
142
+ const scopeId = relationId(grant.scope);
143
+ const key = `${userId}::${scopeKey(scopeId)}`;
144
+ if (!pairs.has(key))
145
+ pairs.set(key, { userId, scopeId });
146
+ }
147
+ const totals = {
148
+ usersScanned: 0,
149
+ grantedLogsBackfilled: 0,
150
+ compositesGranted: 0,
151
+ achievementRequestsEnsured: 0,
152
+ tierRequestsEnsured: 0,
153
+ tierLogsWritten: 0,
154
+ };
155
+ for (const pair of pairs.values()) {
156
+ const result = await reconcileUserProgression({
157
+ payload: args.payload,
158
+ req,
159
+ userId: pair.userId,
160
+ scopeId: pair.scopeId,
161
+ });
162
+ totals.usersScanned += 1;
163
+ totals.grantedLogsBackfilled += result.grantedLogsBackfilled;
164
+ totals.compositesGranted += result.compositesGranted;
165
+ totals.achievementRequestsEnsured += result.achievementRequestsEnsured;
166
+ totals.tierRequestsEnsured += result.tierRequestsEnsured;
167
+ totals.tierLogsWritten += result.tierLogsWritten;
168
+ }
169
+ return totals;
170
+ }
@@ -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,wEAwBA;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,iFAgDA"}
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,iFAgDA"}
@@ -71,6 +71,14 @@ export async function recordMetricChange(args) {
71
71
  change: args.change,
72
72
  reason: args.reason,
73
73
  });
74
+ if (args.req) {
75
+ const { syncTierProgression } = await import('./tierProgression');
76
+ await syncTierProgression({
77
+ req: args.req,
78
+ userId: args.userId,
79
+ scopeId,
80
+ });
81
+ }
74
82
  await syncTierChangedLog({
75
83
  payload: args.payload,
76
84
  req: args.req,
@@ -8,7 +8,7 @@ export type ResolvedTier = {
8
8
  };
9
9
  /**
10
10
  * Derive current tier by walking unlock rules in rank order (lowest first).
11
- * Empty unlock rules pass. `tier-at-least` uses the rank unlocked so far on this walk.
11
+ * Empty unlock rules pass. Tiers with `requiresReview` only count once an approved tier request exists.
12
12
  */
13
13
  export declare function resolveCurrentTier(args: {
14
14
  payload: Payload;
@@ -1 +1 @@
1
- {"version":3,"file":"resolveCurrentTier.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/resolveCurrentTier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAMtD,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,CAAA;AAWD;;;GAGG;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,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAoD/B"}
1
+ {"version":3,"file":"resolveCurrentTier.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/resolveCurrentTier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAOtD,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,CAAA;AAYD;;;GAGG;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,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA6D/B"}
@@ -1,9 +1,10 @@
1
1
  import { collectionOf } from '../../collections/helpers';
2
2
  import { evaluateRules } from './evaluateRules';
3
3
  import { relationId } from './relationId';
4
+ import { hasApprovedTierRequest } from './tierApproval';
4
5
  /**
5
6
  * Derive current tier by walking unlock rules in rank order (lowest first).
6
- * Empty unlock rules pass. `tier-at-least` uses the rank unlocked so far on this walk.
7
+ * Empty unlock rules pass. Tiers with `requiresReview` only count once an approved tier request exists.
7
8
  */
8
9
  export async function resolveCurrentTier(args) {
9
10
  if (!args.userId)
@@ -25,7 +26,6 @@ export async function resolveCurrentTier(args) {
25
26
  ...(where ? { where } : {}),
26
27
  });
27
28
  const tiers = [...result.docs].sort((a, b) => Number(a.rank ?? 0) - Number(b.rank ?? 0));
28
- // evaluateRules requires a PayloadRequest; synthesize a minimal one when only payload is passed.
29
29
  const req = (args.req ?? { payload: args.payload });
30
30
  let current = null;
31
31
  for (const tier of tiers) {
@@ -39,6 +39,16 @@ export async function resolveCurrentTier(args) {
39
39
  });
40
40
  if (!unlocked)
41
41
  continue;
42
+ if (tier.requiresReview === true) {
43
+ const approved = await hasApprovedTierRequest({
44
+ req,
45
+ userId: args.userId,
46
+ tierId: String(tier.id),
47
+ scopeId,
48
+ });
49
+ if (!approved)
50
+ break;
51
+ }
42
52
  current = {
43
53
  id: String(tier.id),
44
54
  rank: Number(tier.rank ?? 0),
@@ -10,12 +10,14 @@ export declare function submitAchievementRequest(args: {
10
10
  scopeId?: string | null;
11
11
  note?: string;
12
12
  }): Promise<import("payload").JsonObject & import("payload").TypeWithID>;
13
- /** Create a grant row, emit achievement.granted, sync tier.changed when the ladder moves. */
13
+ /**
14
+ * Create a grant row. Side effects (logs, composites, tiers) run in Grants hooks.
15
+ */
14
16
  export declare function grantAchievement(args: {
15
17
  req: PayloadRequest;
16
18
  userId: string;
17
19
  achievementId: string;
18
20
  scopeId?: string | null;
19
21
  note?: string | null;
20
- }): Promise<import("payload").JsonObject & import("payload").TypeWithID>;
22
+ }): Promise<Record<string, unknown>>;
21
23
  //# sourceMappingURL=submitAchievementRequest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"submitAchievementRequest.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/submitAchievementRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAMpD;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,wEAeA;AAED,6FAA6F;AAC7F,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,wEAqEA"}
1
+ {"version":3,"file":"submitAchievementRequest.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/submitAchievementRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAK7C;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,wEAcA;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,oCAQA"}
@@ -1,6 +1,5 @@
1
1
  import { collectionOf } from '../../collections/helpers';
2
- import { evaluateRules, ruleGroupHasProgressRequirements } from './evaluateRules';
3
- import { recordLog, syncTierChangedLog } from './recordEvent';
2
+ import { createGrantRow } from './grantSideEffects';
4
3
  /**
5
4
  * Create an achievement request (or auto-approved grant) via the collection.
6
5
  * Hooks bind the requesting user, check eligibility, and grant when approved.
@@ -9,7 +8,6 @@ export async function submitAchievementRequest(args) {
9
8
  return args.req.payload.create({
10
9
  collection: collectionOf('achievementRequests'),
11
10
  data: {
12
- // user is overwritten from req.user in beforeValidate
13
11
  user: args.userId,
14
12
  scope: args.scopeId ?? undefined,
15
13
  achievement: args.achievementId,
@@ -21,106 +19,15 @@ export async function submitAchievementRequest(args) {
21
19
  user: args.req.user ?? undefined,
22
20
  });
23
21
  }
24
- /** Create a grant row, emit achievement.granted, sync tier.changed when the ladder moves. */
22
+ /**
23
+ * Create a grant row. Side effects (logs, composites, tiers) run in Grants hooks.
24
+ */
25
25
  export async function grantAchievement(args) {
26
- const scopeId = args.scopeId ?? null;
27
- const achievementId = args.achievementId;
28
- const definition = (await args.req.payload.findByID({
29
- collection: collectionOf('achievements'),
30
- id: achievementId,
31
- depth: 0,
32
- overrideAccess: true,
33
- req: args.req,
34
- }));
35
- const clauses = [
36
- { user: { equals: args.userId } },
37
- { achievement: { equals: achievementId } },
38
- ];
39
- if (scopeId)
40
- clauses.push({ scope: { equals: scopeId } });
41
- else
42
- clauses.push({ scope: { exists: false } });
43
- const existing = await args.req.payload.find({
44
- collection: collectionOf('grants'),
45
- depth: 0,
46
- limit: 1,
47
- overrideAccess: true,
48
- req: args.req,
49
- where: { and: clauses },
50
- });
51
- if (existing.docs[0])
52
- return existing.docs[0];
53
- const result = await args.req.payload.create({
54
- collection: collectionOf('grants'),
55
- data: {
56
- user: args.userId,
57
- scope: scopeId ?? undefined,
58
- achievement: achievementId,
59
- completedAt: new Date().toISOString(),
60
- ...(args.note ? { note: args.note } : {}),
61
- },
62
- overrideAccess: true,
63
- req: args.req,
64
- });
65
- await recordLog({
66
- payload: args.req.payload,
26
+ return createGrantRow({
67
27
  req: args.req,
68
28
  userId: args.userId,
69
- scopeId,
70
- type: 'achievement.granted',
71
- data: {
72
- achievement: achievementId,
73
- ...(definition?.slug ? { achievementSlug: definition.slug } : {}),
74
- },
75
- });
76
- await syncCompositeGrants({
77
- req: args.req,
78
- userId: args.userId,
79
- scopeId,
80
- });
81
- await syncTierChangedLog({
82
- payload: args.req.payload,
83
- req: args.req,
84
- userId: args.userId,
85
- scopeId,
86
- });
87
- return result;
88
- }
89
- async function syncCompositeGrants(args) {
90
- const where = args.scopeId
91
- ? { or: [{ scope: { equals: args.scopeId } }, { scope: { exists: false } }] }
92
- : undefined;
93
- const catalog = await args.req.payload.find({
94
- collection: collectionOf('achievements'),
95
- depth: 0,
96
- limit: 200,
97
- pagination: false,
98
- overrideAccess: true,
99
- req: args.req,
100
- ...(where ? { where } : {}),
29
+ achievementId: args.achievementId,
30
+ scopeId: args.scopeId,
31
+ note: args.note,
101
32
  });
102
- for (const doc of catalog.docs) {
103
- const def = doc;
104
- if (def.requiresReview !== false)
105
- continue;
106
- if (!ruleGroupHasProgressRequirements(def.completionRules))
107
- continue;
108
- if (String(def.id) === '')
109
- continue;
110
- const complete = await evaluateRules({
111
- payload: args.req.payload,
112
- req: args.req,
113
- rules: def.completionRules,
114
- userId: args.userId,
115
- scopeId: args.scopeId,
116
- });
117
- if (!complete)
118
- continue;
119
- await grantAchievement({
120
- req: args.req,
121
- userId: args.userId,
122
- achievementId: String(def.id),
123
- scopeId: args.scopeId,
124
- });
125
- }
126
33
  }
@@ -0,0 +1,8 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ export declare function hasApprovedTierRequest(args: {
3
+ req: PayloadRequest;
4
+ userId: string;
5
+ tierId: string;
6
+ scopeId: string | null;
7
+ }): Promise<boolean>;
8
+ //# sourceMappingURL=tierApproval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tierApproval.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/tierApproval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAIpD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBnB"}
@@ -0,0 +1,21 @@
1
+ import { collectionOf } from '../../collections/helpers';
2
+ export async function hasApprovedTierRequest(args) {
3
+ const clauses = [
4
+ { user: { equals: args.userId } },
5
+ { tier: { equals: args.tierId } },
6
+ { status: { equals: 'approved' } },
7
+ ];
8
+ if (args.scopeId)
9
+ clauses.push({ scope: { equals: args.scopeId } });
10
+ else
11
+ clauses.push({ scope: { exists: false } });
12
+ const existing = await args.req.payload.find({
13
+ collection: collectionOf('tierRequests'),
14
+ depth: 0,
15
+ limit: 1,
16
+ overrideAccess: true,
17
+ req: args.req,
18
+ where: { and: clauses },
19
+ });
20
+ return Boolean(existing.docs[0]);
21
+ }
@@ -0,0 +1,22 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ export declare function ensureTierRequest(args: {
3
+ req: PayloadRequest;
4
+ userId: string;
5
+ tierId: string;
6
+ scopeId: string | null;
7
+ }): Promise<{
8
+ doc: Record<string, unknown>;
9
+ created: boolean;
10
+ }>;
11
+ /**
12
+ * When unlock rules pass and the tier requires review, ensure a pending/approved request exists.
13
+ */
14
+ export declare function syncTierProgression(args: {
15
+ req: PayloadRequest;
16
+ userId: string;
17
+ scopeId: string | null;
18
+ }): Promise<{
19
+ requested: number;
20
+ }>;
21
+ export { hasApprovedTierRequest } from './tierApproval';
22
+ //# sourceMappingURL=tierProgression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tierProgression.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/tierProgression.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAgBpD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAsC9D;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA0DjC;AAED,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,100 @@
1
+ import { collectionOf } from '../../collections/helpers';
2
+ import { CTX_SYSTEM_REQUEST } from './contextFlags';
3
+ import { evaluateRules } from './evaluateRules';
4
+ import { hasApprovedTierRequest } from './tierApproval';
5
+ export async function ensureTierRequest(args) {
6
+ const clauses = [
7
+ { user: { equals: args.userId } },
8
+ { tier: { equals: args.tierId } },
9
+ { status: { in: ['pending', 'approved'] } },
10
+ ];
11
+ if (args.scopeId)
12
+ clauses.push({ scope: { equals: args.scopeId } });
13
+ else
14
+ clauses.push({ scope: { exists: false } });
15
+ const existing = await args.req.payload.find({
16
+ collection: collectionOf('tierRequests'),
17
+ depth: 0,
18
+ limit: 1,
19
+ overrideAccess: true,
20
+ req: args.req,
21
+ where: { and: clauses },
22
+ });
23
+ if (existing.docs[0]) {
24
+ return { doc: existing.docs[0], created: false };
25
+ }
26
+ args.req.context[CTX_SYSTEM_REQUEST] = true;
27
+ try {
28
+ const doc = (await args.req.payload.create({
29
+ collection: collectionOf('tierRequests'),
30
+ data: {
31
+ user: args.userId,
32
+ scope: args.scopeId ?? undefined,
33
+ tier: args.tierId,
34
+ status: 'pending',
35
+ },
36
+ overrideAccess: true,
37
+ req: args.req,
38
+ }));
39
+ return { doc, created: true };
40
+ }
41
+ finally {
42
+ delete args.req.context[CTX_SYSTEM_REQUEST];
43
+ }
44
+ }
45
+ /**
46
+ * When unlock rules pass and the tier requires review, ensure a pending/approved request exists.
47
+ */
48
+ export async function syncTierProgression(args) {
49
+ const scopeId = args.scopeId;
50
+ const where = scopeId
51
+ ? { or: [{ scope: { equals: scopeId } }, { scope: { exists: false } }] }
52
+ : undefined;
53
+ const result = await args.req.payload.find({
54
+ collection: collectionOf('tiers'),
55
+ depth: 0,
56
+ limit: 100,
57
+ pagination: false,
58
+ overrideAccess: true,
59
+ req: args.req,
60
+ sort: 'rank',
61
+ ...(where ? { where } : {}),
62
+ });
63
+ const tiers = [...result.docs].sort((a, b) => Number(a.rank ?? 0) - Number(b.rank ?? 0));
64
+ let requested = 0;
65
+ let ladderRank = null;
66
+ for (const tier of tiers) {
67
+ const unlocked = await evaluateRules({
68
+ payload: args.req.payload,
69
+ req: args.req,
70
+ rules: tier.unlockRules,
71
+ userId: args.userId,
72
+ scopeId,
73
+ ladderRank,
74
+ });
75
+ if (!unlocked)
76
+ continue;
77
+ if (tier.requiresReview === true) {
78
+ const { created } = await ensureTierRequest({
79
+ req: args.req,
80
+ userId: args.userId,
81
+ tierId: String(tier.id),
82
+ scopeId,
83
+ });
84
+ const approved = await hasApprovedTierRequest({
85
+ req: args.req,
86
+ userId: args.userId,
87
+ tierId: String(tier.id),
88
+ scopeId,
89
+ });
90
+ if (!approved) {
91
+ if (created)
92
+ requested += 1;
93
+ break;
94
+ }
95
+ }
96
+ ladderRank = Number(tier.rank ?? 0);
97
+ }
98
+ return { requested };
99
+ }
100
+ export { hasApprovedTierRequest } from './tierApproval';
package/dist/types.d.ts CHANGED
@@ -33,7 +33,7 @@ export type AchievementRuleType = {
33
33
  progressRole?: 'requirement' | 'gate';
34
34
  };
35
35
  /** Logical collection ids — map to configurable slugs via `collections`. */
36
- export type AchievementCollectionKey = 'tiers' | 'achievements' | 'grants' | 'achievementRequests' | 'logs' | 'eventTypes' | 'metrics';
36
+ export type AchievementCollectionKey = 'tiers' | 'achievements' | 'grants' | 'achievementRequests' | 'tierRequests' | 'logs' | 'eventTypes' | 'metrics';
37
37
  /**
38
38
  * Host overrides layered onto a plugin collection (Form Builder–style).
39
39
  * Access/admin merge shallowly; hook arrays are appended after plugin hooks.
@@ -80,6 +80,12 @@ export type AchievementEndpointsOptions = {
80
80
  * Pass `false` to skip registering the endpoint.
81
81
  */
82
82
  me?: string | false;
83
+ /**
84
+ * Root REST path to repair progression from existing grants (`POST`).
85
+ * Default: `'/achievements/reconcile'`. Requires host `canReview`.
86
+ * Pass `false` to skip.
87
+ */
88
+ reconcile?: string | false;
83
89
  };
84
90
  export type AchievementPluginOptions = {
85
91
  /**
@@ -113,5 +119,5 @@ export type AchievementPluginOptions = {
113
119
  };
114
120
  export type BuiltInRuleType = 'achievement-complete' | 'event-count' | 'metric-minimum' | 'tier-at-least';
115
121
  /** Engine log-type catalog slugs (seeded as system catalog rows). */
116
- export type BuiltInEventType = 'achievement.granted' | 'metric.delta' | 'tier.changed';
122
+ export type BuiltInEventType = 'achievement.granted' | 'achievement.revoked' | 'metric.delta' | 'tier.changed';
117
123
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAE1E,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAA;IAClC,GAAG,EAAE,cAAc,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,+FAA+F;IAC/F,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IACvE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAA;IACtE;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,GAAG,MAAM,CAAA;CACtC,CAAA;AAED,4EAA4E;AAC5E,MAAM,MAAM,wBAAwB,GAChC,OAAO,GACP,cAAc,GACd,QAAQ,GACR,qBAAqB,GACrB,MAAM,GACN,YAAY,GACZ,SAAS,CAAA;AAEb;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IACnC,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACjC,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACjC,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC3B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC,CAAA;IACzD;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,wBAAwB,EAAE,6BAA6B,CAAC,CAAC,CAAA;CACrF,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACnF,+CAA+C;IAC/C,WAAW,CAAC,EAAE,6BAA6B,CAAA;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uDAAuD;IACvD,SAAS,CAAC,EAAE,2BAA2B,CAAA;IACvC,UAAU,CAAC,EAAE;QACX,oDAAoD;QACpD,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAA;KAClC,CAAA;IACD;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,KAAK,CAAC,EAAE,sBAAsB,CAAA;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,uBAAuB,CAAA;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,eAAe,GACvB,sBAAsB,GACtB,aAAa,GACb,gBAAgB,GAChB,eAAe,CAAA;AAEnB,qEAAqE;AACrE,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,GAAG,cAAc,GAAG,cAAc,CAAA"}
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.1.0",
3
+ "version": "0.2.0",
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",
@@ -69,15 +69,15 @@
69
69
  "react": "^19.0.0"
70
70
  },
71
71
  "devDependencies": {
72
+ "@dappermountain/agent-payload": "^0.1.5",
73
+ "@dappermountain/agent-practices": "^0.1.2",
72
74
  "@payloadcms/ui": "3.88.0",
75
+ "@types/bun": "1.4.0",
73
76
  "@types/react": "^19.0.0",
74
77
  "payload": "3.88.0",
75
- "react": "19.2.4",
76
- "typescript": "5.9.2",
77
78
  "prettier": "^3.6.2",
78
- "@types/bun": "1.4.0",
79
- "@dappermountain/agent-practices": "^0.1.0",
80
- "@dappermountain/agent-payload": "^0.1.0"
79
+ "react": "19.2.4",
80
+ "typescript": "5.9.2"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=20"