@dappermountain/payload-plugin-achievements 0.5.0 → 0.6.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 (58) hide show
  1. package/README.md +278 -234
  2. package/dist/catalog.d.ts +3 -0
  3. package/dist/catalog.d.ts.map +1 -1
  4. package/dist/catalog.js +8 -0
  5. package/dist/collections/Grants/hooks/afterGrantMutation.d.ts.map +1 -1
  6. package/dist/collections/Grants/hooks/afterGrantMutation.js +15 -0
  7. package/dist/collections/Logs/hooks/afterLogMutation.d.ts +4 -0
  8. package/dist/collections/Logs/hooks/afterLogMutation.d.ts.map +1 -0
  9. package/dist/collections/Logs/hooks/afterLogMutation.js +47 -0
  10. package/dist/collections/Logs/hooks/index.d.ts.map +1 -1
  11. package/dist/collections/Logs/hooks/index.js +3 -0
  12. package/dist/collections/Logs/index.js +1 -1
  13. package/dist/collections/Reactions/access/index.d.ts +12 -0
  14. package/dist/collections/Reactions/access/index.d.ts.map +1 -0
  15. package/dist/collections/Reactions/access/index.js +2 -0
  16. package/dist/collections/Reactions/index.d.ts +3 -0
  17. package/dist/collections/Reactions/index.d.ts.map +1 -0
  18. package/dist/collections/Reactions/index.js +97 -0
  19. package/dist/collections/fields/ruleFields.d.ts.map +1 -1
  20. package/dist/collections/fields/ruleFields.js +19 -0
  21. package/dist/collections/fields/whenSibling.d.ts +7 -0
  22. package/dist/collections/fields/whenSibling.d.ts.map +1 -0
  23. package/dist/collections/fields/whenSibling.js +21 -0
  24. package/dist/collections/index.d.ts.map +1 -1
  25. package/dist/collections/index.js +2 -0
  26. package/dist/collections/slugs.d.ts.map +1 -1
  27. package/dist/collections/slugs.js +1 -0
  28. package/dist/defaults.d.ts +10 -1
  29. package/dist/defaults.d.ts.map +1 -1
  30. package/dist/defaults.js +34 -5
  31. package/dist/extensions/actions.d.ts +8 -0
  32. package/dist/extensions/actions.d.ts.map +1 -0
  33. package/dist/extensions/actions.js +26 -0
  34. package/dist/extensions/registry.d.ts +2 -0
  35. package/dist/extensions/registry.d.ts.map +1 -1
  36. package/dist/extensions/registry.js +19 -0
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +3 -3
  40. package/dist/plugin.d.ts.map +1 -1
  41. package/dist/plugin.js +9 -3
  42. package/dist/services/achievement/builtInRules.d.ts.map +1 -1
  43. package/dist/services/achievement/builtInRules.js +29 -10
  44. package/dist/services/achievement/eventTypeRequiresActor.d.ts +1 -0
  45. package/dist/services/achievement/eventTypeRequiresActor.d.ts.map +1 -1
  46. package/dist/services/achievement/eventTypeRequiresActor.js +2 -0
  47. package/dist/services/achievement/grantSideEffects.d.ts +11 -0
  48. package/dist/services/achievement/grantSideEffects.d.ts.map +1 -1
  49. package/dist/services/achievement/grantSideEffects.js +32 -0
  50. package/dist/services/achievement/index.d.ts +3 -1
  51. package/dist/services/achievement/index.d.ts.map +1 -1
  52. package/dist/services/achievement/index.js +2 -1
  53. package/dist/services/achievement/runReactions.d.ts +20 -0
  54. package/dist/services/achievement/runReactions.d.ts.map +1 -0
  55. package/dist/services/achievement/runReactions.js +86 -0
  56. package/dist/types.d.ts +46 -6
  57. package/dist/types.d.ts.map +1 -1
  58. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,SAAS,CAAA;AAM7C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC3B,UAAS,wBAA6B,KAAG,MAqCzC,CAAA"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,SAAS,CAAA;AAO7C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC3B,UAAS,wBAA6B,KAAG,MA0CzC,CAAA"}
package/dist/plugin.js CHANGED
@@ -1,15 +1,21 @@
1
1
  import { buildAchievementCollections } from './collections/index.js';
2
2
  import { usersAchievementsGroup } from './collections/fields/usersAchievementsGroup.js';
3
3
  import { buildAchievementEndpoints } from './endpoints/index.js';
4
+ import { resolveSubjectCollectionsFromConfig } from './defaults.js';
4
5
  import { getAchievementOptions, setAchievementOptions } from './options-store.js';
5
6
  /**
6
7
  * Payload achievements plugin — merges collections (hooks own request/grant flow)
7
8
  * and an optional current-user grants endpoint.
8
9
  */
9
10
  export const achievementPlugin = (options = {}) => (config) => {
10
- // Store the host options (not a pre-resolved object) so `subjects.collections`
11
- // survives `setAchievementOptions` → `resolveOptions`.
12
- setAchievementOptions(options);
11
+ // Resolve `'all'` against the host Config before storing — polymorphic
12
+ // `relationTo` is schema-time. Explicit slug lists pass through unchanged.
13
+ setAchievementOptions({
14
+ ...options,
15
+ subjects: options.subjects
16
+ ? { collections: resolveSubjectCollectionsFromConfig(options, config) }
17
+ : options.subjects,
18
+ });
13
19
  const resolved = getAchievementOptions();
14
20
  if (!resolved.enabled)
15
21
  return config;
@@ -1 +1 @@
1
- {"version":3,"file":"builtInRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/builtInRules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AA4OlF,eAAO,MAAM,gBAAgB,EAAE,mBAAmB,EA6IjD,CAAA"}
1
+ {"version":3,"file":"builtInRules.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/builtInRules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AA4PlF,eAAO,MAAM,gBAAgB,EAAE,mBAAmB,EAiJjD,CAAA"}
@@ -20,6 +20,7 @@ async function resolveMetricId(args) {
20
20
  depth: 0,
21
21
  limit: 1,
22
22
  overrideAccess: true,
23
+ req: args.req,
23
24
  where: { slug: { equals: metricSlug } },
24
25
  });
25
26
  return relationId(found.docs[0]);
@@ -34,6 +35,7 @@ async function resolveEventTypeId(args) {
34
35
  depth: 0,
35
36
  limit: 1,
36
37
  overrideAccess: true,
38
+ req: args.req,
37
39
  where: { id: { equals: eventTypeId } },
38
40
  });
39
41
  if (relationId(byId.docs[0]))
@@ -49,6 +51,7 @@ async function resolveEventTypeId(args) {
49
51
  depth: 0,
50
52
  limit: 1,
51
53
  overrideAccess: true,
54
+ req: args.req,
52
55
  where: { slug: { equals: slugCandidate } },
53
56
  });
54
57
  return relationId(found.docs[0]);
@@ -67,6 +70,7 @@ async function resolveActorTierMinimumRank(args) {
67
70
  id: fromRelation,
68
71
  depth: 0,
69
72
  overrideAccess: true,
73
+ req: args.req,
70
74
  select: { rank: true },
71
75
  }));
72
76
  if (doc && typeof doc.rank === 'number' && Number.isFinite(doc.rank))
@@ -83,6 +87,7 @@ async function resolveActorTierMinimumRank(args) {
83
87
  depth: 0,
84
88
  limit: 1,
85
89
  overrideAccess: true,
90
+ req: args.req,
86
91
  where: { slug: { equals: slug } },
87
92
  select: { rank: true },
88
93
  });
@@ -90,7 +95,11 @@ async function resolveActorTierMinimumRank(args) {
90
95
  return typeof rank === 'number' && Number.isFinite(rank) ? rank : null;
91
96
  }
92
97
  async function countMatchingEventLogs(args) {
93
- const eventTypeId = await resolveEventTypeId({ payload: args.payload, rule: args.rule });
98
+ const eventTypeId = await resolveEventTypeId({
99
+ payload: args.payload,
100
+ req: args.req,
101
+ rule: args.rule,
102
+ });
94
103
  if (!eventTypeId)
95
104
  return 0;
96
105
  const where = {
@@ -101,12 +110,14 @@ async function countMatchingEventLogs(args) {
101
110
  };
102
111
  const minimumRank = await resolveActorTierMinimumRank({
103
112
  payload: args.payload,
113
+ req: args.req,
104
114
  rule: args.rule,
105
115
  });
106
116
  if (minimumRank == null) {
107
117
  const result = await args.payload.count({
108
118
  collection: collectionOf('logs'),
109
119
  overrideAccess: true,
120
+ req: args.req,
110
121
  where,
111
122
  });
112
123
  return result.totalDocs;
@@ -116,6 +127,7 @@ async function countMatchingEventLogs(args) {
116
127
  depth: 0,
117
128
  overrideAccess: true,
118
129
  pagination: false,
130
+ req: args.req,
119
131
  select: { actorTiers: true },
120
132
  where,
121
133
  });
@@ -136,6 +148,7 @@ async function resolveAchievementRef(args) {
136
148
  depth: 0,
137
149
  limit: 1,
138
150
  overrideAccess: true,
151
+ req: args.req,
139
152
  where: args.scopeId
140
153
  ? {
141
154
  and: [{ slug: { equals: requiredSlug } }, { scope: { equals: args.scopeId } }],
@@ -154,6 +167,7 @@ async function resolveAchievementRef(args) {
154
167
  id: requiredId,
155
168
  depth: 0,
156
169
  overrideAccess: true,
170
+ req: args.req,
157
171
  }));
158
172
  requiredSlug = requiredSlug || String(doc?.slug ?? '');
159
173
  eligibilityRules = eligibilityRules ?? doc?.eligibilityRules;
@@ -174,6 +188,7 @@ async function isAchievementGranted(args) {
174
188
  depth: args.requiredSlug && !args.requiredId ? 1 : 0,
175
189
  limit: args.requiredId ? 1 : 50,
176
190
  overrideAccess: true,
191
+ req: args.req,
177
192
  where: { and: grantClauses },
178
193
  });
179
194
  if (args.requiredId)
@@ -199,6 +214,7 @@ export const builtInRuleTypes = [
199
214
  id: tierId,
200
215
  depth: 0,
201
216
  overrideAccess: true,
217
+ req,
202
218
  }));
203
219
  requiredRank = tier?.rank;
204
220
  }
@@ -211,6 +227,7 @@ export const builtInRuleTypes = [
211
227
  depth: 0,
212
228
  limit: 1,
213
229
  overrideAccess: true,
230
+ req,
214
231
  where: scopeId
215
232
  ? { and: [{ slug: { equals: tierSlug } }, { scope: { equals: scopeId } }] }
216
233
  : { slug: { equals: tierSlug } },
@@ -235,10 +252,11 @@ export const builtInRuleTypes = [
235
252
  },
236
253
  {
237
254
  type: 'achievement-complete',
238
- async evaluate({ payload, rule, userId, scopeId }) {
239
- const ref = await resolveAchievementRef({ payload, rule, scopeId });
255
+ async evaluate({ payload, req, rule, userId, scopeId }) {
256
+ const ref = await resolveAchievementRef({ payload, req, rule, scopeId });
240
257
  return isAchievementGranted({
241
258
  payload,
259
+ req,
242
260
  userId,
243
261
  scopeId,
244
262
  requiredId: ref.id,
@@ -247,11 +265,12 @@ export const builtInRuleTypes = [
247
265
  },
248
266
  async progress(args) {
249
267
  const { payload, req, rule, userId, scopeId, ladderRank, progressVisited } = args;
250
- const ref = await resolveAchievementRef({ payload, rule, scopeId });
268
+ const ref = await resolveAchievementRef({ payload, req, rule, scopeId });
251
269
  if (!ref.id && !ref.slug)
252
270
  return 0;
253
271
  const granted = await isAchievementGranted({
254
272
  payload,
273
+ req,
255
274
  userId,
256
275
  scopeId,
257
276
  requiredId: ref.id,
@@ -284,7 +303,7 @@ export const builtInRuleTypes = [
284
303
  {
285
304
  type: 'metric-minimum',
286
305
  async evaluate({ payload, req, rule, userId, scopeId }) {
287
- const resolvedId = await resolveMetricId({ payload, rule });
306
+ const resolvedId = await resolveMetricId({ payload, req, rule });
288
307
  if (!resolvedId)
289
308
  return false;
290
309
  const minimum = Number(rule.minimum ?? 0);
@@ -298,7 +317,7 @@ export const builtInRuleTypes = [
298
317
  return value >= minimum;
299
318
  },
300
319
  async progress({ payload, req, rule, userId, scopeId }) {
301
- const resolvedId = await resolveMetricId({ payload, rule });
320
+ const resolvedId = await resolveMetricId({ payload, req, rule });
302
321
  if (!resolvedId)
303
322
  return 0;
304
323
  const minimum = Number(rule.minimum ?? 0);
@@ -316,16 +335,16 @@ export const builtInRuleTypes = [
316
335
  },
317
336
  {
318
337
  type: 'event-count',
319
- async evaluate({ payload, rule, userId, scopeId }) {
338
+ async evaluate({ payload, req, rule, userId, scopeId }) {
320
339
  const needed = Number(rule.count ?? 1);
321
- const total = await countMatchingEventLogs({ payload, rule, userId, scopeId });
340
+ const total = await countMatchingEventLogs({ payload, req, rule, userId, scopeId });
322
341
  return total >= needed;
323
342
  },
324
- async progress({ payload, rule, userId, scopeId }) {
343
+ async progress({ payload, req, rule, userId, scopeId }) {
325
344
  const needed = Number(rule.count ?? 1);
326
345
  if (!(needed > 0))
327
346
  return 1;
328
- const total = await countMatchingEventLogs({ payload, rule, userId, scopeId });
347
+ const total = await countMatchingEventLogs({ payload, req, rule, userId, scopeId });
329
348
  return clamp01(total / needed);
330
349
  },
331
350
  },
@@ -7,6 +7,7 @@ export type EventTypeFlags = {
7
7
  snapshotActorTiers: boolean;
8
8
  /** Allowed host collections for `subject.relationTo` (empty = any plugin allowlist entry). */
9
9
  subjectRelationTo: string[];
10
+ slug: string | null;
10
11
  };
11
12
  /** Read flags from a loaded event-type document (or populated relationship). */
12
13
  export declare function readEventTypeFlags(doc: unknown): EventTypeFlags | null;
@@ -1 +1 @@
1
- {"version":3,"file":"eventTypeRequiresActor.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/eventTypeRequiresActor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAMtD,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;IACvB,eAAe,EAAE,OAAO,CAAA;IACxB,uEAAuE;IACvE,kBAAkB,EAAE,OAAO,CAAA;IAC3B,8FAA8F;IAC9F,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC5B,CAAA;AAmBD,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAyBtE;AAMD,wDAAwD;AACxD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,cAAc,CAAC,CA2C1B;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnB;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOpB"}
1
+ {"version":3,"file":"eventTypeRequiresActor.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/eventTypeRequiresActor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAMtD,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;IACvB,eAAe,EAAE,OAAO,CAAA;IACxB,uEAAuE;IACvE,kBAAkB,EAAE,OAAO,CAAA;IAC3B,8FAA8F;IAC9F,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB,CAAA;AAoBD,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CA0BtE;AAMD,wDAAwD;AACxD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,cAAc,CAAC,CA2C1B;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnB;AAED,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnB;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOpB"}
@@ -7,6 +7,7 @@ const emptyFlags = {
7
7
  requiresSubject: false,
8
8
  snapshotActorTiers: false,
9
9
  subjectRelationTo: [],
10
+ slug: null,
10
11
  };
11
12
  function readSubjectRelationTo(value) {
12
13
  if (!Array.isArray(value))
@@ -37,6 +38,7 @@ export function readEventTypeFlags(doc) {
37
38
  requiresSubject: Boolean(row.requiresSubject),
38
39
  snapshotActorTiers: Boolean(row.snapshotActorTiers),
39
40
  subjectRelationTo: readSubjectRelationTo(row.subjectRelationTo),
41
+ slug: typeof row.slug === 'string' && row.slug ? row.slug : null,
40
42
  };
41
43
  }
42
44
  function flagsFromDoc(doc) {
@@ -51,4 +51,15 @@ export declare function runAfterGrantDeleted(args: {
51
51
  achievementSlug?: string;
52
52
  cascading?: boolean;
53
53
  }): Promise<void>;
54
+ /**
55
+ * Re-evaluate composed achievements and derived tiers after a log write/delete.
56
+ * Skips grant/revoke audit types — Grants hooks already run this chain.
57
+ */
58
+ export declare function syncProgressionFromLog(args: {
59
+ req: PayloadRequest;
60
+ userId: string;
61
+ scopeId: string | null;
62
+ eventTypeSlug: string | null;
63
+ operation: 'create' | 'delete';
64
+ }): Promise<void>;
54
65
  //# sourceMappingURL=grantSideEffects.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grantSideEffects.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/grantSideEffects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAWpD,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AASzG,wEAAwE;AACxE,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,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;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+BnC;AAED,6EAA6E;AAC7E,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;CACxB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuC9D;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsElD;AAED,oFAAoF;AACpF,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDlB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BhB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuChB"}
1
+ {"version":3,"file":"grantSideEffects.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/grantSideEffects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,SAAS,CAAA;AAYpD,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AASzG,wEAAwE;AACxE,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,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;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+BnC;AAED,6EAA6E;AAC7E,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;CACxB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuC9D;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsElD;AAED,oFAAoF;AACpF,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiDlB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BhB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuChB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,GAAG,EAAE,cAAc,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BhB"}
@@ -1,3 +1,4 @@
1
+ import { isGrantAuditEventSlug } from '../../catalog.js';
1
2
  import { collectionOf } from '../../collections/helpers.js';
2
3
  import { CTX_CASCADING_REVOKE, CTX_SYSTEM_REQUEST } from './contextFlags.js';
3
4
  import { evaluateRules, ruleGroupHasProgressRequirements } from './evaluateRules.js';
@@ -268,3 +269,34 @@ export async function runAfterGrantDeleted(args) {
268
269
  scopeId: args.scopeId,
269
270
  });
270
271
  }
272
+ /**
273
+ * Re-evaluate composed achievements and derived tiers after a log write/delete.
274
+ * Skips grant/revoke audit types — Grants hooks already run this chain.
275
+ */
276
+ export async function syncProgressionFromLog(args) {
277
+ if (!args.userId || isGrantAuditEventSlug(args.eventTypeSlug))
278
+ return;
279
+ if (args.operation === 'delete') {
280
+ await revokeIncompleteComposites({
281
+ req: args.req,
282
+ userId: args.userId,
283
+ scopeId: args.scopeId,
284
+ });
285
+ }
286
+ await syncCompositeProgression({
287
+ req: args.req,
288
+ userId: args.userId,
289
+ scopeId: args.scopeId,
290
+ });
291
+ await syncTierProgression({
292
+ req: args.req,
293
+ userId: args.userId,
294
+ scopeId: args.scopeId,
295
+ });
296
+ await syncTierChangedLog({
297
+ payload: args.req.payload,
298
+ req: args.req,
299
+ userId: args.userId,
300
+ scopeId: args.scopeId,
301
+ });
302
+ }
@@ -23,7 +23,9 @@ export type { TierLadderProgress } from './resolveTierProgress.js';
23
23
  export { grantAchievement, submitAchievementRequest } from './submitAchievementRequest.js';
24
24
  export { reviewAchievementRequest } from './reviewAchievementRequest.js';
25
25
  export { validateRuleGroup, validateRuleNode } from './validateRules.js';
26
- export { syncCompositeProgression } from './grantSideEffects.js';
26
+ export { syncCompositeProgression, syncProgressionFromLog } from './grantSideEffects.js';
27
+ export { runReactions, reactionMatchWhere } from './runReactions.js';
28
+ export type { RunReactionsArgs } from './runReactions.js';
27
29
  export { syncTierProgression } from './tierProgression.js';
28
30
  export { reconcileProgression, reconcileUserProgression } from './reconcile.js';
29
31
  export type { ReconcileProgressionArgs, ReconcileProgressionResult } from './reconcile.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAA;AAC5H,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AAC5E,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AAChG,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAA;AAChF,YAAY,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AACjH,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAChG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAC9F,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAC/E,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAC1F,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAA;AAC5H,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAA;AAC5E,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AAChG,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAA;AAChF,YAAY,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AACjH,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAChG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAC9F,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACjG,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACxE,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACxF,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACpE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAC/E,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAC1F,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA"}
@@ -14,7 +14,8 @@ export { resolveTierProgress } from './resolveTierProgress.js';
14
14
  export { grantAchievement, submitAchievementRequest } from './submitAchievementRequest.js';
15
15
  export { reviewAchievementRequest } from './reviewAchievementRequest.js';
16
16
  export { validateRuleGroup, validateRuleNode } from './validateRules.js';
17
- export { syncCompositeProgression } from './grantSideEffects.js';
17
+ export { syncCompositeProgression, syncProgressionFromLog } from './grantSideEffects.js';
18
+ export { runReactions, reactionMatchWhere } from './runReactions.js';
18
19
  export { syncTierProgression } from './tierProgression.js';
19
20
  export { reconcileProgression, reconcileUserProgression } from './reconcile.js';
20
21
  export { didDisableRequiresReview, releasePendingAchievementReviews, releasePendingTierReviews, } from './releaseReviewGate.js';
@@ -0,0 +1,20 @@
1
+ import type { PayloadRequest, Where } from 'payload';
2
+ import type { AchievementReactionTrigger } from '../../types.js';
3
+ export type RunReactionsArgs = {
4
+ req: PayloadRequest;
5
+ trigger: AchievementReactionTrigger;
6
+ userId: string;
7
+ scopeId: string | null;
8
+ eventTypeId?: string | null;
9
+ eventTypeSlug?: string | null;
10
+ achievementId?: string | null;
11
+ log?: Record<string, unknown> | null;
12
+ };
13
+ export declare function reactionMatchWhere(args: {
14
+ trigger: AchievementReactionTrigger;
15
+ scopeId: string | null;
16
+ eventTypeId?: string | null;
17
+ achievementId?: string | null;
18
+ }): Where | null;
19
+ export declare function runReactions(args: RunReactionsArgs): Promise<number>;
20
+ //# sourceMappingURL=runReactions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runReactions.d.ts","sourceRoot":"","sources":["../../../src/services/achievement/runReactions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAKpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAKhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,cAAc,CAAA;IACnB,OAAO,EAAE,0BAA0B,CAAA;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACrC,CAAA;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,OAAO,EAAE,0BAA0B,CAAA;IACnC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B,GAAG,KAAK,GAAG,IAAI,CAoBf;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyD1E"}
@@ -0,0 +1,86 @@
1
+ import { isGrantAuditEventSlug } from '../../catalog.js';
2
+ import { collectionOf } from '../../collections/helpers.js';
3
+ import { BUILTIN_GRANT_ACTION, getHostAction } from '../../extensions/actions.js';
4
+ import { CTX_SKIP_GRANT_SIDE_EFFECTS } from './contextFlags.js';
5
+ import { createGrantRow } from './grantSideEffects.js';
6
+ import { relationId } from './relationId.js';
7
+ export function reactionMatchWhere(args) {
8
+ const clauses = [{ enabled: { equals: true } }, { trigger: { equals: args.trigger } }];
9
+ if (args.trigger === 'event') {
10
+ if (!args.eventTypeId)
11
+ return null;
12
+ clauses.push({ eventType: { equals: args.eventTypeId } });
13
+ }
14
+ else {
15
+ if (!args.achievementId)
16
+ return null;
17
+ clauses.push({ achievement: { equals: args.achievementId } });
18
+ }
19
+ if (args.scopeId) {
20
+ clauses.push({
21
+ or: [{ scope: { equals: args.scopeId } }, { scope: { exists: false } }, { scope: { equals: null } }],
22
+ });
23
+ }
24
+ else {
25
+ clauses.push({ or: [{ scope: { exists: false } }, { scope: { equals: null } }] });
26
+ }
27
+ return { and: clauses };
28
+ }
29
+ export async function runReactions(args) {
30
+ if (!args.userId || args.req.context?.[CTX_SKIP_GRANT_SIDE_EFFECTS])
31
+ return 0;
32
+ if (args.trigger === 'event' && isGrantAuditEventSlug(args.eventTypeSlug))
33
+ return 0;
34
+ const where = reactionMatchWhere({
35
+ trigger: args.trigger,
36
+ scopeId: args.scopeId,
37
+ eventTypeId: args.eventTypeId,
38
+ achievementId: args.achievementId,
39
+ });
40
+ if (!where)
41
+ return 0;
42
+ const found = await args.req.payload.find({
43
+ collection: collectionOf('reactions'),
44
+ depth: 0,
45
+ limit: 200,
46
+ pagination: false,
47
+ overrideAccess: true,
48
+ req: args.req,
49
+ where,
50
+ });
51
+ let ran = 0;
52
+ for (const doc of found.docs) {
53
+ const reaction = doc;
54
+ const action = typeof reaction.action === 'string' ? reaction.action : '';
55
+ if (!action)
56
+ continue;
57
+ if (action === BUILTIN_GRANT_ACTION) {
58
+ const targetId = relationId(reaction.targetAchievement);
59
+ if (!targetId)
60
+ continue;
61
+ await createGrantRow({
62
+ req: args.req,
63
+ userId: args.userId,
64
+ achievementId: targetId,
65
+ scopeId: args.scopeId,
66
+ });
67
+ ran += 1;
68
+ continue;
69
+ }
70
+ const host = getHostAction(action);
71
+ if (!host)
72
+ continue;
73
+ await host.run({
74
+ payload: args.req.payload,
75
+ req: args.req,
76
+ userId: args.userId,
77
+ scopeId: args.scopeId,
78
+ trigger: args.trigger,
79
+ reaction,
80
+ log: args.log ?? null,
81
+ achievementId: args.achievementId ?? null,
82
+ });
83
+ ran += 1;
84
+ }
85
+ return ran;
86
+ }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CollectionConfig, OptionLabel, Payload, PayloadRequest, TypedUser } from 'payload';
1
+ import type { CollectionConfig, Field, OptionLabel, Payload, PayloadRequest, TypedUser } from 'payload';
2
2
  /** Host-registered date anchors for computed elapsed metrics. */
3
3
  export type AchievementMetricAnchor = (args: {
4
4
  payload: Payload;
@@ -48,9 +48,43 @@ export type AchievementRuleType = {
48
48
  * They are omitted from AND averages. Default: `requirement`.
49
49
  */
50
50
  progressRole?: 'requirement' | 'gate';
51
+ /**
52
+ * Admin select label. Payload {@link OptionLabel}: string, `{ en, es }`, or `({ t }) => t(…)`.
53
+ * Defaults to `type` when omitted.
54
+ */
55
+ label?: OptionLabel;
56
+ /**
57
+ * Admin fields shown when this leaf `type` is selected.
58
+ * Merged into the rule row; plugin ANDs visibility with `type === this.type`.
59
+ */
60
+ fields?: Field[];
61
+ };
62
+ export type AchievementReactionTrigger = 'event' | 'achievement.granted' | 'achievement.revoked';
63
+ export type AchievementActionRunArgs = {
64
+ payload: PayloadRequest['payload'];
65
+ req: PayloadRequest;
66
+ userId: string;
67
+ scopeId: string | null;
68
+ trigger: AchievementReactionTrigger;
69
+ /** Reaction catalog row (flat plugin fields plus host param fields). */
70
+ reaction: Record<string, unknown>;
71
+ /** Present when `trigger` is `event`. */
72
+ log?: Record<string, unknown> | null;
73
+ /** Present when `trigger` is grant/revoke. */
74
+ achievementId?: string | null;
75
+ };
76
+ /**
77
+ * Host (or built-in) side effect when a reaction matches.
78
+ * `run` must be **set-based / idempotent** — the plugin does not store reaction run history.
79
+ */
80
+ export type AchievementAction = {
81
+ type: string;
82
+ label?: OptionLabel;
83
+ fields?: Field[];
84
+ run: (args: AchievementActionRunArgs) => Promise<void> | void;
51
85
  };
52
86
  /** Logical collection ids — map to configurable slugs via `collections`. */
53
- export type AchievementCollectionKey = 'tiers' | 'achievements' | 'grants' | 'achievementRequests' | 'tierRequests' | 'logs' | 'eventTypes' | 'metrics' | 'metricBalances';
87
+ export type AchievementCollectionKey = 'tiers' | 'achievements' | 'grants' | 'achievementRequests' | 'tierRequests' | 'logs' | 'eventTypes' | 'metrics' | 'metricBalances' | 'reactions';
54
88
  /**
55
89
  * Host overrides layered onto a plugin collection (Form Builder–style).
56
90
  * Access/admin merge shallowly; hook arrays are appended after plugin hooks.
@@ -117,9 +151,10 @@ export type AchievementEndpointsOptions = {
117
151
  export type AchievementSubjectsOptions = {
118
152
  /**
119
153
  * Collection slugs allowed on log `subject.relationTo` (e.g. `posts`, `comments`).
154
+ * `'all'` = every collection on the host Config except plugin collections and `payload-*`.
120
155
  * Event types pick a subset via `subjectRelationTo`; validation rejects anything outside this list.
121
156
  */
122
- collections?: string[];
157
+ collections?: 'all' | string[];
123
158
  };
124
159
  export type AchievementPluginOptions = {
125
160
  /**
@@ -138,13 +173,18 @@ export type AchievementPluginOptions = {
138
173
  */
139
174
  subjects?: AchievementSubjectsOptions;
140
175
  extensions?: {
141
- /** Custom rule evaluators merged with built-ins. */
176
+ /** Custom rule evaluators merged with built-ins. `label` / `fields` surface in Admin. */
142
177
  ruleTypes?: AchievementRuleType[];
143
178
  /**
144
- * Named date anchors for computed `elapsed` metrics.
145
- * Keys appear in Admin `since` selects; missing keys resolve to null at eval time.
179
+ * Named date anchors for computed `elapsed` metrics.
180
+ * Keys appear in Admin `since` selects; missing keys resolve to null at eval time.
146
181
  */
147
182
  metricAnchors?: Record<string, AchievementMetricAnchorEntry>;
183
+ /**
184
+ * Side-effect commands for the reactions catalog (e.g. `accessGroup.addMember`).
185
+ * Built-in `achievement.grant` is always available. `run` must be idempotent.
186
+ */
187
+ actions?: AchievementAction[];
148
188
  };
149
189
  /**
150
190
  * When true (default), ensure system metrics + event types exist on Payload `onInit`
@@ -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.5.0",
3
+ "version": "0.6.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",