@wfgraph/plugins 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/index.d.ts +2 -599
- package/dist/index.js +4 -930
- package/dist/{test-S28s7Z3a.js → test-DN4ZApY4.js} +1 -1
- package/dist/ui.d.ts +0 -8
- package/dist/ui.js +24 -60
- package/package.json +4 -5
- package/dist/client-DkPUX0ps.js +0 -50
- package/dist/test-DzstoPiR.js +0 -35
- package/dist/{client-DJGI9f53.js → client-LRwmIiPU.js} +1 -1
package/dist/index.js
CHANGED
|
@@ -1,938 +1,13 @@
|
|
|
1
|
-
import { n as createAcuityClient, r as createAcuitySdk, t as callAcuity } from "./client-DkPUX0ps.js";
|
|
2
1
|
import { n as getClerkApiErrorMessage, r as toClerkApiUser, t as createClerkBackendClient } from "./client-BEcFnbOB.js";
|
|
3
2
|
import { t as describeLinearFailure } from "./errors-C4i6-DQE.js";
|
|
4
3
|
import { i as sendResendEmail, t as describeResendFailure } from "./client-C2NIQxhb.js";
|
|
5
4
|
import { n as describeSlackFailure, t as callSlack } from "./client-DDDCNLId.js";
|
|
6
|
-
import { n as describeTwilioFailure, t as createTwilioMessage } from "./client-
|
|
5
|
+
import { n as describeTwilioFailure, t as createTwilioMessage } from "./client-LRwmIiPU.js";
|
|
7
6
|
import { StepFailure, defineIntegration, readAs } from "@wfgraph/core/plugin";
|
|
8
|
-
import { Effect, Result, Schema, SchemaTransformation } from "effect";
|
|
9
7
|
import { omitBy } from "es-toolkit/object";
|
|
10
8
|
import { isNil } from "es-toolkit/predicate";
|
|
9
|
+
import { Effect, Result, Schema, SchemaTransformation } from "effect";
|
|
11
10
|
import { LinearClient } from "@linear/sdk";
|
|
12
|
-
//#region src/acuity/payloads.ts
|
|
13
|
-
/**
|
|
14
|
-
* The wire shapes Acuity's API answers with.
|
|
15
|
-
*
|
|
16
|
-
* Modelled on what the API sends rather than on the SDK's types: the SDK casts the
|
|
17
|
-
* response without validating it, so these describe a system's JSON directly. This
|
|
18
|
-
* layer is owned by no single action -- every action whose output nests an
|
|
19
|
-
* appointment, an appointment type, or an availability slot reads from here.
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* A field the SDK types as `unknown`, kept rather than trimmed.
|
|
23
|
-
*
|
|
24
|
-
* The encode that puts a step's answer on the wire keeps only what the schema
|
|
25
|
-
* admits, so a field left undeclared would not reach the run log at all. There is
|
|
26
|
-
* nothing to say about this one's shape, which is what `Unknown` says; encoding it
|
|
27
|
-
* still refuses a value that is not JSON, so a memoized step result stays safe.
|
|
28
|
-
* The picker has no path to offer for it and drops it, which is correct: nothing
|
|
29
|
-
* downstream can address a shape nobody described.
|
|
30
|
-
*/
|
|
31
|
-
const opaqueJson = Schema.optional(Schema.Unknown);
|
|
32
|
-
/**
|
|
33
|
-
* A whole number the editor offers, described so the picker can show it.
|
|
34
|
-
*
|
|
35
|
-
* A bare `Schema.Number` describes itself as a number or one of the strings
|
|
36
|
-
* "Infinity", "-Infinity" and "NaN", which the field reader cannot use, so a
|
|
37
|
-
* numeric field written without the check drops out of the derived list.
|
|
38
|
-
*/
|
|
39
|
-
function describedNumber(description) {
|
|
40
|
-
return Schema.Number.annotate({ description }).check(Schema.isFinite());
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* A field of a system's payload, as an output schema may describe it.
|
|
44
|
-
*
|
|
45
|
-
* `optionalKey(NullOr(...))` is the one spelling that survives everything a system
|
|
46
|
-
* sends: an absent key, an explicit `null`, or the value. `optional(X)` refuses the
|
|
47
|
-
* null and `NullishOr(X)` refuses the absent key, and either refusal fails the
|
|
48
|
-
* encode and so the whole step. The picker reads the same `nullable` off this as it
|
|
49
|
-
* did off the stricter spellings, so nothing is lost by tolerating both.
|
|
50
|
-
*/
|
|
51
|
-
function externalField(field) {
|
|
52
|
-
return Schema.optionalKey(Schema.NullOr(field));
|
|
53
|
-
}
|
|
54
|
-
/** The same tolerance for a whole number the picker offers. */
|
|
55
|
-
function externalNumber(description) {
|
|
56
|
-
return externalField(describedNumber(description));
|
|
57
|
-
}
|
|
58
|
-
/** The same tolerance for a string the picker offers. */
|
|
59
|
-
function externalText(description) {
|
|
60
|
-
return externalField(Schema.String.annotate({ description }));
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* One answer a client gave on an intake form.
|
|
64
|
-
*
|
|
65
|
-
* Modelled on what the API sends rather than on the SDK's type: the SDK casts the
|
|
66
|
-
* response without validating it, and it is missing a level. `value` is text, a
|
|
67
|
-
* list of text, or nothing, which is one union too many for the picker to name a
|
|
68
|
-
* path for; it is declared anyway, because the encode keeps only what the schema
|
|
69
|
-
* admits and an answer nobody can address by path is still an answer somebody reads
|
|
70
|
-
* in the run log.
|
|
71
|
-
*/
|
|
72
|
-
const formAnswerSchema = Schema.Struct({
|
|
73
|
-
id: externalNumber("Answer ID"),
|
|
74
|
-
fieldID: externalNumber("Form field ID"),
|
|
75
|
-
name: externalText("Question the client answered"),
|
|
76
|
-
value: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.String, Schema.mutable(Schema.Array(Schema.String))])))
|
|
77
|
-
});
|
|
78
|
-
/**
|
|
79
|
-
* One intake form on an appointment, holding the answers given on it.
|
|
80
|
-
*
|
|
81
|
-
* The nesting is what the wire has and the SDK's type does not: `forms` is a list
|
|
82
|
-
* of forms, each carrying its own `values` list of answers. A schema describing
|
|
83
|
-
* those answers one level up fails its encode, and so fails the step, on every
|
|
84
|
-
* appointment that has an intake form.
|
|
85
|
-
*/
|
|
86
|
-
const appointmentFormSchema = Schema.Struct({
|
|
87
|
-
id: externalNumber("Form ID"),
|
|
88
|
-
name: externalText("Form name"),
|
|
89
|
-
values: Schema.optionalKey(Schema.NullOr(Schema.mutable(Schema.Array(formAnswerSchema)).annotate({ description: "Answers given on this form" })))
|
|
90
|
-
});
|
|
91
|
-
/**
|
|
92
|
-
* Acuity's appointment, as the API sends it.
|
|
93
|
-
*
|
|
94
|
-
* Two fields are required, because the actions read them to answer with and a
|
|
95
|
-
* payload without them is not an appointment: everything else is an `externalField`.
|
|
96
|
-
* That is deliberate rather than lazy. This describes somebody else's JSON, the SDK
|
|
97
|
-
* validates none of it, and a field this schema insists on that a real payload
|
|
98
|
-
* omits fails the encode and so fails the step.
|
|
99
|
-
*/
|
|
100
|
-
const appointmentSchema = Schema.Struct({
|
|
101
|
-
id: describedNumber("Appointment ID"),
|
|
102
|
-
datetime: Schema.String.annotate({ description: "Appointment start, ISO 8601 with offset" }),
|
|
103
|
-
firstName: externalText("Client first name"),
|
|
104
|
-
lastName: externalText("Client last name"),
|
|
105
|
-
email: externalText("Client email address"),
|
|
106
|
-
phone: externalText("Client phone number"),
|
|
107
|
-
date: externalText("Appointment date, as Acuity writes it for people"),
|
|
108
|
-
endDate: externalText("Appointment end date, as Acuity writes it for people"),
|
|
109
|
-
time: externalText("Appointment start time"),
|
|
110
|
-
endTime: externalText("Appointment end time"),
|
|
111
|
-
duration: externalText("Duration in minutes"),
|
|
112
|
-
timezone: externalText("The appointment's IANA timezone"),
|
|
113
|
-
calendarTimezone: externalText("IANA timezone of the calendar that owns it"),
|
|
114
|
-
type: externalText("Appointment type name"),
|
|
115
|
-
appointmentTypeID: externalNumber("Appointment type ID"),
|
|
116
|
-
calendar: externalText("Calendar name"),
|
|
117
|
-
calendarID: externalNumber("Calendar ID"),
|
|
118
|
-
price: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite]))),
|
|
119
|
-
paid: externalText("Whether the appointment is paid"),
|
|
120
|
-
notes: externalText("Appointment notes"),
|
|
121
|
-
forms: Schema.optionalKey(Schema.NullOr(Schema.mutable(Schema.Array(appointmentFormSchema)).annotate({ description: "Intake forms on the appointment" }))),
|
|
122
|
-
noShow: externalField(Schema.Boolean.annotate({ description: "Marked as a no-show" })),
|
|
123
|
-
canceled: externalField(Schema.Boolean.annotate({ description: "Whether the appointment is canceled" })),
|
|
124
|
-
certificate: opaqueJson,
|
|
125
|
-
package: opaqueJson,
|
|
126
|
-
scheduledBy: externalText("Who scheduled it, or null for a client not logged in")
|
|
127
|
-
});
|
|
128
|
-
/**
|
|
129
|
-
* Acuity's appointment type, as the API sends it.
|
|
130
|
-
*
|
|
131
|
-
* Same rule as the appointment above: `id` is what identifies one, and everything
|
|
132
|
-
* else tolerates an absent key and an explicit null, because this describes a
|
|
133
|
-
* system's JSON. `price` arrives as a string on some types and a number on others,
|
|
134
|
-
* which the picker has no single type for and drops; it is declared so that the
|
|
135
|
-
* encode keeps it.
|
|
136
|
-
*/
|
|
137
|
-
const appointmentTypeSchema = Schema.Struct({
|
|
138
|
-
id: describedNumber("Appointment type ID"),
|
|
139
|
-
name: externalText("Appointment type name"),
|
|
140
|
-
active: externalField(Schema.Boolean.annotate({ description: "Whether the type is bookable" })),
|
|
141
|
-
description: externalText("Appointment type description"),
|
|
142
|
-
duration: externalNumber("Duration in minutes"),
|
|
143
|
-
category: externalText("Category name"),
|
|
144
|
-
color: externalText("Calendar colour"),
|
|
145
|
-
private: externalField(Schema.Boolean.annotate({ description: "Whether the type is hidden from the public scheduler" })),
|
|
146
|
-
type: externalField(Schema.Literals([
|
|
147
|
-
"service",
|
|
148
|
-
"class",
|
|
149
|
-
"series"
|
|
150
|
-
]).annotate({ description: "Kind of type: service, class, or series" })),
|
|
151
|
-
classSize: externalNumber("Seats in a class, if it is one"),
|
|
152
|
-
paddingAfter: externalNumber("Minutes of padding after the appointment"),
|
|
153
|
-
paddingBefore: externalNumber("Minutes of padding before the appointment"),
|
|
154
|
-
calendarIDs: Schema.optionalKey(Schema.NullOr(Schema.mutable(Schema.Array(Schema.Finite)).annotate({ description: "Calendars offering this type" }))),
|
|
155
|
-
price: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite])))
|
|
156
|
-
});
|
|
157
|
-
const availabilityDateSchema = Schema.Struct({ date: Schema.String.annotate({ description: "Available date, YYYY-MM-DD" }) });
|
|
158
|
-
const availabilityTimeSlotSchema = Schema.Struct({ time: Schema.String.annotate({ description: "Bookable slot, ISO 8601 with offset" }) });
|
|
159
|
-
//#endregion
|
|
160
|
-
//#region src/acuity/shared.ts
|
|
161
|
-
/**
|
|
162
|
-
* Turning what a config field holds into what Acuity's API takes.
|
|
163
|
-
*
|
|
164
|
-
* Acuity wants whole numbers, real booleans and a list of form answers where
|
|
165
|
-
* the editor gives a step text, so each of these reads one field and says, in
|
|
166
|
-
* the words the run log shows, what is wrong with it. They answer an `Effect`,
|
|
167
|
-
* so a step names the field and moves on -- `yield*` is the whole of the
|
|
168
|
-
* handling, where a result object needed a check and an early return after
|
|
169
|
-
* every single one.
|
|
170
|
-
*
|
|
171
|
-
* The messages are the reason the reading stays here rather than moving into
|
|
172
|
-
* the input schemas: a decode that fails inside `defineStep` reports itself as
|
|
173
|
-
* an invalid configuration for the action, and what an author needs to read is
|
|
174
|
-
* which box holds what. The two schema declarations at the top are the other
|
|
175
|
-
* half of the same boundary -- what an input schema says a config field may
|
|
176
|
-
* hold, before one of these parsers reads it.
|
|
177
|
-
*/
|
|
178
|
-
/** Every config field arrives as text, so this is what most of them look like. */
|
|
179
|
-
const optionalText = Schema.optionalKey(Schema.String);
|
|
180
|
-
/**
|
|
181
|
-
* A number a config field carries.
|
|
182
|
-
*
|
|
183
|
-
* A `number` config field may be stored as a number and a template resolves to
|
|
184
|
-
* text, so both arrive and the step parses whichever it got.
|
|
185
|
-
*/
|
|
186
|
-
const optionalNumeric = Schema.optionalKey(Schema.Union([Schema.String, Schema.Finite]));
|
|
187
|
-
/** A config value as text, or nothing when the field was left empty. */
|
|
188
|
-
function normalizeRawValue(value) {
|
|
189
|
-
if (value === void 0) return;
|
|
190
|
-
if (typeof value === "number") return String(value);
|
|
191
|
-
const trimmed = value.trim();
|
|
192
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
193
|
-
}
|
|
194
|
-
function positiveIntegerOr(normalized, fieldLabel) {
|
|
195
|
-
const parsed = Number(normalized);
|
|
196
|
-
return Number.isInteger(parsed) && parsed > 0 ? Effect.succeed(parsed) : Effect.fail(new StepFailure({ message: `${fieldLabel} must be a positive integer.` }));
|
|
197
|
-
}
|
|
198
|
-
function requiredInteger(value, fieldLabel) {
|
|
199
|
-
const normalized = normalizeRawValue(value);
|
|
200
|
-
return normalized ? positiveIntegerOr(normalized, fieldLabel) : Effect.fail(new StepFailure({ message: `${fieldLabel} is required.` }));
|
|
201
|
-
}
|
|
202
|
-
function optionalInteger(value, fieldLabel) {
|
|
203
|
-
const normalized = normalizeRawValue(value);
|
|
204
|
-
return normalized ? positiveIntegerOr(normalized, fieldLabel) : Effect.succeed(void 0);
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* A select whose options are "", "true" and "false", read as the boolean Acuity
|
|
208
|
-
* takes. An empty choice is the absent one, which is how a step says "leave
|
|
209
|
-
* Acuity's own default alone".
|
|
210
|
-
*/
|
|
211
|
-
function optionalBoolean(value, fieldLabel) {
|
|
212
|
-
if (value === void 0 || value === "") return Effect.succeed(void 0);
|
|
213
|
-
const normalized = value.trim().toLowerCase();
|
|
214
|
-
if (normalized === "true") return Effect.succeed(true);
|
|
215
|
-
if (normalized === "false") return Effect.succeed(false);
|
|
216
|
-
return Effect.fail(new StepFailure({ message: `${fieldLabel} must be true, false, or empty.` }));
|
|
217
|
-
}
|
|
218
|
-
function optionalIntegerList(value, fieldLabel) {
|
|
219
|
-
const normalized = normalizeRawValue(value);
|
|
220
|
-
if (!normalized) return Effect.succeed(void 0);
|
|
221
|
-
const entries = normalized.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
222
|
-
if (entries.length === 0) return Effect.succeed(void 0);
|
|
223
|
-
const parsed = entries.map((entry) => Number(entry));
|
|
224
|
-
return parsed.some((entry) => !Number.isInteger(entry) || entry <= 0) ? Effect.fail(new StepFailure({ message: `${fieldLabel} must contain only positive integers (comma separated).` })) : Effect.succeed(parsed);
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Acuity takes the answers to a booking form's custom questions as fieldID/value
|
|
228
|
-
* pairs. Workflow authors type those pairs as a JSON string into the node config,
|
|
229
|
-
* so this schema is the boundary between that text and the Acuity client.
|
|
230
|
-
*
|
|
231
|
-
* `errors: "all"` because what the decode says about the text is what the author
|
|
232
|
-
* reads back in the step's failure: naming one mistake at a time would send them
|
|
233
|
-
* round the loop once per mistake.
|
|
234
|
-
*/
|
|
235
|
-
const acuityCustomFieldsSchema = Schema.mutable(Schema.Array(Schema.Struct({
|
|
236
|
-
fieldID: Schema.Finite.check(Schema.isInt(), Schema.isGreaterThan(0)),
|
|
237
|
-
value: Schema.Union([Schema.String, Schema.mutable(Schema.Array(Schema.String))])
|
|
238
|
-
})));
|
|
239
|
-
const decodeCustomFields = Schema.decodeUnknownResult(acuityCustomFieldsSchema, { errors: "all" });
|
|
240
|
-
function optionalCustomFields(value) {
|
|
241
|
-
const normalized = normalizeRawValue(value);
|
|
242
|
-
if (!normalized) return Effect.succeed(void 0);
|
|
243
|
-
let parsed;
|
|
244
|
-
try {
|
|
245
|
-
parsed = JSON.parse(normalized);
|
|
246
|
-
} catch {
|
|
247
|
-
return Effect.fail(new StepFailure({ message: "Custom Fields JSON must be valid JSON in the format [{\"fieldID\":1234,\"value\":\"text\"}]." }));
|
|
248
|
-
}
|
|
249
|
-
const result = decodeCustomFields(parsed);
|
|
250
|
-
return Result.isFailure(result) ? Effect.fail(new StepFailure({ message: `Custom Fields JSON must be an array of objects with numeric fieldID and value (string or string[]). ${result.failure.message}` })) : Effect.succeed(result.success);
|
|
251
|
-
}
|
|
252
|
-
//#endregion
|
|
253
|
-
//#region src/acuity/index.ts
|
|
254
|
-
const acuityCredentialFields = {
|
|
255
|
-
ACUITY_USER_ID: {
|
|
256
|
-
label: "User ID",
|
|
257
|
-
type: "text",
|
|
258
|
-
placeholder: "12345678",
|
|
259
|
-
helpText: "Your Acuity User ID used as Basic auth username."
|
|
260
|
-
},
|
|
261
|
-
ACUITY_API_KEY: {
|
|
262
|
-
label: "API Key",
|
|
263
|
-
type: "password",
|
|
264
|
-
placeholder: "••••••••",
|
|
265
|
-
helpText: "Your Acuity API key used as Basic auth password."
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
/** What a tri-state Acuity flag offers: leave it alone, or say yes or no. */
|
|
269
|
-
const yesNoSelect = [
|
|
270
|
-
{
|
|
271
|
-
value: "",
|
|
272
|
-
label: "Default"
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
value: "true",
|
|
276
|
-
label: "Yes"
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
value: "false",
|
|
280
|
-
label: "No"
|
|
281
|
-
}
|
|
282
|
-
];
|
|
283
|
-
/**
|
|
284
|
-
* The two flags every mutating Acuity action takes, written once.
|
|
285
|
-
*
|
|
286
|
-
* `satisfies` with the keys spelled out is what keeps each `key` a literal type:
|
|
287
|
-
* an action checks a field's key against its input schema, and a widened `string`
|
|
288
|
-
* names no key at all. All three actions that use this group declare both.
|
|
289
|
-
*/
|
|
290
|
-
const mutationFlagsGroup = {
|
|
291
|
-
type: "group",
|
|
292
|
-
label: "Mutation Flags",
|
|
293
|
-
fields: [{
|
|
294
|
-
key: "admin",
|
|
295
|
-
label: "Run as Admin",
|
|
296
|
-
type: "select",
|
|
297
|
-
defaultValue: "",
|
|
298
|
-
options: yesNoSelect
|
|
299
|
-
}, {
|
|
300
|
-
key: "noEmail",
|
|
301
|
-
label: "Suppress Acuity Emails",
|
|
302
|
-
type: "select",
|
|
303
|
-
defaultValue: "",
|
|
304
|
-
options: yesNoSelect
|
|
305
|
-
}]
|
|
306
|
-
};
|
|
307
|
-
const listAppointmentTypesInput = Schema.Struct({});
|
|
308
|
-
const listAppointmentTypesOutput = Schema.Struct({
|
|
309
|
-
appointmentTypes: Schema.mutable(Schema.Array(appointmentTypeSchema)).annotate({ description: "Array of appointment types" }),
|
|
310
|
-
count: describedNumber("Number of appointment types returned")
|
|
311
|
-
});
|
|
312
|
-
const listAppointmentsInput = Schema.Struct({
|
|
313
|
-
appointmentTypeId: optionalText,
|
|
314
|
-
calendarId: optionalText,
|
|
315
|
-
minDate: optionalText,
|
|
316
|
-
maxDate: optionalText,
|
|
317
|
-
timezone: optionalText,
|
|
318
|
-
email: optionalText,
|
|
319
|
-
phone: optionalText,
|
|
320
|
-
canceled: optionalText,
|
|
321
|
-
showAll: optionalText,
|
|
322
|
-
limit: optionalNumeric,
|
|
323
|
-
page: optionalNumeric
|
|
324
|
-
});
|
|
325
|
-
const listAppointmentsOutput = Schema.Struct({
|
|
326
|
-
appointments: Schema.mutable(Schema.Array(appointmentSchema)).annotate({ description: "Array of appointments" }),
|
|
327
|
-
count: describedNumber("Number of appointments returned")
|
|
328
|
-
});
|
|
329
|
-
const getAppointmentInput = Schema.Struct({
|
|
330
|
-
appointmentId: Schema.String,
|
|
331
|
-
pastFormAnswers: optionalText
|
|
332
|
-
});
|
|
333
|
-
const getAppointmentOutput = Schema.Struct({
|
|
334
|
-
appointment: appointmentSchema.annotate({ description: "The appointment details" }),
|
|
335
|
-
id: describedNumber("Appointment ID"),
|
|
336
|
-
datetime: Schema.String.annotate({ description: "Appointment datetime" })
|
|
337
|
-
});
|
|
338
|
-
const getAvailabilityDatesInput = Schema.Struct({
|
|
339
|
-
month: Schema.String,
|
|
340
|
-
appointmentTypeId: Schema.String,
|
|
341
|
-
calendarId: optionalText,
|
|
342
|
-
timezone: optionalText
|
|
343
|
-
});
|
|
344
|
-
const getAvailabilityDatesOutput = Schema.Struct({
|
|
345
|
-
dates: Schema.mutable(Schema.Array(availabilityDateSchema)).annotate({ description: "Available dates" }),
|
|
346
|
-
count: describedNumber("Number of dates returned")
|
|
347
|
-
});
|
|
348
|
-
const getAvailabilityTimesInput = Schema.Struct({
|
|
349
|
-
date: Schema.String,
|
|
350
|
-
appointmentTypeId: Schema.String,
|
|
351
|
-
calendarId: optionalText,
|
|
352
|
-
timezone: optionalText,
|
|
353
|
-
/** Comma-separated, which is how a single text field carries a list. */
|
|
354
|
-
ignoreAppointmentIds: optionalText
|
|
355
|
-
});
|
|
356
|
-
const getAvailabilityTimesOutput = Schema.Struct({
|
|
357
|
-
slots: Schema.mutable(Schema.Array(availabilityTimeSlotSchema)).annotate({ description: "Available time slots" }),
|
|
358
|
-
count: describedNumber("Number of slots returned")
|
|
359
|
-
});
|
|
360
|
-
const createAppointmentInput = Schema.Struct({
|
|
361
|
-
datetime: Schema.String,
|
|
362
|
-
appointmentTypeId: Schema.String,
|
|
363
|
-
firstName: Schema.String,
|
|
364
|
-
lastName: Schema.String,
|
|
365
|
-
email: Schema.String,
|
|
366
|
-
phone: Schema.String,
|
|
367
|
-
calendarId: optionalText,
|
|
368
|
-
notes: optionalText,
|
|
369
|
-
smsOptIn: optionalText,
|
|
370
|
-
/** JSON the workflow author typed, parsed by the step. */
|
|
371
|
-
customFieldsJson: optionalText,
|
|
372
|
-
admin: optionalText,
|
|
373
|
-
noEmail: optionalText
|
|
374
|
-
});
|
|
375
|
-
const createAppointmentOutput = Schema.Struct({
|
|
376
|
-
appointment: appointmentSchema.annotate({ description: "Created appointment payload" }),
|
|
377
|
-
id: describedNumber("Created appointment ID"),
|
|
378
|
-
datetime: Schema.String.annotate({ description: "Created appointment datetime" })
|
|
379
|
-
});
|
|
380
|
-
const rescheduleAppointmentInput = Schema.Struct({
|
|
381
|
-
appointmentId: Schema.String,
|
|
382
|
-
datetime: Schema.String,
|
|
383
|
-
calendarId: optionalText,
|
|
384
|
-
admin: optionalText,
|
|
385
|
-
noEmail: optionalText
|
|
386
|
-
});
|
|
387
|
-
const rescheduleAppointmentOutput = Schema.Struct({
|
|
388
|
-
appointment: appointmentSchema.annotate({ description: "Rescheduled appointment payload" }),
|
|
389
|
-
id: describedNumber("Appointment ID"),
|
|
390
|
-
datetime: Schema.String.annotate({ description: "New appointment datetime" })
|
|
391
|
-
});
|
|
392
|
-
const cancelAppointmentInput = Schema.Struct({
|
|
393
|
-
appointmentId: Schema.String,
|
|
394
|
-
cancelNote: optionalText,
|
|
395
|
-
noShow: optionalText,
|
|
396
|
-
admin: optionalText,
|
|
397
|
-
noEmail: optionalText
|
|
398
|
-
});
|
|
399
|
-
const cancelAppointmentOutput = Schema.Struct({
|
|
400
|
-
appointment: appointmentSchema.annotate({ description: "Canceled appointment payload" }),
|
|
401
|
-
id: describedNumber("Canceled appointment ID"),
|
|
402
|
-
canceled: Schema.optional(Schema.Boolean.annotate({ description: "Cancellation flag" }))
|
|
403
|
-
});
|
|
404
|
-
function createAcuity(createSdk) {
|
|
405
|
-
return defineIntegration({
|
|
406
|
-
type: "acuity",
|
|
407
|
-
label: "Acuity",
|
|
408
|
-
description: "Manage appointments and availability in Acuity Scheduling",
|
|
409
|
-
credentials: acuityCredentialFields,
|
|
410
|
-
test: async () => {
|
|
411
|
-
const { testAcuity } = await import("./test-DzstoPiR.js");
|
|
412
|
-
return (creds) => testAcuity(creds, createSdk);
|
|
413
|
-
},
|
|
414
|
-
actions: {
|
|
415
|
-
"list-appointment-types": {
|
|
416
|
-
label: "List Appointment Types",
|
|
417
|
-
description: "Fetch appointment types configured in Acuity",
|
|
418
|
-
input: listAppointmentTypesInput,
|
|
419
|
-
output: listAppointmentTypesOutput,
|
|
420
|
-
configFields: [],
|
|
421
|
-
/** The action takes no configuration, so the whole of it is the read. */
|
|
422
|
-
handler: Effect.fn(function* (bag) {
|
|
423
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
424
|
-
const appointmentTypes = yield* bag.step.run("list-types", callAcuity("Failed to list appointment types.", () => client.appointments.types()));
|
|
425
|
-
return {
|
|
426
|
-
appointmentTypes,
|
|
427
|
-
count: appointmentTypes.length
|
|
428
|
-
};
|
|
429
|
-
})
|
|
430
|
-
},
|
|
431
|
-
"list-appointments": {
|
|
432
|
-
label: "List Appointments",
|
|
433
|
-
description: "List appointments with optional filters",
|
|
434
|
-
input: listAppointmentsInput,
|
|
435
|
-
output: listAppointmentsOutput,
|
|
436
|
-
configFields: [
|
|
437
|
-
{
|
|
438
|
-
key: "appointmentTypeId",
|
|
439
|
-
label: "Appointment Type ID",
|
|
440
|
-
type: "template-input",
|
|
441
|
-
placeholder: "12345"
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
key: "calendarId",
|
|
445
|
-
label: "Calendar ID",
|
|
446
|
-
type: "template-input",
|
|
447
|
-
placeholder: "67890"
|
|
448
|
-
},
|
|
449
|
-
{
|
|
450
|
-
key: "minDate",
|
|
451
|
-
label: "Min Date (YYYY-MM-DD)",
|
|
452
|
-
type: "template-input",
|
|
453
|
-
placeholder: "2026-03-01"
|
|
454
|
-
},
|
|
455
|
-
{
|
|
456
|
-
key: "maxDate",
|
|
457
|
-
label: "Max Date (YYYY-MM-DD)",
|
|
458
|
-
type: "template-input",
|
|
459
|
-
placeholder: "2026-03-31"
|
|
460
|
-
},
|
|
461
|
-
{
|
|
462
|
-
key: "timezone",
|
|
463
|
-
label: "Timezone",
|
|
464
|
-
type: "template-input",
|
|
465
|
-
placeholder: "America/New_York"
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
type: "group",
|
|
469
|
-
label: "Additional Filters",
|
|
470
|
-
fields: [
|
|
471
|
-
{
|
|
472
|
-
key: "email",
|
|
473
|
-
label: "Client Email",
|
|
474
|
-
type: "template-input",
|
|
475
|
-
placeholder: "person@example.com"
|
|
476
|
-
},
|
|
477
|
-
{
|
|
478
|
-
key: "phone",
|
|
479
|
-
label: "Client Phone",
|
|
480
|
-
type: "template-input",
|
|
481
|
-
placeholder: "+15551234567"
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
key: "canceled",
|
|
485
|
-
label: "Only Canceled",
|
|
486
|
-
type: "select",
|
|
487
|
-
defaultValue: "",
|
|
488
|
-
options: [
|
|
489
|
-
{
|
|
490
|
-
value: "",
|
|
491
|
-
label: "No filter"
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
value: "true",
|
|
495
|
-
label: "Yes"
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
value: "false",
|
|
499
|
-
label: "No"
|
|
500
|
-
}
|
|
501
|
-
]
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
key: "showAll",
|
|
505
|
-
label: "Include Inactive",
|
|
506
|
-
type: "select",
|
|
507
|
-
defaultValue: "",
|
|
508
|
-
options: yesNoSelect
|
|
509
|
-
},
|
|
510
|
-
{
|
|
511
|
-
key: "limit",
|
|
512
|
-
label: "Limit",
|
|
513
|
-
type: "number",
|
|
514
|
-
min: 1,
|
|
515
|
-
defaultValue: "50"
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
key: "page",
|
|
519
|
-
label: "Page",
|
|
520
|
-
type: "number",
|
|
521
|
-
min: 1,
|
|
522
|
-
defaultValue: "1"
|
|
523
|
-
}
|
|
524
|
-
]
|
|
525
|
-
}
|
|
526
|
-
],
|
|
527
|
-
/**
|
|
528
|
-
* What this step decides is which filters the config asked for, and each
|
|
529
|
-
* field says for itself what is wrong with it.
|
|
530
|
-
*/
|
|
531
|
-
handler: Effect.fn(function* (bag) {
|
|
532
|
-
const { input } = bag;
|
|
533
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
534
|
-
const appointmentTypeID = yield* optionalInteger(input.appointmentTypeId, "Appointment Type ID");
|
|
535
|
-
const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
|
|
536
|
-
const limit = yield* optionalInteger(input.limit, "Limit");
|
|
537
|
-
const page = yield* optionalInteger(input.page, "Page");
|
|
538
|
-
const canceled = yield* optionalBoolean(input.canceled, "Only Canceled");
|
|
539
|
-
const showall = yield* optionalBoolean(input.showAll, "Include Inactive");
|
|
540
|
-
const params = {
|
|
541
|
-
appointmentTypeID,
|
|
542
|
-
calendarID,
|
|
543
|
-
minDate: input.minDate,
|
|
544
|
-
maxDate: input.maxDate,
|
|
545
|
-
timezone: input.timezone,
|
|
546
|
-
email: input.email,
|
|
547
|
-
phone: input.phone,
|
|
548
|
-
canceled,
|
|
549
|
-
showall,
|
|
550
|
-
limit,
|
|
551
|
-
page
|
|
552
|
-
};
|
|
553
|
-
const appointments = yield* bag.step.run("list-appointments", callAcuity("Failed to list appointments.", () => client.appointments.list(params)));
|
|
554
|
-
return {
|
|
555
|
-
appointments,
|
|
556
|
-
count: appointments.length
|
|
557
|
-
};
|
|
558
|
-
})
|
|
559
|
-
},
|
|
560
|
-
"get-appointment": {
|
|
561
|
-
label: "Get Appointment",
|
|
562
|
-
description: "Fetch one appointment by ID",
|
|
563
|
-
input: getAppointmentInput,
|
|
564
|
-
output: getAppointmentOutput,
|
|
565
|
-
configFields: [{
|
|
566
|
-
key: "appointmentId",
|
|
567
|
-
label: "Appointment ID",
|
|
568
|
-
type: "template-input",
|
|
569
|
-
placeholder: "123456789",
|
|
570
|
-
required: true
|
|
571
|
-
}, {
|
|
572
|
-
key: "pastFormAnswers",
|
|
573
|
-
label: "Include Past Form Answers",
|
|
574
|
-
type: "select",
|
|
575
|
-
defaultValue: "false",
|
|
576
|
-
options: [{
|
|
577
|
-
value: "false",
|
|
578
|
-
label: "No"
|
|
579
|
-
}, {
|
|
580
|
-
value: "true",
|
|
581
|
-
label: "Yes"
|
|
582
|
-
}]
|
|
583
|
-
}],
|
|
584
|
-
handler: Effect.fn(function* (bag) {
|
|
585
|
-
const { input } = bag;
|
|
586
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
587
|
-
const appointmentId = yield* requiredInteger(input.appointmentId, "Appointment ID");
|
|
588
|
-
const pastFormAnswers = yield* optionalBoolean(input.pastFormAnswers, "Include Past Form Answers");
|
|
589
|
-
const appointment = yield* bag.step.run("get-appointment", callAcuity("Failed to fetch appointment.", () => client.appointments.get(appointmentId, { pastFormAnswers })));
|
|
590
|
-
return {
|
|
591
|
-
appointment,
|
|
592
|
-
id: appointment.id,
|
|
593
|
-
datetime: appointment.datetime
|
|
594
|
-
};
|
|
595
|
-
})
|
|
596
|
-
},
|
|
597
|
-
"get-availability-dates": {
|
|
598
|
-
label: "Get Availability Dates",
|
|
599
|
-
description: "List dates that still have available slots",
|
|
600
|
-
input: getAvailabilityDatesInput,
|
|
601
|
-
output: getAvailabilityDatesOutput,
|
|
602
|
-
configFields: [
|
|
603
|
-
{
|
|
604
|
-
key: "month",
|
|
605
|
-
label: "Month (YYYY-MM)",
|
|
606
|
-
type: "template-input",
|
|
607
|
-
placeholder: "2026-03",
|
|
608
|
-
required: true
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
key: "appointmentTypeId",
|
|
612
|
-
label: "Appointment Type ID",
|
|
613
|
-
type: "template-input",
|
|
614
|
-
placeholder: "12345",
|
|
615
|
-
required: true
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
key: "calendarId",
|
|
619
|
-
label: "Calendar ID",
|
|
620
|
-
type: "template-input",
|
|
621
|
-
placeholder: "67890"
|
|
622
|
-
},
|
|
623
|
-
{
|
|
624
|
-
key: "timezone",
|
|
625
|
-
label: "Timezone",
|
|
626
|
-
type: "template-input",
|
|
627
|
-
placeholder: "America/New_York"
|
|
628
|
-
}
|
|
629
|
-
],
|
|
630
|
-
handler: Effect.fn(function* (bag) {
|
|
631
|
-
const { input } = bag;
|
|
632
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
633
|
-
const appointmentTypeID = yield* requiredInteger(input.appointmentTypeId, "Appointment Type ID");
|
|
634
|
-
const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
|
|
635
|
-
if (!input.month.trim()) return yield* new StepFailure({ message: "Month is required and must use YYYY-MM format." });
|
|
636
|
-
const params = {
|
|
637
|
-
month: input.month,
|
|
638
|
-
appointmentTypeID,
|
|
639
|
-
calendarID,
|
|
640
|
-
timezone: input.timezone
|
|
641
|
-
};
|
|
642
|
-
const dates = yield* bag.step.run("get-dates", callAcuity("Failed to fetch availability dates.", () => client.availability.dates(params)));
|
|
643
|
-
return {
|
|
644
|
-
dates,
|
|
645
|
-
count: dates.length
|
|
646
|
-
};
|
|
647
|
-
})
|
|
648
|
-
},
|
|
649
|
-
"get-availability-times": {
|
|
650
|
-
label: "Get Availability Times",
|
|
651
|
-
description: "List available time slots for a date",
|
|
652
|
-
input: getAvailabilityTimesInput,
|
|
653
|
-
output: getAvailabilityTimesOutput,
|
|
654
|
-
configFields: [
|
|
655
|
-
{
|
|
656
|
-
key: "date",
|
|
657
|
-
label: "Date (YYYY-MM-DD)",
|
|
658
|
-
type: "template-input",
|
|
659
|
-
placeholder: "2026-03-15",
|
|
660
|
-
required: true
|
|
661
|
-
},
|
|
662
|
-
{
|
|
663
|
-
key: "appointmentTypeId",
|
|
664
|
-
label: "Appointment Type ID",
|
|
665
|
-
type: "template-input",
|
|
666
|
-
placeholder: "12345",
|
|
667
|
-
required: true
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
key: "calendarId",
|
|
671
|
-
label: "Calendar ID",
|
|
672
|
-
type: "template-input",
|
|
673
|
-
placeholder: "67890"
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
key: "timezone",
|
|
677
|
-
label: "Timezone",
|
|
678
|
-
type: "template-input",
|
|
679
|
-
placeholder: "America/New_York"
|
|
680
|
-
},
|
|
681
|
-
{
|
|
682
|
-
key: "ignoreAppointmentIds",
|
|
683
|
-
label: "Ignore Appointment IDs (comma separated)",
|
|
684
|
-
type: "template-input",
|
|
685
|
-
placeholder: "111,222"
|
|
686
|
-
}
|
|
687
|
-
],
|
|
688
|
-
handler: Effect.fn(function* (bag) {
|
|
689
|
-
const { input } = bag;
|
|
690
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
691
|
-
const appointmentTypeID = yield* requiredInteger(input.appointmentTypeId, "Appointment Type ID");
|
|
692
|
-
const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
|
|
693
|
-
const ignoreAppointmentIDs = yield* optionalIntegerList(input.ignoreAppointmentIds, "Ignore Appointment IDs");
|
|
694
|
-
if (!input.date.trim()) return yield* new StepFailure({ message: "Date is required and must use YYYY-MM-DD format." });
|
|
695
|
-
const params = {
|
|
696
|
-
date: input.date,
|
|
697
|
-
appointmentTypeID,
|
|
698
|
-
calendarID,
|
|
699
|
-
timezone: input.timezone,
|
|
700
|
-
ignoreAppointmentIDs
|
|
701
|
-
};
|
|
702
|
-
const slots = yield* bag.step.run("get-times", callAcuity("Failed to fetch availability times.", () => client.availability.times(params)));
|
|
703
|
-
return {
|
|
704
|
-
slots,
|
|
705
|
-
count: slots.length
|
|
706
|
-
};
|
|
707
|
-
})
|
|
708
|
-
},
|
|
709
|
-
"create-appointment": {
|
|
710
|
-
label: "Create Appointment",
|
|
711
|
-
description: "Book a new appointment in Acuity",
|
|
712
|
-
input: createAppointmentInput,
|
|
713
|
-
output: createAppointmentOutput,
|
|
714
|
-
configFields: [
|
|
715
|
-
{
|
|
716
|
-
key: "datetime",
|
|
717
|
-
label: "Datetime (ISO 8601)",
|
|
718
|
-
type: "template-input",
|
|
719
|
-
placeholder: "2026-03-15T15:00:00-04:00",
|
|
720
|
-
required: true
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
key: "appointmentTypeId",
|
|
724
|
-
label: "Appointment Type ID",
|
|
725
|
-
type: "template-input",
|
|
726
|
-
placeholder: "12345",
|
|
727
|
-
required: true
|
|
728
|
-
},
|
|
729
|
-
{
|
|
730
|
-
key: "firstName",
|
|
731
|
-
label: "First Name",
|
|
732
|
-
type: "template-input",
|
|
733
|
-
placeholder: "Alice",
|
|
734
|
-
required: true
|
|
735
|
-
},
|
|
736
|
-
{
|
|
737
|
-
key: "lastName",
|
|
738
|
-
label: "Last Name",
|
|
739
|
-
type: "template-input",
|
|
740
|
-
placeholder: "Johnson",
|
|
741
|
-
required: true
|
|
742
|
-
},
|
|
743
|
-
{
|
|
744
|
-
key: "email",
|
|
745
|
-
label: "Email",
|
|
746
|
-
type: "template-input",
|
|
747
|
-
placeholder: "alice@example.com",
|
|
748
|
-
required: true
|
|
749
|
-
},
|
|
750
|
-
{
|
|
751
|
-
key: "phone",
|
|
752
|
-
label: "Phone",
|
|
753
|
-
type: "template-input",
|
|
754
|
-
placeholder: "+15551234567",
|
|
755
|
-
required: true
|
|
756
|
-
},
|
|
757
|
-
{
|
|
758
|
-
type: "group",
|
|
759
|
-
label: "Optional Appointment Fields",
|
|
760
|
-
fields: [
|
|
761
|
-
{
|
|
762
|
-
key: "calendarId",
|
|
763
|
-
label: "Calendar ID",
|
|
764
|
-
type: "template-input",
|
|
765
|
-
placeholder: "67890"
|
|
766
|
-
},
|
|
767
|
-
{
|
|
768
|
-
key: "notes",
|
|
769
|
-
label: "Notes",
|
|
770
|
-
type: "template-textarea",
|
|
771
|
-
placeholder: "Optional internal notes",
|
|
772
|
-
rows: 3
|
|
773
|
-
},
|
|
774
|
-
{
|
|
775
|
-
key: "smsOptIn",
|
|
776
|
-
label: "SMS Opt-In",
|
|
777
|
-
type: "select",
|
|
778
|
-
defaultValue: "",
|
|
779
|
-
options: yesNoSelect
|
|
780
|
-
},
|
|
781
|
-
{
|
|
782
|
-
key: "customFieldsJson",
|
|
783
|
-
label: "Custom Fields JSON",
|
|
784
|
-
type: "template-textarea",
|
|
785
|
-
rows: 5,
|
|
786
|
-
placeholder: "[{\"fieldID\":1234,\"value\":\"Some answer\"},{\"fieldID\":5678,\"value\":[\"A\",\"B\"]}]"
|
|
787
|
-
}
|
|
788
|
-
]
|
|
789
|
-
},
|
|
790
|
-
mutationFlagsGroup
|
|
791
|
-
],
|
|
792
|
-
/**
|
|
793
|
-
* What this step decides is what a booking looks like, and each field says
|
|
794
|
-
* for itself what is wrong with it.
|
|
795
|
-
*/
|
|
796
|
-
handler: Effect.fn(function* (bag) {
|
|
797
|
-
const { input } = bag;
|
|
798
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
799
|
-
const appointmentTypeID = yield* requiredInteger(input.appointmentTypeId, "Appointment Type ID");
|
|
800
|
-
const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
|
|
801
|
-
const smsOptIn = yield* optionalBoolean(input.smsOptIn, "SMS Opt-In");
|
|
802
|
-
const admin = yield* optionalBoolean(input.admin, "Run as Admin");
|
|
803
|
-
const noEmail = yield* optionalBoolean(input.noEmail, "Suppress Acuity Emails");
|
|
804
|
-
const fields = yield* optionalCustomFields(input.customFieldsJson);
|
|
805
|
-
if (!input.datetime.trim()) return yield* new StepFailure({ message: "Datetime is required (ISO 8601 format)." });
|
|
806
|
-
if (!(input.firstName.trim() && input.lastName.trim())) return yield* new StepFailure({ message: "First Name and Last Name are required." });
|
|
807
|
-
if (!(input.email.trim() && input.phone.trim())) return yield* new StepFailure({ message: "Email and Phone are required." });
|
|
808
|
-
const payload = {
|
|
809
|
-
datetime: input.datetime,
|
|
810
|
-
appointmentTypeID,
|
|
811
|
-
firstName: input.firstName,
|
|
812
|
-
lastName: input.lastName,
|
|
813
|
-
email: input.email,
|
|
814
|
-
phone: input.phone,
|
|
815
|
-
calendarID,
|
|
816
|
-
notes: input.notes,
|
|
817
|
-
smsOptIn,
|
|
818
|
-
fields
|
|
819
|
-
};
|
|
820
|
-
const appointment = yield* bag.step.run("create-appointment", callAcuity("Failed to create appointment.", () => client.appointments.create(payload, {
|
|
821
|
-
admin,
|
|
822
|
-
noEmail
|
|
823
|
-
})));
|
|
824
|
-
return {
|
|
825
|
-
appointment,
|
|
826
|
-
id: appointment.id,
|
|
827
|
-
datetime: appointment.datetime
|
|
828
|
-
};
|
|
829
|
-
})
|
|
830
|
-
},
|
|
831
|
-
"reschedule-appointment": {
|
|
832
|
-
label: "Reschedule Appointment",
|
|
833
|
-
description: "Move an appointment to a new datetime",
|
|
834
|
-
input: rescheduleAppointmentInput,
|
|
835
|
-
output: rescheduleAppointmentOutput,
|
|
836
|
-
configFields: [
|
|
837
|
-
{
|
|
838
|
-
key: "appointmentId",
|
|
839
|
-
label: "Appointment ID",
|
|
840
|
-
type: "template-input",
|
|
841
|
-
placeholder: "123456789",
|
|
842
|
-
required: true
|
|
843
|
-
},
|
|
844
|
-
{
|
|
845
|
-
key: "datetime",
|
|
846
|
-
label: "New Datetime (ISO 8601)",
|
|
847
|
-
type: "template-input",
|
|
848
|
-
placeholder: "2026-03-16T10:00:00-04:00",
|
|
849
|
-
required: true
|
|
850
|
-
},
|
|
851
|
-
{
|
|
852
|
-
key: "calendarId",
|
|
853
|
-
label: "Calendar ID",
|
|
854
|
-
type: "template-input",
|
|
855
|
-
placeholder: "67890"
|
|
856
|
-
},
|
|
857
|
-
mutationFlagsGroup
|
|
858
|
-
],
|
|
859
|
-
handler: Effect.fn(function* (bag) {
|
|
860
|
-
const { input } = bag;
|
|
861
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
862
|
-
const appointmentId = yield* requiredInteger(input.appointmentId, "Appointment ID");
|
|
863
|
-
if (!input.datetime.trim()) return yield* new StepFailure({ message: "New Datetime is required (ISO 8601 format)." });
|
|
864
|
-
const calendarID = yield* optionalInteger(input.calendarId, "Calendar ID");
|
|
865
|
-
const admin = yield* optionalBoolean(input.admin, "Run as Admin");
|
|
866
|
-
const noEmail = yield* optionalBoolean(input.noEmail, "Suppress Acuity Emails");
|
|
867
|
-
const appointment = yield* bag.step.run("reschedule-appointment", callAcuity("Failed to reschedule appointment.", () => client.appointments.reschedule(appointmentId, {
|
|
868
|
-
datetime: input.datetime,
|
|
869
|
-
calendarID
|
|
870
|
-
}, {
|
|
871
|
-
admin,
|
|
872
|
-
noEmail
|
|
873
|
-
})));
|
|
874
|
-
return {
|
|
875
|
-
appointment,
|
|
876
|
-
id: appointment.id,
|
|
877
|
-
datetime: appointment.datetime
|
|
878
|
-
};
|
|
879
|
-
})
|
|
880
|
-
},
|
|
881
|
-
"cancel-appointment": {
|
|
882
|
-
label: "Cancel Appointment",
|
|
883
|
-
description: "Cancel an appointment in Acuity",
|
|
884
|
-
input: cancelAppointmentInput,
|
|
885
|
-
output: cancelAppointmentOutput,
|
|
886
|
-
configFields: [
|
|
887
|
-
{
|
|
888
|
-
key: "appointmentId",
|
|
889
|
-
label: "Appointment ID",
|
|
890
|
-
type: "template-input",
|
|
891
|
-
placeholder: "123456789",
|
|
892
|
-
required: true
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
key: "cancelNote",
|
|
896
|
-
label: "Cancel Note",
|
|
897
|
-
type: "template-textarea",
|
|
898
|
-
rows: 3,
|
|
899
|
-
placeholder: "Optional cancellation reason"
|
|
900
|
-
},
|
|
901
|
-
{
|
|
902
|
-
key: "noShow",
|
|
903
|
-
label: "Mark as No-Show",
|
|
904
|
-
type: "select",
|
|
905
|
-
defaultValue: "",
|
|
906
|
-
options: yesNoSelect
|
|
907
|
-
},
|
|
908
|
-
mutationFlagsGroup
|
|
909
|
-
],
|
|
910
|
-
handler: Effect.fn(function* (bag) {
|
|
911
|
-
const { input } = bag;
|
|
912
|
-
const client = yield* createAcuityClient(bag, createSdk);
|
|
913
|
-
const appointmentId = yield* requiredInteger(input.appointmentId, "Appointment ID");
|
|
914
|
-
const noShow = yield* optionalBoolean(input.noShow, "Mark as No-Show");
|
|
915
|
-
const admin = yield* optionalBoolean(input.admin, "Run as Admin");
|
|
916
|
-
const noEmail = yield* optionalBoolean(input.noEmail, "Suppress Acuity Emails");
|
|
917
|
-
const appointment = yield* bag.step.run("cancel-appointment", callAcuity("Failed to cancel appointment.", () => client.appointments.cancel(appointmentId, {
|
|
918
|
-
cancelNote: input.cancelNote,
|
|
919
|
-
noShow
|
|
920
|
-
}, {
|
|
921
|
-
admin,
|
|
922
|
-
noEmail
|
|
923
|
-
})));
|
|
924
|
-
return {
|
|
925
|
-
appointment,
|
|
926
|
-
id: appointment.id,
|
|
927
|
-
canceled: appointment.canceled
|
|
928
|
-
};
|
|
929
|
-
})
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
const acuity = createAcuity(createAcuitySdk);
|
|
935
|
-
//#endregion
|
|
936
11
|
//#region src/clerk/metadata.ts
|
|
937
12
|
/**
|
|
938
13
|
* The JSON a workflow author types into a metadata field, as Clerk takes it.
|
|
@@ -2089,7 +1164,7 @@ const twilio = defineIntegration({
|
|
|
2089
1164
|
label: "Twilio",
|
|
2090
1165
|
description: "Send SMS messages with Twilio Programmable Messaging",
|
|
2091
1166
|
credentials: twilioCredentialFields,
|
|
2092
|
-
test: async () => (await import("./test-
|
|
1167
|
+
test: async () => (await import("./test-DN4ZApY4.js")).testTwilio,
|
|
2093
1168
|
actions: { "send-sms": {
|
|
2094
1169
|
label: "Send SMS",
|
|
2095
1170
|
description: "Send an SMS via Twilio",
|
|
@@ -2225,7 +1300,6 @@ const twilio = defineIntegration({
|
|
|
2225
1300
|
//#endregion
|
|
2226
1301
|
//#region src/index.ts
|
|
2227
1302
|
const builtInIntegrations = [
|
|
2228
|
-
acuity,
|
|
2229
1303
|
clerk,
|
|
2230
1304
|
linear,
|
|
2231
1305
|
resend,
|
|
@@ -2233,4 +1307,4 @@ const builtInIntegrations = [
|
|
|
2233
1307
|
twilio
|
|
2234
1308
|
];
|
|
2235
1309
|
//#endregion
|
|
2236
|
-
export {
|
|
1310
|
+
export { builtInIntegrations, clerk, linear, resend, slack, twilio };
|