@curviate/sdk 0.43.0 → 0.44.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/CHANGELOG.md +67 -1
- package/README.md +24 -10
- package/dist/index.d.ts +2058 -538
- package/dist/index.js +611 -155
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -281,10 +281,17 @@ var ERROR_CODES = [
|
|
|
281
281
|
"IMPORT_QUEUE_FULL",
|
|
282
282
|
// Sequences (`Sequence`, `validateSequence`). `SEQUENCE_INVALID` (422 from
|
|
283
283
|
// the API, or thrown offline by `toDocument()` / `Sequence.fromDocument`):
|
|
284
|
-
// the Sequence document breaks one or more rules. `errors` lists every one
|
|
285
|
-
// `
|
|
286
|
-
// saved. user_fixable, never
|
|
284
|
+
// the Sequence document breaks one or more rules. `errors` lists every one
|
|
285
|
+
// (`path`, `rule`, `code`, `pointer`, `hint`, `docs_url`) in step order, so
|
|
286
|
+
// one fix round covers them all. Nothing is saved. user_fixable, never
|
|
287
|
+
// retryable as sent.
|
|
288
|
+
//
|
|
289
|
+
// `SEQUENCE_CONFLICT` (409): `motions.update` sent `if_sequence_revision`
|
|
290
|
+
// and someone saved the Sequence since; nothing is saved. `current` carries
|
|
291
|
+
// the stored document and its `sequence_revision`: merge, or send yours again
|
|
292
|
+
// with that revision to replace it.
|
|
287
293
|
"SEQUENCE_INVALID",
|
|
294
|
+
"SEQUENCE_CONFLICT",
|
|
288
295
|
// Motions and Enrollments (`motions`, `enrollments`).
|
|
289
296
|
//
|
|
290
297
|
// - `MOTION_NOT_STARTABLE` (422): `start` or `resume` found gaps; `errors`
|
|
@@ -294,15 +301,33 @@ var ERROR_CODES = [
|
|
|
294
301
|
// Motion's status; the message names it and the operations it allows.
|
|
295
302
|
// Only a draft can be deleted: archive instead.
|
|
296
303
|
// - `ENROLLMENT_NOT_ACTIVE` (409): the Enrollment's status does not take
|
|
297
|
-
// this operation (stop: pending, active or paused;
|
|
298
|
-
// paused; resume: active, paused or finished, not when the
|
|
299
|
-
// Contact is gone).
|
|
304
|
+
// this operation (stop and pause: pending, active or paused; events:
|
|
305
|
+
// active or paused; resume: active, paused or finished, not when the
|
|
306
|
+
// account or Contact is gone).
|
|
307
|
+
// - `STEP_IN_FLIGHT` (409): a Step of the Enrollment is being sent right
|
|
308
|
+
// now; pause and reassign wait until it settles. Retry in a minute.
|
|
309
|
+
// - `NOTHING_TO_RETRY` (409, or thrown by `enrollments.retry`): Retry found no
|
|
310
|
+
// Step whose latest outcome is `failed`; nothing was resumed or sent.
|
|
311
|
+
// - `ACCOUNT_ALREADY_ENGAGED` (409): reassign refused: the Enrollment's
|
|
312
|
+
// account already did something the Contact can see.
|
|
313
|
+
// - `ALREADY_ENROLLED_ON_ACCOUNT` (409): reassign refused: the target
|
|
314
|
+
// account already holds this Contact in the Motion, or is its own account.
|
|
315
|
+
// - `KNOWN_CHAT` (409): reassign refused: the Motion excludes known chats
|
|
316
|
+
// and the Contact already has a chat with your team.
|
|
300
317
|
// - `CONTACT_NOT_ENROLLABLE` (422): adding a Contact to a Motion from a chat
|
|
301
318
|
// was refused (already enrolled, excluded, in another Motion, filtered).
|
|
319
|
+
// - `CHAT_NOT_ONE_TO_ONE` (422): a Unibox Contact action on a group chat,
|
|
320
|
+
// or a chat whose counterpart is not a person.
|
|
302
321
|
"MOTION_NOT_STARTABLE",
|
|
303
322
|
"MOTION_STATUS_CONFLICT",
|
|
304
323
|
"ENROLLMENT_NOT_ACTIVE",
|
|
324
|
+
"STEP_IN_FLIGHT",
|
|
325
|
+
"NOTHING_TO_RETRY",
|
|
326
|
+
"ACCOUNT_ALREADY_ENGAGED",
|
|
327
|
+
"ALREADY_ENROLLED_ON_ACCOUNT",
|
|
328
|
+
"KNOWN_CHAT",
|
|
305
329
|
"CONTACT_NOT_ENROLLABLE",
|
|
330
|
+
"CHAT_NOT_ONE_TO_ONE",
|
|
306
331
|
// Client-side, never sent by the API. `CLIENT_TIMEOUT`: no response within
|
|
307
332
|
// the client's `timeout` (default 30 s, per attempt). A read is retried
|
|
308
333
|
// first; a write is not, and may still have reached the API, so read back
|
|
@@ -330,6 +355,7 @@ var CurviateError = class _CurviateError extends Error {
|
|
|
330
355
|
this.blocked = init.blocked;
|
|
331
356
|
this.connectionLocation = init.connectionLocation;
|
|
332
357
|
this.errors = init.errors;
|
|
358
|
+
this.current = init.current;
|
|
333
359
|
this.feature = init.feature;
|
|
334
360
|
this.requiredTier = init.requiredTier;
|
|
335
361
|
this.currentTier = init.currentTier;
|
|
@@ -360,6 +386,7 @@ var CurviateError = class _CurviateError extends Error {
|
|
|
360
386
|
if (this.blocked !== void 0) json.blocked = this.blocked;
|
|
361
387
|
if (this.connectionLocation !== void 0) json.connectionLocation = this.connectionLocation;
|
|
362
388
|
if (this.errors !== void 0) json.errors = this.errors;
|
|
389
|
+
if (this.current !== void 0) json.current = this.current;
|
|
363
390
|
if (this.feature !== void 0) json.feature = this.feature;
|
|
364
391
|
if (this.requiredTier !== void 0) json.requiredTier = this.requiredTier;
|
|
365
392
|
if (this.currentTier !== void 0) json.currentTier = this.currentTier;
|
|
@@ -403,11 +430,19 @@ function resolveConfig(config) {
|
|
|
403
430
|
}
|
|
404
431
|
|
|
405
432
|
// src/transport.ts
|
|
433
|
+
var DETAIL_KEYS = ["code", "message", "pointer", "hint", "docs_url"];
|
|
406
434
|
function toErrorDetails(errors) {
|
|
407
435
|
if (!Array.isArray(errors)) return void 0;
|
|
408
436
|
return errors.flatMap((e) => {
|
|
409
|
-
const
|
|
410
|
-
|
|
437
|
+
const entry2 = typeof e === "object" && e !== null ? e : {};
|
|
438
|
+
const { path, rule } = entry2;
|
|
439
|
+
if (typeof path !== "string" || typeof rule !== "string") return [];
|
|
440
|
+
const detail = { path, rule };
|
|
441
|
+
for (const k of DETAIL_KEYS) {
|
|
442
|
+
const v = entry2[k];
|
|
443
|
+
if (typeof v === "string") detail[k] = v;
|
|
444
|
+
}
|
|
445
|
+
return [detail];
|
|
411
446
|
});
|
|
412
447
|
}
|
|
413
448
|
var BASE_DELAY_MS = 500;
|
|
@@ -497,6 +532,7 @@ async function errorFromResponse(res) {
|
|
|
497
532
|
...typeof env?.blocked === "boolean" ? { blocked: env.blocked } : {},
|
|
498
533
|
...env?.connection_location !== null && typeof env?.connection_location === "object" ? { connectionLocation: env.connection_location } : {},
|
|
499
534
|
...Array.isArray(env?.errors) ? { errors: toErrorDetails(env.errors) } : {},
|
|
535
|
+
...env?.code === "SEQUENCE_CONFLICT" && env.current !== null && typeof env.current === "object" && !Array.isArray(env.current) ? { current: env.current } : {},
|
|
500
536
|
// `TIER_REQUIRED` details. `required_tier` / `current_tier` are null-BEARING
|
|
501
537
|
// (no Tier has it / the team has none), so null survives; a non-string
|
|
502
538
|
// value (neither a name nor null) is dropped rather than mistyped.
|
|
@@ -2886,7 +2922,7 @@ var TagsResource = class {
|
|
|
2886
2922
|
|
|
2887
2923
|
// src/sequence/order.ts
|
|
2888
2924
|
var DOC = ["$schema", "on_reply", "steps"];
|
|
2889
|
-
var STEP = ["id", "do", "wait", "until", "if", "emit", "tag", "timeout", "variants", "with", "then", "else", "next", "note"];
|
|
2925
|
+
var STEP = ["id", "do", "wait", "until", "if", "emit", "tag", "timeout", "variants", "with", "event", "then", "else", "next", "note"];
|
|
2890
2926
|
var WITH = ["post", "newer_than", "reaction", "subject", "text", "note", "note_fallback", "fallback", "withdraw_after"];
|
|
2891
2927
|
var VARIANT = ["id", "subject", "text"];
|
|
2892
2928
|
var FALLBACK = ["subject", "text"];
|
|
@@ -2921,7 +2957,7 @@ var schema_v1_default = {
|
|
|
2921
2957
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2922
2958
|
$id: "https://docs.curviate.com/schemas/sequence/v1.json",
|
|
2923
2959
|
title: "Sequence document v1",
|
|
2924
|
-
description:
|
|
2960
|
+
description: 'A Motion\'s Sequence: a flat steps array run from the first step, ending explicitly in "end". Served documents use this key order: $schema, on_reply, steps; in a step id, the kind key, timeout, variants, with, event, then, else, next, note; in with post, newer_than, reaction, subject, text, note, note_fallback, fallback, withdraw_after; in a variant id, subject, text. A text holds at most 50 variables. Variables: `{{first_name}}` (any standard Contact field), `{{my_first_name}}`, `{{my_last_name}}`, `{{my_full_name}}` (the sending Account), any other `{{<key>}}` is the custom field `contact.extra.<key>`; the long forms `{{contact.<field>}}`, `{{contact.extra.<key>}}` and `{{account.<name>}}` stay valid. Delays are about their duration: every wait and until timeout gets a small random spread that only ever adds time.',
|
|
2925
2961
|
type: "object",
|
|
2926
2962
|
additionalProperties: false,
|
|
2927
2963
|
required: [
|
|
@@ -2966,6 +3002,11 @@ var schema_v1_default = {
|
|
|
2966
3002
|
additionalProperties: false,
|
|
2967
3003
|
properties: {}
|
|
2968
3004
|
},
|
|
3005
|
+
event: {
|
|
3006
|
+
type: "string",
|
|
3007
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3008
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3009
|
+
},
|
|
2969
3010
|
next: {
|
|
2970
3011
|
type: "string",
|
|
2971
3012
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3005,6 +3046,11 @@ var schema_v1_default = {
|
|
|
3005
3046
|
additionalProperties: false,
|
|
3006
3047
|
properties: {}
|
|
3007
3048
|
},
|
|
3049
|
+
event: {
|
|
3050
|
+
type: "string",
|
|
3051
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3052
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3053
|
+
},
|
|
3008
3054
|
next: {
|
|
3009
3055
|
type: "string",
|
|
3010
3056
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3044,6 +3090,11 @@ var schema_v1_default = {
|
|
|
3044
3090
|
additionalProperties: false,
|
|
3045
3091
|
properties: {}
|
|
3046
3092
|
},
|
|
3093
|
+
event: {
|
|
3094
|
+
type: "string",
|
|
3095
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3096
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3097
|
+
},
|
|
3047
3098
|
next: {
|
|
3048
3099
|
type: "string",
|
|
3049
3100
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3106,7 +3157,7 @@ var schema_v1_default = {
|
|
|
3106
3157
|
type: "array",
|
|
3107
3158
|
minItems: 2,
|
|
3108
3159
|
maxItems: 5,
|
|
3109
|
-
description: "A/B variants, split evenly and sticky per Enrollment. Exclusive with the text in with.",
|
|
3160
|
+
description: "A/B variants: exactly 2, split evenly and sticky per Enrollment. Exclusive with the text in with. More than 2 (up to 5) is deprecated: accepted with warning W13 until 2027-01-19.",
|
|
3110
3161
|
items: {
|
|
3111
3162
|
type: "object",
|
|
3112
3163
|
additionalProperties: false,
|
|
@@ -3127,6 +3178,11 @@ var schema_v1_default = {
|
|
|
3127
3178
|
}
|
|
3128
3179
|
}
|
|
3129
3180
|
},
|
|
3181
|
+
event: {
|
|
3182
|
+
type: "string",
|
|
3183
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3184
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3185
|
+
},
|
|
3130
3186
|
next: {
|
|
3131
3187
|
type: "string",
|
|
3132
3188
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3166,6 +3222,11 @@ var schema_v1_default = {
|
|
|
3166
3222
|
additionalProperties: false,
|
|
3167
3223
|
properties: {}
|
|
3168
3224
|
},
|
|
3225
|
+
event: {
|
|
3226
|
+
type: "string",
|
|
3227
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3228
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3229
|
+
},
|
|
3169
3230
|
next: {
|
|
3170
3231
|
type: "string",
|
|
3171
3232
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3220,7 +3281,7 @@ var schema_v1_default = {
|
|
|
3220
3281
|
type: "array",
|
|
3221
3282
|
minItems: 2,
|
|
3222
3283
|
maxItems: 5,
|
|
3223
|
-
description: "A/B variants, split evenly and sticky per Enrollment. Exclusive with the text in with.",
|
|
3284
|
+
description: "A/B variants: exactly 2, split evenly and sticky per Enrollment. Exclusive with the text in with. More than 2 (up to 5) is deprecated: accepted with warning W13 until 2027-01-19.",
|
|
3224
3285
|
items: {
|
|
3225
3286
|
type: "object",
|
|
3226
3287
|
additionalProperties: false,
|
|
@@ -3241,6 +3302,11 @@ var schema_v1_default = {
|
|
|
3241
3302
|
}
|
|
3242
3303
|
}
|
|
3243
3304
|
},
|
|
3305
|
+
event: {
|
|
3306
|
+
type: "string",
|
|
3307
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3308
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3309
|
+
},
|
|
3244
3310
|
next: {
|
|
3245
3311
|
type: "string",
|
|
3246
3312
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3314,7 +3380,7 @@ var schema_v1_default = {
|
|
|
3314
3380
|
type: "array",
|
|
3315
3381
|
minItems: 2,
|
|
3316
3382
|
maxItems: 5,
|
|
3317
|
-
description: "A/B variants, split evenly and sticky per Enrollment. Exclusive with the text in with.",
|
|
3383
|
+
description: "A/B variants: exactly 2, split evenly and sticky per Enrollment. Exclusive with the text in with. More than 2 (up to 5) is deprecated: accepted with warning W13 until 2027-01-19.",
|
|
3318
3384
|
items: {
|
|
3319
3385
|
type: "object",
|
|
3320
3386
|
additionalProperties: false,
|
|
@@ -3340,6 +3406,11 @@ var schema_v1_default = {
|
|
|
3340
3406
|
}
|
|
3341
3407
|
}
|
|
3342
3408
|
},
|
|
3409
|
+
event: {
|
|
3410
|
+
type: "string",
|
|
3411
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3412
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3413
|
+
},
|
|
3343
3414
|
next: {
|
|
3344
3415
|
type: "string",
|
|
3345
3416
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3404,6 +3475,11 @@ var schema_v1_default = {
|
|
|
3404
3475
|
}
|
|
3405
3476
|
}
|
|
3406
3477
|
},
|
|
3478
|
+
event: {
|
|
3479
|
+
type: "string",
|
|
3480
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3481
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3482
|
+
},
|
|
3407
3483
|
next: {
|
|
3408
3484
|
type: "string",
|
|
3409
3485
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3470,7 +3546,7 @@ var schema_v1_default = {
|
|
|
3470
3546
|
type: "array",
|
|
3471
3547
|
minItems: 2,
|
|
3472
3548
|
maxItems: 5,
|
|
3473
|
-
description: "A/B variants, split evenly and sticky per Enrollment. Exclusive with the text in with.",
|
|
3549
|
+
description: "A/B variants: exactly 2, split evenly and sticky per Enrollment. Exclusive with the text in with. More than 2 (up to 5) is deprecated: accepted with warning W13 until 2027-01-19.",
|
|
3474
3550
|
items: {
|
|
3475
3551
|
type: "object",
|
|
3476
3552
|
additionalProperties: false,
|
|
@@ -3491,6 +3567,11 @@ var schema_v1_default = {
|
|
|
3491
3567
|
}
|
|
3492
3568
|
}
|
|
3493
3569
|
},
|
|
3570
|
+
event: {
|
|
3571
|
+
type: "string",
|
|
3572
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3573
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3574
|
+
},
|
|
3494
3575
|
next: {
|
|
3495
3576
|
type: "string",
|
|
3496
3577
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3530,6 +3611,11 @@ var schema_v1_default = {
|
|
|
3530
3611
|
additionalProperties: false,
|
|
3531
3612
|
properties: {}
|
|
3532
3613
|
},
|
|
3614
|
+
event: {
|
|
3615
|
+
type: "string",
|
|
3616
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
3617
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
3618
|
+
},
|
|
3533
3619
|
next: {
|
|
3534
3620
|
type: "string",
|
|
3535
3621
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -3563,7 +3649,7 @@ var schema_v1_default = {
|
|
|
3563
3649
|
wait: {
|
|
3564
3650
|
type: "string",
|
|
3565
3651
|
pattern: "^[1-9][0-9]*(m|h|d)$",
|
|
3566
|
-
description: "Calendar time to wait (1m to 365d)."
|
|
3652
|
+
description: "Calendar time to wait, about this long (1m to 365d; a small random spread is added, never subtracted)."
|
|
3567
3653
|
},
|
|
3568
3654
|
next: {
|
|
3569
3655
|
type: "string",
|
|
@@ -3604,15 +3690,15 @@ var schema_v1_default = {
|
|
|
3604
3690
|
},
|
|
3605
3691
|
{
|
|
3606
3692
|
type: "string",
|
|
3607
|
-
pattern: "^signal\\.[a-z][a-z0-9_]{0,
|
|
3693
|
+
pattern: "^(event|signal)\\.[a-z][a-z0-9_.]{0,63}$"
|
|
3608
3694
|
}
|
|
3609
3695
|
],
|
|
3610
|
-
description: "`invite.accepted`, `message.replied`, `message.read`, or `signal.<name
|
|
3696
|
+
description: "`invite.accepted`, `message.replied`, `message.read`, or `event.<name>` (a custom event an agent sends). `signal.<name>` is the deprecated spelling of `event.<name>`: accepted with warning W13 until 2027-01-19."
|
|
3611
3697
|
},
|
|
3612
3698
|
timeout: {
|
|
3613
3699
|
type: "string",
|
|
3614
3700
|
pattern: "^[1-9][0-9]*(m|h|d)$",
|
|
3615
|
-
description: "Give up waiting after this long (1h to 180d)."
|
|
3701
|
+
description: "Give up waiting after about this long (1h to 180d; a small random spread is added, never subtracted)."
|
|
3616
3702
|
},
|
|
3617
3703
|
then: {
|
|
3618
3704
|
type: "string",
|
|
@@ -4257,6 +4343,19 @@ var schema_v1_default = {
|
|
|
4257
4343
|
}
|
|
4258
4344
|
}
|
|
4259
4345
|
},
|
|
4346
|
+
{
|
|
4347
|
+
type: "object",
|
|
4348
|
+
additionalProperties: false,
|
|
4349
|
+
required: [
|
|
4350
|
+
"message.seen"
|
|
4351
|
+
],
|
|
4352
|
+
properties: {
|
|
4353
|
+
"message.seen": {
|
|
4354
|
+
type: "boolean",
|
|
4355
|
+
description: "if only: the Enrollment's last message.send or inmail.send was seen (a read receipt, or a reply after it). False before any message."
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
},
|
|
4260
4359
|
{
|
|
4261
4360
|
type: "object",
|
|
4262
4361
|
additionalProperties: false,
|
|
@@ -4874,6 +4973,19 @@ var schema_v1_default = {
|
|
|
4874
4973
|
type: "boolean"
|
|
4875
4974
|
}
|
|
4876
4975
|
}
|
|
4976
|
+
},
|
|
4977
|
+
{
|
|
4978
|
+
type: "object",
|
|
4979
|
+
additionalProperties: false,
|
|
4980
|
+
required: [
|
|
4981
|
+
"message.seen"
|
|
4982
|
+
],
|
|
4983
|
+
properties: {
|
|
4984
|
+
"message.seen": {
|
|
4985
|
+
type: "boolean",
|
|
4986
|
+
description: "if only: the Enrollment's last message.send or inmail.send was seen (a read receipt, or a reply after it). False before any message."
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4877
4989
|
}
|
|
4878
4990
|
]
|
|
4879
4991
|
}
|
|
@@ -4914,7 +5026,7 @@ var schema_v1_default = {
|
|
|
4914
5026
|
emit: {
|
|
4915
5027
|
type: "string",
|
|
4916
5028
|
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
4917
|
-
description: "
|
|
5029
|
+
description: "Legacy: a custom event name. New documents put event on the step that matters."
|
|
4918
5030
|
},
|
|
4919
5031
|
next: {
|
|
4920
5032
|
type: "string",
|
|
@@ -4948,6 +5060,11 @@ var schema_v1_default = {
|
|
|
4948
5060
|
pattern: "^[^,;]*[^,;\\s][^,;]*$",
|
|
4949
5061
|
description: "Tag added to the Contact."
|
|
4950
5062
|
},
|
|
5063
|
+
event: {
|
|
5064
|
+
type: "string",
|
|
5065
|
+
pattern: "^[a-z][a-z0-9_.]{0,63}$",
|
|
5066
|
+
description: "Custom event sent once each time this step runs (not when skipped or failed), for example invite_sent."
|
|
5067
|
+
},
|
|
4951
5068
|
next: {
|
|
4952
5069
|
type: "string",
|
|
4953
5070
|
pattern: "^(end|[a-z][a-z0-9_]{0,39})$",
|
|
@@ -4982,10 +5099,10 @@ var END = "end";
|
|
|
4982
5099
|
var H = 60;
|
|
4983
5100
|
var D = 24 * H;
|
|
4984
5101
|
var DURATION_BOUNDS = {
|
|
4985
|
-
wait: { min: 1, max: 365 * D, label: "1m and 365d" },
|
|
4986
|
-
timeout: { min: 1 * H, max: 180 * D, label: "1h and 180d" },
|
|
4987
|
-
withdraw_after: { min: 14 * D, max: 180 * D, label: "14d and 180d" },
|
|
4988
|
-
newer_than: { min: 1 * H, max: 365 * D, label: "1h and 365d" }
|
|
5102
|
+
wait: { min: 1, max: 365 * D, label: "1m and 365d", example: "1d" },
|
|
5103
|
+
timeout: { min: 1 * H, max: 180 * D, label: "1h and 180d", example: "21d" },
|
|
5104
|
+
withdraw_after: { min: 14 * D, max: 180 * D, label: "14d and 180d", example: "21d" },
|
|
5105
|
+
newer_than: { min: 1 * H, max: 365 * D, label: "1h and 365d", example: "30d" }
|
|
4989
5106
|
};
|
|
4990
5107
|
var TEXT_LIMITS = {
|
|
4991
5108
|
message: 8e3,
|
|
@@ -5001,6 +5118,53 @@ var MAX_QUOTED_TOKEN_CHARS = 64;
|
|
|
5001
5118
|
var ACCOUNT_FIELDS = ["first_name", "last_name", "full_name"];
|
|
5002
5119
|
var NAME_FIELDS = ["full_name", "first_name", "last_name"];
|
|
5003
5120
|
var MOTION_KEY_PATTERN = "^[a-z][a-z0-9_]{0,39}$";
|
|
5121
|
+
var MAX_VARIANTS = 2;
|
|
5122
|
+
var DEPRECATED_ACCEPTED_UNTIL = "2027-01-19";
|
|
5123
|
+
var SHORT_VARIABLE_PATTERN = "^[a-z][a-z0-9_]{0,63}$";
|
|
5124
|
+
var MY_FIELDS = ["my_first_name", "my_last_name", "my_full_name"];
|
|
5125
|
+
|
|
5126
|
+
// src/sequence/rules.ts
|
|
5127
|
+
var SEQUENCE_RULES_DOCS_URL = "https://docs.curviate.com/reference/sequence-rules";
|
|
5128
|
+
var HINTS = {
|
|
5129
|
+
R1: () => "Keep only $schema, on_reply and steps at the top level; $schema, when present, is https://docs.curviate.com/schemas/sequence/v1.json.",
|
|
5130
|
+
R2: () => "Keep the document to 1 to 100 steps and 256 KiB; split a longer flow into two Motions.",
|
|
5131
|
+
R3: () => "Give each step a unique id: lowercase letters, digits and _, starting with a letter, at most 40 characters, not end.",
|
|
5132
|
+
R4: () => "Give the step exactly one of do, wait, until, if, emit or tag.",
|
|
5133
|
+
R5: () => "Remove the key: each step kind takes only its own keys, and the sending Account is chosen with settings.assign.",
|
|
5134
|
+
R6: () => 'Point the exit at a step id of this document, or at "end".',
|
|
5135
|
+
R7: () => "Add then: the step to go to when the event happens or the condition is true.",
|
|
5136
|
+
R8: () => 'Break the loop: point one exit of the cycle at a later step or at "end".',
|
|
5137
|
+
R9: () => "Lead an exit of another step here, or delete the step.",
|
|
5138
|
+
R10: () => 'End the last step explicitly: set its exits to a step id or "end".',
|
|
5139
|
+
R11: () => 'Set on_reply to a step id or "end".',
|
|
5140
|
+
R12: (t) => `Use a duration between ${t}.`,
|
|
5141
|
+
R13: () => "Use an action from the list and only the parameters it takes, with values of their type.",
|
|
5142
|
+
R14: () => "Wait for invite.accepted, message.replied, message.read or event.<name>, and give a timeout.",
|
|
5143
|
+
R15: () => "Use one predicate key per object with its typed operand; chain if steps instead of all or any. account.* and message.* work in if steps only.",
|
|
5144
|
+
R16: () => "Write a variable as {{first_name}}, {{my_first_name}} or a custom field {{opener}}; the long forms {{contact.first_name}}, {{contact.extra.opener}} and {{account.first_name}} work too.",
|
|
5145
|
+
R17: () => "Remove the part after | and add a variable-free fallback on the step instead.",
|
|
5146
|
+
R18: () => "Write a variable with double braces, like {{first_name}}, or remove the braces.",
|
|
5147
|
+
R19: () => 'Add a variable-free fallback: with.fallback, with.note_fallback (or "drop") on an invitation, "skip" on a comment.',
|
|
5148
|
+
R20: () => "Use a Contact variable in the comment, for example a custom field {{comment}} your agent writes per Contact.",
|
|
5149
|
+
R21: () => "Set withdraw_after between 14d and 180d, for example 21d.",
|
|
5150
|
+
R22: () => "Shorten the text to the limit (variables are not counted) or use fewer variables.",
|
|
5151
|
+
R23: () => "Give the step exactly 2 variants (A/B) with ids unique in the document, or one text in with.",
|
|
5152
|
+
R24: () => "Name the event with lowercase letters, digits, _ and ., starting with a letter, like reply.received; a Tag is 1 to 64 characters without , or ;.",
|
|
5153
|
+
W1: () => "If the merge is intended, keep it; otherwise give the jumping step its own continuation.",
|
|
5154
|
+
W5: () => "Remove the follow step: the invitation already follows the person.",
|
|
5155
|
+
W6: () => "Send an invitation on this path before waiting for its acceptance.",
|
|
5156
|
+
W7: () => "Write the comment per Contact, for example in a custom field {{comment}}.",
|
|
5157
|
+
W8: () => "Keep the link for a later message.",
|
|
5158
|
+
W9: () => "Motion variables are deprecated: write the text in place, or use a custom field.",
|
|
5159
|
+
W11: (t) => `Did you mean ${t}?`,
|
|
5160
|
+
W12: () => "Send a message on this path before checking whether it was seen.",
|
|
5161
|
+
W13: (t) => `Use ${t} instead.`
|
|
5162
|
+
};
|
|
5163
|
+
function ruleHelp(code, token = "") {
|
|
5164
|
+
const hint = HINTS[code];
|
|
5165
|
+
if (!hint) throw new Error(`no hint for ${code}`);
|
|
5166
|
+
return { hint: hint(token), docs_url: `${SEQUENCE_RULES_DOCS_URL}#${code.toLowerCase()}` };
|
|
5167
|
+
}
|
|
5004
5168
|
|
|
5005
5169
|
// src/sequence/vocab.ts
|
|
5006
5170
|
var SUPPORTED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
@@ -5143,7 +5307,7 @@ function vocabulary(schema) {
|
|
|
5143
5307
|
maxNoteChars: num(obj(propsOf(alts[0])["note"], "note")["maxLength"], "note.maxLength"),
|
|
5144
5308
|
durationPattern: str(obj(byKind("wait")["wait"], "wait")["pattern"], "wait.pattern"),
|
|
5145
5309
|
events: arr(until[0]["enum"], "until.enum").map((e) => str(e, "until.enum[]")),
|
|
5146
|
-
|
|
5310
|
+
eventNamePattern: after(str(until[1]["pattern"], "until.pattern"), "^(event|signal)\\."),
|
|
5147
5311
|
emitPattern: str(obj(byKind("emit")["emit"], "emit")["pattern"], "emit.pattern"),
|
|
5148
5312
|
tagPattern: str(tag["pattern"], "tag.pattern"),
|
|
5149
5313
|
maxTagChars: num(tag["maxLength"], "tag.maxLength"),
|
|
@@ -5159,6 +5323,18 @@ function vocabulary(schema) {
|
|
|
5159
5323
|
}
|
|
5160
5324
|
|
|
5161
5325
|
// src/sequence/validate.ts
|
|
5326
|
+
var issue = (ctx, i) => ctx.issues.push(i);
|
|
5327
|
+
var warning = (ctx, i) => ctx.warnings.push(i);
|
|
5328
|
+
function jsonPointer(...tokens) {
|
|
5329
|
+
return tokens.map((t) => "/" + String(t).replace(/~/g, "~0").replace(/\//g, "~1")).join("");
|
|
5330
|
+
}
|
|
5331
|
+
function entry(code, path, pointer, rule, token) {
|
|
5332
|
+
return { path, rule, code, message: rule, pointer, ...ruleHelp(code, token) };
|
|
5333
|
+
}
|
|
5334
|
+
function entries(issues) {
|
|
5335
|
+
return issues.sort((a, b) => a.order - b.order).map((i) => entry(i.code, i.path, i.pointer, i.rule, i.token));
|
|
5336
|
+
}
|
|
5337
|
+
var UNTIL = `; accepted until ${DEPRECATED_ACCEPTED_UNTIL}`;
|
|
5162
5338
|
var VARIABLE_RE = /\{\{([^{}]*)\}\}/g;
|
|
5163
5339
|
var SINGLE_BRACE_RE = /\{[A-Za-z_][A-Za-z0-9_.]*\}/g;
|
|
5164
5340
|
function keysOf(o) {
|
|
@@ -5198,6 +5374,7 @@ function durationMinutes(v) {
|
|
|
5198
5374
|
function durationRule(bounds) {
|
|
5199
5375
|
return `must be a duration between ${bounds.label}: a number then m, h or d, like 30d`;
|
|
5200
5376
|
}
|
|
5377
|
+
var durationHint = (bounds) => `${bounds.label}, for example ${bounds.example}`;
|
|
5201
5378
|
function durationOk(ctx, v, bounds) {
|
|
5202
5379
|
if (typeof v !== "string" || !ctx.re.duration.test(v)) return false;
|
|
5203
5380
|
const m = durationMinutes(v);
|
|
@@ -5215,75 +5392,123 @@ function hasVariable(text) {
|
|
|
5215
5392
|
function staticLength(text) {
|
|
5216
5393
|
return [...text.replace(VARIABLE_RE, "")].length;
|
|
5217
5394
|
}
|
|
5395
|
+
function longVariable(ctx, inner) {
|
|
5396
|
+
if (inner.includes(".")) return inner;
|
|
5397
|
+
if (!ctx.re.short.test(inner)) return null;
|
|
5398
|
+
if (MY_FIELDS.includes(inner)) return `account.${inner.slice(3)}`;
|
|
5399
|
+
if (inner.startsWith("my_")) return null;
|
|
5400
|
+
if (ctx.v.standardFields.includes(inner)) return `contact.${inner}`;
|
|
5401
|
+
return `contact.extra.${inner}`;
|
|
5402
|
+
}
|
|
5218
5403
|
function isKnownVariable(ctx, inner) {
|
|
5219
|
-
|
|
5220
|
-
if (
|
|
5221
|
-
if (
|
|
5404
|
+
const long = longVariable(ctx, inner);
|
|
5405
|
+
if (long === null) return false;
|
|
5406
|
+
if (long.startsWith("contact.extra.")) return ctx.re.extraKey.test(long.slice("contact.extra.".length));
|
|
5407
|
+
if (long.startsWith("contact.")) return ctx.v.standardFields.includes(long.slice(8));
|
|
5408
|
+
if (long.startsWith("account.")) return ACCOUNT_FIELDS.includes(long.slice(8));
|
|
5222
5409
|
return false;
|
|
5223
5410
|
}
|
|
5224
|
-
function
|
|
5411
|
+
function distance(a, b, cap) {
|
|
5412
|
+
if (Math.abs(a.length - b.length) > cap) return cap + 1;
|
|
5413
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
5414
|
+
for (let i = 1; i <= a.length; i++) {
|
|
5415
|
+
const cur = [i];
|
|
5416
|
+
for (let j = 1; j <= b.length; j++) cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
5417
|
+
prev = cur;
|
|
5418
|
+
}
|
|
5419
|
+
return prev[b.length];
|
|
5420
|
+
}
|
|
5421
|
+
function likelyTypoOf(ctx, key) {
|
|
5422
|
+
let best = null;
|
|
5423
|
+
let bestD = 3;
|
|
5424
|
+
for (const n of [...ctx.v.standardFields, ...MY_FIELDS]) {
|
|
5425
|
+
const d = distance(key, n, 2);
|
|
5426
|
+
if (d < bestD) [best, bestD] = [n, d];
|
|
5427
|
+
}
|
|
5428
|
+
return best;
|
|
5429
|
+
}
|
|
5430
|
+
function checkText(ctx, order, path, pointer, parent, value, limit) {
|
|
5225
5431
|
if (typeof value !== "string" || value.trim() === "") {
|
|
5226
|
-
ctx
|
|
5432
|
+
issue(ctx, { order, code: "R13", path, pointer: value === void 0 ? parent : pointer, rule: "must be non-empty text" });
|
|
5227
5433
|
return null;
|
|
5228
5434
|
}
|
|
5435
|
+
const at = { order, path, pointer };
|
|
5229
5436
|
const vars = variablesIn(value);
|
|
5230
5437
|
if (vars.length > MAX_VARIABLE_REFS_PER_TEXT) {
|
|
5231
|
-
ctx
|
|
5438
|
+
issue(ctx, { ...at, code: "R22", rule: `${vars.length} variables; the limit is ${MAX_VARIABLE_REFS_PER_TEXT} per text` });
|
|
5232
5439
|
}
|
|
5233
5440
|
for (const v of vars.length > MAX_VARIABLE_REFS_PER_TEXT ? [] : vars) {
|
|
5234
5441
|
if (v.inner.includes("|")) {
|
|
5235
|
-
ctx
|
|
5442
|
+
issue(ctx, { ...at, code: "R17", rule: "variable defaults are not supported; add a fallback" });
|
|
5236
5443
|
continue;
|
|
5237
5444
|
}
|
|
5238
5445
|
if (v.inner.startsWith("motion.") && ctx.re.motion.test(v.inner.slice(7))) {
|
|
5239
|
-
|
|
5446
|
+
const key = v.inner.slice(7);
|
|
5447
|
+
warning(ctx, {
|
|
5448
|
+
...at,
|
|
5449
|
+
code: "W13",
|
|
5450
|
+
rule: `${quote(v.token)}: Motion variables were removed${UNTIL}; write the text in place or use a custom field`,
|
|
5451
|
+
token: `text in place or a custom field such as {{${key}}}`
|
|
5452
|
+
});
|
|
5453
|
+
warning(ctx, { ...at, code: "W9", rule: `${quote(v.token)} is checked against settings.variables by the server only` });
|
|
5454
|
+
continue;
|
|
5455
|
+
}
|
|
5456
|
+
if (!v.inner.includes(".") && v.inner.startsWith("my_") && !MY_FIELDS.includes(v.inner)) {
|
|
5457
|
+
issue(ctx, { ...at, code: "R16", rule: `${quote(v.token)}: the my_ prefix is reserved for my_first_name, my_last_name and my_full_name` });
|
|
5240
5458
|
continue;
|
|
5241
5459
|
}
|
|
5242
5460
|
if (!isKnownVariable(ctx, v.inner)) {
|
|
5243
|
-
ctx
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
rule: `unknown variable ${quote(v.token)}; use contact.<field>, contact.extra.<key>, account.first_name, account.last_name, account.full_name
|
|
5461
|
+
issue(ctx, {
|
|
5462
|
+
...at,
|
|
5463
|
+
code: "R16",
|
|
5464
|
+
rule: `unknown variable ${quote(v.token)}; use a field ({{first_name}}), your name ({{my_first_name}}), a custom field ({{opener}}), or contact.<field>, contact.extra.<key>, account.first_name, account.last_name, account.full_name`
|
|
5247
5465
|
});
|
|
5466
|
+
continue;
|
|
5467
|
+
}
|
|
5468
|
+
if (!v.inner.includes(".") && longVariable(ctx, v.inner).startsWith("contact.extra.")) {
|
|
5469
|
+
const near = likelyTypoOf(ctx, v.inner);
|
|
5470
|
+
if (near !== null) warning(ctx, { ...at, code: "W11", rule: `${quote(v.token)} is read as a custom field; did you mean ${near}?`, token: near });
|
|
5248
5471
|
}
|
|
5249
5472
|
}
|
|
5250
5473
|
const rest = value.replace(VARIABLE_RE, " ");
|
|
5251
5474
|
if (rest.includes("{{")) {
|
|
5252
|
-
ctx
|
|
5475
|
+
issue(ctx, { ...at, code: "R16", rule: "unclosed variable; write a variable as {{contact.first_name}}" });
|
|
5253
5476
|
} else {
|
|
5254
5477
|
for (const m of rest.matchAll(SINGLE_BRACE_RE)) {
|
|
5255
|
-
ctx
|
|
5478
|
+
issue(ctx, { ...at, code: "R18", rule: `${quote(m[0])} is not a variable; write {{contact.<field>}} or remove the braces` });
|
|
5256
5479
|
}
|
|
5257
5480
|
}
|
|
5258
5481
|
const len = staticLength(value);
|
|
5259
|
-
if (len > limit) ctx
|
|
5482
|
+
if (len > limit) issue(ctx, { ...at, code: "R22", rule: `${len} characters; the limit is ${limit}, variables not counted` });
|
|
5260
5483
|
return value;
|
|
5261
5484
|
}
|
|
5262
|
-
function checkFallbackText(ctx, order, path, value, limit, keyword) {
|
|
5485
|
+
function checkFallbackText(ctx, order, path, pointer, parent, value, limit, keyword) {
|
|
5263
5486
|
if (keyword !== void 0 && value === keyword) return;
|
|
5487
|
+
const at = { order, code: "R19", path, pointer };
|
|
5264
5488
|
if (typeof value !== "string") {
|
|
5265
|
-
ctx
|
|
5489
|
+
issue(ctx, { ...at, pointer: value === void 0 ? parent : pointer, rule: keyword ? `must be text or "${keyword}"` : "must be text" });
|
|
5266
5490
|
return;
|
|
5267
5491
|
}
|
|
5268
5492
|
if (value.trim() === "") {
|
|
5269
|
-
ctx
|
|
5493
|
+
issue(ctx, { ...at, rule: "a fallback must not be empty" });
|
|
5270
5494
|
return;
|
|
5271
5495
|
}
|
|
5272
5496
|
if (hasVariable(value)) {
|
|
5273
|
-
ctx
|
|
5497
|
+
issue(ctx, { ...at, rule: "a fallback must not contain a variable" });
|
|
5274
5498
|
return;
|
|
5275
5499
|
}
|
|
5276
|
-
checkText(ctx, order, path, value, limit);
|
|
5500
|
+
checkText(ctx, order, path, pointer, parent, value, limit);
|
|
5277
5501
|
}
|
|
5278
5502
|
function createValidator(v) {
|
|
5279
5503
|
const re = {
|
|
5280
5504
|
id: new RegExp(v.idPattern),
|
|
5281
5505
|
duration: new RegExp(v.durationPattern),
|
|
5282
5506
|
emit: new RegExp(v.emitPattern),
|
|
5283
|
-
|
|
5507
|
+
event: new RegExp(v.eventNamePattern),
|
|
5284
5508
|
extraKey: new RegExp(v.extraKeyPattern),
|
|
5285
5509
|
tag: new RegExp(v.tagPattern),
|
|
5286
|
-
motion: new RegExp(MOTION_KEY_PATTERN)
|
|
5510
|
+
motion: new RegExp(MOTION_KEY_PATTERN),
|
|
5511
|
+
short: new RegExp(SHORT_VARIABLE_PATTERN)
|
|
5287
5512
|
};
|
|
5288
5513
|
return (doc) => run(doc, { v, re, issues: [], warnings: [] });
|
|
5289
5514
|
}
|
|
@@ -5310,9 +5535,9 @@ function firstUnstorable(doc) {
|
|
|
5310
5535
|
} else if (Array.isArray(value)) {
|
|
5311
5536
|
for (let i = value.length - 1; i >= 0; i--) stack.push(["value", value[i], `${path}[${i}]`]);
|
|
5312
5537
|
} else if (typeof value === "object" && value !== null) {
|
|
5313
|
-
const
|
|
5314
|
-
for (let i =
|
|
5315
|
-
const [k, v] =
|
|
5538
|
+
const entries2 = Object.entries(value);
|
|
5539
|
+
for (let i = entries2.length - 1; i >= 0; i--) {
|
|
5540
|
+
const [k, v] = entries2[i];
|
|
5316
5541
|
stack.push(["value", v, path ? `${path}.${k}` : k], ["key", k, path]);
|
|
5317
5542
|
}
|
|
5318
5543
|
}
|
|
@@ -5324,53 +5549,55 @@ function run(doc, ctx) {
|
|
|
5324
5549
|
if (bad !== null) return { errors: [{ path: bad, rule: UNSTORABLE_RULE }], warnings: [] };
|
|
5325
5550
|
const size = compactBytes(doc);
|
|
5326
5551
|
if (size > MAX_SEQUENCE_BYTES) {
|
|
5327
|
-
return { errors: [
|
|
5552
|
+
return { errors: [entry("R2", "$", "", `the document is ${size} bytes; the limit is ${MAX_SEQUENCE_BYTES}, UTF-8 compact JSON`)], warnings: [] };
|
|
5328
5553
|
}
|
|
5329
5554
|
return rules(doc, ctx);
|
|
5330
5555
|
}
|
|
5331
5556
|
function rules(doc, ctx) {
|
|
5332
5557
|
const { v } = ctx;
|
|
5333
5558
|
const DOC2 = -1;
|
|
5334
|
-
if (!isObject(doc)) return { errors: [
|
|
5559
|
+
if (!isObject(doc)) return { errors: [entry("R1", "$", "", "a Sequence document is a JSON object")], warnings: [] };
|
|
5335
5560
|
for (const k of keysOf(doc)) {
|
|
5336
5561
|
if (k !== "$schema" && k !== "on_reply" && k !== "steps") {
|
|
5337
|
-
ctx
|
|
5562
|
+
issue(ctx, { order: DOC2, code: "R1", path: quote(k), pointer: jsonPointer(k), rule: "unknown key; a Sequence document holds only $schema, on_reply and steps" });
|
|
5338
5563
|
}
|
|
5339
5564
|
}
|
|
5340
5565
|
if (has(doc, "$schema") && doc["$schema"] !== v.schemaUrl) {
|
|
5341
|
-
ctx
|
|
5566
|
+
issue(ctx, { order: DOC2, code: "R1", path: "$schema", pointer: "/$schema", rule: `must be ${v.schemaUrl}` });
|
|
5342
5567
|
}
|
|
5343
5568
|
const rawSteps = doc["steps"];
|
|
5344
5569
|
const stepsArr = Array.isArray(rawSteps) ? rawSteps : [];
|
|
5345
5570
|
if (!Array.isArray(rawSteps) || rawSteps.length < v.minSteps) {
|
|
5346
|
-
ctx
|
|
5571
|
+
issue(ctx, { order: DOC2, code: "R2", path: "steps", pointer: has(doc, "steps") ? "/steps" : "", rule: `required: an array of ${v.minSteps} to ${v.maxSteps} steps` });
|
|
5347
5572
|
} else if (rawSteps.length > v.maxSteps) {
|
|
5348
|
-
ctx
|
|
5573
|
+
issue(ctx, { order: DOC2, code: "R2", path: "steps", pointer: "/steps", rule: `${rawSteps.length} steps; the limit is ${v.maxSteps}` });
|
|
5349
5574
|
}
|
|
5350
5575
|
const infos = [];
|
|
5351
5576
|
const firstIndex = /* @__PURE__ */ new Map();
|
|
5352
5577
|
let structural = !Array.isArray(rawSteps) || rawSteps.length < v.minSteps || rawSteps.length > v.maxSteps;
|
|
5353
5578
|
stepsArr.forEach((raw, index) => {
|
|
5354
|
-
const
|
|
5579
|
+
const ptr = jsonPointer("steps", index);
|
|
5580
|
+
const info = { index, prefix: `steps[${index}]`, ptr, id: null, kind: null, raw: null };
|
|
5355
5581
|
infos.push(info);
|
|
5356
5582
|
if (!isObject(raw)) {
|
|
5357
|
-
ctx
|
|
5583
|
+
issue(ctx, { order: index, code: "R4", path: info.prefix, pointer: ptr, rule: "a step is a JSON object" });
|
|
5358
5584
|
structural = true;
|
|
5359
5585
|
return;
|
|
5360
5586
|
}
|
|
5361
5587
|
info.raw = raw;
|
|
5362
5588
|
const id = raw["id"];
|
|
5589
|
+
const idAt = { order: index, code: "R3", path: `${info.prefix}.id`, pointer: `${ptr}/id` };
|
|
5363
5590
|
if (!has(raw, "id")) {
|
|
5364
|
-
ctx
|
|
5591
|
+
issue(ctx, { ...idAt, pointer: ptr, rule: `required: matching ${v.idPattern}` });
|
|
5365
5592
|
structural = true;
|
|
5366
5593
|
} else if (typeof id !== "string" || !ctx.re.id.test(id)) {
|
|
5367
|
-
ctx
|
|
5594
|
+
issue(ctx, { ...idAt, rule: `must match ${v.idPattern}` });
|
|
5368
5595
|
structural = true;
|
|
5369
5596
|
} else if (id === END) {
|
|
5370
|
-
ctx
|
|
5597
|
+
issue(ctx, { ...idAt, rule: '"end" is reserved' });
|
|
5371
5598
|
structural = true;
|
|
5372
5599
|
} else if (firstIndex.has(id)) {
|
|
5373
|
-
ctx
|
|
5600
|
+
issue(ctx, { ...idAt, rule: `duplicate step id ${id}` });
|
|
5374
5601
|
structural = true;
|
|
5375
5602
|
} else {
|
|
5376
5603
|
firstIndex.set(id, index);
|
|
@@ -5379,9 +5606,11 @@ function rules(doc, ctx) {
|
|
|
5379
5606
|
}
|
|
5380
5607
|
const kinds = v.stepKinds.filter((k) => has(raw, k));
|
|
5381
5608
|
if (kinds.length !== 1) {
|
|
5382
|
-
ctx
|
|
5609
|
+
issue(ctx, {
|
|
5383
5610
|
order: index,
|
|
5611
|
+
code: "R4",
|
|
5384
5612
|
path: info.prefix,
|
|
5613
|
+
pointer: ptr,
|
|
5385
5614
|
rule: kinds.length === 0 ? `a step needs exactly one of ${v.stepKinds.join(", ")}` : `a step has exactly one kind; found ${kinds.join(", ")}`
|
|
5386
5615
|
});
|
|
5387
5616
|
structural = true;
|
|
@@ -5391,93 +5620,115 @@ function rules(doc, ctx) {
|
|
|
5391
5620
|
});
|
|
5392
5621
|
const onReply = doc["on_reply"];
|
|
5393
5622
|
const targetOk = (t) => t === END || typeof t === "string" && firstIndex.has(t);
|
|
5623
|
+
const replyAt = { order: DOC2, code: "R11", path: "on_reply", pointer: "/on_reply" };
|
|
5394
5624
|
if (!has(doc, "on_reply")) {
|
|
5395
|
-
ctx
|
|
5625
|
+
issue(ctx, { ...replyAt, pointer: "", rule: 'required: a step id or "end"' });
|
|
5396
5626
|
structural = true;
|
|
5397
5627
|
} else if (typeof onReply !== "string") {
|
|
5398
|
-
ctx
|
|
5628
|
+
issue(ctx, { ...replyAt, rule: 'must be a step id or "end"' });
|
|
5399
5629
|
structural = true;
|
|
5400
5630
|
} else if (!targetOk(onReply)) {
|
|
5401
|
-
ctx
|
|
5631
|
+
issue(ctx, { ...replyAt, rule: `unknown step ${quote(onReply)}` });
|
|
5402
5632
|
structural = true;
|
|
5403
5633
|
}
|
|
5404
5634
|
const usedIds = new Set(firstIndex.keys());
|
|
5405
5635
|
for (const info of infos) {
|
|
5406
5636
|
if (!info.raw || !info.kind) continue;
|
|
5407
|
-
const { raw, kind, index: order, prefix: p } = info;
|
|
5637
|
+
const { raw, kind, index: order, prefix: p, ptr } = info;
|
|
5408
5638
|
const keys = v.stepKeys[kind] ?? [];
|
|
5409
5639
|
for (const k of keysOf(raw)) {
|
|
5410
5640
|
if (keys.includes(k)) continue;
|
|
5411
|
-
ctx
|
|
5641
|
+
issue(ctx, {
|
|
5412
5642
|
order,
|
|
5643
|
+
code: "R5",
|
|
5413
5644
|
path: `${p}.${quote(k)}`,
|
|
5645
|
+
pointer: `${ptr}${jsonPointer(k)}`,
|
|
5414
5646
|
rule: k === "by" ? "step-level by is not supported; use settings.assign" : `unknown key for a ${kind} step`
|
|
5415
5647
|
});
|
|
5416
5648
|
}
|
|
5417
5649
|
if (has(raw, "note")) {
|
|
5418
5650
|
const n = raw["note"];
|
|
5419
5651
|
if (typeof n !== "string" || [...n].length > v.maxNoteChars) {
|
|
5420
|
-
ctx
|
|
5652
|
+
issue(ctx, { order, code: "R22", path: `${p}.note`, pointer: `${ptr}/note`, rule: `must be text of at most ${v.maxNoteChars} characters` });
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
if (has(raw, "event") && keys.includes("event")) {
|
|
5656
|
+
const e = raw["event"];
|
|
5657
|
+
if (typeof e !== "string" || !ctx.re.event.test(e)) {
|
|
5658
|
+
issue(ctx, { order, code: "R24", path: `${p}.event`, pointer: `${ptr}/event`, rule: `must match ${v.eventNamePattern}, like invite_sent` });
|
|
5421
5659
|
}
|
|
5422
5660
|
}
|
|
5423
5661
|
switch (kind) {
|
|
5424
5662
|
case "do":
|
|
5425
|
-
checkDo(ctx, order, p, raw, usedIds);
|
|
5663
|
+
checkDo(ctx, order, p, ptr, raw, usedIds);
|
|
5426
5664
|
break;
|
|
5427
5665
|
case "wait":
|
|
5428
|
-
if (!durationOk(ctx, raw["wait"], DURATION_BOUNDS.wait))
|
|
5666
|
+
if (!durationOk(ctx, raw["wait"], DURATION_BOUNDS.wait)) {
|
|
5667
|
+
issue(ctx, { order, code: "R12", path: `${p}.wait`, pointer: `${ptr}/wait`, rule: durationRule(DURATION_BOUNDS.wait), token: durationHint(DURATION_BOUNDS.wait) });
|
|
5668
|
+
}
|
|
5429
5669
|
break;
|
|
5430
5670
|
case "until":
|
|
5431
|
-
checkUntil(ctx, order, p, raw);
|
|
5671
|
+
checkUntil(ctx, order, p, ptr, raw);
|
|
5432
5672
|
break;
|
|
5433
5673
|
case "if":
|
|
5434
|
-
checkPredicate(ctx, raw["if"], `${p}.if`, order);
|
|
5674
|
+
checkPredicate(ctx, raw["if"], `${p}.if`, `${ptr}/if`, order);
|
|
5435
5675
|
break;
|
|
5436
5676
|
case "emit": {
|
|
5437
5677
|
const e = raw["emit"];
|
|
5438
5678
|
if (typeof e !== "string" || !ctx.re.emit.test(e)) {
|
|
5439
|
-
ctx
|
|
5679
|
+
issue(ctx, { order, code: "R24", path: `${p}.emit`, pointer: `${ptr}/emit`, rule: `must match ${v.emitPattern}, like reply.received` });
|
|
5440
5680
|
}
|
|
5441
5681
|
break;
|
|
5442
5682
|
}
|
|
5443
5683
|
case "tag":
|
|
5444
|
-
if (!tagOk(ctx, raw["tag"])) ctx
|
|
5684
|
+
if (!tagOk(ctx, raw["tag"])) issue(ctx, { order, code: "R24", path: `${p}.tag`, pointer: `${ptr}/tag`, rule: "a Tag name is 1 to 64 characters, without , or ;" });
|
|
5445
5685
|
break;
|
|
5446
5686
|
}
|
|
5447
5687
|
if ((kind === "until" || kind === "if") && !has(raw, "then")) {
|
|
5448
|
-
ctx
|
|
5688
|
+
issue(ctx, { order, code: "R7", path: `${p}.then`, pointer: ptr, rule: `required on an ${kind} step` });
|
|
5449
5689
|
structural = true;
|
|
5450
5690
|
}
|
|
5451
5691
|
for (const f of ["then", "else", "next"]) {
|
|
5452
5692
|
if (!has(raw, f) || !keys.includes(f)) continue;
|
|
5453
5693
|
const t = raw[f];
|
|
5694
|
+
const at = { order, code: "R6", path: `${p}.${f}`, pointer: `${ptr}/${f}` };
|
|
5454
5695
|
if (typeof t !== "string") {
|
|
5455
|
-
ctx
|
|
5696
|
+
issue(ctx, { ...at, rule: 'must be a step id or "end"' });
|
|
5456
5697
|
structural = true;
|
|
5457
5698
|
} else if (!targetOk(t)) {
|
|
5458
|
-
ctx
|
|
5699
|
+
issue(ctx, { ...at, rule: `unknown step ${quote(t)}` });
|
|
5459
5700
|
structural = true;
|
|
5460
5701
|
}
|
|
5461
5702
|
}
|
|
5462
5703
|
}
|
|
5463
5704
|
if (!structural) graph(ctx, infos, firstIndex, onReply);
|
|
5464
|
-
|
|
5465
|
-
return {
|
|
5466
|
-
errors: ctx.issues.sort(byOrder).map(({ path, rule }) => ({ path, rule })),
|
|
5467
|
-
warnings: ctx.warnings.sort(byOrder).map(({ code, path, rule }) => ({ code, path, rule }))
|
|
5468
|
-
};
|
|
5705
|
+
return { errors: entries(ctx.issues), warnings: entries(ctx.warnings) };
|
|
5469
5706
|
}
|
|
5470
|
-
function checkUntil(ctx, order, p, raw) {
|
|
5707
|
+
function checkUntil(ctx, order, p, ptr, raw) {
|
|
5471
5708
|
const ev = raw["until"];
|
|
5472
|
-
|
|
5473
|
-
|
|
5709
|
+
const at = { order, code: "R14", path: `${p}.until`, pointer: `${ptr}/until` };
|
|
5710
|
+
const custom = typeof ev === "string" ? /^(event|signal)\.(.*)$/s.exec(ev) : null;
|
|
5711
|
+
if (custom) {
|
|
5712
|
+
const [, ns, name] = custom;
|
|
5713
|
+
if (!ctx.re.event.test(name)) {
|
|
5714
|
+
issue(ctx, { ...at, rule: `an event name must match ${ctx.v.eventNamePattern}` });
|
|
5715
|
+
} else if (ns === "signal") {
|
|
5716
|
+
warning(ctx, { ...at, code: "W13", rule: `until signal.${quote(name)} is now until event.${quote(name)}${UNTIL}`, token: `event.${quote(name)}` });
|
|
5717
|
+
}
|
|
5474
5718
|
} else if (!ctx.v.events.includes(ev)) {
|
|
5475
|
-
ctx
|
|
5719
|
+
issue(ctx, { ...at, rule: `unknown event; use ${ctx.v.events.join(", ")} or event.<name>` });
|
|
5476
5720
|
}
|
|
5477
5721
|
if (!has(raw, "timeout")) {
|
|
5478
|
-
ctx
|
|
5722
|
+
issue(ctx, { order, code: "R14", path: `${p}.timeout`, pointer: ptr, rule: "required on an until step" });
|
|
5479
5723
|
} else if (!durationOk(ctx, raw["timeout"], DURATION_BOUNDS.timeout)) {
|
|
5480
|
-
ctx
|
|
5724
|
+
issue(ctx, {
|
|
5725
|
+
order,
|
|
5726
|
+
code: "R12",
|
|
5727
|
+
path: `${p}.timeout`,
|
|
5728
|
+
pointer: `${ptr}/timeout`,
|
|
5729
|
+
rule: durationRule(DURATION_BOUNDS.timeout),
|
|
5730
|
+
token: durationHint(DURATION_BOUNDS.timeout)
|
|
5731
|
+
});
|
|
5481
5732
|
}
|
|
5482
5733
|
}
|
|
5483
5734
|
function limitOf(a, field) {
|
|
@@ -5486,110 +5737,135 @@ function limitOf(a, field) {
|
|
|
5486
5737
|
if (a === "invite.send") return TEXT_LIMITS.invite_note;
|
|
5487
5738
|
return field === "subject" ? TEXT_LIMITS.inmail_subject : TEXT_LIMITS.inmail_text;
|
|
5488
5739
|
}
|
|
5489
|
-
function checkDo(ctx, order, p, raw, usedIds) {
|
|
5740
|
+
function checkDo(ctx, order, p, ptr, raw, usedIds) {
|
|
5490
5741
|
const { v } = ctx;
|
|
5491
5742
|
const a = raw["do"];
|
|
5492
5743
|
if (typeof a !== "string" || !v.actions.includes(a)) {
|
|
5493
|
-
ctx
|
|
5744
|
+
issue(ctx, { order, code: "R13", path: `${p}.do`, pointer: `${ptr}/do`, rule: `unknown action; use ${v.actions.join(", ")}` });
|
|
5494
5745
|
return;
|
|
5495
5746
|
}
|
|
5496
5747
|
const params = v.actionParams[a] ?? [];
|
|
5748
|
+
const wp = `${ptr}/with`;
|
|
5749
|
+
const withParent = isObject(raw["with"]) ? wp : ptr;
|
|
5497
5750
|
let w = {};
|
|
5498
5751
|
if (has(raw, "with")) {
|
|
5499
5752
|
if (!isObject(raw["with"])) {
|
|
5500
|
-
ctx
|
|
5753
|
+
issue(ctx, { order, code: "R13", path: `${p}.with`, pointer: wp, rule: "must be an object" });
|
|
5501
5754
|
} else {
|
|
5502
5755
|
w = raw["with"];
|
|
5503
5756
|
for (const k of keysOf(w)) {
|
|
5504
5757
|
if (!params.includes(k)) {
|
|
5505
|
-
ctx
|
|
5758
|
+
issue(ctx, {
|
|
5759
|
+
order,
|
|
5760
|
+
code: "R13",
|
|
5761
|
+
path: `${p}.with.${quote(k)}`,
|
|
5762
|
+
pointer: `${wp}${jsonPointer(k)}`,
|
|
5763
|
+
rule: `unknown parameter; ${a} takes ${params.length ? params.join(", ") : "no parameters"}`
|
|
5764
|
+
});
|
|
5506
5765
|
}
|
|
5507
5766
|
}
|
|
5508
5767
|
}
|
|
5509
5768
|
}
|
|
5510
5769
|
const messaging = v.messagingActions.includes(a);
|
|
5511
5770
|
const hasVariants = has(raw, "variants");
|
|
5512
|
-
|
|
5771
|
+
const vptr = `${ptr}/variants`;
|
|
5772
|
+
if (hasVariants && !messaging) issue(ctx, { order, code: "R23", path: `${p}.variants`, pointer: vptr, rule: "variants are allowed on messaging steps only" });
|
|
5513
5773
|
if (has(w, "post") && params.includes("post") && w["post"] !== v.post) {
|
|
5514
|
-
ctx
|
|
5774
|
+
issue(ctx, { order, code: "R13", path: `${p}.with.post`, pointer: `${wp}/post`, rule: `only "${v.post}" is supported` });
|
|
5515
5775
|
}
|
|
5516
5776
|
if (has(w, "newer_than") && params.includes("newer_than") && !durationOk(ctx, w["newer_than"], DURATION_BOUNDS.newer_than)) {
|
|
5517
|
-
ctx
|
|
5777
|
+
issue(ctx, {
|
|
5778
|
+
order,
|
|
5779
|
+
code: "R12",
|
|
5780
|
+
path: `${p}.with.newer_than`,
|
|
5781
|
+
pointer: `${wp}/newer_than`,
|
|
5782
|
+
rule: durationRule(DURATION_BOUNDS.newer_than),
|
|
5783
|
+
token: durationHint(DURATION_BOUNDS.newer_than)
|
|
5784
|
+
});
|
|
5518
5785
|
}
|
|
5519
5786
|
if (has(w, "reaction") && params.includes("reaction") && !v.reactions.includes(w["reaction"])) {
|
|
5520
|
-
ctx
|
|
5787
|
+
issue(ctx, { order, code: "R13", path: `${p}.with.reaction`, pointer: `${wp}/reaction`, rule: `must be one of ${v.reactions.join(", ")}` });
|
|
5521
5788
|
}
|
|
5522
5789
|
if (has(w, "withdraw_after") && params.includes("withdraw_after") && !durationOk(ctx, w["withdraw_after"], DURATION_BOUNDS.withdraw_after)) {
|
|
5523
|
-
ctx
|
|
5790
|
+
issue(ctx, { order, code: "R21", path: `${p}.with.withdraw_after`, pointer: `${wp}/withdraw_after`, rule: durationRule(DURATION_BOUNDS.withdraw_after) });
|
|
5524
5791
|
}
|
|
5525
5792
|
if (!messaging) return;
|
|
5526
5793
|
const textKeys = a === "inmail.send" ? ["subject", "text"] : ["text"];
|
|
5527
5794
|
const withTextKeys = a === "invite.send" ? ["note"] : textKeys;
|
|
5528
5795
|
const variants = hasVariants ? raw["variants"] : void 0;
|
|
5529
5796
|
if (hasVariants && withTextKeys.some((k) => has(w, k) && w[k] !== null)) {
|
|
5530
|
-
ctx
|
|
5797
|
+
issue(ctx, { order, code: "R23", path: `${p}.variants`, pointer: vptr, rule: `use either variants or with.${withTextKeys.join(" and with.")}, not both` });
|
|
5531
5798
|
}
|
|
5532
5799
|
const texts = [];
|
|
5533
5800
|
if (hasVariants) {
|
|
5534
|
-
|
|
5535
|
-
|
|
5801
|
+
const n = Array.isArray(variants) ? variants.length : null;
|
|
5802
|
+
if (n !== null && n > MAX_VARIANTS && n <= v.maxVariants) {
|
|
5803
|
+
warning(ctx, { order, code: "W13", path: `${p}.variants`, pointer: vptr, rule: `${n} variants; A/B takes exactly ${MAX_VARIANTS}${UNTIL}`, token: `${MAX_VARIANTS} variants` });
|
|
5804
|
+
} else if (n === null || n < v.minVariants || n > MAX_VARIANTS) {
|
|
5805
|
+
issue(ctx, {
|
|
5536
5806
|
order,
|
|
5807
|
+
code: "R23",
|
|
5537
5808
|
path: `${p}.variants`,
|
|
5538
|
-
|
|
5809
|
+
pointer: vptr,
|
|
5810
|
+
rule: `needs exactly ${MAX_VARIANTS} variants (A/B)${n === null ? "" : `; found ${n}`}`
|
|
5539
5811
|
});
|
|
5540
5812
|
}
|
|
5541
5813
|
(Array.isArray(variants) ? variants : []).forEach((variant, j) => {
|
|
5542
5814
|
let vp = `${p}.variants[${j}]`;
|
|
5815
|
+
const vptrJ = `${vptr}/${j}`;
|
|
5543
5816
|
if (!isObject(variant)) {
|
|
5544
|
-
ctx
|
|
5817
|
+
issue(ctx, { order, code: "R23", path: vp, pointer: vptrJ, rule: "a variant is a JSON object" });
|
|
5545
5818
|
return;
|
|
5546
5819
|
}
|
|
5547
5820
|
const vid = variant["id"];
|
|
5821
|
+
const idAt = { order, code: "R23", path: `${vp}.id`, pointer: has(variant, "id") ? `${vptrJ}/id` : vptrJ };
|
|
5548
5822
|
if (typeof vid !== "string" || !ctx.re.id.test(vid)) {
|
|
5549
|
-
ctx
|
|
5823
|
+
issue(ctx, { ...idAt, rule: `must match ${v.idPattern}` });
|
|
5550
5824
|
} else if (vid === END) {
|
|
5551
|
-
ctx
|
|
5825
|
+
issue(ctx, { ...idAt, rule: '"end" is reserved' });
|
|
5552
5826
|
} else if (usedIds.has(vid)) {
|
|
5553
|
-
ctx
|
|
5827
|
+
issue(ctx, { ...idAt, rule: `${vid} is already a step or variant id` });
|
|
5554
5828
|
} else {
|
|
5555
5829
|
usedIds.add(vid);
|
|
5556
5830
|
vp = `${p}.variants.${vid}`;
|
|
5557
5831
|
}
|
|
5558
5832
|
for (const k of keysOf(variant)) {
|
|
5559
5833
|
if (k !== "id" && !textKeys.includes(k)) {
|
|
5560
|
-
ctx
|
|
5834
|
+
issue(ctx, { order, code: "R23", path: `${vp}.${quote(k)}`, pointer: `${vptrJ}${jsonPointer(k)}`, rule: `unknown key; a variant holds id and ${textKeys.join(" and ")}` });
|
|
5561
5835
|
}
|
|
5562
5836
|
}
|
|
5563
5837
|
for (const field of textKeys) {
|
|
5564
|
-
const value = checkText(ctx, order, `${vp}.${field}`, variant[field], limitOf(a, field));
|
|
5565
|
-
if (value !== null) texts.push({ path: `${vp}.${field}`, value });
|
|
5838
|
+
const value = checkText(ctx, order, `${vp}.${field}`, `${vptrJ}/${field}`, vptrJ, variant[field], limitOf(a, field));
|
|
5839
|
+
if (value !== null) texts.push({ path: `${vp}.${field}`, pointer: `${vptrJ}/${field}`, value });
|
|
5566
5840
|
}
|
|
5567
5841
|
});
|
|
5568
5842
|
} else {
|
|
5569
5843
|
for (const field of withTextKeys) {
|
|
5844
|
+
const fp = `${wp}/${field}`;
|
|
5570
5845
|
if (a === "invite.send") {
|
|
5571
5846
|
if (has(w, "note") && w["note"] !== null) {
|
|
5572
|
-
const value = checkText(ctx, order, `${p}.with.note`, w["note"], limitOf(a, field));
|
|
5573
|
-
if (value !== null) texts.push({ path: `${p}.with.note`, value });
|
|
5847
|
+
const value = checkText(ctx, order, `${p}.with.note`, fp, withParent, w["note"], limitOf(a, field));
|
|
5848
|
+
if (value !== null) texts.push({ path: `${p}.with.note`, pointer: fp, value });
|
|
5574
5849
|
}
|
|
5575
5850
|
} else if (!has(w, field)) {
|
|
5576
|
-
ctx
|
|
5851
|
+
issue(ctx, { order, code: "R13", path: `${p}.with.${field}`, pointer: withParent, rule: "required, or use variants" });
|
|
5577
5852
|
} else {
|
|
5578
|
-
const value = checkText(ctx, order, `${p}.with.${field}`, w[field], limitOf(a, field));
|
|
5579
|
-
if (value !== null) texts.push({ path: `${p}.with.${field}`, value });
|
|
5853
|
+
const value = checkText(ctx, order, `${p}.with.${field}`, fp, withParent, w[field], limitOf(a, field));
|
|
5854
|
+
if (value !== null) texts.push({ path: `${p}.with.${field}`, pointer: fp, value });
|
|
5580
5855
|
}
|
|
5581
5856
|
}
|
|
5582
5857
|
}
|
|
5583
5858
|
if (a === "post.comment") {
|
|
5584
5859
|
for (const t of texts) {
|
|
5585
|
-
const contactVars = variablesIn(t.value).map((x) => x.inner).filter((
|
|
5860
|
+
const contactVars = variablesIn(t.value).filter((x) => isKnownVariable(ctx, x.inner)).map((x) => longVariable(ctx, x.inner)).filter((l) => l.startsWith("contact."));
|
|
5586
5861
|
if (contactVars.length === 0) {
|
|
5587
|
-
ctx
|
|
5588
|
-
} else if (contactVars.every((
|
|
5589
|
-
ctx
|
|
5862
|
+
issue(ctx, { order, code: "R20", path: t.path, pointer: t.pointer, rule: "a comment must use a contact variable, such as {{contact.extra.comment}}" });
|
|
5863
|
+
} else if (contactVars.every((l) => NAME_FIELDS.includes(l.slice(8)))) {
|
|
5864
|
+
warning(ctx, {
|
|
5590
5865
|
order,
|
|
5591
5866
|
code: "W7",
|
|
5592
5867
|
path: t.path,
|
|
5868
|
+
pointer: t.pointer,
|
|
5593
5869
|
rule: "the comment's only per-Contact variables are name fields; write it per Contact, for example in contact.extra"
|
|
5594
5870
|
});
|
|
5595
5871
|
}
|
|
@@ -5598,35 +5874,38 @@ function checkDo(ctx, order, p, raw, usedIds) {
|
|
|
5598
5874
|
const usesVariable = texts.some((t) => hasVariable(t.value));
|
|
5599
5875
|
if (a === "invite.send") {
|
|
5600
5876
|
const fp = `${p}.with.note_fallback`;
|
|
5601
|
-
if (has(w, "note_fallback")) checkFallbackText(ctx, order, fp, w["note_fallback"], TEXT_LIMITS.invite_note, "drop");
|
|
5602
|
-
else if (usesVariable) ctx
|
|
5877
|
+
if (has(w, "note_fallback")) checkFallbackText(ctx, order, fp, `${wp}/note_fallback`, withParent, w["note_fallback"], TEXT_LIMITS.invite_note, "drop");
|
|
5878
|
+
else if (usesVariable) issue(ctx, { order, code: "R19", path: fp, pointer: withParent, rule: 'required because the note uses a variable: text, or "drop"' });
|
|
5603
5879
|
} else if (a === "inmail.send") {
|
|
5604
5880
|
const fp = `${p}.with.fallback`;
|
|
5881
|
+
const fptr = `${wp}/fallback`;
|
|
5605
5882
|
if (has(w, "fallback")) {
|
|
5606
5883
|
const f = w["fallback"];
|
|
5607
5884
|
if (!isObject(f)) {
|
|
5608
|
-
ctx
|
|
5885
|
+
issue(ctx, { order, code: "R19", path: fp, pointer: fptr, rule: "must be an object with subject and text" });
|
|
5609
5886
|
} else {
|
|
5610
5887
|
for (const k of keysOf(f)) {
|
|
5611
|
-
if (k !== "subject" && k !== "text")
|
|
5888
|
+
if (k !== "subject" && k !== "text") {
|
|
5889
|
+
issue(ctx, { order, code: "R19", path: `${fp}.${quote(k)}`, pointer: `${fptr}${jsonPointer(k)}`, rule: "unknown key; a fallback holds subject and text" });
|
|
5890
|
+
}
|
|
5612
5891
|
}
|
|
5613
|
-
checkFallbackText(ctx, order, `${fp}.subject`, f["subject"], TEXT_LIMITS.inmail_subject);
|
|
5614
|
-
checkFallbackText(ctx, order, `${fp}.text`, f["text"], TEXT_LIMITS.inmail_text);
|
|
5892
|
+
checkFallbackText(ctx, order, `${fp}.subject`, `${fptr}/subject`, fptr, f["subject"], TEXT_LIMITS.inmail_subject);
|
|
5893
|
+
checkFallbackText(ctx, order, `${fp}.text`, `${fptr}/text`, fptr, f["text"], TEXT_LIMITS.inmail_text);
|
|
5615
5894
|
}
|
|
5616
5895
|
} else if (usesVariable) {
|
|
5617
|
-
ctx
|
|
5896
|
+
issue(ctx, { order, code: "R19", path: fp, pointer: withParent, rule: "required because the subject or text uses a variable: an object with subject and text" });
|
|
5618
5897
|
}
|
|
5619
5898
|
} else {
|
|
5620
5899
|
const fp = `${p}.with.fallback`;
|
|
5621
5900
|
const comment = a === "post.comment";
|
|
5622
|
-
if (has(w, "fallback")) checkFallbackText(ctx, order, fp, w["fallback"], limitOf(a, "text"), comment ? "skip" : void 0);
|
|
5623
|
-
else if (comment) ctx
|
|
5624
|
-
else if (usesVariable) ctx
|
|
5901
|
+
if (has(w, "fallback")) checkFallbackText(ctx, order, fp, `${wp}/fallback`, withParent, w["fallback"], limitOf(a, "text"), comment ? "skip" : void 0);
|
|
5902
|
+
else if (comment) issue(ctx, { order, code: "R19", path: fp, pointer: withParent, rule: 'required on a comment: text, or "skip"' });
|
|
5903
|
+
else if (usesVariable) issue(ctx, { order, code: "R19", path: fp, pointer: withParent, rule: "required because the text uses a variable" });
|
|
5625
5904
|
}
|
|
5626
5905
|
}
|
|
5627
|
-
function checkPredicate(ctx, pred, path, order, nested = false) {
|
|
5906
|
+
function checkPredicate(ctx, pred, path, pointer, order, nested = false) {
|
|
5628
5907
|
const { v } = ctx;
|
|
5629
|
-
const push = (rule) => ctx
|
|
5908
|
+
const push = (rule) => issue(ctx, { order, code: "R15", path, pointer, rule });
|
|
5630
5909
|
if (!nested && deeperThan(pred, MAX_PREDICATE_DEPTH)) {
|
|
5631
5910
|
push(`a predicate nests at most ${MAX_PREDICATE_DEPTH} levels`);
|
|
5632
5911
|
return;
|
|
@@ -5639,7 +5918,7 @@ function checkPredicate(ctx, pred, path, order, nested = false) {
|
|
|
5639
5918
|
const val = pred[key];
|
|
5640
5919
|
if (key === "not") {
|
|
5641
5920
|
if (nested) push("not cannot wrap another not");
|
|
5642
|
-
else checkPredicate(ctx, val, `${path}.not`, order, true);
|
|
5921
|
+
else checkPredicate(ctx, val, `${path}.not`, `${pointer}/not`, order, true);
|
|
5643
5922
|
return;
|
|
5644
5923
|
}
|
|
5645
5924
|
if (key.startsWith("account.")) {
|
|
@@ -5647,6 +5926,11 @@ function checkPredicate(ctx, pred, path, order, nested = false) {
|
|
|
5647
5926
|
else if (typeof val !== "boolean") push("account.premium takes true or false");
|
|
5648
5927
|
return;
|
|
5649
5928
|
}
|
|
5929
|
+
if (key.startsWith("message.")) {
|
|
5930
|
+
if (key !== "message.seen") push(`unknown predicate ${quote(key)}`);
|
|
5931
|
+
else if (typeof val !== "boolean") push("message.seen takes true or false");
|
|
5932
|
+
return;
|
|
5933
|
+
}
|
|
5650
5934
|
if (v.booleanPredicates.includes(key)) {
|
|
5651
5935
|
if (typeof val !== "boolean") push(`${key} takes true or false`);
|
|
5652
5936
|
return;
|
|
@@ -5700,11 +5984,18 @@ function graph(ctx, infos, ids, onReply) {
|
|
|
5700
5984
|
return [pick("next")];
|
|
5701
5985
|
}
|
|
5702
5986
|
});
|
|
5987
|
+
const exitPtr = (i, e) => e.fallThrough ? infos[i].ptr : `${infos[i].ptr}/${e.field}`;
|
|
5703
5988
|
const graphIssues = [];
|
|
5704
5989
|
infos.forEach((info, i) => {
|
|
5705
5990
|
for (const e of edges[i]) {
|
|
5706
5991
|
if (e.to === "off") {
|
|
5707
|
-
graphIssues.push({
|
|
5992
|
+
graphIssues.push({
|
|
5993
|
+
order: i,
|
|
5994
|
+
code: "R10",
|
|
5995
|
+
path: `${info.prefix}.${e.field}`,
|
|
5996
|
+
pointer: exitPtr(i, e),
|
|
5997
|
+
rule: `the last step must end explicitly; set ${e.field} to a step id or "end"`
|
|
5998
|
+
});
|
|
5708
5999
|
}
|
|
5709
6000
|
}
|
|
5710
6001
|
});
|
|
@@ -5718,7 +6009,7 @@ function graph(ctx, infos, ids, onReply) {
|
|
|
5718
6009
|
for (const e of edges[i]) if (typeof e.to === "number") queue.push(e.to);
|
|
5719
6010
|
}
|
|
5720
6011
|
infos.forEach((info, i) => {
|
|
5721
|
-
if (!seen.has(i)) graphIssues.push({ order: i, path: info.prefix, rule: "unreachable: no path leads here from the first step or from on_reply" });
|
|
6012
|
+
if (!seen.has(i)) graphIssues.push({ order: i, code: "R9", path: info.prefix, pointer: info.ptr, rule: "unreachable: no path leads here from the first step or from on_reply" });
|
|
5722
6013
|
});
|
|
5723
6014
|
const state = new Array(n).fill(0);
|
|
5724
6015
|
const stack = [];
|
|
@@ -5729,7 +6020,7 @@ function graph(ctx, infos, ids, onReply) {
|
|
|
5729
6020
|
if (typeof e.to !== "number") continue;
|
|
5730
6021
|
if (state[e.to] === 1) {
|
|
5731
6022
|
const rule = `cycle: ${[...stack.slice(stack.indexOf(e.to)), e.to].map((k) => infos[k].prefix).join(" -> ")}`;
|
|
5732
|
-
if (!graphIssues.some((x) => x.rule === rule)) graphIssues.push({ order: i, path: `${infos[i].prefix}.${e.field}`, rule });
|
|
6023
|
+
if (!graphIssues.some((x) => x.rule === rule)) graphIssues.push({ order: i, code: "R8", path: `${infos[i].prefix}.${e.field}`, pointer: exitPtr(i, e), rule });
|
|
5733
6024
|
} else if (state[e.to] === 0) {
|
|
5734
6025
|
dfs(e.to);
|
|
5735
6026
|
}
|
|
@@ -5748,10 +6039,11 @@ function graph(ctx, infos, ids, onReply) {
|
|
|
5748
6039
|
const jumpFrom = preds[i].filter((x) => !x.edge.fallThrough && x.from !== i - 1).map((x) => x.from);
|
|
5749
6040
|
if (fallFrom.length && jumpFrom.length) {
|
|
5750
6041
|
const names = (xs) => [...new Set(xs)].map((k) => infos[k].prefix).join(", ");
|
|
5751
|
-
ctx
|
|
6042
|
+
warning(ctx, {
|
|
5752
6043
|
order: i,
|
|
5753
6044
|
code: "W1",
|
|
5754
6045
|
path: info.prefix,
|
|
6046
|
+
pointer: info.ptr,
|
|
5755
6047
|
rule: `merge point: reached by fall-through from ${names(fallFrom)} and by a jump from ${names(jumpFrom)}`
|
|
5756
6048
|
});
|
|
5757
6049
|
}
|
|
@@ -5759,22 +6051,36 @@ function graph(ctx, infos, ids, onReply) {
|
|
|
5759
6051
|
const order = topo(n, edges, roots);
|
|
5760
6052
|
const afterInvite = new Array(n).fill(false);
|
|
5761
6053
|
const withoutInvite = new Array(n).fill(false);
|
|
6054
|
+
const withoutMessage = new Array(n).fill(false);
|
|
5762
6055
|
const fromFirst = new Set(topo(n, edges, [0]));
|
|
5763
|
-
for (const r of roots) if (r === 0 || !fromFirst.has(r)) withoutInvite[r] = true;
|
|
6056
|
+
for (const r of roots) if (r === 0 || !fromFirst.has(r)) withoutInvite[r] = withoutMessage[r] = true;
|
|
5764
6057
|
for (const i of order) {
|
|
5765
6058
|
for (const { from, edge } of preds[i]) {
|
|
5766
6059
|
const invited = action(from) === "invite.send" && edge.field === "next";
|
|
5767
6060
|
if (afterInvite[from] || invited) afterInvite[i] = true;
|
|
5768
6061
|
if (withoutInvite[from] && !invited) withoutInvite[i] = true;
|
|
6062
|
+
const messaged = (action(from) === "message.send" || action(from) === "inmail.send") && edge.field === "next";
|
|
6063
|
+
if (withoutMessage[from] && !messaged) withoutMessage[i] = true;
|
|
5769
6064
|
}
|
|
5770
6065
|
}
|
|
5771
6066
|
infos.forEach((info, i) => {
|
|
5772
6067
|
if (!seen.has(i)) return;
|
|
5773
6068
|
if (action(i) === "profile.follow" && afterInvite[i]) {
|
|
5774
|
-
ctx
|
|
6069
|
+
warning(ctx, { order: i, code: "W5", path: `${info.prefix}.do`, pointer: `${info.ptr}/do`, rule: "profile.follow after invite.send on the same path; an invitation already follows" });
|
|
6070
|
+
}
|
|
6071
|
+
const cond = info.raw["if"];
|
|
6072
|
+
const seenPred = isObject(cond) && (has(cond, "message.seen") || isObject(cond["not"]) && has(cond["not"], "message.seen"));
|
|
6073
|
+
if (seenPred && withoutMessage[i]) {
|
|
6074
|
+
warning(ctx, {
|
|
6075
|
+
order: i,
|
|
6076
|
+
code: "W12",
|
|
6077
|
+
path: `${info.prefix}.if`,
|
|
6078
|
+
pointer: `${info.ptr}/if`,
|
|
6079
|
+
rule: "if message.seen on a path with no message.send or inmail.send before it; it is always false there"
|
|
6080
|
+
});
|
|
5775
6081
|
}
|
|
5776
6082
|
if (info.raw["until"] === "invite.accepted" && withoutInvite[i]) {
|
|
5777
|
-
ctx
|
|
6083
|
+
warning(ctx, { order: i, code: "W6", path: `${info.prefix}.until`, pointer: `${info.ptr}/until`, rule: "until invite.accepted on a path with no invite.send before it" });
|
|
5778
6084
|
}
|
|
5779
6085
|
});
|
|
5780
6086
|
const flagged = /* @__PURE__ */ new Set();
|
|
@@ -5791,7 +6097,7 @@ function graph(ctx, infos, ids, onReply) {
|
|
|
5791
6097
|
if (!flagged.has(k)) {
|
|
5792
6098
|
flagged.add(k);
|
|
5793
6099
|
const link = messageTexts(infos[k]).find((t) => t.value.includes("http"));
|
|
5794
|
-
if (link) ctx
|
|
6100
|
+
if (link) warning(ctx, { order: k, code: "W8", path: link.path, pointer: link.pointer, rule: "a link in the first message after the invitation is accepted" });
|
|
5795
6101
|
}
|
|
5796
6102
|
continue;
|
|
5797
6103
|
}
|
|
@@ -5807,12 +6113,12 @@ function messageTexts(info) {
|
|
|
5807
6113
|
raw["variants"].forEach((variant, j) => {
|
|
5808
6114
|
if (isObject(variant) && typeof variant["text"] === "string") {
|
|
5809
6115
|
const id = typeof variant["id"] === "string" ? `variants.${variant["id"]}` : `variants[${j}]`;
|
|
5810
|
-
out.push({ path: `${info.prefix}.${id}.text`, value: variant["text"] });
|
|
6116
|
+
out.push({ path: `${info.prefix}.${id}.text`, pointer: `${info.ptr}/variants/${j}/text`, value: variant["text"] });
|
|
5811
6117
|
}
|
|
5812
6118
|
});
|
|
5813
6119
|
}
|
|
5814
|
-
if (typeof w["text"] === "string") out.push({ path: `${info.prefix}.with.text`, value: w["text"] });
|
|
5815
|
-
if (typeof w["fallback"] === "string") out.push({ path: `${info.prefix}.with.fallback`, value: w["fallback"] });
|
|
6120
|
+
if (typeof w["text"] === "string") out.push({ path: `${info.prefix}.with.text`, pointer: `${info.ptr}/with/text`, value: w["text"] });
|
|
6121
|
+
if (typeof w["fallback"] === "string") out.push({ path: `${info.prefix}.with.fallback`, pointer: `${info.ptr}/with/fallback`, value: w["fallback"] });
|
|
5816
6122
|
return out;
|
|
5817
6123
|
}
|
|
5818
6124
|
function topo(n, edges, roots) {
|
|
@@ -5880,7 +6186,7 @@ var _Sequence = class _Sequence {
|
|
|
5880
6186
|
* Appends steps in order: constructor output or object literals.
|
|
5881
6187
|
*
|
|
5882
6188
|
* @example
|
|
5883
|
-
* const next = seq.add(step.wait("pause", "2d"), step.
|
|
6189
|
+
* const next = seq.add(step.wait("pause", "2d"), step.tag("done", "sequence-done", { event: "sequence_done", next: "end" }));
|
|
5884
6190
|
*/
|
|
5885
6191
|
add(...steps) {
|
|
5886
6192
|
return __privateMethod(this, _Sequence_instances, with_fn).call(this, [...__privateGet(this, _doc).steps, ...clone(steps)]);
|
|
@@ -6029,7 +6335,10 @@ var MotionsResource = class {
|
|
|
6029
6335
|
* Change a Motion. Objects (`schedule`, `settings`) merge by key; arrays,
|
|
6030
6336
|
* `where` and `sequence` replace whole; `null` clears. `sequence` takes a
|
|
6031
6337
|
* builder or a document, as in `create`. After start, a structural Sequence
|
|
6032
|
-
* change makes a new version; running Enrollments keep theirs.
|
|
6338
|
+
* change makes a new version; running Enrollments keep theirs. Send
|
|
6339
|
+
* `if_sequence_revision` (the `sequence_revision` you read) to refuse the
|
|
6340
|
+
* save when someone saved the Sequence since: `SEQUENCE_CONFLICT`, nothing
|
|
6341
|
+
* saved, the stored document in `err.current`. Omitted: the last write wins.
|
|
6033
6342
|
* `PATCH /v1/motions/{id}`
|
|
6034
6343
|
*
|
|
6035
6344
|
* Needs Automate: a Team below that Tier is refused with `TIER_REQUIRED`.
|
|
@@ -6038,6 +6347,7 @@ var MotionsResource = class {
|
|
|
6038
6347
|
* const motion = await curviate.motions.get("mot_123");
|
|
6039
6348
|
* await curviate.motions.update("mot_123", {
|
|
6040
6349
|
* sequence: Sequence.fromDocument(motion.sequence).replace("pause", step.wait("pause", "3d", { next: "end" })),
|
|
6350
|
+
* if_sequence_revision: motion.sequence_revision,
|
|
6041
6351
|
* });
|
|
6042
6352
|
*/
|
|
6043
6353
|
async update(motionId, body) {
|
|
@@ -6172,15 +6482,22 @@ var EnrollmentsResource = class {
|
|
|
6172
6482
|
}
|
|
6173
6483
|
/**
|
|
6174
6484
|
* List Enrollments, newest first. Filters: `motion_id`, `contact_id`,
|
|
6175
|
-
* `account_id`, `status` (any of them), `step_id
|
|
6176
|
-
* `
|
|
6485
|
+
* `account_id`, `status` (any of them), `step_id` (at that Step now),
|
|
6486
|
+
* `version`; by Step outcome: `done_step` with `outcome` (how that Step
|
|
6487
|
+
* ended), `reason` (catalogue codes, any of them), `variant_id`,
|
|
6488
|
+
* `relationship_at_enrollment` (any of them). Pages with an opaque `cursor`
|
|
6489
|
+
* until it is `null`.
|
|
6177
6490
|
* `GET /v1/enrollments`
|
|
6178
6491
|
*
|
|
6179
6492
|
* @example
|
|
6180
|
-
* const { items } = await curviate.enrollments.list({ motion_id: "mot_123",
|
|
6493
|
+
* const { items } = await curviate.enrollments.list({ motion_id: "mot_123", done_step: "opener", outcome: "failed" });
|
|
6181
6494
|
*/
|
|
6182
6495
|
list(params) {
|
|
6183
|
-
return this.ctx.request({
|
|
6496
|
+
return this.ctx.request({
|
|
6497
|
+
method: "GET",
|
|
6498
|
+
path: "/v1/enrollments",
|
|
6499
|
+
query: commaLists(params, "status", "reason", "relationship_at_enrollment")
|
|
6500
|
+
});
|
|
6184
6501
|
}
|
|
6185
6502
|
/**
|
|
6186
6503
|
* One Enrollment with its `history` of Steps (metadata only, no rendered
|
|
@@ -6205,29 +6522,131 @@ var EnrollmentsResource = class {
|
|
|
6205
6522
|
return this.ctx.request({ method: "POST", path: apiPath`/v1/enrollments/${enrollmentId}/stop` });
|
|
6206
6523
|
}
|
|
6207
6524
|
/**
|
|
6208
|
-
*
|
|
6209
|
-
*
|
|
6210
|
-
* `
|
|
6525
|
+
* Hold one `pending`, `active` or `paused` Enrollment until you `resume` it
|
|
6526
|
+
* (`waiting.kind: "paused_by_user"`); its waits freeze. A Step being sent
|
|
6527
|
+
* right now is `STEP_IN_FLIGHT`: retry in a minute. Works on every plan.
|
|
6528
|
+
* `POST /v1/enrollments/{id}/pause`
|
|
6529
|
+
*
|
|
6530
|
+
* @example
|
|
6531
|
+
* await curviate.enrollments.pause("enr_123");
|
|
6532
|
+
*/
|
|
6533
|
+
pause(enrollmentId) {
|
|
6534
|
+
return this.ctx.request({ method: "POST", path: apiPath`/v1/enrollments/${enrollmentId}/pause` });
|
|
6535
|
+
}
|
|
6536
|
+
/**
|
|
6537
|
+
* Without `at`: lift your own pause; the Enrollment continues where it was,
|
|
6538
|
+
* with the time its wait had left. With `at`: an explicit jump to that Step
|
|
6539
|
+
* of its Sequence version, `on_reply` re-armed (the out-of-office path).
|
|
6540
|
+
* The jump is not guarded: it re-enters the Step you name even when that
|
|
6541
|
+
* Step already sent, so a message can go out again. To retry a failed Step,
|
|
6542
|
+
* use `enrollments.retry(id)`, which acts only on a failure. Works on
|
|
6543
|
+
* `active`, `paused` and most `finished` Enrollments; else
|
|
6544
|
+
* `ENROLLMENT_NOT_ACTIVE`.
|
|
6211
6545
|
* `POST /v1/enrollments/{id}/resume`
|
|
6212
6546
|
*
|
|
6213
6547
|
* Needs Automate: a Team below that Tier is refused with `TIER_REQUIRED`.
|
|
6214
6548
|
*
|
|
6215
6549
|
* @example
|
|
6216
|
-
* await curviate.enrollments.resume("enr_123"
|
|
6550
|
+
* await curviate.enrollments.resume("enr_123"); // lift your pause
|
|
6551
|
+
* await curviate.enrollments.resume("enr_123", { at: "bump" }); // jump to a Step after an out-of-office reply
|
|
6217
6552
|
*/
|
|
6218
6553
|
resume(enrollmentId, body) {
|
|
6219
6554
|
return this.ctx.request({ method: "POST", path: apiPath`/v1/enrollments/${enrollmentId}/resume`, body });
|
|
6220
6555
|
}
|
|
6221
6556
|
/**
|
|
6222
|
-
* Send
|
|
6223
|
-
*
|
|
6224
|
-
*
|
|
6557
|
+
* Send a custom event to an `active` or `paused` Enrollment whose Sequence
|
|
6558
|
+
* waits for it (`until event.<name>`), for example once your agent wrote
|
|
6559
|
+
* the researched text to the Contact's custom fields. Input only: fires no
|
|
6560
|
+
* webhook. A name no Step of its version awaits is `INVALID_REQUEST`,
|
|
6561
|
+
* listing the awaited names.
|
|
6562
|
+
* `POST /v1/enrollments/{id}/events`
|
|
6563
|
+
*
|
|
6564
|
+
* Needs Automate: a Team below that Tier is refused with `TIER_REQUIRED`.
|
|
6565
|
+
*
|
|
6566
|
+
* @example
|
|
6567
|
+
* await curviate.enrollments.events("enr_123", { name: "research_ready" });
|
|
6568
|
+
*/
|
|
6569
|
+
events(enrollmentId, body) {
|
|
6570
|
+
return this.ctx.request({ method: "POST", path: apiPath`/v1/enrollments/${enrollmentId}/events`, body });
|
|
6571
|
+
}
|
|
6572
|
+
/**
|
|
6573
|
+
* Move an Enrollment to another sending account of its Motion; it restarts
|
|
6574
|
+
* there at the first Step of its Sequence version (one you paused stays
|
|
6575
|
+
* paused until you resume it). `warnings` names each Step that will be
|
|
6576
|
+
* skipped. Refused, nothing changed, with `ACCOUNT_ALREADY_ENGAGED` (its
|
|
6577
|
+
* account already contacted the person beyond a profile view),
|
|
6578
|
+
* `ALREADY_ENROLLED_ON_ACCOUNT`, `KNOWN_CHAT` or `STEP_IN_FLIGHT`.
|
|
6579
|
+
* `POST /v1/enrollments/{id}/reassign`
|
|
6580
|
+
*
|
|
6581
|
+
* Needs Automate: a Team below that Tier is refused with `TIER_REQUIRED`.
|
|
6582
|
+
*
|
|
6583
|
+
* @example
|
|
6584
|
+
* const { warnings } = await curviate.enrollments.reassign("enr_123", { account_id: "acc_456" });
|
|
6585
|
+
*/
|
|
6586
|
+
reassign(enrollmentId, body) {
|
|
6587
|
+
return this.ctx.request({ method: "POST", path: apiPath`/v1/enrollments/${enrollmentId}/reassign`, body });
|
|
6588
|
+
}
|
|
6589
|
+
/**
|
|
6590
|
+
* Pause, resume, stop, retry, send an event to or reassign up to 1,000
|
|
6591
|
+
* Enrollments, by `ids` or by a `filter` of the list's fields (`motion_id`
|
|
6592
|
+
* required). Each is decided alone, as its single call would be, with its
|
|
6593
|
+
* own result (`ok`, or `error` with that call's code; a server error on one
|
|
6594
|
+
* is `INTERNAL` for it); not atomic. List with the same filter to see the
|
|
6595
|
+
* matches first. Below Automate only `pause` and `stop` run.
|
|
6596
|
+
* `POST /v1/enrollments/bulk`
|
|
6597
|
+
*
|
|
6598
|
+
* @example
|
|
6599
|
+
* const { results } = await curviate.enrollments.bulk({
|
|
6600
|
+
* action: "retry",
|
|
6601
|
+
* filter: { motion_id: "mot_123", done_step: "opener", outcome: "failed" },
|
|
6602
|
+
* });
|
|
6603
|
+
*/
|
|
6604
|
+
bulk(body) {
|
|
6605
|
+
return this.ctx.request({ method: "POST", path: "/v1/enrollments/bulk", body });
|
|
6606
|
+
}
|
|
6607
|
+
/**
|
|
6608
|
+
* Retry the failed Step of one Enrollment: the guarded Retry. It acts only
|
|
6609
|
+
* while that Step's latest outcome is `failed`, so a repeated or looped
|
|
6610
|
+
* call never sends a delivered message again. Resolves with the
|
|
6611
|
+
* Enrollment's result when it was resumed; otherwise throws its refusal as
|
|
6612
|
+
* a `CurviateError` (`NOTHING_TO_RETRY` when nothing failed,
|
|
6613
|
+
* `STEP_IN_FLIGHT`, `ENROLLMENT_NOT_ACTIVE`, `RESOURCE_NOT_FOUND`, ...),
|
|
6614
|
+
* with no `httpStatus`. Sent as `bulk` with this one id.
|
|
6615
|
+
* `POST /v1/enrollments/bulk`
|
|
6616
|
+
*
|
|
6617
|
+
* Needs Automate: a Team below that Tier is refused with `TIER_REQUIRED`.
|
|
6618
|
+
*
|
|
6619
|
+
* @example
|
|
6620
|
+
* try {
|
|
6621
|
+
* await curviate.enrollments.retry("enr_123");
|
|
6622
|
+
* } catch (err) {
|
|
6623
|
+
* if (isCurviateError(err) && err.code === "NOTHING_TO_RETRY") console.log("nothing failed");
|
|
6624
|
+
* }
|
|
6625
|
+
*/
|
|
6626
|
+
async retry(enrollmentId) {
|
|
6627
|
+
const res = await this.bulk({ action: "retry", ids: [enrollmentId] });
|
|
6628
|
+
const item = res.results[0];
|
|
6629
|
+
if (item?.outcome === "ok") return item;
|
|
6630
|
+
const known = item?.error && KNOWN_ERROR_CODES.has(item.error.code);
|
|
6631
|
+
const code = known ? item.error.code : "INTERNAL";
|
|
6632
|
+
throw new CurviateError({
|
|
6633
|
+
code,
|
|
6634
|
+
message: item?.error?.message ?? `No retry result for ${enrollmentId}.`,
|
|
6635
|
+
userFixable: false,
|
|
6636
|
+
retryLikelyToSucceed: code === "STEP_IN_FLIGHT"
|
|
6637
|
+
});
|
|
6638
|
+
}
|
|
6639
|
+
/**
|
|
6640
|
+
* Deprecated alias of {@link EnrollmentsResource.events}, answered the same
|
|
6641
|
+
* plus a `Deprecation` header and warning W13; accepted until 2027-01-19.
|
|
6225
6642
|
* `POST /v1/enrollments/{id}/signal`
|
|
6226
6643
|
*
|
|
6227
6644
|
* Needs Automate: a Team below that Tier is refused with `TIER_REQUIRED`.
|
|
6228
6645
|
*
|
|
6646
|
+
* @deprecated Use `enrollments.events`; `signal` stops working on 2027-01-19.
|
|
6647
|
+
*
|
|
6229
6648
|
* @example
|
|
6230
|
-
* await curviate.enrollments.
|
|
6649
|
+
* await curviate.enrollments.events("enr_123", { name: "demo_booked" }); // instead of signal
|
|
6231
6650
|
*/
|
|
6232
6651
|
signal(enrollmentId, body) {
|
|
6233
6652
|
return this.ctx.request({ method: "POST", path: apiPath`/v1/enrollments/${enrollmentId}/signal`, body });
|
|
@@ -7868,6 +8287,42 @@ var step = {
|
|
|
7868
8287
|
tag: (id, name, fields) => build(id, "tag", name, fields)
|
|
7869
8288
|
};
|
|
7870
8289
|
|
|
8290
|
+
// src/sequence/ids.ts
|
|
8291
|
+
var ID_BASE = {
|
|
8292
|
+
"profile.view": "visit",
|
|
8293
|
+
"profile.follow": "follow",
|
|
8294
|
+
"profile.unfollow": "unfollow",
|
|
8295
|
+
"invite.send": "invite",
|
|
8296
|
+
"invite.withdraw": "withdraw",
|
|
8297
|
+
"message.send": "message",
|
|
8298
|
+
"inmail.send": "inmail",
|
|
8299
|
+
"post.react": "like",
|
|
8300
|
+
"post.comment": "comment",
|
|
8301
|
+
"skill.endorse": "endorse",
|
|
8302
|
+
wait: "wait",
|
|
8303
|
+
until: "accepted",
|
|
8304
|
+
if: "check",
|
|
8305
|
+
emit: "event",
|
|
8306
|
+
tag: "tag"
|
|
8307
|
+
};
|
|
8308
|
+
var MAX_ID = 40;
|
|
8309
|
+
var idOf = (v) => isObject(v) && typeof v["id"] === "string" ? v["id"] : null;
|
|
8310
|
+
var fit = (stem, suffix) => stem.slice(0, MAX_ID - suffix.length) + suffix;
|
|
8311
|
+
function newStepId(doc, kind, beforeId) {
|
|
8312
|
+
const taken = /* @__PURE__ */ new Set();
|
|
8313
|
+
for (const s of isObject(doc) && Array.isArray(doc["steps"]) ? doc["steps"] : []) {
|
|
8314
|
+
const id = idOf(s);
|
|
8315
|
+
if (id) taken.add(id);
|
|
8316
|
+
const vs = isObject(s) ? s["variants"] : void 0;
|
|
8317
|
+
if (Array.isArray(vs)) {
|
|
8318
|
+
for (const v of vs) if (idOf(v)) taken.add(idOf(v));
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
8321
|
+
const base = kind === "wait" && beforeId !== void 0 ? `w_${beforeId}` : ID_BASE[kind];
|
|
8322
|
+
if (!taken.has(fit(base, ""))) return fit(base, "");
|
|
8323
|
+
for (let n = 2; ; n++) if (!taken.has(fit(base, `_${n}`))) return fit(base, `_${n}`);
|
|
8324
|
+
}
|
|
8325
|
+
|
|
7871
8326
|
// src/webhooks.ts
|
|
7872
8327
|
var WebhookSignatureError = class _WebhookSignatureError extends Error {
|
|
7873
8328
|
constructor(reason, message) {
|
|
@@ -8012,6 +8467,7 @@ export {
|
|
|
8012
8467
|
WebhookSignatureError,
|
|
8013
8468
|
constructEvent,
|
|
8014
8469
|
isCurviateError,
|
|
8470
|
+
newStepId,
|
|
8015
8471
|
step,
|
|
8016
8472
|
validateSequence
|
|
8017
8473
|
};
|