@dappermountain/payload-plugin-achievements 0.3.0 → 0.4.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.
- package/README.md +91 -12
- package/dist/admin/SubjectField.d.ts +14 -0
- package/dist/admin/SubjectField.d.ts.map +1 -0
- package/dist/admin/SubjectField.js +38 -0
- package/dist/admin/useEventTypeFlags.d.ts +2 -0
- package/dist/admin/useEventTypeFlags.d.ts.map +1 -1
- package/dist/admin/useEventTypeFlags.js +21 -3
- package/dist/cli/reconcile.d.ts +1 -1
- package/dist/cli/reconcile.d.ts.map +1 -1
- package/dist/collections/EventTypes/index.d.ts.map +1 -1
- package/dist/collections/EventTypes/index.js +30 -0
- package/dist/collections/Logs/fields/validators.d.ts +1 -0
- package/dist/collections/Logs/fields/validators.d.ts.map +1 -1
- package/dist/collections/Logs/fields/validators.js +37 -1
- package/dist/collections/Logs/hooks/ensureTypeRequirements.d.ts.map +1 -1
- package/dist/collections/Logs/hooks/ensureTypeRequirements.js +22 -13
- package/dist/collections/Logs/index.d.ts.map +1 -1
- package/dist/collections/Logs/index.js +34 -3
- package/dist/defaults.d.ts +5 -1
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +17 -1
- package/dist/endpoints/me.d.ts +2 -1
- package/dist/endpoints/me.d.ts.map +1 -1
- package/dist/endpoints/me.js +7 -1
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/seed/index.d.ts +7 -0
- package/dist/seed/index.d.ts.map +1 -1
- package/dist/seed/index.js +4 -0
- package/dist/services/achievement/buildUnlockRequirementLeaves.d.ts +48 -0
- package/dist/services/achievement/buildUnlockRequirementLeaves.d.ts.map +1 -0
- package/dist/services/achievement/buildUnlockRequirementLeaves.js +192 -0
- package/dist/services/achievement/duration.js +1 -1
- package/dist/services/achievement/eventTypeRequiresActor.d.ts +17 -0
- package/dist/services/achievement/eventTypeRequiresActor.d.ts.map +1 -1
- package/dist/services/achievement/eventTypeRequiresActor.js +56 -5
- package/dist/services/achievement/getUserProgress.d.ts +7 -0
- package/dist/services/achievement/getUserProgress.d.ts.map +1 -1
- package/dist/services/achievement/getUserProgress.js +11 -1
- package/dist/services/achievement/index.d.ts +7 -0
- package/dist/services/achievement/index.d.ts.map +1 -1
- package/dist/services/achievement/index.js +3 -0
- package/dist/services/achievement/loadUserProgressReviews.d.ts +23 -0
- package/dist/services/achievement/loadUserProgressReviews.d.ts.map +1 -0
- package/dist/services/achievement/loadUserProgressReviews.js +79 -0
- package/dist/services/achievement/logSubject.d.ts +13 -0
- package/dist/services/achievement/logSubject.d.ts.map +1 -0
- package/dist/services/achievement/logSubject.js +20 -0
- package/dist/services/achievement/recordEvent.d.ts +6 -0
- package/dist/services/achievement/recordEvent.d.ts.map +1 -1
- package/dist/services/achievement/recordEvent.js +32 -4
- package/dist/types.d.ts +19 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@dappermountain/payload-plugin-achievements`
|
|
2
2
|
|
|
3
|
-
A Payload CMS plugin for a **rule-based achievements and ranking system** — tiers, badges, points, grants, review workflows, and an append-only activity log — configured in Admin
|
|
3
|
+
A Payload CMS plugin for a **rule-based achievements and ranking system** — tiers, badges, points, grants, review workflows, and an append-only activity log — configured in Admin.
|
|
4
4
|
|
|
5
5
|
Most gamification features end up as one-off collections and scattered hooks. This plugin gives you a reusable engine: define *what* can be earned and *how* it unlocks in Payload Admin, then record events from your application code. Current tier and progress are **derived** from rules and history, so you don’t maintain a separate progress table that drifts out of sync.
|
|
6
6
|
|
|
@@ -16,8 +16,9 @@ Most gamification features end up as one-off collections and scattered hooks. Th
|
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
|
19
|
-
- Payload `^3.
|
|
20
|
-
- `@payloadcms/ui` `^3.
|
|
19
|
+
- Payload `^3.89`
|
|
20
|
+
- `@payloadcms/ui` `^3.89`
|
|
21
|
+
- `@payloadcms/richtext-lexical` `^3.89`
|
|
21
22
|
- React `^19`
|
|
22
23
|
|
|
23
24
|
## Install
|
|
@@ -120,11 +121,15 @@ Unlock (tiers), eligibility, and completion (achievements) share the same rule t
|
|
|
120
121
|
| `event-count` / `metric-minimum` | Fraction of the target |
|
|
121
122
|
| `achievement-complete` | `1` if granted; otherwise rolls up that achievement’s completion (or eligibility) rules |
|
|
122
123
|
|
|
123
|
-
**Computed elapsed metrics** — `kind: computed` + `compute: elapsed` + `unit` (`seconds` | `minutes` | `hours` | `days` | `years`) + `since` (built-in `user-created-at` / `first-event`, or a host `metricAnchors` key). Gate tenure with `metric-minimum` against that metric. Years use a fixed 365-day length
|
|
124
|
+
**Computed elapsed metrics** — `kind: computed` + `compute: elapsed` + `unit` (`seconds` | `minutes` | `hours` | `days` | `years`) + `since` (built-in `user-created-at` / `first-event`, or a host `metricAnchors` key). Gate tenure with `metric-minimum` against that metric. Years use a fixed 365-day length. System seed only creates stored `points` — product computed metrics (e.g. `days`) are host-seeded after registering anchors.
|
|
124
125
|
|
|
125
126
|
**Stored metrics** — totals live in `achievement-metric-balances`, updated by `recordMetricChange` (log + balance dual-write). Logs remain the audit/rebuild source; reconcile backfills balances.
|
|
126
127
|
|
|
127
|
-
**Leaderboards** — `getMetricLeaderboard` / `GET /api/achievements/leaderboard?metric=points` ranks stored balances (authenticated). Disable with `endpoints.leaderboard: false`.
|
|
128
|
+
**Leaderboards** — `getMetricLeaderboard` / `GET /api/achievements/leaderboard?metric=points` ranks **stored** metric balances (authenticated, optional `scope`). Computed/elapsed metrics (e.g. tenure days) have no balance rows and are rejected — resolve a single user with `resolveMetricValue`, or materialize snapshots if you need a ranked computed board. Disable with `endpoints.leaderboard: false`. Hosts compose member directories (names, current tier, earned counts, who may appear) themselves; the plugin returns `{ user, value, rank }` only.
|
|
129
|
+
|
|
130
|
+
**Unlock checklist** — `buildUnlockRequirementLeaves` walks a rule tree (including Payload leaves that carry empty `rules: []`), evaluates met/progress per leaf, and returns catalog subjects (`relations`, `target`, `unit`, `since`). It does **not** build UI copy — hosts format labels. `achievement-complete` is omitted by default so those stay on catalog groups. `eachRuleLeaf` is also exported if you need the walker alone.
|
|
131
|
+
|
|
132
|
+
**Review snapshot** — `getUserProgress({ include: { reviews: true } })` or `loadUserProgressReviews` adds pending achievement keys and open/rejected tier requests so hosts do not re-query the engine for badge state.
|
|
128
133
|
|
|
129
134
|
**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
135
|
|
|
@@ -226,15 +231,59 @@ achievementPlugin({
|
|
|
226
231
|
| --- | --- | --- |
|
|
227
232
|
| `includeJoins` | `true` | Injects an `achievements` group on the users collection with joins to grants and requests. Set `false` if you own that UI yourself. |
|
|
228
233
|
|
|
234
|
+
### `subjects`
|
|
235
|
+
|
|
236
|
+
Host documents that logs may point at via a polymorphic `subject` field (same idea as `requiresActor` / `requiresMetric`).
|
|
237
|
+
|
|
238
|
+
| Option | Default | Notes |
|
|
239
|
+
| --- | --- | --- |
|
|
240
|
+
| `collections` | `[]` | Allowlist of collection slugs (e.g. `posts`, `comments`). Empty / omit → subject feature is off (no log field, no event-type subject flags). |
|
|
241
|
+
|
|
242
|
+
When configured:
|
|
243
|
+
|
|
244
|
+
- Event types gain **Requires a subject** + optional **Subject collections** (`subjectRelationTo`).
|
|
245
|
+
- Logs gain a polymorphic **Subject** relationship (Admin links to the host doc).
|
|
246
|
+
- `recordLog({ subject: { relationTo, value } })` (or `logSubject(relationTo, value)`) writes the ref.
|
|
247
|
+
- Prefer `subject` over stuffing ids into `data` JSON.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
achievementPlugin({
|
|
251
|
+
canReview,
|
|
252
|
+
subjects: {
|
|
253
|
+
collections: ['posts', 'comments', 'endorsements'],
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
await seedAchievements(payload, {
|
|
258
|
+
eventTypes: [
|
|
259
|
+
{
|
|
260
|
+
name: 'Comment created',
|
|
261
|
+
slug: 'comment.created',
|
|
262
|
+
requiresSubject: true,
|
|
263
|
+
subjectRelationTo: ['comments'],
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
await recordLog({
|
|
269
|
+
payload,
|
|
270
|
+
userId,
|
|
271
|
+
scopeId,
|
|
272
|
+
type: 'comment.created',
|
|
273
|
+
actorId: userId,
|
|
274
|
+
subject: logSubject('comments', commentId),
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
229
278
|
### `endpoints`
|
|
230
279
|
|
|
231
280
|
| Option | Default | Notes |
|
|
232
281
|
| --- | --- | --- |
|
|
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. |
|
|
282
|
+
| `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. Optional `?reviews=1` adds `reviews`. |
|
|
283
|
+
| `leaderboard` | `'/achievements/leaderboard'` | Registers `GET /api/achievements/leaderboard?metric=<slug>` for authenticated users (**stored** metrics only). Pass `false` to skip. |
|
|
235
284
|
| `reconcile` | `'/achievements/reconcile'` | Registers `POST /api/achievements/reconcile` for reviewers. Pass `false` to skip. |
|
|
236
285
|
|
|
237
|
-
Query params (`me`): `scope`, `limit`, `page`, `requestsLimit`. Leaderboard: `metric` (required), `scope`, `limit`, `page`.
|
|
286
|
+
Query params (`me`): `scope`, `limit`, `page`, `requestsLimit`, `reviews`. Leaderboard: `metric` (required), `scope`, `limit`, `page`.
|
|
238
287
|
|
|
239
288
|
### `extensions.ruleTypes` / `metricAnchors`
|
|
240
289
|
|
|
@@ -246,7 +295,7 @@ achievementPlugin({
|
|
|
246
295
|
extensions: {
|
|
247
296
|
metricAnchors: {
|
|
248
297
|
'membership-granted': {
|
|
249
|
-
// Admin UI i18n (Payload label
|
|
298
|
+
// Admin UI i18n (Payload label / OptionLabel)
|
|
250
299
|
label: ({ t }) => t('custom:achievements:anchors:membershipGranted'),
|
|
251
300
|
resolve: async ({ payload, userId, scopeId }) => {
|
|
252
301
|
// return a Date from host data, or null
|
|
@@ -271,7 +320,7 @@ achievementPlugin({
|
|
|
271
320
|
|
|
272
321
|
## Server helpers
|
|
273
322
|
|
|
274
|
-
Use these from hooks, jobs, and trusted endpoints
|
|
323
|
+
Use these from hooks, jobs, and trusted endpoints:
|
|
275
324
|
|
|
276
325
|
```ts
|
|
277
326
|
import {
|
|
@@ -285,14 +334,34 @@ import {
|
|
|
285
334
|
resolveCurrentTier,
|
|
286
335
|
resolveTierProgress,
|
|
287
336
|
getUserProgress,
|
|
337
|
+
loadUserProgressReviews,
|
|
338
|
+
buildUnlockRequirementLeaves,
|
|
288
339
|
submitAchievementRequest,
|
|
289
340
|
reviewAchievementRequest,
|
|
341
|
+
logSubject,
|
|
290
342
|
} from '@dappermountain/payload-plugin-achievements'
|
|
291
343
|
|
|
292
344
|
await recordLog({ payload, userId, scopeId, type: 'host.custom-kind', actorId })
|
|
345
|
+
await recordLog({
|
|
346
|
+
payload,
|
|
347
|
+
userId,
|
|
348
|
+
scopeId,
|
|
349
|
+
type: 'comment.created',
|
|
350
|
+
actorId: userId,
|
|
351
|
+
subject: logSubject('comments', commentId),
|
|
352
|
+
})
|
|
293
353
|
await recordMetricChange({ payload, userId, scopeId, metric: 'points', change: 10 })
|
|
354
|
+
const points = await resolveMetricValue({ payload, userId, scopeId, metric: 'points' })
|
|
294
355
|
const board = await getMetricLeaderboard({ payload, metric: 'points', scopeId, limit: 20 })
|
|
295
356
|
const tier = await resolveCurrentTier({ payload, userId, scopeId })
|
|
357
|
+
const progress = await getUserProgress({ payload, userId, scopeId, include: { reviews: true } })
|
|
358
|
+
const unlockLeaves = await buildUnlockRequirementLeaves({
|
|
359
|
+
payload,
|
|
360
|
+
userId,
|
|
361
|
+
scopeId,
|
|
362
|
+
rules: nextTier.unlockRules,
|
|
363
|
+
ladderRank: tier?.rank ?? null,
|
|
364
|
+
})
|
|
296
365
|
|
|
297
366
|
// Repair users whose grants predate side-effect hooks (idempotent)
|
|
298
367
|
await reconcileProgression({ payload })
|
|
@@ -326,6 +395,8 @@ Engine log `data` for those system events uses a shared transition envelope:
|
|
|
326
395
|
| `achievement.granted` | `null` | achievement |
|
|
327
396
|
| `achievement.revoked` | achievement | `null` |
|
|
328
397
|
|
|
398
|
+
Host document links belong on polymorphic **`subject`** (when `subjects.collections` is set), not inside `data`.
|
|
399
|
+
|
|
329
400
|
Turning **Requires review** off on a definition or tier **approves pending requests** for that row only (each waiting user goes through the existing request hooks).
|
|
330
401
|
|
|
331
402
|
**Admin:** Grants list includes **Repair progression**, which `POST`s the reconcile endpoint (requires `canReview`).
|
|
@@ -334,7 +405,7 @@ Turning **Requires review** off on a definition or tier **approves pending reque
|
|
|
334
405
|
|
|
335
406
|
| Goal | Request |
|
|
336
407
|
| --- | --- |
|
|
337
|
-
| Current user snapshot | `GET /api/achievements/me?scope=<scopeId>&limit=10&page=1` |
|
|
408
|
+
| Current user snapshot | `GET /api/achievements/me?scope=<scopeId>&limit=10&page=1` (`&reviews=1` for pending/rejected review keys) |
|
|
338
409
|
| Stored metric leaderboard | `GET /api/achievements/leaderboard?metric=points&scope=<scopeId>&limit=20&page=1` |
|
|
339
410
|
| Repair progression | `POST /api/achievements/reconcile` (reviewer; optional `{ userId, scopeId, achievementId\|achievementSlug, tierId\|tierSlug, limit }`) |
|
|
340
411
|
| List grants | `GET /api/achievement-grants?where[user][equals]=<userId>` |
|
|
@@ -357,7 +428,15 @@ import {
|
|
|
357
428
|
await seedAchievementCatalog(payload) // system metrics + event types (English)
|
|
358
429
|
|
|
359
430
|
await seedAchievements(payload, {
|
|
360
|
-
eventTypes: [
|
|
431
|
+
eventTypes: [
|
|
432
|
+
{ name: 'Custom kind', slug: 'host.custom-kind', requiresActor: true },
|
|
433
|
+
{
|
|
434
|
+
name: 'Comment created',
|
|
435
|
+
slug: 'comment.created',
|
|
436
|
+
requiresSubject: true,
|
|
437
|
+
subjectRelationTo: ['comments'],
|
|
438
|
+
},
|
|
439
|
+
],
|
|
361
440
|
metrics: [{ name: 'Streak days', slug: 'streak-days' }],
|
|
362
441
|
tiers: [
|
|
363
442
|
{
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RelationshipFieldClientProps } from 'payload';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
type SubjectFieldProps = RelationshipFieldClientProps & {
|
|
4
|
+
eventTypesSlug: string;
|
|
5
|
+
/** Plugin allowlist — used when the event type leaves `subjectRelationTo` empty. */
|
|
6
|
+
subjectCollections: string[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Polymorphic subject — only when the selected event type requires a host document.
|
|
10
|
+
* Clears the value when the type does not require a subject.
|
|
11
|
+
*/
|
|
12
|
+
export declare function SubjectField(props: SubjectFieldProps): React.JSX.Element | null;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=SubjectField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubjectField.d.ts","sourceRoot":"","sources":["../../src/admin/SubjectField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAA;AAE3D,OAAO,KAA6B,MAAM,OAAO,CAAA;AAIjD,KAAK,iBAAiB,GAAG,4BAA4B,GAAG;IACtD,cAAc,EAAE,MAAM,CAAA;IACtB,oFAAoF;IACpF,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAC7B,CAAA;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,4BAoCpD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { RelationshipField, useField } from '@payloadcms/ui';
|
|
4
|
+
import { useEffect, useMemo } from 'react';
|
|
5
|
+
import { useEventTypeFlags } from './useEventTypeFlags.js';
|
|
6
|
+
/**
|
|
7
|
+
* Polymorphic subject — only when the selected event type requires a host document.
|
|
8
|
+
* Clears the value when the type does not require a subject.
|
|
9
|
+
*/
|
|
10
|
+
export function SubjectField(props) {
|
|
11
|
+
const { eventTypesSlug, subjectCollections, ...fieldArgs } = props;
|
|
12
|
+
const { requiresSubject, subjectRelationTo } = useEventTypeFlags(eventTypesSlug);
|
|
13
|
+
const { setValue, value } = useField({ path: fieldArgs.path });
|
|
14
|
+
const allowedCollections = useMemo(() => {
|
|
15
|
+
if (subjectRelationTo.length > 0) {
|
|
16
|
+
return subjectRelationTo.filter((slug) => subjectCollections.includes(slug));
|
|
17
|
+
}
|
|
18
|
+
return subjectCollections;
|
|
19
|
+
}, [subjectCollections, subjectRelationTo]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!requiresSubject && value != null && value !== '') {
|
|
22
|
+
setValue(null);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!requiresSubject || !value || typeof value !== 'object')
|
|
26
|
+
return;
|
|
27
|
+
const relationTo = value.relationTo;
|
|
28
|
+
if (typeof relationTo === 'string' && allowedCollections.length > 0 && !allowedCollections.includes(relationTo)) {
|
|
29
|
+
setValue(null);
|
|
30
|
+
}
|
|
31
|
+
}, [requiresSubject, setValue, value, allowedCollections]);
|
|
32
|
+
if (!requiresSubject || allowedCollections.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
return (_jsx(RelationshipField, { ...fieldArgs, field: {
|
|
35
|
+
...fieldArgs.field,
|
|
36
|
+
relationTo: allowedCollections,
|
|
37
|
+
} }));
|
|
38
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type EventTypeFlags = {
|
|
2
2
|
requiresActor: boolean;
|
|
3
3
|
requiresMetric: boolean;
|
|
4
|
+
requiresSubject: boolean;
|
|
5
|
+
subjectRelationTo: string[];
|
|
4
6
|
};
|
|
5
7
|
/** Resolve event-type flags for the selected `type` field (fetch when only an id is present). */
|
|
6
8
|
export declare function useEventTypeFlags(eventTypesSlug: string): EventTypeFlags;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEventTypeFlags.d.ts","sourceRoot":"","sources":["../../src/admin/useEventTypeFlags.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"useEventTypeFlags.d.ts","sourceRoot":"","sources":["../../src/admin/useEventTypeFlags.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,OAAO,CAAA;IACtB,cAAc,EAAE,OAAO,CAAA;IACvB,eAAe,EAAE,OAAO,CAAA;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC5B,CAAA;AAqDD,iGAAiG;AACjG,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,cAAc,CA4CxE"}
|
|
@@ -15,19 +15,35 @@ function relationId(value) {
|
|
|
15
15
|
}
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
+
function readSubjectRelationTo(value) {
|
|
19
|
+
if (!Array.isArray(value))
|
|
20
|
+
return [];
|
|
21
|
+
return value.filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
22
|
+
}
|
|
18
23
|
function flagsFromValue(typeValue) {
|
|
19
24
|
if (!typeValue || typeof typeValue !== 'object')
|
|
20
25
|
return null;
|
|
21
|
-
if (!('requiresActor' in typeValue ||
|
|
26
|
+
if (!('requiresActor' in typeValue ||
|
|
27
|
+
'requiresMetric' in typeValue ||
|
|
28
|
+
'requiresSubject' in typeValue ||
|
|
29
|
+
'subjectRelationTo' in typeValue ||
|
|
30
|
+
'slug' in typeValue)) {
|
|
22
31
|
return null;
|
|
23
32
|
}
|
|
24
33
|
const row = typeValue;
|
|
25
34
|
return {
|
|
26
35
|
requiresActor: Boolean(row.requiresActor),
|
|
27
36
|
requiresMetric: Boolean(row.requiresMetric) || row.slug === 'metric.delta',
|
|
37
|
+
requiresSubject: Boolean(row.requiresSubject),
|
|
38
|
+
subjectRelationTo: readSubjectRelationTo(row.subjectRelationTo),
|
|
28
39
|
};
|
|
29
40
|
}
|
|
30
|
-
const empty = {
|
|
41
|
+
const empty = {
|
|
42
|
+
requiresActor: false,
|
|
43
|
+
requiresMetric: false,
|
|
44
|
+
requiresSubject: false,
|
|
45
|
+
subjectRelationTo: [],
|
|
46
|
+
};
|
|
31
47
|
/** Resolve event-type flags for the selected `type` field (fetch when only an id is present). */
|
|
32
48
|
export function useEventTypeFlags(eventTypesSlug) {
|
|
33
49
|
const { config } = useConfig();
|
|
@@ -47,7 +63,9 @@ export function useEventTypeFlags(eventTypesSlug) {
|
|
|
47
63
|
const controller = new AbortController();
|
|
48
64
|
const api = config.routes?.api ?? '/api';
|
|
49
65
|
const serverURL = config.serverURL ?? '';
|
|
50
|
-
const url = `${serverURL}${api}/${eventTypesSlug}/${typeId}
|
|
66
|
+
const url = `${serverURL}${api}/${eventTypesSlug}/${typeId}` +
|
|
67
|
+
`?depth=0&select[requiresActor]=true&select[requiresMetric]=true` +
|
|
68
|
+
`&select[requiresSubject]=true&select[subjectRelationTo]=true&select[slug]=true`;
|
|
51
69
|
void fetch(url, { credentials: 'include', signal: controller.signal })
|
|
52
70
|
.then(async (res) => {
|
|
53
71
|
if (!res.ok) {
|
package/dist/cli/reconcile.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare function runReconcileCli(args: {
|
|
|
12
12
|
config: SanitizedConfig | Promise<SanitizedConfig>;
|
|
13
13
|
argv?: string[];
|
|
14
14
|
filters?: ReconcileCliFilters;
|
|
15
|
-
/** When false, return the result
|
|
15
|
+
/** When false, return the result and leave the process running. Default true. */
|
|
16
16
|
exit?: boolean;
|
|
17
17
|
}): Promise<ReconcileProgressionResult>;
|
|
18
18
|
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -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,
|
|
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,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4BtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/EventTypes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/EventTypes/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;AAqCtD,wBAAgB,yBAAyB,IAAI,gBAAgB,CAoE5D"}
|
|
@@ -1,8 +1,36 @@
|
|
|
1
|
+
import { getAchievementOptions } from '../../options-store.js';
|
|
1
2
|
import { collectionAdmin, collectionOf, columnsWithOptionalScope, optionalFields, slugOf } from '../helpers.js';
|
|
2
3
|
import { localizedNameField, scopeField } from '../fields/index.js';
|
|
3
4
|
import { access } from './access/index.js';
|
|
4
5
|
import { hooks } from './hooks/index.js';
|
|
6
|
+
function subjectCatalogFields() {
|
|
7
|
+
const collections = getAchievementOptions().subjectCollections;
|
|
8
|
+
if (collections.length === 0)
|
|
9
|
+
return [];
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
name: 'requiresSubject',
|
|
13
|
+
type: 'checkbox',
|
|
14
|
+
defaultValue: false,
|
|
15
|
+
label: 'Requires a subject',
|
|
16
|
+
admin: {
|
|
17
|
+
description: 'Turn this on when the log points at a host document (for example a post or comment). We’ll ask for that document when it’s logged.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'subjectRelationTo',
|
|
22
|
+
type: 'select',
|
|
23
|
+
hasMany: true,
|
|
24
|
+
options: collections.map((slug) => ({ label: slug, value: slug })),
|
|
25
|
+
admin: {
|
|
26
|
+
condition: (_, siblingData) => Boolean(siblingData?.requiresSubject),
|
|
27
|
+
description: 'Which host collections this type may reference. Leave empty to allow any collection from the plugin subjects allowlist.',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
5
32
|
export function buildEventTypesCollection() {
|
|
33
|
+
const subjectFields = subjectCatalogFields();
|
|
6
34
|
return {
|
|
7
35
|
slug: slugOf('eventTypes'),
|
|
8
36
|
access: { ...access },
|
|
@@ -14,6 +42,7 @@ export function buildEventTypesCollection() {
|
|
|
14
42
|
'slug',
|
|
15
43
|
'requiresActor',
|
|
16
44
|
'requiresMetric',
|
|
45
|
+
...(subjectFields.length > 0 ? ['requiresSubject'] : []),
|
|
17
46
|
'system',
|
|
18
47
|
'scope',
|
|
19
48
|
]),
|
|
@@ -46,6 +75,7 @@ export function buildEventTypesCollection() {
|
|
|
46
75
|
description: 'Turn this on when the entry changes a score (like points). We’ll ask which metric and by how much.',
|
|
47
76
|
},
|
|
48
77
|
},
|
|
78
|
+
...subjectFields,
|
|
49
79
|
{
|
|
50
80
|
name: 'system',
|
|
51
81
|
type: 'checkbox',
|
|
@@ -2,4 +2,5 @@ import type { Validate } from 'payload';
|
|
|
2
2
|
export declare const validateActor: Validate;
|
|
3
3
|
export declare const validateMetric: Validate;
|
|
4
4
|
export declare const validateChange: Validate;
|
|
5
|
+
export declare const validateSubject: Validate;
|
|
5
6
|
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../src/collections/Logs/fields/validators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../../../src/collections/Logs/fields/validators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAavC,eAAO,MAAM,aAAa,EAAE,QAW3B,CAAA;AAED,eAAO,MAAM,cAAc,EAAE,QA0B5B,CAAA;AAED,eAAO,MAAM,cAAc,EAAE,QAW5B,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,QAqC7B,CAAA"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { getAchievementOptions } from '../../../options-store.js';
|
|
1
2
|
import { collectionOf } from '../../../collections/helpers.js';
|
|
2
|
-
import { eventTypeRequiresActor, eventTypeRequiresMetric, } from '../../../services/achievement/eventTypeRequiresActor.js';
|
|
3
|
+
import { eventTypeRequiresActor, eventTypeRequiresMetric, eventTypeSubjectRelationTo, getEventTypeFlags, } from '../../../services/achievement/eventTypeRequiresActor.js';
|
|
4
|
+
import { readLogSubject } from '../../../services/achievement/logSubject.js';
|
|
3
5
|
import { relationId } from '../../../services/achievement/relationId.js';
|
|
4
6
|
export const validateActor = async (value, { data, req }) => {
|
|
5
7
|
if (!req?.payload)
|
|
@@ -55,3 +57,37 @@ export const validateChange = async (value, { data, req }) => {
|
|
|
55
57
|
}
|
|
56
58
|
return true;
|
|
57
59
|
};
|
|
60
|
+
export const validateSubject = async (value, { data, req }) => {
|
|
61
|
+
if (!req?.payload)
|
|
62
|
+
return true;
|
|
63
|
+
const allowlist = getAchievementOptions().subjectCollections;
|
|
64
|
+
if (allowlist.length === 0)
|
|
65
|
+
return true;
|
|
66
|
+
const typeId = relationId(data?.type);
|
|
67
|
+
if (!typeId)
|
|
68
|
+
return true;
|
|
69
|
+
const flags = await getEventTypeFlags({
|
|
70
|
+
payload: req.payload,
|
|
71
|
+
req,
|
|
72
|
+
typeIdOrDoc: typeId,
|
|
73
|
+
});
|
|
74
|
+
const subject = readLogSubject(value);
|
|
75
|
+
if (!flags.requiresSubject) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (!subject) {
|
|
79
|
+
return 'This log type needs a subject (the host document it is about).';
|
|
80
|
+
}
|
|
81
|
+
if (!allowlist.includes(subject.relationTo)) {
|
|
82
|
+
return `Subject collection "${subject.relationTo}" is not in the plugin subjects allowlist.`;
|
|
83
|
+
}
|
|
84
|
+
const allowed = await eventTypeSubjectRelationTo({
|
|
85
|
+
payload: req.payload,
|
|
86
|
+
req,
|
|
87
|
+
typeIdOrDoc: typeId,
|
|
88
|
+
});
|
|
89
|
+
if (allowed.length > 0 && !allowed.includes(subject.relationTo)) {
|
|
90
|
+
return `This log type only allows subjects from: ${allowed.join(', ')}.`;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensureTypeRequirements.d.ts","sourceRoot":"","sources":["../../../../src/collections/Logs/hooks/ensureTypeRequirements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ensureTypeRequirements.d.ts","sourceRoot":"","sources":["../../../../src/collections/Logs/hooks/ensureTypeRequirements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAA;AAc3D,eAAO,MAAM,sBAAsB,EAAE,4BA+DpC,CAAA"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { APIError } from 'payload';
|
|
2
|
+
import { getAchievementOptions } from '../../../options-store.js';
|
|
2
3
|
import { collectionOf } from '../../../collections/helpers.js';
|
|
3
|
-
import {
|
|
4
|
+
import { eventTypeSubjectRelationTo, getEventTypeFlags, } from '../../../services/achievement/eventTypeRequiresActor.js';
|
|
5
|
+
import { readLogSubject } from '../../../services/achievement/logSubject.js';
|
|
4
6
|
import { relationId } from '../../../services/achievement/relationId.js';
|
|
5
7
|
export const ensureTypeRequirements = async ({ data, req }) => {
|
|
6
8
|
if (!data)
|
|
@@ -8,18 +10,11 @@ export const ensureTypeRequirements = async ({ data, req }) => {
|
|
|
8
10
|
const typeId = relationId(data.type);
|
|
9
11
|
if (!typeId)
|
|
10
12
|
return data;
|
|
11
|
-
const flags = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}),
|
|
17
|
-
requiresMetric: await eventTypeRequiresMetric({
|
|
18
|
-
payload: req.payload,
|
|
19
|
-
req,
|
|
20
|
-
typeIdOrDoc: typeId,
|
|
21
|
-
}),
|
|
22
|
-
};
|
|
13
|
+
const flags = await getEventTypeFlags({
|
|
14
|
+
payload: req.payload,
|
|
15
|
+
req,
|
|
16
|
+
typeIdOrDoc: typeId,
|
|
17
|
+
});
|
|
23
18
|
if (flags.requiresActor && !relationId(data.actor)) {
|
|
24
19
|
throw new APIError('This log type needs an actor (who caused it).', 400);
|
|
25
20
|
}
|
|
@@ -44,5 +39,19 @@ export const ensureTypeRequirements = async ({ data, req }) => {
|
|
|
44
39
|
throw new APIError('Computed metrics cannot be changed with a metric delta. Use a stored metric such as Points.', 400);
|
|
45
40
|
}
|
|
46
41
|
}
|
|
42
|
+
if (flags.requiresSubject && getAchievementOptions().subjectCollections.length > 0) {
|
|
43
|
+
const subject = readLogSubject(data.subject);
|
|
44
|
+
if (!subject) {
|
|
45
|
+
throw new APIError('This log type needs a subject (the host document it is about).', 400);
|
|
46
|
+
}
|
|
47
|
+
const allowed = await eventTypeSubjectRelationTo({
|
|
48
|
+
payload: req.payload,
|
|
49
|
+
req,
|
|
50
|
+
typeIdOrDoc: typeId,
|
|
51
|
+
});
|
|
52
|
+
if (allowed.length > 0 && !allowed.includes(subject.relationTo)) {
|
|
53
|
+
throw new APIError(`This log type only allows subjects from: ${allowed.join(', ')}.`, 400);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
47
56
|
return data;
|
|
48
57
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Logs/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/Logs/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAS,MAAM,SAAS,CAAA;AAqCtD,+EAA+E;AAC/E,wBAAgB,mBAAmB,IAAI,gBAAgB,CAuHtD"}
|
|
@@ -3,12 +3,34 @@ import { collectionAdmin, collectionOf, optionalFields, slugOf } from '../helper
|
|
|
3
3
|
import { getAchievementOptions } from '../../options-store.js';
|
|
4
4
|
import { scopeField, userField } from '../fields/index.js';
|
|
5
5
|
import { access } from './access/index.js';
|
|
6
|
-
import { validateActor, validateChange, validateMetric } from './fields/validators.js';
|
|
6
|
+
import { validateActor, validateChange, validateMetric, validateSubject, } from './fields/validators.js';
|
|
7
7
|
import { hooks } from './hooks/index.js';
|
|
8
|
+
function subjectLogField(eventTypesSlug) {
|
|
9
|
+
const collections = getAchievementOptions().subjectCollections;
|
|
10
|
+
if (collections.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
return {
|
|
13
|
+
name: 'subject',
|
|
14
|
+
type: 'relationship',
|
|
15
|
+
relationTo: collections,
|
|
16
|
+
index: true,
|
|
17
|
+
validate: validateSubject,
|
|
18
|
+
admin: {
|
|
19
|
+
description: 'Host document this entry is about (post, comment, …).',
|
|
20
|
+
components: {
|
|
21
|
+
Field: {
|
|
22
|
+
path: '@dappermountain/payload-plugin-achievements/client#SubjectField',
|
|
23
|
+
clientProps: { eventTypesSlug, subjectCollections: collections },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
8
29
|
/** Append-only occurrence log. `type` / `metric` are catalog relationships. */
|
|
9
30
|
export function buildLogsCollection() {
|
|
10
31
|
const users = getAchievementOptions().usersCollectionSlug ?? 'users';
|
|
11
32
|
const eventTypesSlug = collectionOf('eventTypes');
|
|
33
|
+
const subjectField = subjectLogField(eventTypesSlug);
|
|
12
34
|
return {
|
|
13
35
|
slug: slugOf('logs'),
|
|
14
36
|
labels: {
|
|
@@ -19,7 +41,15 @@ export function buildLogsCollection() {
|
|
|
19
41
|
hooks,
|
|
20
42
|
admin: collectionAdmin({
|
|
21
43
|
useAsTitle: 'id',
|
|
22
|
-
defaultColumns: [
|
|
44
|
+
defaultColumns: [
|
|
45
|
+
'type',
|
|
46
|
+
'user',
|
|
47
|
+
'actor',
|
|
48
|
+
...(subjectField ? ['subject'] : []),
|
|
49
|
+
'metric',
|
|
50
|
+
'change',
|
|
51
|
+
'createdAt',
|
|
52
|
+
],
|
|
23
53
|
description: 'Member activity history — grants, revokes, score changes, tier moves, and host custom events. Not a second grant store.',
|
|
24
54
|
}),
|
|
25
55
|
fields: [
|
|
@@ -88,6 +118,7 @@ export function buildLogsCollection() {
|
|
|
88
118
|
},
|
|
89
119
|
],
|
|
90
120
|
},
|
|
121
|
+
...(subjectField ? [subjectField] : []),
|
|
91
122
|
{
|
|
92
123
|
type: 'collapsible',
|
|
93
124
|
label: 'Details',
|
|
@@ -105,7 +136,7 @@ export function buildLogsCollection() {
|
|
|
105
136
|
name: 'data',
|
|
106
137
|
type: 'json',
|
|
107
138
|
admin: {
|
|
108
|
-
description: 'Engine transition envelope: `{ from, to }` each `{ id, slug? }` or null (tier.changed, achievement.granted/revoked).
|
|
139
|
+
description: 'Engine transition envelope: `{ from, to }` each `{ id, slug? }` or null (tier.changed, achievement.granted/revoked). Prefer `subject` for host document links.',
|
|
109
140
|
},
|
|
110
141
|
},
|
|
111
142
|
],
|
package/dist/defaults.d.ts
CHANGED
|
@@ -4,13 +4,15 @@ export declare const DEFAULT_USERS_COLLECTION = "users";
|
|
|
4
4
|
export declare const DEFAULT_ME_ENDPOINT_PATH = "/achievements/me";
|
|
5
5
|
export declare const DEFAULT_LEADERBOARD_ENDPOINT_PATH = "/achievements/leaderboard";
|
|
6
6
|
export declare const DEFAULT_RECONCILE_ENDPOINT_PATH = "/achievements/reconcile";
|
|
7
|
-
export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users' | 'endpoints'> & {
|
|
7
|
+
export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users' | 'endpoints' | 'subjects'> & {
|
|
8
8
|
enabled: boolean;
|
|
9
9
|
seedSystemCatalog: boolean;
|
|
10
10
|
usersCollectionSlug: string;
|
|
11
11
|
users: {
|
|
12
12
|
includeJoins: boolean;
|
|
13
13
|
};
|
|
14
|
+
/** Host collections allowed as polymorphic log subjects (empty = feature off). */
|
|
15
|
+
subjectCollections: string[];
|
|
14
16
|
/** Resolved collection slug map (after prefix + overrides). */
|
|
15
17
|
collectionSlugs: ReturnType<typeof resolveCollectionSlugs>;
|
|
16
18
|
/** Admin nav group for plugin collections (`false` = ungrouped). */
|
|
@@ -21,6 +23,8 @@ export type ResolvedAchievementOptions = Omit<AchievementPluginOptions, 'users'
|
|
|
21
23
|
reconcile: string | false;
|
|
22
24
|
};
|
|
23
25
|
};
|
|
26
|
+
/** Unique, non-empty collection slugs from plugin `subjects.collections`. */
|
|
27
|
+
export declare function resolveSubjectCollections(options?: AchievementPluginOptions): string[];
|
|
24
28
|
export declare function resolveMeEndpointPath(me?: string | false): string | false;
|
|
25
29
|
export declare function resolveLeaderboardEndpointPath(path?: string | false): string | false;
|
|
26
30
|
export declare function resolveReconcileEndpointPath(path?: string | false): string | false;
|
package/dist/defaults.d.ts.map
CHANGED
|
@@ -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,iCAAiC,8BAA8B,CAAA;AAC5E,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AAExE,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,wBAAwB,EAAE,OAAO,GAAG,WAAW,CAAC,GAAG;
|
|
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,GAAG,UAAU,CAAC,GAAG;IAC5G,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,kFAAkF;IAClF,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B,+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;AAED,6EAA6E;AAC7E,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,MAAM,EAAE,CAWtF;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,CAmBjG"}
|