@domino-sdk/relay 1.0.0-next.9 → 1.1.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 (59) hide show
  1. package/README.md +1 -1
  2. package/dist/activity-B8k9Sa_a.d.ts +9112 -0
  3. package/dist/authoring.d.ts +1053 -259
  4. package/dist/authoring.js +478 -277
  5. package/dist/automatic-outcomes-B1xrETxm.d.ts +758 -0
  6. package/dist/avatar.d.ts +2 -2
  7. package/dist/browser-J7tUpXxy.d.ts +4154 -0
  8. package/dist/browser.d.ts +11 -2613
  9. package/dist/browser.js +8 -6
  10. package/dist/{integrations-Ba5TtqTv.d.ts → capability-error-DNMwKmlL.d.ts} +124 -76
  11. package/dist/{chunk-PHU5VDLR.js → chunk-36HTX2BM.js} +389 -169
  12. package/dist/chunk-6QJ3LADK.js +112 -0
  13. package/dist/{chunk-4LBUTHD6.js → chunk-BGGKNIRP.js} +1 -1
  14. package/dist/chunk-EYH5RDIE.js +496 -0
  15. package/dist/chunk-GQKYAQM6.js +84 -0
  16. package/dist/chunk-GUP7YRR4.js +42 -0
  17. package/dist/{chunk-X2F2RAR3.js → chunk-GZ6FJAXN.js} +380 -128
  18. package/dist/chunk-JON5SESL.js +2986 -0
  19. package/dist/chunk-MC6V6VMG.js +49 -0
  20. package/dist/chunk-NPGENI7K.js +37 -0
  21. package/dist/chunk-VFHHLN6A.js +284 -0
  22. package/dist/chunk-WJSSJ7AF.js +151 -0
  23. package/dist/{chunk-7Q4BGGT6.js → chunk-XFMSUPNS.js} +1 -1
  24. package/dist/{chunk-P6FK43GG.js → chunk-ZVIRG24J.js} +145 -42
  25. package/dist/{client-CT4-S5vc.d.ts → client-XiuFnug_.d.ts} +2161 -615
  26. package/dist/{submission-input-0OOGaq8c.d.ts → groups-Ck-OxzjD.d.ts} +916 -1139
  27. package/dist/identity-integration-GxAHdKR2.d.ts +58 -0
  28. package/dist/images-Dv-P5VBw.d.ts +234 -0
  29. package/dist/index.d.ts +13 -9
  30. package/dist/index.js +17 -6
  31. package/dist/internal.d.ts +82 -44
  32. package/dist/internal.js +43 -89
  33. package/dist/offers-Cy-e4o5x.d.ts +431 -0
  34. package/dist/{pages-D3titw5B.d.ts → outcomes-kVB-Juzs.d.ts} +2157 -1448
  35. package/dist/pages-BBYD21IE.d.ts +359 -0
  36. package/dist/participant-rewards-3duLb8YJ.d.ts +668 -0
  37. package/dist/portal-proxy.js +22 -4
  38. package/dist/{profiles-DDMFBRKe.d.ts → profiles-D2aFcOxu.d.ts} +15 -15
  39. package/dist/react/index.d.ts +3038 -1029
  40. package/dist/react/index.js +1627 -779
  41. package/dist/request-error-DWduJCGV.d.ts +62 -0
  42. package/dist/schema-DfJzH2ci.d.ts +5644 -0
  43. package/dist/schema.d.ts +15 -4168
  44. package/dist/schema.js +310 -81
  45. package/dist/server/index.d.ts +81722 -0
  46. package/dist/server/index.js +2569 -0
  47. package/dist/{streaks-CwGReWGt.d.ts → streaks-CWyq8Px5.d.ts} +2 -4
  48. package/dist/submission-input-BZkm6ohU.d.ts +290 -0
  49. package/dist/vite.d.ts +1 -1
  50. package/dist/vite.js +1 -0
  51. package/dist/{wallet-BGOfFkG9.d.ts → wallet-YssWxGg8.d.ts} +35 -38
  52. package/package.json +8 -2
  53. package/dist/chunk-QR7ZA2BJ.js +0 -257
  54. package/dist/chunk-YYD4QUBO.js +0 -2348
  55. package/dist/chunk-ZWY3R6B3.js +0 -26
  56. package/dist/collection-BGneJ6Bk.d.ts +0 -114
  57. package/dist/images-BTgk6OmC.d.ts +0 -1564
  58. package/dist/point-ledgers-iS00Z_v0.d.ts +0 -37
  59. package/dist/schema-Di3aLSiK.d.ts +0 -3532
package/dist/authoring.js CHANGED
@@ -3,36 +3,65 @@ import {
3
3
  parseUnits
4
4
  } from "./chunk-V7UKZLHA.js";
5
5
  import {
6
- authoringKey,
6
+ CapabilityError,
7
+ capabilityErrorCodes
8
+ } from "./chunk-MC6V6VMG.js";
9
+ import {
10
+ sourceDataSchema,
11
+ sourceRoles,
12
+ validateQuestShape
13
+ } from "./chunk-GQKYAQM6.js";
14
+ import {
15
+ cosmosDenomSchema,
16
+ cosmosNetworkSchema,
17
+ defineOffer,
7
18
  engagementDefinitionSchema,
8
19
  engagementScoreInputSchema,
9
20
  engagementScoreSchema,
10
21
  isEvmChainId,
11
22
  leaderboardAuthoringSchema,
23
+ pointLedgerDeclarationSchema,
12
24
  referralAuthoringSchema
13
- } from "./chunk-PHU5VDLR.js";
25
+ } from "./chunk-36HTX2BM.js";
26
+ import "./chunk-NPGENI7K.js";
14
27
  import {
15
- admittedContentSchema,
28
+ authoringKey,
29
+ balanceAtLeast,
16
30
  budgetSchema,
31
+ completed,
32
+ contentMetricNameSchema,
17
33
  contentPerformancePolicySchema,
18
34
  contentPerformanceSnapshotSchema,
19
- contentPolicySchema,
35
+ counterGoalSchema,
36
+ counterPolicySchema,
37
+ counterWindowSchema,
20
38
  customSourceSchema,
21
39
  decisionSchema,
22
40
  defaultTriggerMode,
41
+ defineCampaign,
42
+ defineGroup,
43
+ discordMessagesSourceSchema,
44
+ discordReactionsSourceSchema,
45
+ discordRewardSchema,
46
+ evaluatorIntegrationProviderSchema,
47
+ evaluatorProtocol,
23
48
  identifier,
24
- integrationRewardSchema,
49
+ inGroup,
50
+ ledgerKey,
51
+ ledgerSettingSchema,
25
52
  parseSettings,
26
53
  pointsRewardSchema,
27
54
  questDescriptionSchema,
28
55
  quizAnswersSchema,
29
56
  quizContentSchema,
57
+ resourceHandle,
30
58
  resourceSourceSchema,
31
59
  rewardBundleSchema,
32
60
  schemaCheckedReader,
33
61
  settingFieldSchema,
34
62
  settingInstant,
35
63
  settingResourceId,
64
+ sourceContent,
36
65
  sourceIdentities,
37
66
  streakPolicySchema,
38
67
  textOptionsSchema,
@@ -46,42 +75,20 @@ import {
46
75
  urlOptionsSchema,
47
76
  whereSchemaIssue,
48
77
  whereSettings,
49
- xAccountRoles,
50
78
  xSearchSourceSchema
51
- } from "./chunk-YYD4QUBO.js";
79
+ } from "./chunk-JON5SESL.js";
52
80
 
53
81
  // src/builders.ts
54
82
  import { z } from "zod";
55
- function content(tracker, policy = {}) {
56
- if (tracker.source.kind !== "x-search") {
57
- throw new Error("Content quests require an X tracker");
58
- }
59
- return {
60
- kind: "automatic",
61
- content: contentPolicySchema.parse({
62
- tracker: tracker.key,
63
- window: tracker.window,
64
- ...policy
65
- })
66
- };
67
- }
68
- function perContent() {
69
- return {
70
- kind: "keyed",
71
- perContent: true,
72
- key: (ctx) => {
73
- if (!ctx.content) {
74
- throw new Error("perContent requires an admitted content item");
75
- }
76
- return ctx.content.completionKey;
77
- }
78
- };
83
+ function defineLedger(input) {
84
+ return resourceHandle("ledger", pointLedgerDeclarationSchema.parse(input));
79
85
  }
80
86
  function defineEngagementReward(input) {
81
- const { target, ...config } = input;
87
+ const { target, ledger, ...config } = input;
82
88
  const definition = engagementDefinitionSchema.parse({
83
89
  ...config,
84
- qualifiedBy: typeof input.qualifiedBy === "string" ? input.qualifiedBy : input.qualifiedBy.key
90
+ qualifiedBy: input.qualifiedBy.key,
91
+ balance: ledgerKey(ledger)
85
92
  });
86
93
  const selected = z.record(
87
94
  z.enum(input.metrics),
@@ -99,29 +106,99 @@ function defineEngagementReward(input) {
99
106
  }
100
107
  };
101
108
  }
102
- var streak = (options) => streakPolicySchema.parse({ kind: "streak", ...options });
109
+ var streak = (options) => {
110
+ if ("timeZone" in options) {
111
+ throw new Error(
112
+ "streak() no longer takes a timeZone. Set the project timezone with calendar.timeZone in relay.json or in Console settings."
113
+ );
114
+ }
115
+ return streakPolicySchema.strict().parse({ kind: "streak", ...options });
116
+ };
117
+ var count = (count2) => counterGoalSchema.brand().parse({ kind: "count", count: count2 });
118
+ var calendar = (period, options = {}) => counterWindowSchema.brand().parse({ kind: "calendar", period, ...options });
119
+ var rolling = ({ days }) => counterWindowSchema.brand().parse({ kind: "rolling", durationMs: days * 864e5 });
120
+ var questCompleted = (quest) => ({
121
+ kind: "quest",
122
+ quest
123
+ });
103
124
  function defineReferral(input) {
104
- const definition = referralAuthoringSchema.parse(input);
105
- return {
106
- ...definition,
107
- /** @deprecated Use key. */
108
- id: definition.key
109
- };
125
+ const { qualification, inviterGroup, milestones, bonuses, ...properties } = input;
126
+ const definition = referralAuthoringSchema.parse({
127
+ ...properties,
128
+ qualification: qualification.kind === "quest" ? { kind: "quest", quest: qualification.quest.key } : qualification,
129
+ ...inviterGroup ? { inviterGroup: identifier.parse(inviterGroup.key) } : {},
130
+ milestones: milestones?.map(({ key, ...rule }) => ({
131
+ ...rule,
132
+ id: authoringKey({ key, ...rule })
133
+ })),
134
+ bonuses: bonuses?.map(({ key, ledger, quests, ...rule }) => ({
135
+ ...rule,
136
+ id: authoringKey({ key, ...rule }),
137
+ balance: ledgerKey(ledger),
138
+ quests: quests?.map((quest) => quest.key)
139
+ }))
140
+ });
141
+ return resourceHandle("referral", definition);
142
+ }
143
+ function earned(options = {}) {
144
+ return { kind: "earned", ...options };
110
145
  }
111
146
  function defineLeaderboard(input) {
112
- const definition = leaderboardAuthoringSchema.parse(input);
113
- return {
114
- ...definition,
115
- /** @deprecated Use key. */
116
- id: definition.key
117
- };
147
+ const { ledger, group, score, ...properties } = input;
148
+ const definition = leaderboardAuthoringSchema.parse({
149
+ ...properties,
150
+ balance: ledgerKey(ledger),
151
+ ...group ? { group: identifier.parse(group.key) } : {},
152
+ score: score?.kind === "earned" ? { ...score, quests: score.quests?.map((quest) => quest.key) } : score
153
+ });
154
+ return resourceHandle("leaderboard", definition);
118
155
  }
119
156
  var x = {
120
- /** Posts matching an X search. Each record's `data` is the provider's post payload. */
157
+ provider: "x",
158
+ /**
159
+ * Posts matching an X search. Each record's `data` is the provider's post payload. Its content
160
+ * is the post's normalized body (`XContentBody`), measured by the six X counters, so `content()`
161
+ * quests can use it.
162
+ */
121
163
  search: (input) => xSearchSourceSchema.parse({ kind: "x-search", ...input })
122
164
  };
123
165
  function customSource(namespace, options) {
124
- const { data, ...rest } = options ?? {};
166
+ const { data, content: content2, ...rest } = options ?? {};
167
+ if (content2 !== void 0) {
168
+ if (data !== void 0) {
169
+ throw new Error(
170
+ `customSource("${namespace}") takes data or content, not both: a content source's records carry { body, metrics }`
171
+ );
172
+ }
173
+ if (!isZodSchema(content2.body)) {
174
+ throw new Error(
175
+ `customSource("${namespace}") needs a content body schema: customSource(namespace, { content: { body: z.object({ ... }), metrics: ["likes"] } })`
176
+ );
177
+ }
178
+ const metrics = [...content2.metrics ?? []];
179
+ for (const metric of metrics) {
180
+ contentMetricNameSchema.parse(metric);
181
+ }
182
+ return customSourceSchema.parse({
183
+ kind: "custom",
184
+ namespace,
185
+ ...rest,
186
+ data: trackerDataSchema(
187
+ z.object({
188
+ body: content2.body,
189
+ metrics: z.strictObject(
190
+ Object.fromEntries(
191
+ metrics.map((metric) => [
192
+ metric,
193
+ z.number().int().safe().nonnegative().optional()
194
+ ])
195
+ )
196
+ ).optional()
197
+ })
198
+ ),
199
+ content: { metrics }
200
+ });
201
+ }
125
202
  if (!isZodSchema(data)) {
126
203
  throw new Error(
127
204
  `customSource("${namespace}") needs a data schema: customSource(namespace, { data: z.object({ ... }) }) describes each record's data`
@@ -138,9 +215,30 @@ function isZodSchema(value) {
138
215
  return typeof value === "object" && value !== null && "_zod" in value;
139
216
  }
140
217
  function defineTracker(input) {
141
- return trackerDefinitionSchema.parse(input);
218
+ return resourceHandle(
219
+ "tracker",
220
+ trackerDefinitionSchema.parse(input)
221
+ );
222
+ }
223
+ function messages(options = {}) {
224
+ return discordMessagesSourceSchema.parse({
225
+ kind: "discord-messages",
226
+ ...options.text ? { text: true } : {}
227
+ });
228
+ }
229
+ function reactions() {
230
+ return discordReactionsSourceSchema.parse({ kind: "discord-reactions" });
142
231
  }
143
232
  var discord = {
233
+ provider: "discord",
234
+ grantRole: (roleId) => discordRewardSchema.parse({
235
+ kind: "integration",
236
+ provider: "discord",
237
+ action: "grant-role",
238
+ roleId
239
+ }),
240
+ messages,
241
+ reactions,
144
242
  roles(options = {}) {
145
243
  return resourceSourceSchema.parse({
146
244
  provider: "discord",
@@ -168,11 +266,11 @@ var settings = {
168
266
  const field = {
169
267
  ...options,
170
268
  kind: "point-ledger",
171
- default: identifier.parse(options.default)
269
+ default: options.default ? identifier.parse(options.default.key) : ""
172
270
  };
173
271
  return setting(
174
272
  "point-ledger",
175
- identifier.default(field.default).meta(field)
273
+ ledgerSettingSchema.prefault(field.default).meta(field)
176
274
  );
177
275
  },
178
276
  datetime(options) {
@@ -245,12 +343,10 @@ var settings = {
245
343
  // src/authoring.ts
246
344
  import { z as z2 } from "zod";
247
345
  import { z as z3 } from "zod";
248
- var points = (balance, amount) => pointsRewardSchema.parse({ kind: "points", balance, amount });
249
- var discordRole = (roleId) => integrationRewardSchema.parse({
250
- kind: "integration",
251
- provider: "discord",
252
- action: "grant-role",
253
- roleId
346
+ var points = (ledger, amount) => pointsRewardSchema.parse({
347
+ kind: "points",
348
+ balance: ledgerKey(ledger),
349
+ amount
254
350
  });
255
351
  var evidence = {
256
352
  text: (options = {}) => ({
@@ -280,69 +376,128 @@ function manual(options) {
280
376
  };
281
377
  }
282
378
  var triggerDataSchemas = /* @__PURE__ */ new WeakMap();
379
+ var triggerContentMetrics = /* @__PURE__ */ new WeakMap();
380
+ function trackerTrigger(input, constructor) {
381
+ const {
382
+ tracker,
383
+ beneficiary = "author",
384
+ on,
385
+ where,
386
+ enrollment,
387
+ since,
388
+ historical,
389
+ content: content2
390
+ } = input;
391
+ if (!tracker?.source) {
392
+ throw new Error(
393
+ constructor === "automatic" ? "automatic() takes no argument, or { tracker } with a tracker handle from defineTracker" : "content() takes { tracker } with a tracker handle from defineTracker"
394
+ );
395
+ }
396
+ const { source } = tracker;
397
+ if (!sourceIdentities(source)) {
398
+ throw new Error(
399
+ 'Tracker triggers need attributable records: declare customSource(namespace, { identities: ["wallet"] }) or other identity kinds'
400
+ );
401
+ }
402
+ if (content2 && !sourceContent(source)) {
403
+ throw new Error(
404
+ `content() needs a tracker whose source has content: x.search(), or customSource(namespace, { content: { body, metrics } }). Tracker "${tracker.key}" has none; use automatic({ tracker }) instead.`
405
+ );
406
+ }
407
+ if (since === "enrollment" && enrollment !== "required") {
408
+ throw new Error(
409
+ `${constructor}() counts from enrollment only on a quest that requires it: add enrollment: "required"`
410
+ );
411
+ }
412
+ const roles = sourceRoles(source);
413
+ if (!roles.includes(beneficiary)) {
414
+ throw new Error(
415
+ source.kind === "custom" ? `Tracker "${tracker.key}" has no "${beneficiary}" account: declare it in customSource(namespace, { accounts: ["${beneficiary}"] })` : `Tracker "${tracker.key}" pays ${roles.map((role) => `"${role}"`).join(" or ")}, not "${beneficiary}"`
416
+ );
417
+ }
418
+ const policy = trackerTriggerPolicySchema.parse({
419
+ tracker: tracker.key,
420
+ source: source.kind,
421
+ // A content item is decided by its first observation; later observations run its stage.
422
+ on: content2 ? "new-record" : on ?? defaultTriggerMode(source.kind),
423
+ ...where ? { where } : {},
424
+ beneficiary,
425
+ // Defaults are left out too: "none", "activation", and "staff" ("claim" on content).
426
+ ...enrollment === "required" ? { enrollment } : {},
427
+ ...since && since !== "activation" ? { since } : {},
428
+ ...historical && historical !== (content2 ? "claim" : "staff") ? { historical } : {},
429
+ ...content2 ? { content: content2 } : {}
430
+ });
431
+ const schema = sourceDataSchema(source);
432
+ const issue = schema ? whereSchemaIssue(schema, policy.where) : null;
433
+ if (issue) {
434
+ throw new Error(issue);
435
+ }
436
+ const trigger = { kind: "automatic", tracker: policy };
437
+ if (schema) {
438
+ triggerDataSchemas.set(trigger, schema);
439
+ }
440
+ const declared = sourceContent(source);
441
+ if (declared) {
442
+ triggerContentMetrics.set(trigger, declared.metrics);
443
+ }
444
+ return trigger;
445
+ }
283
446
  function automatic(input) {
284
447
  if (!input) {
285
448
  return { kind: "automatic" };
286
449
  }
287
- if ("tracker" in input) {
288
- const { tracker, beneficiary = "author", on, where } = input;
289
- const { source } = tracker;
290
- if (source.kind === "x-search" && !xAccountRoles.includes(beneficiary)) {
291
- throw new Error(
292
- `X trackers pay "author" or "inReplyTo", not "${beneficiary}"`
293
- );
294
- }
295
- if (!sourceIdentities(source)) {
296
- throw new Error(
297
- 'Tracker triggers need attributable records: declare customSource(namespace, { identities: ["wallet"] }) or other identity kinds'
298
- );
299
- }
300
- if (source.kind === "custom" && beneficiary !== "author" && !source.accounts?.includes(beneficiary)) {
301
- throw new Error(
302
- `Tracker "${tracker.key}" has no "${beneficiary}" account: declare it in customSource(namespace, { accounts: ["${beneficiary}"] })`
303
- );
304
- }
305
- const policy = trackerTriggerPolicySchema.parse({
306
- tracker: tracker.key,
307
- source: source.kind,
308
- // The source's default mode is left out, so default quests keep the policy shape
309
- // earlier SDKs and servers read.
310
- ...on && on !== defaultTriggerMode(source.kind) ? { on } : {},
311
- ...where ? { where } : {},
312
- beneficiary
313
- });
314
- const issue = source.kind === "custom" && source.data ? whereSchemaIssue(source.data, policy.where) : null;
315
- if (issue) {
316
- throw new Error(issue);
317
- }
318
- const trigger = { kind: "automatic", tracker: policy };
319
- if (source.kind === "custom" && source.data) {
320
- triggerDataSchemas.set(trigger, source.data);
321
- }
322
- return trigger;
450
+ return trackerTrigger(input, "automatic");
451
+ }
452
+ function content(input) {
453
+ if (input && typeof input === "object" && "source" in input && "key" in input && !("tracker" in input)) {
454
+ throw new Error(
455
+ "content() takes { tracker }: write content({ tracker: posts }) instead of content(posts)"
456
+ );
323
457
  }
324
- return { kind: "automatic", observation: input };
458
+ return trackerTrigger({ ...input, content: true }, "content");
325
459
  }
326
- var budget = (definition) => budgetSchema.parse(definition);
460
+ var budget = (definition) => {
461
+ const { key: _, cost, ...properties } = definition;
462
+ return budgetSchema.parse({
463
+ ...properties,
464
+ id: authoringKey(definition),
465
+ cost: typeof cost === "number" ? cost : { balance: ledgerKey(cost.ledger) }
466
+ });
467
+ };
327
468
  var keyed = (key) => ({
328
469
  kind: "keyed",
329
470
  key
330
471
  });
331
- var completed = (quest) => ({
332
- quest: typeof quest === "string" ? quest : authoringKey(quest)
333
- });
472
+ var progress = (definition) => {
473
+ const { distinct, ...policy } = definition;
474
+ return {
475
+ ...counterPolicySchema.parse({
476
+ kind: "progress",
477
+ window: { kind: "lifetime" },
478
+ ...policy
479
+ }),
480
+ distinct
481
+ };
482
+ };
334
483
  var untracked = () => ({ kind: "untracked" });
335
484
  var staffHandover = () => ({ kind: "staff-handover" });
485
+ var automaticFulfillment = () => ({ kind: "automatic" });
336
486
  function defineTieredReward(input) {
337
- const { key: _, id: __, ...properties } = input;
487
+ const { key: _, tiers, ...properties } = input;
338
488
  const key = authoringKey(input);
339
- const reward = tieredRewardDefinitionSchema.parse({ id: key, ...properties });
340
- return {
489
+ const reward = tieredRewardDefinitionSchema.parse({
490
+ id: key,
491
+ ...properties,
492
+ tiers: tiers.map(({ key: key2, ...tier }) => ({
493
+ ...tier,
494
+ id: authoringKey({ key: key2, ...tier })
495
+ }))
496
+ });
497
+ return resourceHandle("tiered-reward", {
341
498
  key: reward.id,
342
- /** @deprecated Authoring key alias. Use key. */
343
- id: reward.id,
344
499
  tier: (tier) => tierRewardSchema.parse({ kind: "tier", reward, tier })
345
- };
500
+ });
346
501
  }
347
502
  var once = () => ({ kind: "once" });
348
503
  function accept(options = {}) {
@@ -353,6 +508,11 @@ function review(options) {
353
508
  }
354
509
  var decision = {
355
510
  accept,
511
+ defer: (options) => ({ kind: "defer", ...options }),
512
+ progress: (options) => ({
513
+ kind: "progress",
514
+ ...options
515
+ }),
356
516
  reject: (options) => ({
357
517
  kind: "reject",
358
518
  ...options
@@ -386,221 +546,262 @@ var evaluators = {
386
546
  };
387
547
  }
388
548
  };
389
- function defineQuest(definition) {
390
- const key = authoringKey(definition);
391
- const contentTrigger = definition.trigger.kind === "automatic" && definition.trigger.content !== void 0;
392
- if (definition.performance && !contentTrigger) {
393
- throw new Error("Performance requires a content trigger");
394
- }
395
- const trackerTrigger = definition.trigger.kind === "automatic" && definition.trigger.tracker !== void 0;
396
- const contentCompletion = "perContent" in definition.completion;
397
- if (contentTrigger !== contentCompletion) {
398
- throw new Error(
399
- "Content triggers require perContent completion, and perContent requires a content trigger"
400
- );
401
- }
402
- const bareAutomatic = definition.trigger.kind === "automatic" && !definition.trigger.observation && !definition.trigger.content && !definition.trigger.tracker;
403
- if (bareAutomatic && !definition.requires?.length) {
404
- throw new Error(
405
- "A bare automatic() quest needs at least one requires entry, such as requires: [completed(quest)]. Give it an observation, content or tracker trigger, or a prerequisite."
406
- );
407
- }
408
- if (bareAutomatic && definition.completion.kind !== "once") {
409
- throw new Error(
410
- `A bare automatic() quest cannot use ${definition.completion.kind}() completion. Give it a tracker or observation trigger, or use once().`
549
+ function questBuilder(template) {
550
+ return function defineQuest2(definition) {
551
+ const key = authoringKey(definition);
552
+ const integrations = [
553
+ ...new Set(
554
+ (definition.integrations ?? []).map(
555
+ (module) => evaluatorIntegrationProviderSchema.parse(module.provider)
556
+ )
557
+ )
558
+ ];
559
+ const contentTrigger = definition.trigger.kind === "automatic" && definition.trigger.tracker?.content === true;
560
+ const trackerTrigger2 = definition.trigger.kind === "automatic" && definition.trigger.tracker !== void 0;
561
+ const completion = definition.completion;
562
+ if (contentTrigger && completion !== void 0) {
563
+ throw new Error(
564
+ "A content quest completes once per content item: remove its completion"
565
+ );
566
+ }
567
+ if (!contentTrigger && completion === void 0) {
568
+ throw new Error(
569
+ "A quest needs a completion, such as once(), keyed(...), streak(...) or progress(...)"
570
+ );
571
+ }
572
+ validateQuestShape(
573
+ {
574
+ trigger: definition.trigger,
575
+ completion: completion ?? { kind: "keyed" },
576
+ requires: definition.requires ?? [],
577
+ performance: definition.performance ? contentPerformancePolicySchema.parse({
578
+ metrics: definition.performance.metrics,
579
+ maxAgeMs: definition.performance.maxAgeMs
580
+ }) : void 0
581
+ },
582
+ { template }
411
583
  );
412
- }
413
- if (definition.trigger.kind === "manual" && definition.trigger.input !== "none" && !definition.evaluate) {
414
- throw new Error("Evidence quests require an evaluator");
415
- }
416
- const fields = Object.fromEntries(
417
- Object.entries(definition.settings.shape).map(([key2, schema]) => {
418
- const field = settingFieldSchema.safeParse(schema.meta());
419
- if (!field.success) {
584
+ if (definition.performance) {
585
+ const declared = triggerContentMetrics.get(definition.trigger) ?? [];
586
+ const unknown = definition.performance.metrics.filter(
587
+ (metric) => !declared.includes(metric)
588
+ );
589
+ if (unknown.length) {
420
590
  throw new Error(
421
- `Setting "${key2}" is not a quest setting: build it with a settings builder, such as settings.integer({ label, default, min, max })`
591
+ `The tracker's source does not measure ${unknown.join(", ")}`
422
592
  );
423
593
  }
424
- return [key2, field.data];
425
- })
426
- );
427
- for (const setting2 of whereSettings(
428
- definition.trigger.kind === "automatic" ? definition.trigger.tracker?.where : void 0
429
- )) {
430
- if (fields[setting2]?.kind !== "integer") {
431
- throw new Error(
432
- `The tracker filter compares with setting "${setting2}", which is not an integer setting`
433
- );
434
594
  }
435
- }
436
- function describe(input) {
437
- const settings2 = definition.settings.parse(input);
438
- const values = parseSettings(fields, settings2);
439
- const rewards = rewardBundleSchema.parse(
440
- typeof definition.rewards === "function" ? definition.rewards({ settings: settings2 }) : definition.rewards
595
+ if (definition.trigger.kind === "manual" && definition.trigger.input !== "none" && !definition.evaluate) {
596
+ throw new Error("Evidence quests require an evaluator");
597
+ }
598
+ const fields = Object.fromEntries(
599
+ Object.entries(definition.settings.shape).map(([key2, schema]) => {
600
+ const field = settingFieldSchema.safeParse(schema.meta());
601
+ if (!field.success) {
602
+ throw new Error(
603
+ `Setting "${key2}" is not a quest setting: build it with a settings builder, such as settings.integer({ label, default, min, max })`
604
+ );
605
+ }
606
+ return [key2, field.data];
607
+ })
441
608
  );
442
- return questDescriptionSchema.parse({
443
- quest: key,
444
- runtimeVersion: 2,
445
- title: typeof definition.title === "function" ? definition.title({ settings: settings2 }) : definition.title,
446
- presentation: definition.presentation?.({ settings: settings2 }) ?? {},
447
- fields,
448
- values,
449
- rewards,
450
- humanReview: definition.review?.({ settings: settings2 })?.enabled ?? true,
451
- trigger: definition.trigger,
452
- ...definition.performance ? {
453
- performance: contentPerformancePolicySchema.parse({
454
- metrics: definition.performance.metrics,
455
- maxAgeMs: definition.performance.maxAgeMs
456
- })
457
- } : {},
458
- integrations: [
459
- .../* @__PURE__ */ new Set([
460
- ...definition.integrations ?? [],
461
- ...rewards.flatMap(
462
- (reward) => reward.kind === "integration" ? [reward.provider] : []
463
- ),
464
- ...Object.values(fields).flatMap(
465
- (field) => field.kind === "resource" && field.source.provider !== "domino" ? [field.source.provider] : []
466
- )
467
- ])
468
- ],
469
- completion: definition.completion.kind === "streak" ? definition.completion : { kind: definition.completion.kind },
470
- visibility: definition.visibility ?? "visible",
471
- // Only when set: a release without the key is hidden and stays readable by older clients.
472
- ...definition.profile ? { profile: definition.profile } : {},
473
- budgets: definition.budgets ?? [],
474
- requires: definition.requires ?? []
475
- });
476
- }
477
- return {
478
- key,
479
- /** @deprecated Authoring key alias. Use key. */
480
- id: key,
481
- describe,
482
- async evaluate(ctx) {
483
- const settings2 = definition.settings.parse(ctx.settings);
484
- const pinned = trackerTrigger ? trackerTriggerContextSchema.parse(ctx.tracker) : null;
485
- const schema = triggerDataSchemas.get(definition.trigger);
486
- const dataIssue = pinned && schema ? trackerDataIssue(schema, pinned.record.data) : null;
487
- if (dataIssue) {
488
- return decisionSchema.parse({
489
- kind: "reject",
490
- reason: `The record's data does not match the data schema this quest was built with: ${dataIssue.path} ${dataIssue.reason}`
491
- });
609
+ for (const setting2 of whereSettings(
610
+ definition.trigger.kind === "automatic" ? definition.trigger.tracker?.where : void 0
611
+ )) {
612
+ if (fields[setting2]?.kind !== "integer") {
613
+ throw new Error(
614
+ `The tracker filter compares with setting "${setting2}", which is not an integer setting`
615
+ );
616
+ }
617
+ }
618
+ function describe(input) {
619
+ const settings2 = definition.settings.parse(input);
620
+ const values = parseSettings(fields, settings2);
621
+ const rewards = rewardBundleSchema.parse(
622
+ typeof definition.rewards === "function" ? definition.rewards({ settings: settings2 }) : definition.rewards
623
+ );
624
+ const required = [
625
+ ...rewards.flatMap(
626
+ (reward) => reward.kind === "integration" ? [reward.provider] : []
627
+ ),
628
+ ...Object.values(fields).flatMap(
629
+ (field) => field.kind === "resource" && field.source.provider !== "domino" ? [field.source.provider] : []
630
+ )
631
+ ];
632
+ for (const provider of required) {
633
+ if (!integrations.includes(provider)) {
634
+ throw new Error(`Declare the ${provider} integration on this quest`);
635
+ }
492
636
  }
493
- const tracker = pinned;
494
- if (ctx.performance) {
495
- if (!definition.performance) {
496
- throw new Error("Quest has no performance evaluator");
637
+ return questDescriptionSchema.parse({
638
+ quest: key,
639
+ evaluatorProtocol,
640
+ title: typeof definition.title === "function" ? definition.title({ settings: settings2 }) : definition.title,
641
+ presentation: definition.presentation?.({ settings: settings2 }) ?? {},
642
+ fields,
643
+ values,
644
+ rewards,
645
+ humanReview: definition.review?.({ settings: settings2 })?.enabled ?? true,
646
+ trigger: definition.trigger,
647
+ ...definition.performance ? {
648
+ performance: contentPerformancePolicySchema.parse({
649
+ metrics: definition.performance.metrics,
650
+ maxAgeMs: definition.performance.maxAgeMs
651
+ })
652
+ } : {},
653
+ integrations,
654
+ // A content quest's completion is keyed by its content item, with a key Relay owns.
655
+ completion: !completion ? { kind: "keyed" } : completion.kind === "progress" ? {
656
+ kind: completion.kind,
657
+ goal: completion.goal,
658
+ window: completion.window,
659
+ repeat: completion.repeat
660
+ } : completion.kind === "streak" ? completion : { kind: completion.kind },
661
+ visibility: definition.visibility ?? "visible",
662
+ profile: definition.profile ?? { visible: false },
663
+ budgets: definition.budgets ?? [],
664
+ requires: definition.requires ?? [],
665
+ ...definition.campaign ? { campaign: definition.campaign } : {},
666
+ ...definition.availability ? { availability: definition.availability } : {}
667
+ });
668
+ }
669
+ return resourceHandle("quest", {
670
+ protocol: evaluatorProtocol,
671
+ key,
672
+ describe,
673
+ async evaluate(ctx) {
674
+ const settings2 = definition.settings.parse(ctx.settings);
675
+ const pinned = trackerTrigger2 ? trackerTriggerContextSchema.parse(ctx.tracker) : null;
676
+ const schema = triggerDataSchemas.get(definition.trigger);
677
+ const dataIssue = pinned && schema ? trackerDataIssue(schema, pinned.record.data) : null;
678
+ if (dataIssue) {
679
+ return decisionSchema.parse({
680
+ kind: "reject",
681
+ reason: `The record's data does not match the data schema this quest was built with: ${dataIssue.path} ${dataIssue.reason}`
682
+ });
683
+ }
684
+ const tracker = pinned;
685
+ if (ctx.performance) {
686
+ if (!definition.performance) {
687
+ throw new Error("Quest has no performance evaluator");
688
+ }
689
+ const sample = contentPerformanceSnapshotSchema.parse(ctx.performance);
690
+ const metrics = z3.record(
691
+ z3.enum(definition.performance.metrics),
692
+ z3.number().int().safe().nonnegative()
693
+ ).parse(sample.metrics);
694
+ const result2 = await definition.performance.evaluate({
695
+ settings: settings2,
696
+ member: ctx.member,
697
+ tracker,
698
+ metrics,
699
+ observedAt: sample.observedAt
700
+ });
701
+ if (!result2) {
702
+ throw new Error("Performance evaluator returned no decision");
703
+ }
704
+ if (result2.rewards !== void 0) {
705
+ throw new Error(
706
+ "A performance evaluator cannot select rewards. It gates the rewards content evaluation selected: return accept, review or reject with a reason only."
707
+ );
708
+ }
709
+ return decisionSchema.parse(
710
+ result2.kind === "accept" ? { kind: "accept", rewards: sample.rewards } : result2.kind === "review" && ctx.humanReview ? {
711
+ kind: "review",
712
+ reason: result2.reason,
713
+ rewards: sample.rewards
714
+ } : {
715
+ kind: "reject",
716
+ reason: result2.reason,
717
+ ...result2.kind === "review" ? { requestedReview: true } : {}
718
+ }
719
+ );
497
720
  }
498
- const sample = contentPerformanceSnapshotSchema.parse(ctx.performance);
499
- const metrics = z3.record(
500
- z3.enum(definition.performance.metrics),
501
- z3.number().int().safe().nonnegative()
502
- ).parse(sample.metrics);
503
- const result2 = await definition.performance.evaluate({
721
+ const result = definition.evaluate ? await definition.evaluate({
504
722
  settings: settings2,
505
723
  member: ctx.member,
506
- content: admittedContentSchema.parse(ctx.content),
507
- metrics,
508
- observedAt: sample.observedAt
509
- });
510
- if (!result2) {
511
- throw new Error("Performance evaluator returned no decision");
724
+ input: ctx.input,
725
+ tracker,
726
+ eligibleAt: ctx.eligibleAt,
727
+ calendar: ctx.calendar,
728
+ now: ctx.now,
729
+ review: ctx.review,
730
+ trackers: schemaCheckedReader(ctx.trackers),
731
+ wallet: ctx.wallet,
732
+ images: ctx.images,
733
+ // The declared module providers select these keys; unrelated host readers are omitted.
734
+ ...Object.fromEntries(
735
+ integrations.map((provider) => [provider, ctx[provider]])
736
+ )
737
+ }) : decision.accept();
738
+ if (!result) {
739
+ throw new Error("Evaluator returned no decision");
512
740
  }
513
- if (result2.rewards !== void 0) {
514
- throw new Error(
515
- "A performance evaluator cannot select rewards. It gates the rewards content evaluation selected: return accept, review or reject with a reason only."
516
- );
741
+ if (result.kind === "review" && !ctx.humanReview) {
742
+ return decisionSchema.parse({
743
+ kind: "reject",
744
+ reason: result.reason,
745
+ requestedReview: true
746
+ });
517
747
  }
518
748
  return decisionSchema.parse(
519
- result2.kind === "accept" ? {
520
- kind: "accept",
521
- rewards: sample.rewards,
522
- completionKey: admittedContentSchema.parse(ctx.content).completionKey
523
- } : result2.kind === "review" && ctx.humanReview ? {
524
- kind: "review",
525
- reason: result2.reason,
526
- rewards: sample.rewards
527
- } : {
528
- kind: "reject",
529
- reason: result2.reason,
530
- ...result2.kind === "review" ? { requestedReview: true } : {}
531
- }
749
+ result.kind === "accept" || result.kind === "review" ? {
750
+ ...result,
751
+ rewards: result.rewards ?? ctx.rewards,
752
+ ...completion?.kind === "keyed" || completion?.kind === "progress" ? {
753
+ completionKey: completion.kind === "progress" ? completion.distinct({ ...ctx, tracker: pinned }) : result.completionKey ?? completion.key({ ...ctx, tracker: pinned })
754
+ } : {}
755
+ } : result
532
756
  );
533
757
  }
534
- const result = definition.evaluate ? await definition.evaluate({
535
- settings: settings2,
536
- member: ctx.member,
537
- input: ctx.input,
538
- content: contentTrigger ? admittedContentSchema.parse(ctx.content) : null,
539
- observation: ctx.observation ?? null,
540
- tracker,
541
- eligibleAt: ctx.eligibleAt,
542
- now: ctx.now,
543
- review: ctx.review,
544
- trackers: schemaCheckedReader(ctx.trackers),
545
- wallet: ctx.wallet,
546
- x: ctx.x,
547
- images: ctx.images,
548
- discord: ctx.discord
549
- }) : decision.accept();
550
- if (!result) {
551
- throw new Error("Evaluator returned no decision");
552
- }
553
- if (result.kind === "review" && !ctx.humanReview) {
554
- return decisionSchema.parse({
555
- kind: "reject",
556
- reason: result.reason,
557
- requestedReview: true
558
- });
559
- }
560
- return decisionSchema.parse(
561
- result.kind === "accept" || result.kind === "review" ? {
562
- ...result,
563
- rewards: result.rewards ?? ctx.rewards,
564
- ...definition.completion.kind === "keyed" ? {
565
- completionKey: definition.completion.key({
566
- ...ctx,
567
- observation: ctx.observation ?? null,
568
- tracker: pinned
569
- })
570
- } : {}
571
- } : result
572
- );
573
- }
758
+ });
574
759
  };
575
760
  }
576
- var defineQuestType = defineQuest;
761
+ var defineQuest = questBuilder(false);
762
+ var defineQuestType = questBuilder(true);
577
763
  export {
764
+ CapabilityError,
578
765
  automatic,
766
+ automaticFulfillment,
767
+ balanceAtLeast,
579
768
  budget,
769
+ calendar,
770
+ capabilityErrorCodes,
580
771
  completed,
581
772
  content,
773
+ cosmosDenomSchema,
774
+ cosmosNetworkSchema,
775
+ count,
582
776
  customSource,
583
777
  decision,
778
+ defineCampaign,
584
779
  defineEngagementReward,
780
+ defineGroup,
585
781
  defineLeaderboard,
782
+ defineLedger,
783
+ defineOffer,
586
784
  defineQuest,
587
785
  defineQuestType,
588
786
  defineReferral,
589
787
  defineTieredReward,
590
788
  defineTracker,
591
789
  discord,
592
- discordRole,
790
+ earned,
593
791
  evaluators,
594
792
  evidence,
595
793
  formatUnits,
794
+ inGroup,
596
795
  isEvmChainId,
597
796
  keyed,
598
797
  manual,
599
798
  once,
600
799
  parseUnits,
601
- perContent,
602
800
  pointLedgers,
603
801
  points,
802
+ progress,
803
+ questCompleted,
804
+ rolling,
604
805
  settings,
605
806
  staffHandover,
606
807
  streak,