@dappermountain/payload-plugin-achievements 0.2.6 → 0.2.7

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 +64 -3
  18. package/dist/collections/fields/ruleFields.d.ts.map +1 -1
  19. package/dist/collections/fields/ruleFields.js +45 -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
package/README.md CHANGED
@@ -79,7 +79,8 @@ Slugs are prefixed with `achievement-` by default so they don’t collide with y
79
79
 
80
80
  | Config key | Default slug | Purpose |
81
81
  | --- | --- | --- |
82
- | `metrics` | `achievement-metrics` | Named scores (points, streaks, …). Totals are derived by summing `metric.delta` logs. |
82
+ | `metrics` | `achievement-metrics` | Named numbers: **stored** (score totals) or **computed** (e.g. elapsed days). |
83
+ | `metricBalances` | `achievement-metric-balances` | Projection of stored metric totals per user (+ scope) for fast reads / leaderboards. |
83
84
  | `eventTypes` | `achievement-event-types` | Kinds of log entries you can count or require. |
84
85
  | `tiers` | `achievement-tiers` | Ordered ladder steps with unlock rules. |
85
86
  | `achievements` | `achievement-definitions` | Things a user can earn (with eligibility / completion rules). |
@@ -106,7 +107,7 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
106
107
 
107
108
  - Top-level empty list → everyone passes
108
109
  - Nested groups → AND (“all of these”) or OR (“any of these”)
109
- - Built-in leaves → `tier-at-least`, `achievement-complete`, `metric-minimum`, `event-count`
110
+ - Built-in leaves → `tier-at-least`, `achievement-complete`, `metric-minimum`, `event-count`, `elapsed-since`
110
111
  - Custom leaves → register via `extensions.ruleTypes`
111
112
 
112
113
  **Current tier** is computed by walking tiers in `rank` order and evaluating each tier’s unlock rules (`resolveCurrentTier`). Tiers with **`requiresReview`** only become current after an **approved tier request**. **Next-tier fill** uses fractional progress on the same trees (`resolveTierProgress` / `evaluateRuleProgress`):
@@ -116,9 +117,17 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
116
117
  | AND | Equal average of *requirement* children |
117
118
  | OR | Best child (max) |
118
119
  | `tier-at-least` | Gate — skipped in AND averages |
119
- | `event-count` / `metric-minimum` | Fraction of the target |
120
+ | `event-count` / `metric-minimum` / `elapsed-since` | Fraction of the target |
120
121
  | `achievement-complete` | `1` if granted; otherwise rolls up that achievement’s completion (or eligibility) rules |
121
122
 
123
+ **`elapsed-since`** — time since an anchor ≥ amount (unit: `days` | `hours` | `minutes`). Built-in anchors: user `createdAt`, or earliest log of an event type (`since: 'first-event'`). Hosts register more anchors via `extensions.metricAnchors` (same keys work on computed metrics).
124
+
125
+ **Stored vs computed metrics** — `kind: stored` (default) totals live in `achievement-metric-balances`, updated by `recordMetricChange` (log + balance dual-write). Logs remain the audit/rebuild source; reconcile backfills balances. `kind: computed` + `compute: elapsed` exposes a live number (e.g. days since an anchor); use `metric-minimum` against it. System seed only creates stored `points` — product computed metrics (e.g. `days`) are host-seeded after registering anchors.
126
+
127
+ **Leaderboards** — `getMetricLeaderboard` / `GET /api/achievements/leaderboard?metric=points` ranks stored balances (authenticated). Disable with `endpoints.leaderboard: false`.
128
+
129
+ **System catalog** — by default the plugin runs `seedAchievementCatalog` on Payload `onInit` (opt out with `seedSystemCatalog: false`). Hosts still seed product catalog + locales via `seedAchievements`.
130
+
122
131
  **Eligibility rules** gate who may request an achievement. **Completion rules** define what a composed achievement is made of. When `requiresReview` is false and completion rules pass, the parent can auto-grant. Cycles in nested achievement graphs are ignored.
123
132
 
124
133
  ### Access model
@@ -159,8 +168,11 @@ achievementPlugin({
159
168
  overrides?: Partial<Record<AchievementCollectionKey, AchievementCollectionOverride>>
160
169
  }
161
170
  users?: { includeJoins?: boolean }
162
- endpoints?: { me?: string | false }
163
- extensions?: { ruleTypes?: AchievementRuleType[] }
171
+ endpoints?: { me?: string | false; leaderboard?: string | false; reconcile?: string | false }
172
+ extensions?: {
173
+ ruleTypes?: AchievementRuleType[]
174
+ metricAnchors?: Record<string, AchievementMetricAnchor>
175
+ }
164
176
  })
165
177
  ```
166
178
 
@@ -169,10 +181,12 @@ achievementPlugin({
169
181
  | Option | Default | Notes |
170
182
  | --- | --- | --- |
171
183
  | `enabled` | `true` | Set `false` to leave the plugin installed but inactive (no collections / endpoints merged). |
184
+ | `seedSystemCatalog` | `true` | On `onInit`, upsert system metrics + event types (`points`, `metric.delta`, …). |
172
185
  | `usersCollectionSlug` | `'users'` | Users collection the plugin relates to (grants, requests, joins). |
173
186
  | `canReview` | _(denied)_ | **Important.** Without this, users cannot manage catalogs or approve requests. Receives `(user, scopeId)` where `scopeId` may be `null`. |
174
187
  | `scope` | _(none)_ | Multi-tenant hook-up. `collection` is the relation target (e.g. `'tenants'`). `relationField` defaults to `'scope'`. Adds an optional scope field across plugin collections. |
175
188
  | `mediaCollection` | _(none)_ | When set (e.g. `'media'`), tiers get an optional upload field for ladder badge images. Icon string keys still work without this. |
189
+ | `extensions.metricAnchors` | _(none)_ | Named `(ctx) => Date | null` resolvers for computed elapsed metrics / `elapsed-since`. Keys appear in Admin `since` selects. |
176
190
 
177
191
  ### `collections`
178
192
 
@@ -217,17 +231,25 @@ achievementPlugin({
217
231
  | Option | Default | Notes |
218
232
  | --- | --- | --- |
219
233
  | `me` | `'/achievements/me'` | Registers `GET /api/achievements/me` for the signed-in user’s grants snapshot (+ derived tiers and lean requests). Pass `false` to skip. Having review access does **not** widen this endpoint to other users. |
234
+ | `leaderboard` | `'/achievements/leaderboard'` | Registers `GET /api/achievements/leaderboard?metric=<slug>` for authenticated users (stored metrics only). Pass `false` to skip. |
235
+ | `reconcile` | `'/achievements/reconcile'` | Registers `POST /api/achievements/reconcile` for reviewers. Pass `false` to skip. |
220
236
 
221
- Query params: `scope`, `limit`, `page`, `requestsLimit`.
237
+ Query params (`me`): `scope`, `limit`, `page`, `requestsLimit`. Leaderboard: `metric` (required), `scope`, `limit`, `page`.
222
238
 
223
- ### `extensions.ruleTypes`
239
+ ### `extensions.ruleTypes` / `metricAnchors`
224
240
 
225
- Register custom rule evaluators merged with the built-ins:
241
+ Register custom rule evaluators and/or date anchors:
226
242
 
227
243
  ```ts
228
244
  achievementPlugin({
229
245
  canReview,
230
246
  extensions: {
247
+ metricAnchors: {
248
+ 'membership-granted': async ({ payload, userId, scopeId }) => {
249
+ // return a Date from host data, or null
250
+ return null
251
+ },
252
+ },
231
253
  ruleTypes: [
232
254
  {
233
255
  type: 'host.custom-check',
@@ -251,6 +273,8 @@ Use these from hooks, jobs, and trusted endpoints — not from the public client
251
273
  import {
252
274
  recordLog,
253
275
  recordMetricChange,
276
+ getMetricLeaderboard,
277
+ resolveMetricValue,
254
278
  grantAchievement,
255
279
  reconcileProgression,
256
280
  reconcileUserProgression,
@@ -263,6 +287,7 @@ import {
263
287
 
264
288
  await recordLog({ payload, userId, scopeId, type: 'host.custom-kind', actorId })
265
289
  await recordMetricChange({ payload, userId, scopeId, metric: 'points', change: 10 })
290
+ const board = await getMetricLeaderboard({ payload, metric: 'points', scopeId, limit: 20 })
266
291
  const tier = await resolveCurrentTier({ payload, userId, scopeId })
267
292
 
268
293
  // Repair users whose grants predate side-effect hooks (idempotent)
@@ -306,6 +331,7 @@ Turning **Requires review** off on a definition or tier **approves pending reque
306
331
  | Goal | Request |
307
332
  | --- | --- |
308
333
  | Current user snapshot | `GET /api/achievements/me?scope=<scopeId>&limit=10&page=1` |
334
+ | Stored metric leaderboard | `GET /api/achievements/leaderboard?metric=points&scope=<scopeId>&limit=20&page=1` |
309
335
  | Repair progression | `POST /api/achievements/reconcile` (reviewer; optional `{ userId, scopeId, achievementId\|achievementSlug, tierId\|tierSlug, limit }`) |
310
336
  | List grants | `GET /api/achievement-grants?where[user][equals]=<userId>` |
311
337
  | Log history | `GET /api/achievement-logs?where[user][equals]=<userId>` |
package/dist/catalog.d.ts CHANGED
@@ -15,6 +15,7 @@ export declare const ENGINE_EVENT_TYPES: readonly [{
15
15
  export declare const ENGINE_METRICS: readonly [{
16
16
  readonly slug: "points";
17
17
  readonly name: "Points";
18
+ readonly kind: "stored";
18
19
  }];
19
20
  export type EngineEventTypeSlug = (typeof ENGINE_EVENT_TYPES)[number]['slug'];
20
21
  export type EngineMetricSlug = (typeof ENGINE_METRICS)[number]['slug'];
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;EAKrB,CAAA;AAEV,eAAO,MAAM,cAAc;;;EAAgD,CAAA;AAE3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtE,eAAO,MAAM,+BAA+B,EAAE,gBAA2B,CAAA;AAEzE,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,UAAkC,CAAA"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;EAKrB,CAAA;AAEV,eAAO,MAAM,cAAc;;;;EAAyE,CAAA;AAEpG,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAC7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtE,eAAO,MAAM,+BAA+B,EAAE,gBAA2B,CAAA;AAEzE,+DAA+D;AAC/D,eAAO,MAAM,0BAA0B,UAAkC,CAAA"}
package/dist/catalog.js CHANGED
@@ -4,7 +4,7 @@ export const ENGINE_EVENT_TYPES = [
4
4
  { slug: 'metric.delta', name: 'Metric delta', requiresMetric: true },
5
5
  { slug: 'tier.changed', name: 'Tier changed' },
6
6
  ];
7
- export const ENGINE_METRICS = [{ slug: 'points', name: 'Points' }];
7
+ export const ENGINE_METRICS = [{ slug: 'points', name: 'Points', kind: 'stored' }];
8
8
  export const DEFAULT_ACHIEVEMENT_METRIC_SLUG = 'points';
9
9
  /** @deprecated Use {@link DEFAULT_ACHIEVEMENT_METRIC_SLUG}. */
10
10
  export const DEFAULT_ACHIEVEMENT_METRIC = DEFAULT_ACHIEVEMENT_METRIC_SLUG;
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/cli/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAE1D,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,sCAAsC,CAAA;AAE7C,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,wBAAwB,EAAE,SAAS,GAAG,KAAK,CAAC,CAAA;AA2BnF,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,mBAAmB,CAUjG;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAc5C;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAClD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,CAAC,EAAE,mBAAmB,CAAA;IAC7B,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA2BtC"}
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/cli/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAE1D,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAChC,MAAM,sCAAsC,CAAA;AAE7C,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,wBAAwB,EAAE,SAAS,GAAG,KAAK,CAAC,CAAA;AA2BnF,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,mBAAmB,CAUjG;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAc5C;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAClD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,CAAC,EAAE,mBAAmB,CAAA;IAC7B,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4BtC"}
@@ -70,6 +70,7 @@ export async function runReconcileCli(args) {
70
70
  achievementRequestsEnsured: 0,
71
71
  tierRequestsEnsured: 0,
72
72
  tierLogsWritten: 0,
73
+ metricBalancesRebuilt: 0,
73
74
  };
74
75
  }
75
76
  const filters = args.filters ?? parseReconcileCliArgs(argv);
@@ -0,0 +1,4 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ /** Projection rows — writes only via trusted helpers (`overrideAccess`). */
3
+ export declare const access: NonNullable<CollectionConfig['access']>;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/MetricBalances/access/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAK/C,4EAA4E;AAC5E,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAK1D,CAAA"}
@@ -0,0 +1,9 @@
1
+ import { requireOne } from '../../../access/index.js';
2
+ import { isReviewer } from '../../../access/roles/index.js';
3
+ /** Projection rows — writes only via trusted helpers (`overrideAccess`). */
4
+ export const access = {
5
+ read: requireOne(isReviewer('metricBalances')),
6
+ create: () => false,
7
+ update: () => false,
8
+ delete: requireOne(isReviewer('metricBalances')),
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare const hooks: CollectionConfig['hooks'];
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/collections/MetricBalances/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,eAAO,MAAM,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAM,CAAA"}
@@ -0,0 +1 @@
1
+ export const hooks = {};
@@ -0,0 +1,3 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ export declare function buildMetricBalancesCollection(): CollectionConfig;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/MetricBalances/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAa/C,wBAAgB,6BAA6B,IAAI,gBAAgB,CA4ChE"}
@@ -0,0 +1,48 @@
1
+ import { collectionAdmin, collectionOf, columnsWithOptionalScope, optionalFields, slugOf, } from '../helpers.js';
2
+ import { scopeField, userField } from '../fields/index.js';
3
+ import { access } from './access/index.js';
4
+ import { hooks } from './hooks/index.js';
5
+ export function buildMetricBalancesCollection() {
6
+ return {
7
+ slug: slugOf('metricBalances'),
8
+ access: { ...access },
9
+ hooks,
10
+ admin: collectionAdmin({
11
+ useAsTitle: 'key',
12
+ defaultColumns: columnsWithOptionalScope(['metric', 'user', 'value', 'scope', 'updatedAt']),
13
+ description: 'Stored metric totals per user (and scope). Updated by recordMetricChange; repair with reconcile. Not for computed metrics.',
14
+ }),
15
+ fields: [
16
+ userField(),
17
+ ...optionalFields(scopeField()),
18
+ {
19
+ name: 'metric',
20
+ type: 'relationship',
21
+ relationTo: collectionOf('metrics'),
22
+ required: true,
23
+ index: true,
24
+ },
25
+ {
26
+ name: 'value',
27
+ type: 'number',
28
+ required: true,
29
+ defaultValue: 0,
30
+ index: true,
31
+ admin: {
32
+ description: 'Current total (sum of metric.delta changes).',
33
+ },
34
+ },
35
+ {
36
+ name: 'key',
37
+ type: 'text',
38
+ required: true,
39
+ unique: true,
40
+ index: true,
41
+ admin: {
42
+ readOnly: true,
43
+ description: 'Stable unique key: user:scope:metric.',
44
+ },
45
+ },
46
+ ],
47
+ };
48
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAO/C,wBAAgB,sBAAsB,IAAI,gBAAgB,CA8BzD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAS/C,wBAAgB,sBAAsB,IAAI,gBAAgB,CA2FzD"}
@@ -1,5 +1,7 @@
1
- import { collectionAdmin, columnsWithOptionalScope, optionalFields, slugOf } from '../helpers.js';
1
+ import { collectionAdmin, collectionOf, columnsWithOptionalScope, optionalFields, slugOf } from '../helpers.js';
2
2
  import { localizedNameField, scopeField } from '../fields/index.js';
3
+ import { catalogFilterOptions } from '../fields/catalogFilter.js';
4
+ import { elapsedSinceSelectOptions } from '../../extensions/elapsedSinceOptions.js';
3
5
  import { access } from './access/index.js';
4
6
  import { hooks } from './hooks/index.js';
5
7
  export function buildMetricsCollection() {
@@ -9,8 +11,8 @@ export function buildMetricsCollection() {
9
11
  hooks,
10
12
  admin: collectionAdmin({
11
13
  useAsTitle: 'name',
12
- defaultColumns: columnsWithOptionalScope(['name', 'slug', 'system', 'scope']),
13
- description: 'Scores and totals you track for someone — like points or a streak.',
14
+ defaultColumns: columnsWithOptionalScope(['name', 'slug', 'kind', 'system', 'scope']),
15
+ description: 'Named numbers. Stored metrics are score totals from log deltas; computed metrics are derived at evaluation time.',
14
16
  }),
15
17
  fields: [
16
18
  ...optionalFields(scopeField()),
@@ -21,6 +23,65 @@ export function buildMetricsCollection() {
21
23
  { name: 'slug', type: 'text', required: true, unique: true, index: true, admin: { width: '50%' } },
22
24
  ],
23
25
  },
26
+ {
27
+ name: 'kind',
28
+ type: 'select',
29
+ required: true,
30
+ defaultValue: 'stored',
31
+ options: [
32
+ { label: 'Stored (log deltas)', value: 'stored' },
33
+ { label: 'Computed', value: 'computed' },
34
+ ],
35
+ admin: {
36
+ description: 'Stored = sum of metric.delta logs (via balances). Computed = derived value.',
37
+ },
38
+ },
39
+ {
40
+ name: 'compute',
41
+ type: 'select',
42
+ defaultValue: 'elapsed',
43
+ options: [{ label: 'Elapsed time', value: 'elapsed' }],
44
+ admin: {
45
+ condition: (_, sibling) => sibling?.kind === 'computed',
46
+ description: 'How this number is derived.',
47
+ },
48
+ },
49
+ {
50
+ name: 'unit',
51
+ type: 'select',
52
+ defaultValue: 'days',
53
+ options: [
54
+ { label: 'Days', value: 'days' },
55
+ { label: 'Hours', value: 'hours' },
56
+ { label: 'Minutes', value: 'minutes' },
57
+ ],
58
+ admin: {
59
+ condition: (_, sibling) => sibling?.kind === 'computed' && sibling?.compute === 'elapsed',
60
+ description: 'Unit of the exposed number (e.g. whole days since the anchor).',
61
+ },
62
+ },
63
+ {
64
+ name: 'since',
65
+ type: 'select',
66
+ defaultValue: 'user-created-at',
67
+ options: elapsedSinceSelectOptions(),
68
+ admin: {
69
+ condition: (_, sibling) => sibling?.kind === 'computed' && sibling?.compute === 'elapsed',
70
+ description: 'Which timestamp starts the clock.',
71
+ },
72
+ },
73
+ {
74
+ name: 'eventType',
75
+ type: 'relationship',
76
+ relationTo: collectionOf('eventTypes'),
77
+ filterOptions: catalogFilterOptions,
78
+ admin: {
79
+ condition: (_, sibling) => sibling?.kind === 'computed' &&
80
+ sibling?.compute === 'elapsed' &&
81
+ sibling?.since === 'first-event',
82
+ description: 'Event type whose first log starts the clock.',
83
+ },
84
+ },
24
85
  {
25
86
  name: 'system',
26
87
  type: 'checkbox',
@@ -1 +1 @@
1
- {"version":3,"file":"ruleFields.d.ts","sourceRoot":"","sources":["../../../src/collections/fields/ruleFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAqGpC;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,KAAK,EAAE,CAqB7C;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,IAAI,KAAK,EAAE,CAE5C"}
1
+ {"version":3,"file":"ruleFields.d.ts","sourceRoot":"","sources":["../../../src/collections/fields/ruleFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAkJpC;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,KAAK,EAAE,CAqB7C;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,IAAI,KAAK,EAAE,CAE5C"}
@@ -1,5 +1,6 @@
1
1
  import { catalogFilterOptions } from './catalogFilter.js';
2
2
  import { collectionOf } from '../helpers.js';
3
+ import { elapsedSinceSelectOptions } from '../../extensions/elapsedSinceOptions.js';
3
4
  function combinatorField(visibleWhenGroup) {
4
5
  return {
5
6
  name: 'combinator',
@@ -25,6 +26,7 @@ function ruleTypeSelect(includeGroup) {
25
26
  { label: 'Achievement complete', value: 'achievement-complete' },
26
27
  { label: 'Metric minimum', value: 'metric-minimum' },
27
28
  { label: 'Event count', value: 'event-count' },
29
+ { label: 'Elapsed since', value: 'elapsed-since' },
28
30
  ...(includeGroup ? [{ label: 'Group', value: 'group' }] : []),
29
31
  ],
30
32
  };
@@ -75,8 +77,9 @@ function ruleParamFields() {
75
77
  relationTo: collectionOf('eventTypes'),
76
78
  filterOptions: catalogFilterOptions,
77
79
  admin: {
78
- condition: (_, sibling) => sibling?.type === 'event-count',
79
- description: 'Which event type to count.',
80
+ condition: (_, sibling) => sibling?.type === 'event-count' ||
81
+ (sibling?.type === 'elapsed-since' && sibling?.since === 'first-event'),
82
+ description: 'Event type to count, or (for elapsed since) the type whose first log starts the clock.',
80
83
  },
81
84
  },
82
85
  {
@@ -87,6 +90,46 @@ function ruleParamFields() {
87
90
  description: 'How many times it needs to have happened.',
88
91
  },
89
92
  },
93
+ {
94
+ name: 'since',
95
+ type: 'select',
96
+ defaultValue: 'user-created-at',
97
+ options: elapsedSinceSelectOptions(),
98
+ admin: {
99
+ condition: (_, sibling) => sibling?.type === 'elapsed-since',
100
+ description: 'Which timestamp starts the clock.',
101
+ },
102
+ },
103
+ {
104
+ type: 'row',
105
+ admin: {
106
+ condition: (_, sibling) => sibling?.type === 'elapsed-since',
107
+ },
108
+ fields: [
109
+ {
110
+ name: 'amount',
111
+ type: 'number',
112
+ admin: {
113
+ width: '50%',
114
+ description: 'How much time must pass.',
115
+ },
116
+ },
117
+ {
118
+ name: 'unit',
119
+ type: 'select',
120
+ defaultValue: 'days',
121
+ options: [
122
+ { label: 'Days', value: 'days' },
123
+ { label: 'Hours', value: 'hours' },
124
+ { label: 'Minutes', value: 'minutes' },
125
+ ],
126
+ admin: {
127
+ width: '50%',
128
+ description: 'Unit for the amount.',
129
+ },
130
+ },
131
+ ],
132
+ },
90
133
  ];
91
134
  }
92
135
  function buildRuleLeafFields() {
@@ -1 +1 @@
1
- {"version":3,"file":"preventCatalogDelete.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/preventCatalogDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AA6CzD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAA;CAC7B,GAAG,0BAA0B,CAqD7B"}
1
+ {"version":3,"file":"preventCatalogDelete.d.ts","sourceRoot":"","sources":["../../../src/collections/hooks/preventCatalogDelete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AA6CzD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,WAAW,GAAG,QAAQ,CAAA;CAC7B,GAAG,0BAA0B,CA+D7B"}
@@ -61,7 +61,8 @@ export function preventCatalogDelete(args) {
61
61
  parts.push(`${logs.totalDocs} log row(s)`);
62
62
  const rules = await countMatchingLeaves({
63
63
  req,
64
- match: (rule) => rule.type === 'event-count' && relationId(rule.eventType) === id,
64
+ match: (rule) => (rule.type === 'event-count' || rule.type === 'elapsed-since') &&
65
+ relationId(rule.eventType) === id,
65
66
  });
66
67
  if (rules > 0)
67
68
  parts.push(`${rules} rule(s)`);
@@ -75,6 +76,14 @@ export function preventCatalogDelete(args) {
75
76
  });
76
77
  if (logs.totalDocs > 0)
77
78
  parts.push(`${logs.totalDocs} log row(s)`);
79
+ const balances = await req.payload.count({
80
+ collection: collectionOf('metricBalances'),
81
+ overrideAccess: true,
82
+ req,
83
+ where: { metric: { equals: id } },
84
+ });
85
+ if (balances.totalDocs > 0)
86
+ parts.push(`${balances.totalDocs} balance row(s)`);
78
87
  const rules = await countMatchingLeaves({
79
88
  req,
80
89
  match: (rule) => rule.type === 'metric-minimum' && relationId(rule.metric) === id,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAyB/C,qEAAqE;AACrE,wBAAgB,2BAA2B,IAAI,gBAAgB,EAAE,CAMhE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AA2B/C,qEAAqE;AACrE,wBAAgB,2BAA2B,IAAI,gBAAgB,EAAE,CAMhE"}
@@ -5,11 +5,13 @@ import { buildAchievementsCollection } from './Achievements/index.js';
5
5
  import { buildEventTypesCollection } from './EventTypes/index.js';
6
6
  import { buildGrantsCollection } from './Grants/index.js';
7
7
  import { buildLogsCollection } from './Logs/index.js';
8
+ import { buildMetricBalancesCollection } from './MetricBalances/index.js';
8
9
  import { buildMetricsCollection } from './Metrics/index.js';
9
10
  import { buildTierRequestsCollection } from './TierRequests/index.js';
10
11
  import { buildTiersCollection } from './Tiers/index.js';
11
12
  const builders = {
12
13
  metrics: buildMetricsCollection,
14
+ metricBalances: buildMetricBalancesCollection,
13
15
  eventTypes: buildEventTypesCollection,
14
16
  tiers: buildTiersCollection,
15
17
  achievements: buildAchievementsCollection,
@@ -1 +1 @@
1
- {"version":3,"file":"slugs.d.ts","sourceRoot":"","sources":["../../src/collections/slugs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAErF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,gBAAgB,CAAA;AAEtD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CASlF,CAAA;AAED,eAAO,MAAM,mBAAmB,iBAAiB,CAAA;AAEjD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAkB1C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,MAAM,GAAG,KAAK,CAMxF"}
1
+ {"version":3,"file":"slugs.d.ts","sourceRoot":"","sources":["../../src/collections/slugs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAErF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,gBAAgB,CAAA;AAEtD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAUlF,CAAA;AAED,eAAO,MAAM,mBAAmB,iBAAiB,CAAA;AAEjD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAkB1C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,MAAM,GAAG,KAAK,CAMxF"}
@@ -18,6 +18,7 @@ export const DEFAULT_COLLECTION_BASE_SLUGS = {
18
18
  logs: 'logs',
19
19
  eventTypes: 'event-types',
20
20
  metrics: 'metrics',
21
+ metricBalances: 'metric-balances',
21
22
  };
22
23
  export const DEFAULT_ADMIN_GROUP = 'Achievements';
23
24
  /**
@@ -2,9 +2,11 @@ import type { AchievementPluginOptions } from './types.js';
2
2
  import { resolveCollectionSlugs } from './collections/slugs.js';
3
3
  export declare const DEFAULT_USERS_COLLECTION = "users";
4
4
  export declare const DEFAULT_ME_ENDPOINT_PATH = "/achievements/me";
5
+ export declare const DEFAULT_LEADERBOARD_ENDPOINT_PATH = "/achievements/leaderboard";
5
6
  export declare const DEFAULT_RECONCILE_ENDPOINT_PATH = "/achievements/reconcile";
6
7
  export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users' | 'endpoints'> & {
7
8
  enabled: boolean;
9
+ seedSystemCatalog: boolean;
8
10
  usersCollectionSlug: string;
9
11
  users: {
10
12
  includeJoins: boolean;
@@ -15,10 +17,12 @@ export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users'
15
17
  adminGroup: string | false;
16
18
  endpoints: {
17
19
  me: string | false;
20
+ leaderboard: string | false;
18
21
  reconcile: string | false;
19
22
  };
20
23
  };
21
24
  export declare function resolveMeEndpointPath(me?: string | false): string | false;
25
+ export declare function resolveLeaderboardEndpointPath(path?: string | false): string | false;
22
26
  export declare function resolveReconcileEndpointPath(path?: string | false): string | false;
23
27
  export declare function resolveOptions(options?: AchievementPluginOptions): ResolvedAchievementOptions;
24
28
  //# sourceMappingURL=defaults.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAEL,sBAAsB,EACvB,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,wBAAwB,UAAU,CAAA;AAC/C,eAAO,MAAM,wBAAwB,qBAAqB,CAAA;AAC1D,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AAExE,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,wBAAwB,EAAE,OAAO,GAAG,WAAW,CAAC,GAAG;IAC/F,OAAO,EAAE,OAAO,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,CAAA;IAChC,+DAA+D;IAC/D,eAAe,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAA;IAC1D,oEAAoE;IACpE,UAAU,EAAE,MAAM,GAAG,KAAK,CAAA;IAC1B,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAA;CAC7D,CAAA;AAQD,wBAAgB,qBAAqB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIzE;AAED,wBAAgB,4BAA4B,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIlF;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,wBAA6B,GAAG,0BAA0B,CAejG"}
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAC1D,OAAO,EAEL,sBAAsB,EACvB,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,wBAAwB,UAAU,CAAA;AAC/C,eAAO,MAAM,wBAAwB,qBAAqB,CAAA;AAC1D,eAAO,MAAM,iCAAiC,8BAA8B,CAAA;AAC5E,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AAExE,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,wBAAwB,EAAE,OAAO,GAAG,WAAW,CAAC,GAAG;IAC/F,OAAO,EAAE,OAAO,CAAA;IAChB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,CAAA;IAChC,+DAA+D;IAC/D,eAAe,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAA;IAC1D,oEAAoE;IACpE,UAAU,EAAE,MAAM,GAAG,KAAK,CAAA;IAC1B,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAA;CAC1F,CAAA;AAQD,wBAAgB,qBAAqB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIzE;AAED,wBAAgB,8BAA8B,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIpF;AAED,wBAAgB,4BAA4B,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAIlF;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,wBAA6B,GAAG,0BAA0B,CAiBjG"}
package/dist/defaults.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { resolveAdminGroup, resolveCollectionSlugs, } from './collections/slugs.js';
2
2
  export const DEFAULT_USERS_COLLECTION = 'users';
3
3
  export const DEFAULT_ME_ENDPOINT_PATH = '/achievements/me';
4
+ export const DEFAULT_LEADERBOARD_ENDPOINT_PATH = '/achievements/leaderboard';
4
5
  export const DEFAULT_RECONCILE_ENDPOINT_PATH = '/achievements/reconcile';
5
6
  function normalizeEndpointPath(path, fallback) {
6
7
  const trimmed = path.trim();
@@ -15,6 +16,13 @@ export function resolveMeEndpointPath(me) {
15
16
  return DEFAULT_ME_ENDPOINT_PATH;
16
17
  return normalizeEndpointPath(me, DEFAULT_ME_ENDPOINT_PATH);
17
18
  }
19
+ export function resolveLeaderboardEndpointPath(path) {
20
+ if (path === false)
21
+ return false;
22
+ if (path == null)
23
+ return DEFAULT_LEADERBOARD_ENDPOINT_PATH;
24
+ return normalizeEndpointPath(path, DEFAULT_LEADERBOARD_ENDPOINT_PATH);
25
+ }
18
26
  export function resolveReconcileEndpointPath(path) {
19
27
  if (path === false)
20
28
  return false;
@@ -26,6 +34,7 @@ export function resolveOptions(options = {}) {
26
34
  return {
27
35
  ...options,
28
36
  enabled: options.enabled !== false,
37
+ seedSystemCatalog: options.seedSystemCatalog !== false,
29
38
  usersCollectionSlug: options.usersCollectionSlug ?? DEFAULT_USERS_COLLECTION,
30
39
  users: {
31
40
  includeJoins: options.users?.includeJoins !== false,
@@ -34,6 +43,7 @@ export function resolveOptions(options = {}) {
34
43
  adminGroup: resolveAdminGroup(options),
35
44
  endpoints: {
36
45
  me: resolveMeEndpointPath(options.endpoints?.me),
46
+ leaderboard: resolveLeaderboardEndpointPath(options.endpoints?.leaderboard),
37
47
  reconcile: resolveReconcileEndpointPath(options.endpoints?.reconcile),
38
48
  },
39
49
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoints/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAIhE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CASzF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoints/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA;AAKhE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CAYzF"}
@@ -1,3 +1,4 @@
1
+ import { buildLeaderboardEndpoint } from './leaderboard.js';
1
2
  import { buildMeEndpoint } from './me.js';
2
3
  import { buildReconcileEndpoint } from './reconcile.js';
3
4
  export function buildAchievementEndpoints(options) {
@@ -5,6 +6,9 @@ export function buildAchievementEndpoints(options) {
5
6
  if (options.endpoints.me !== false) {
6
7
  endpoints.push(buildMeEndpoint(options.endpoints.me));
7
8
  }
9
+ if (options.endpoints.leaderboard !== false) {
10
+ endpoints.push(buildLeaderboardEndpoint(options.endpoints.leaderboard));
11
+ }
8
12
  if (options.endpoints.reconcile !== false) {
9
13
  endpoints.push(buildReconcileEndpoint(options.endpoints.reconcile));
10
14
  }
@@ -0,0 +1,6 @@
1
+ import type { Endpoint } from 'payload';
2
+ /**
3
+ * `GET` ranked stored-metric balances. Query: `metric` (required), optional `scope` / `limit` / `page`.
4
+ */
5
+ export declare function buildLeaderboardEndpoint(path: string): Endpoint;
6
+ //# sourceMappingURL=leaderboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leaderboard.d.ts","sourceRoot":"","sources":["../../src/endpoints/leaderboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;AAsBvD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAuC/D"}