zitejs 0.9.134 → 0.9.135
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/dist/cjs/backend/index.d.ts +30 -46
- package/dist/cjs/backend/index.js +4 -0
- package/dist/cjs/backend/index.test.js +52 -41
- package/dist/cjs/sync/lib.js +5 -6
- package/dist/cjs/sync/lib.test.js +3 -3
- package/dist/esm/backend/index.d.ts +30 -46
- package/dist/esm/backend/index.js +4 -0
- package/dist/esm/backend/index.test.js +52 -41
- package/dist/esm/sync/lib.js +5 -6
- package/dist/esm/sync/lib.test.js +3 -3
- package/package.json +1 -1
|
@@ -7,8 +7,8 @@ export type ZiteWebhook = {
|
|
|
7
7
|
* A provider trigger: what should fire the endpoint, declared as a literal.
|
|
8
8
|
* The platform registers it with the provider at publish, verifies each
|
|
9
9
|
* delivery and queues it; the endpoint only sees the payload. A `poll`
|
|
10
|
-
* trigger runs the endpoint on a cadence
|
|
11
|
-
*
|
|
10
|
+
* trigger runs the endpoint on a cadence. Either way `execute` gets `trigger`
|
|
11
|
+
* ({@link ZiteTriggerContext}) to read the cursor and record what it handled.
|
|
12
12
|
*/
|
|
13
13
|
export type ZiteTrigger = {
|
|
14
14
|
/**
|
|
@@ -86,45 +86,39 @@ export type ZiteTriggerUnsubscribeInput = {
|
|
|
86
86
|
};
|
|
87
87
|
export type ZiteTriggerSubscribe = (input: ZiteTriggerSubscribeInput) => Promise<ZiteTriggerSubscribeResult> | ZiteTriggerSubscribeResult;
|
|
88
88
|
export type ZiteTriggerUnsubscribe = (input: ZiteTriggerUnsubscribeInput) => Promise<void> | void;
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*/
|
|
93
|
-
export type ZitePollInput = {
|
|
94
|
-
/** What the previous successful run returned as `cursor`; null on the first run. */
|
|
95
|
-
cursor: string | null;
|
|
96
|
-
/** Ids earlier runs returned as `handled`, newest last. Skip these. */
|
|
97
|
-
seen: string[];
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* What a provider trigger's delivery carries as `input.__webhook`, beside the
|
|
101
|
-
* provider's own body. `cursor` and `seen` work as they do for a poll: for a
|
|
102
|
-
* provider whose notification only says that something changed, `subscribe`
|
|
103
|
-
* returns a starting `cursor` and each delivery returns the next one. A
|
|
104
|
-
* trigger that keeps a cursor gets one delivery at a time.
|
|
105
|
-
*/
|
|
106
|
-
export type ZiteTriggerDelivery = {
|
|
89
|
+
/** Where a provider delivery came from; app event triggers get only this. */
|
|
90
|
+
export type ZiteTriggerDeliveryInfo = {
|
|
91
|
+
/** The delivery's headers, lowercased. */
|
|
107
92
|
headers: Record<string, string>;
|
|
108
93
|
/** The provider's id for this delivery; redeliveries share it. */
|
|
109
94
|
deliveryId?: string;
|
|
110
95
|
provider?: string;
|
|
111
|
-
/** What `subscribe` or the previous successful delivery returned as `cursor`. */
|
|
112
|
-
cursor: string | null;
|
|
113
|
-
/** Ids earlier deliveries returned as `handled`, newest last. Skip these. */
|
|
114
|
-
seen: string[];
|
|
115
96
|
};
|
|
116
|
-
/** What an app event trigger's run carries as `input.__webhook`: one event, no cursor. */
|
|
117
|
-
export type ZiteAppEventDelivery = Omit<ZiteTriggerDelivery, "cursor" | "seen">;
|
|
118
97
|
/**
|
|
119
|
-
* What a poll
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
98
|
+
* What a poll run, or a provider trigger's delivery, gets as `trigger`. Record
|
|
99
|
+
* each item acted on with `handled` as you go: it is kept even if the run
|
|
100
|
+
* later throws, so the next run skips it. The cursor staged with `advance` is
|
|
101
|
+
* committed only when the run succeeds.
|
|
123
102
|
*/
|
|
124
|
-
export type
|
|
125
|
-
cursor
|
|
126
|
-
|
|
103
|
+
export type ZiteTriggerRun = {
|
|
104
|
+
/** The last committed cursor: what `subscribe` returned, or the last successful run's `advance`. Null when there is none yet. */
|
|
105
|
+
cursor: string | null;
|
|
106
|
+
/** Whether an earlier run, or this one, recorded `id` as handled. */
|
|
107
|
+
seen(id: string): boolean;
|
|
108
|
+
/** Record an item as acted on (1 to 512 characters). */
|
|
109
|
+
handled(id: string): void;
|
|
110
|
+
/** Stage the next cursor (at most 4096 characters). The last call wins. */
|
|
111
|
+
advance(cursor: string): void;
|
|
127
112
|
};
|
|
113
|
+
export type ZiteTriggerDeliveryRun = ZiteTriggerRun & ZiteTriggerDeliveryInfo;
|
|
114
|
+
/** The `trigger` param for an endpoint declaring `TTrigger`. */
|
|
115
|
+
export type ZiteTriggerContext<TTrigger> = TTrigger extends {
|
|
116
|
+
every: string;
|
|
117
|
+
} ? ZiteTriggerRun : TTrigger extends {
|
|
118
|
+
events: string[];
|
|
119
|
+
} ? ZiteTriggerDeliveryInfo : TTrigger extends {
|
|
120
|
+
integration: string;
|
|
121
|
+
} ? ZiteTriggerDeliveryRun : never;
|
|
128
122
|
export interface ZiteRequestContext {
|
|
129
123
|
user: {
|
|
130
124
|
id: string;
|
|
@@ -221,22 +215,12 @@ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream ext
|
|
|
221
215
|
*/
|
|
222
216
|
validatesInput?: boolean;
|
|
223
217
|
execute: (params: {
|
|
224
|
-
input:
|
|
225
|
-
every: string;
|
|
226
|
-
} ? TInput & {
|
|
227
|
-
__poll?: ZitePollInput;
|
|
228
|
-
} : TTrigger extends {
|
|
229
|
-
events: string[];
|
|
230
|
-
} ? TInput & {
|
|
231
|
-
__webhook?: ZiteAppEventDelivery;
|
|
232
|
-
} : TTrigger extends {
|
|
233
|
-
integration: string;
|
|
234
|
-
} ? TInput & {
|
|
235
|
-
__webhook?: ZiteTriggerDelivery;
|
|
236
|
-
} : TInput;
|
|
218
|
+
input: TInput;
|
|
237
219
|
context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : TWebhook extends ZiteWebhook ? ZiteRequestContext | ZiteScheduledContext : TTrigger extends ZiteTrigger ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;
|
|
238
220
|
} & (TStream extends true ? {
|
|
239
221
|
stream: ZiteStreamInterface;
|
|
222
|
+
} : {}) & (TTrigger extends ZiteTrigger ? {
|
|
223
|
+
trigger?: ZiteTriggerContext<TTrigger>;
|
|
240
224
|
} : {})) => Promise<TOutput> | TOutput;
|
|
241
225
|
}
|
|
242
226
|
/**
|
|
@@ -74,6 +74,10 @@ function createEndpoint(config) {
|
|
|
74
74
|
// request callers doesn't describe them.
|
|
75
75
|
if (isPlatformTriggeredInput(params.input))
|
|
76
76
|
return execute(params);
|
|
77
|
+
// A trigger's run carries the provider's body, not a request's input.
|
|
78
|
+
if (params.trigger !== undefined) {
|
|
79
|
+
return execute(params);
|
|
80
|
+
}
|
|
77
81
|
// Same, for a fire with no input object to hold the sentinel: the runner
|
|
78
82
|
// only merges `__cron` into a plain object.
|
|
79
83
|
if (firesWithoutARequest && params.input === undefined) {
|
|
@@ -133,28 +133,51 @@ const requestContext = { user: { id: "u1", email: "a@b.c", roles: [] } };
|
|
|
133
133
|
});
|
|
134
134
|
await (0, vitest_1.expect)(endpoint.execute({ input: undefined, context: requestContext })).rejects.toMatchObject({ code: "BAD_REQUEST" });
|
|
135
135
|
});
|
|
136
|
-
(0, vitest_1.it)("hands a poll
|
|
136
|
+
(0, vitest_1.it)("hands a poll a typed trigger and skips the input schema for it", async () => {
|
|
137
|
+
const handled = [];
|
|
137
138
|
const endpoint = (0, index_js_1.createEndpoint)({
|
|
138
|
-
inputSchema: zod_1.z.object({}),
|
|
139
|
+
inputSchema: zod_1.z.object({ required: zod_1.z.string() }),
|
|
139
140
|
trigger: { every: "5m", integration: "stripe" },
|
|
140
|
-
execute: async ({
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
execute: async ({ trigger }) => {
|
|
142
|
+
if (!trigger)
|
|
143
|
+
return { ran: 0 };
|
|
144
|
+
const fresh = ["a", "b"].filter((id) => !trigger.seen(id));
|
|
145
|
+
for (const id of fresh)
|
|
146
|
+
trigger.handled(id);
|
|
147
|
+
trigger.advance("c2");
|
|
148
|
+
return { ran: fresh.length, from: trigger.cursor };
|
|
144
149
|
},
|
|
145
150
|
});
|
|
151
|
+
const seen = new Set(["a"]);
|
|
146
152
|
await (0, vitest_1.expect)(endpoint.execute({
|
|
147
|
-
input: {
|
|
153
|
+
input: {},
|
|
148
154
|
context: { user: null },
|
|
149
|
-
|
|
155
|
+
trigger: {
|
|
156
|
+
cursor: "c1",
|
|
157
|
+
seen: (id) => seen.has(id),
|
|
158
|
+
handled: (id) => void handled.push(id),
|
|
159
|
+
advance: () => { },
|
|
160
|
+
},
|
|
161
|
+
})).resolves.toEqual({ ran: 1, from: "c1" });
|
|
162
|
+
(0, vitest_1.expect)(handled).toEqual(["b"]);
|
|
150
163
|
});
|
|
151
|
-
(0, vitest_1.it)("
|
|
164
|
+
(0, vitest_1.it)("types trigger by the declared kind", () => {
|
|
152
165
|
(0, index_js_1.createEndpoint)({
|
|
153
|
-
inputSchema: zod_1.z.object({ action: zod_1.z.string() }),
|
|
154
166
|
trigger: { integration: "github" },
|
|
155
|
-
execute: async ({
|
|
156
|
-
|
|
157
|
-
|
|
167
|
+
execute: async ({ trigger }) => trigger?.deliveryId ?? trigger?.cursor,
|
|
168
|
+
});
|
|
169
|
+
(0, index_js_1.createEndpoint)({
|
|
170
|
+
trigger: { integration: "team-slack", events: ["message.channels"] },
|
|
171
|
+
execute: async ({ trigger }) => {
|
|
172
|
+
// @ts-expect-error an app event has no cursor API
|
|
173
|
+
trigger?.advance("x");
|
|
174
|
+
return trigger?.headers;
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
(0, index_js_1.createEndpoint)({
|
|
178
|
+
execute: async (params) => {
|
|
179
|
+
// @ts-expect-error a plain endpoint gets no trigger
|
|
180
|
+
return params.trigger;
|
|
158
181
|
},
|
|
159
182
|
});
|
|
160
183
|
});
|
|
@@ -184,40 +207,28 @@ const requestContext = { user: { id: "u1", email: "a@b.c", roles: [] } };
|
|
|
184
207
|
endpointId: "onPush",
|
|
185
208
|
});
|
|
186
209
|
});
|
|
187
|
-
(0, vitest_1.it)("hands a provider
|
|
210
|
+
(0, vitest_1.it)("hands a provider delivery its body as input and the cursor API on trigger", async () => {
|
|
188
211
|
const endpoint = (0, index_js_1.createEndpoint)({
|
|
189
|
-
inputSchema: zod_1.z.object({}),
|
|
212
|
+
inputSchema: zod_1.z.object({ required: zod_1.z.string() }),
|
|
190
213
|
trigger: { integration: "googleCalendar" },
|
|
191
|
-
execute: async ({ input }) => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
},
|
|
214
|
+
execute: async ({ input, trigger }) => ({
|
|
215
|
+
body: input,
|
|
216
|
+
cursor: trigger?.cursor,
|
|
217
|
+
delivery: trigger?.deliveryId,
|
|
218
|
+
}),
|
|
196
219
|
});
|
|
197
220
|
await (0, vitest_1.expect)(endpoint.execute({
|
|
198
|
-
input: {
|
|
199
|
-
__webhook: { headers: {}, cursor: "sync1", seen: ["e1"] },
|
|
200
|
-
},
|
|
221
|
+
input: { kind: "sync" },
|
|
201
222
|
context: { user: null },
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// @ts-expect-error an app event delivery has no cursor
|
|
210
|
-
void input.__webhook?.cursor;
|
|
211
|
-
return { id: input.__webhook?.deliveryId ?? null };
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
await (0, vitest_1.expect)(endpoint.execute({
|
|
215
|
-
input: {
|
|
216
|
-
event: { text: "hi" },
|
|
217
|
-
__webhook: { headers: {}, deliveryId: "slack:Ev1" },
|
|
223
|
+
trigger: {
|
|
224
|
+
cursor: "sync1",
|
|
225
|
+
seen: () => false,
|
|
226
|
+
handled: () => { },
|
|
227
|
+
advance: () => { },
|
|
228
|
+
headers: {},
|
|
229
|
+
deliveryId: "chan:2",
|
|
218
230
|
},
|
|
219
|
-
|
|
220
|
-
})).resolves.toEqual({ id: "slack:Ev1" });
|
|
231
|
+
})).resolves.toEqual({ body: { kind: "sync" }, cursor: "sync1", delivery: "chan:2" });
|
|
221
232
|
});
|
|
222
233
|
(0, vitest_1.it)("refuses events on a trigger that also polls", () => {
|
|
223
234
|
(0, index_js_1.createEndpoint)({
|
package/dist/cjs/sync/lib.js
CHANGED
|
@@ -1232,13 +1232,13 @@ function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1232
1232
|
"// Auto-generated type-narrowing wrapper. Do not edit manually.",
|
|
1233
1233
|
"// Re-exports createEndpoint with context.user typed to the app User.",
|
|
1234
1234
|
"",
|
|
1235
|
-
"import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext, ZiteErrorCode, ZiteSchedule, ZiteStreamInterface, ZiteTrigger,
|
|
1235
|
+
"import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext, ZiteErrorCode, ZiteSchedule, ZiteStreamInterface, ZiteTrigger, ZiteTriggerContext, ZiteWebhook } from 'zitejs/backend/base';",
|
|
1236
1236
|
"import type { User } from 'zitejs/auth';",
|
|
1237
1237
|
"",
|
|
1238
1238
|
"export type { ZiteErrorCode, ZiteSchedule, ZiteScheduledContext, ZiteStreamInterface, ZiteTrigger, ZiteWebhook };",
|
|
1239
1239
|
// A trigger's registration exports are typed against these, and they
|
|
1240
1240
|
// only reach app code through this file.
|
|
1241
|
-
"export type {
|
|
1241
|
+
"export type { ZiteTriggerContext, ZiteTriggerDeliveryInfo, ZiteTriggerDeliveryRun, ZiteTriggerRun, ZiteTriggerSubscribe, ZiteTriggerSubscribeInput, ZiteTriggerSubscribeResult, ZiteTriggerUnsubscribe, ZiteTriggerUnsubscribeInput } from 'zitejs/backend/base';",
|
|
1242
1242
|
// The pre-monorepo SDK put this in scope for every endpoint, so migrated
|
|
1243
1243
|
// code can name it. `createEndpoint` infers the same thing without it.
|
|
1244
1244
|
"export type InferSchemaType<T> = T extends { _output: infer U } ? U : T;",
|
|
@@ -1330,11 +1330,10 @@ function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1330
1330
|
" trigger?: TTrigger;",
|
|
1331
1331
|
" execute: (",
|
|
1332
1332
|
" params: {",
|
|
1333
|
-
|
|
1334
|
-
// trigger's delivery `__webhook`.
|
|
1335
|
-
" input: TTrigger extends { every: string } ? TInput & { __poll?: ZitePollInput } : TTrigger extends { events: string[] } ? TInput & { __webhook?: ZiteAppEventDelivery } : TTrigger extends { integration: string } ? TInput & { __webhook?: ZiteTriggerDelivery } : TInput;",
|
|
1333
|
+
" input: TInput;",
|
|
1336
1334
|
" context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : TWebhook extends ZiteWebhook ? ZiteRequestContext | ZiteScheduledContext : TTrigger extends ZiteTrigger ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;",
|
|
1337
|
-
|
|
1335
|
+
// Mirrors the base module: a trigger's run gets `trigger`.
|
|
1336
|
+
" } & (TStream extends true ? { stream: ZiteStreamInterface } : {}) & (TTrigger extends ZiteTrigger ? { trigger?: ZiteTriggerContext<TTrigger> } : {}),",
|
|
1338
1337
|
" ) => Promise<TOutput> | TOutput;",
|
|
1339
1338
|
"}",
|
|
1340
1339
|
"",
|
|
@@ -390,9 +390,9 @@ const duplicateIdentifierErrorsIn = (source) => {
|
|
|
390
390
|
(0, vitest_1.it)('passes the trigger registration types and trigger inputs through to app code', () => {
|
|
391
391
|
(0, vitest_1.expect)(output).toContain('ZiteTriggerSubscribe, ZiteTriggerSubscribeInput');
|
|
392
392
|
(0, vitest_1.expect)(output).toContain('ZiteTriggerUnsubscribe, ZiteTriggerUnsubscribeInput');
|
|
393
|
-
(0, vitest_1.expect)(output).toContain(
|
|
394
|
-
(0, vitest_1.expect)(output).toContain('
|
|
395
|
-
(0, vitest_1.expect)(output).toContain(
|
|
393
|
+
(0, vitest_1.expect)(output).toContain('ZiteTriggerContext, ZiteTriggerDeliveryInfo, ZiteTriggerDeliveryRun, ZiteTriggerRun');
|
|
394
|
+
(0, vitest_1.expect)(output).toContain('(TTrigger extends ZiteTrigger ? { trigger?: ZiteTriggerContext<TTrigger> } : {})');
|
|
395
|
+
(0, vitest_1.expect)(output).not.toContain('__poll');
|
|
396
396
|
(0, vitest_1.expect)(output).not.toContain('src/triggers/');
|
|
397
397
|
});
|
|
398
398
|
(0, vitest_1.it)('exports ZiteError', () => {
|
|
@@ -7,8 +7,8 @@ export type ZiteWebhook = {
|
|
|
7
7
|
* A provider trigger: what should fire the endpoint, declared as a literal.
|
|
8
8
|
* The platform registers it with the provider at publish, verifies each
|
|
9
9
|
* delivery and queues it; the endpoint only sees the payload. A `poll`
|
|
10
|
-
* trigger runs the endpoint on a cadence
|
|
11
|
-
*
|
|
10
|
+
* trigger runs the endpoint on a cadence. Either way `execute` gets `trigger`
|
|
11
|
+
* ({@link ZiteTriggerContext}) to read the cursor and record what it handled.
|
|
12
12
|
*/
|
|
13
13
|
export type ZiteTrigger = {
|
|
14
14
|
/**
|
|
@@ -86,45 +86,39 @@ export type ZiteTriggerUnsubscribeInput = {
|
|
|
86
86
|
};
|
|
87
87
|
export type ZiteTriggerSubscribe = (input: ZiteTriggerSubscribeInput) => Promise<ZiteTriggerSubscribeResult> | ZiteTriggerSubscribeResult;
|
|
88
88
|
export type ZiteTriggerUnsubscribe = (input: ZiteTriggerUnsubscribeInput) => Promise<void> | void;
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*/
|
|
93
|
-
export type ZitePollInput = {
|
|
94
|
-
/** What the previous successful run returned as `cursor`; null on the first run. */
|
|
95
|
-
cursor: string | null;
|
|
96
|
-
/** Ids earlier runs returned as `handled`, newest last. Skip these. */
|
|
97
|
-
seen: string[];
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* What a provider trigger's delivery carries as `input.__webhook`, beside the
|
|
101
|
-
* provider's own body. `cursor` and `seen` work as they do for a poll: for a
|
|
102
|
-
* provider whose notification only says that something changed, `subscribe`
|
|
103
|
-
* returns a starting `cursor` and each delivery returns the next one. A
|
|
104
|
-
* trigger that keeps a cursor gets one delivery at a time.
|
|
105
|
-
*/
|
|
106
|
-
export type ZiteTriggerDelivery = {
|
|
89
|
+
/** Where a provider delivery came from; app event triggers get only this. */
|
|
90
|
+
export type ZiteTriggerDeliveryInfo = {
|
|
91
|
+
/** The delivery's headers, lowercased. */
|
|
107
92
|
headers: Record<string, string>;
|
|
108
93
|
/** The provider's id for this delivery; redeliveries share it. */
|
|
109
94
|
deliveryId?: string;
|
|
110
95
|
provider?: string;
|
|
111
|
-
/** What `subscribe` or the previous successful delivery returned as `cursor`. */
|
|
112
|
-
cursor: string | null;
|
|
113
|
-
/** Ids earlier deliveries returned as `handled`, newest last. Skip these. */
|
|
114
|
-
seen: string[];
|
|
115
96
|
};
|
|
116
|
-
/** What an app event trigger's run carries as `input.__webhook`: one event, no cursor. */
|
|
117
|
-
export type ZiteAppEventDelivery = Omit<ZiteTriggerDelivery, "cursor" | "seen">;
|
|
118
97
|
/**
|
|
119
|
-
* What a poll
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
98
|
+
* What a poll run, or a provider trigger's delivery, gets as `trigger`. Record
|
|
99
|
+
* each item acted on with `handled` as you go: it is kept even if the run
|
|
100
|
+
* later throws, so the next run skips it. The cursor staged with `advance` is
|
|
101
|
+
* committed only when the run succeeds.
|
|
123
102
|
*/
|
|
124
|
-
export type
|
|
125
|
-
cursor
|
|
126
|
-
|
|
103
|
+
export type ZiteTriggerRun = {
|
|
104
|
+
/** The last committed cursor: what `subscribe` returned, or the last successful run's `advance`. Null when there is none yet. */
|
|
105
|
+
cursor: string | null;
|
|
106
|
+
/** Whether an earlier run, or this one, recorded `id` as handled. */
|
|
107
|
+
seen(id: string): boolean;
|
|
108
|
+
/** Record an item as acted on (1 to 512 characters). */
|
|
109
|
+
handled(id: string): void;
|
|
110
|
+
/** Stage the next cursor (at most 4096 characters). The last call wins. */
|
|
111
|
+
advance(cursor: string): void;
|
|
127
112
|
};
|
|
113
|
+
export type ZiteTriggerDeliveryRun = ZiteTriggerRun & ZiteTriggerDeliveryInfo;
|
|
114
|
+
/** The `trigger` param for an endpoint declaring `TTrigger`. */
|
|
115
|
+
export type ZiteTriggerContext<TTrigger> = TTrigger extends {
|
|
116
|
+
every: string;
|
|
117
|
+
} ? ZiteTriggerRun : TTrigger extends {
|
|
118
|
+
events: string[];
|
|
119
|
+
} ? ZiteTriggerDeliveryInfo : TTrigger extends {
|
|
120
|
+
integration: string;
|
|
121
|
+
} ? ZiteTriggerDeliveryRun : never;
|
|
128
122
|
export interface ZiteRequestContext {
|
|
129
123
|
user: {
|
|
130
124
|
id: string;
|
|
@@ -221,22 +215,12 @@ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream ext
|
|
|
221
215
|
*/
|
|
222
216
|
validatesInput?: boolean;
|
|
223
217
|
execute: (params: {
|
|
224
|
-
input:
|
|
225
|
-
every: string;
|
|
226
|
-
} ? TInput & {
|
|
227
|
-
__poll?: ZitePollInput;
|
|
228
|
-
} : TTrigger extends {
|
|
229
|
-
events: string[];
|
|
230
|
-
} ? TInput & {
|
|
231
|
-
__webhook?: ZiteAppEventDelivery;
|
|
232
|
-
} : TTrigger extends {
|
|
233
|
-
integration: string;
|
|
234
|
-
} ? TInput & {
|
|
235
|
-
__webhook?: ZiteTriggerDelivery;
|
|
236
|
-
} : TInput;
|
|
218
|
+
input: TInput;
|
|
237
219
|
context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : TWebhook extends ZiteWebhook ? ZiteRequestContext | ZiteScheduledContext : TTrigger extends ZiteTrigger ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;
|
|
238
220
|
} & (TStream extends true ? {
|
|
239
221
|
stream: ZiteStreamInterface;
|
|
222
|
+
} : {}) & (TTrigger extends ZiteTrigger ? {
|
|
223
|
+
trigger?: ZiteTriggerContext<TTrigger>;
|
|
240
224
|
} : {})) => Promise<TOutput> | TOutput;
|
|
241
225
|
}
|
|
242
226
|
/**
|
|
@@ -69,6 +69,10 @@ export function createEndpoint(config) {
|
|
|
69
69
|
// request callers doesn't describe them.
|
|
70
70
|
if (isPlatformTriggeredInput(params.input))
|
|
71
71
|
return execute(params);
|
|
72
|
+
// A trigger's run carries the provider's body, not a request's input.
|
|
73
|
+
if (params.trigger !== undefined) {
|
|
74
|
+
return execute(params);
|
|
75
|
+
}
|
|
72
76
|
// Same, for a fire with no input object to hold the sentinel: the runner
|
|
73
77
|
// only merges `__cron` into a plain object.
|
|
74
78
|
if (firesWithoutARequest && params.input === undefined) {
|
|
@@ -131,28 +131,51 @@ describe("platform-triggered runs", () => {
|
|
|
131
131
|
});
|
|
132
132
|
await expect(endpoint.execute({ input: undefined, context: requestContext })).rejects.toMatchObject({ code: "BAD_REQUEST" });
|
|
133
133
|
});
|
|
134
|
-
it("hands a poll
|
|
134
|
+
it("hands a poll a typed trigger and skips the input schema for it", async () => {
|
|
135
|
+
const handled = [];
|
|
135
136
|
const endpoint = createEndpoint({
|
|
136
|
-
inputSchema: z.object({}),
|
|
137
|
+
inputSchema: z.object({ required: z.string() }),
|
|
137
138
|
trigger: { every: "5m", integration: "stripe" },
|
|
138
|
-
execute: async ({
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
execute: async ({ trigger }) => {
|
|
140
|
+
if (!trigger)
|
|
141
|
+
return { ran: 0 };
|
|
142
|
+
const fresh = ["a", "b"].filter((id) => !trigger.seen(id));
|
|
143
|
+
for (const id of fresh)
|
|
144
|
+
trigger.handled(id);
|
|
145
|
+
trigger.advance("c2");
|
|
146
|
+
return { ran: fresh.length, from: trigger.cursor };
|
|
142
147
|
},
|
|
143
148
|
});
|
|
149
|
+
const seen = new Set(["a"]);
|
|
144
150
|
await expect(endpoint.execute({
|
|
145
|
-
input: {
|
|
151
|
+
input: {},
|
|
146
152
|
context: { user: null },
|
|
147
|
-
|
|
153
|
+
trigger: {
|
|
154
|
+
cursor: "c1",
|
|
155
|
+
seen: (id) => seen.has(id),
|
|
156
|
+
handled: (id) => void handled.push(id),
|
|
157
|
+
advance: () => { },
|
|
158
|
+
},
|
|
159
|
+
})).resolves.toEqual({ ran: 1, from: "c1" });
|
|
160
|
+
expect(handled).toEqual(["b"]);
|
|
148
161
|
});
|
|
149
|
-
it("
|
|
162
|
+
it("types trigger by the declared kind", () => {
|
|
150
163
|
createEndpoint({
|
|
151
|
-
inputSchema: z.object({ action: z.string() }),
|
|
152
164
|
trigger: { integration: "github" },
|
|
153
|
-
execute: async ({
|
|
154
|
-
|
|
155
|
-
|
|
165
|
+
execute: async ({ trigger }) => trigger?.deliveryId ?? trigger?.cursor,
|
|
166
|
+
});
|
|
167
|
+
createEndpoint({
|
|
168
|
+
trigger: { integration: "team-slack", events: ["message.channels"] },
|
|
169
|
+
execute: async ({ trigger }) => {
|
|
170
|
+
// @ts-expect-error an app event has no cursor API
|
|
171
|
+
trigger?.advance("x");
|
|
172
|
+
return trigger?.headers;
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
createEndpoint({
|
|
176
|
+
execute: async (params) => {
|
|
177
|
+
// @ts-expect-error a plain endpoint gets no trigger
|
|
178
|
+
return params.trigger;
|
|
156
179
|
},
|
|
157
180
|
});
|
|
158
181
|
});
|
|
@@ -182,40 +205,28 @@ describe("platform-triggered runs", () => {
|
|
|
182
205
|
endpointId: "onPush",
|
|
183
206
|
});
|
|
184
207
|
});
|
|
185
|
-
it("hands a provider
|
|
208
|
+
it("hands a provider delivery its body as input and the cursor API on trigger", async () => {
|
|
186
209
|
const endpoint = createEndpoint({
|
|
187
|
-
inputSchema: z.object({}),
|
|
210
|
+
inputSchema: z.object({ required: z.string() }),
|
|
188
211
|
trigger: { integration: "googleCalendar" },
|
|
189
|
-
execute: async ({ input }) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
},
|
|
212
|
+
execute: async ({ input, trigger }) => ({
|
|
213
|
+
body: input,
|
|
214
|
+
cursor: trigger?.cursor,
|
|
215
|
+
delivery: trigger?.deliveryId,
|
|
216
|
+
}),
|
|
194
217
|
});
|
|
195
218
|
await expect(endpoint.execute({
|
|
196
|
-
input: {
|
|
197
|
-
__webhook: { headers: {}, cursor: "sync1", seen: ["e1"] },
|
|
198
|
-
},
|
|
219
|
+
input: { kind: "sync" },
|
|
199
220
|
context: { user: null },
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// @ts-expect-error an app event delivery has no cursor
|
|
208
|
-
void input.__webhook?.cursor;
|
|
209
|
-
return { id: input.__webhook?.deliveryId ?? null };
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
await expect(endpoint.execute({
|
|
213
|
-
input: {
|
|
214
|
-
event: { text: "hi" },
|
|
215
|
-
__webhook: { headers: {}, deliveryId: "slack:Ev1" },
|
|
221
|
+
trigger: {
|
|
222
|
+
cursor: "sync1",
|
|
223
|
+
seen: () => false,
|
|
224
|
+
handled: () => { },
|
|
225
|
+
advance: () => { },
|
|
226
|
+
headers: {},
|
|
227
|
+
deliveryId: "chan:2",
|
|
216
228
|
},
|
|
217
|
-
|
|
218
|
-
})).resolves.toEqual({ id: "slack:Ev1" });
|
|
229
|
+
})).resolves.toEqual({ body: { kind: "sync" }, cursor: "sync1", delivery: "chan:2" });
|
|
219
230
|
});
|
|
220
231
|
it("refuses events on a trigger that also polls", () => {
|
|
221
232
|
createEndpoint({
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -1217,13 +1217,13 @@ export function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1217
1217
|
"// Auto-generated type-narrowing wrapper. Do not edit manually.",
|
|
1218
1218
|
"// Re-exports createEndpoint with context.user typed to the app User.",
|
|
1219
1219
|
"",
|
|
1220
|
-
"import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext, ZiteErrorCode, ZiteSchedule, ZiteStreamInterface, ZiteTrigger,
|
|
1220
|
+
"import type { ZiteRequestContext as _ZiteRequestContext, ZiteScheduledContext, ZiteErrorCode, ZiteSchedule, ZiteStreamInterface, ZiteTrigger, ZiteTriggerContext, ZiteWebhook } from 'zitejs/backend/base';",
|
|
1221
1221
|
"import type { User } from 'zitejs/auth';",
|
|
1222
1222
|
"",
|
|
1223
1223
|
"export type { ZiteErrorCode, ZiteSchedule, ZiteScheduledContext, ZiteStreamInterface, ZiteTrigger, ZiteWebhook };",
|
|
1224
1224
|
// A trigger's registration exports are typed against these, and they
|
|
1225
1225
|
// only reach app code through this file.
|
|
1226
|
-
"export type {
|
|
1226
|
+
"export type { ZiteTriggerContext, ZiteTriggerDeliveryInfo, ZiteTriggerDeliveryRun, ZiteTriggerRun, ZiteTriggerSubscribe, ZiteTriggerSubscribeInput, ZiteTriggerSubscribeResult, ZiteTriggerUnsubscribe, ZiteTriggerUnsubscribeInput } from 'zitejs/backend/base';",
|
|
1227
1227
|
// The pre-monorepo SDK put this in scope for every endpoint, so migrated
|
|
1228
1228
|
// code can name it. `createEndpoint` infers the same thing without it.
|
|
1229
1229
|
"export type InferSchemaType<T> = T extends { _output: infer U } ? U : T;",
|
|
@@ -1315,11 +1315,10 @@ export function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1315
1315
|
" trigger?: TTrigger;",
|
|
1316
1316
|
" execute: (",
|
|
1317
1317
|
" params: {",
|
|
1318
|
-
|
|
1319
|
-
// trigger's delivery `__webhook`.
|
|
1320
|
-
" input: TTrigger extends { every: string } ? TInput & { __poll?: ZitePollInput } : TTrigger extends { events: string[] } ? TInput & { __webhook?: ZiteAppEventDelivery } : TTrigger extends { integration: string } ? TInput & { __webhook?: ZiteTriggerDelivery } : TInput;",
|
|
1318
|
+
" input: TInput;",
|
|
1321
1319
|
" context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : TWebhook extends ZiteWebhook ? ZiteRequestContext | ZiteScheduledContext : TTrigger extends ZiteTrigger ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;",
|
|
1322
|
-
|
|
1320
|
+
// Mirrors the base module: a trigger's run gets `trigger`.
|
|
1321
|
+
" } & (TStream extends true ? { stream: ZiteStreamInterface } : {}) & (TTrigger extends ZiteTrigger ? { trigger?: ZiteTriggerContext<TTrigger> } : {}),",
|
|
1323
1322
|
" ) => Promise<TOutput> | TOutput;",
|
|
1324
1323
|
"}",
|
|
1325
1324
|
"",
|
|
@@ -385,9 +385,9 @@ describe('generateBackendWrapperTs', () => {
|
|
|
385
385
|
it('passes the trigger registration types and trigger inputs through to app code', () => {
|
|
386
386
|
expect(output).toContain('ZiteTriggerSubscribe, ZiteTriggerSubscribeInput');
|
|
387
387
|
expect(output).toContain('ZiteTriggerUnsubscribe, ZiteTriggerUnsubscribeInput');
|
|
388
|
-
expect(output).toContain(
|
|
389
|
-
expect(output).toContain('
|
|
390
|
-
expect(output).toContain(
|
|
388
|
+
expect(output).toContain('ZiteTriggerContext, ZiteTriggerDeliveryInfo, ZiteTriggerDeliveryRun, ZiteTriggerRun');
|
|
389
|
+
expect(output).toContain('(TTrigger extends ZiteTrigger ? { trigger?: ZiteTriggerContext<TTrigger> } : {})');
|
|
390
|
+
expect(output).not.toContain('__poll');
|
|
391
391
|
expect(output).not.toContain('src/triggers/');
|
|
392
392
|
});
|
|
393
393
|
it('exports ZiteError', () => {
|