@automate.ax/integration-contracts 0.112.10 → 0.113.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.
@@ -0,0 +1,37 @@
1
+ import { type GoogleSheetsGridRange } from "./schemas.js";
2
+ export interface ParsedGoogleSheetsA1Range extends Omit<GoogleSheetsGridRange, "sheet"> {
3
+ /** Sheet title. Omit to use the spreadsheet's first visible sheet. */
4
+ sheet?: string;
5
+ }
6
+ /**
7
+ * Accepts either a raw spreadsheet ID or a standard Google Sheets URL.
8
+ *
9
+ * @param spreadsheet - Spreadsheet ID or URL.
10
+ * @throws When a URL is not a standard Google Sheets URL.
11
+ */
12
+ export declare function normalizeGoogleSheetsSpreadsheetId(spreadsheet: string): string;
13
+ /**
14
+ * Parses the practical A1 range forms accepted by Google Sheets.
15
+ *
16
+ * Partial two-ended ranges preserve the v5 parser's normalization: a missing
17
+ * coordinate is inferred from the opposite endpoint where needed, then left
18
+ * unbounded on the ending side.
19
+ *
20
+ * @param input - A1 cell or range with an optional sheet prefix.
21
+ */
22
+ export declare function parseGoogleSheetsA1Range(input: string): ParsedGoogleSheetsA1Range;
23
+ /**
24
+ * Converts a one-based column number to its A1 label.
25
+ *
26
+ * @param column - One-based column number.
27
+ * @throws When the number is outside the Google Sheets column limit.
28
+ */
29
+ export declare function googleSheetsColumnToA1(column: number): string;
30
+ /**
31
+ * Builds a canonical, quoted A1 cell reference.
32
+ *
33
+ * @param sheetTitle - Sheet title to quote.
34
+ * @param row - One-based row number.
35
+ * @param column - One-based column number.
36
+ */
37
+ export declare function buildGoogleSheetsCellA1(sheetTitle: string, row: number, column: number): string;
@@ -0,0 +1,252 @@
1
+ import { GOOGLE_SHEETS_MAX_COLUMN, GOOGLE_SHEETS_MAX_ROW, } from "./schemas.js";
2
+ const GOOGLE_SHEETS_URL_HOSTS = new Set([
3
+ "docs.google.com",
4
+ "sheets.google.com",
5
+ ]);
6
+ const A1_ENDPOINT_PATTERN = /^(\$?[A-Za-z]{1,3})?(\$?\d+)?$/;
7
+ /**
8
+ * Accepts either a raw spreadsheet ID or a standard Google Sheets URL.
9
+ *
10
+ * @param spreadsheet - Spreadsheet ID or URL.
11
+ * @throws When a URL is not a standard Google Sheets URL.
12
+ */
13
+ export function normalizeGoogleSheetsSpreadsheetId(spreadsheet) {
14
+ const value = spreadsheet.trim();
15
+ if (!URL.canParse(value))
16
+ return value;
17
+ const url = new URL(value);
18
+ if (!GOOGLE_SHEETS_URL_HOSTS.has(url.hostname)) {
19
+ throw new Error(`Expected a Google Sheets URL, received "${value}".`);
20
+ }
21
+ const parts = url.pathname.split("/");
22
+ const spreadsheetsIndex = parts.indexOf("spreadsheets");
23
+ const documentIndex = spreadsheetsIndex === -1 ? -1 : parts.indexOf("d", spreadsheetsIndex + 1);
24
+ const spreadsheetId = documentIndex === -1 ? undefined : parts[documentIndex + 1];
25
+ if (!spreadsheetId) {
26
+ throw new Error(`Could not find a spreadsheet ID in "${value}".`);
27
+ }
28
+ return spreadsheetId;
29
+ }
30
+ /**
31
+ * Parses the practical A1 range forms accepted by Google Sheets.
32
+ *
33
+ * Partial two-ended ranges preserve the v5 parser's normalization: a missing
34
+ * coordinate is inferred from the opposite endpoint where needed, then left
35
+ * unbounded on the ending side.
36
+ *
37
+ * @param input - A1 cell or range with an optional sheet prefix.
38
+ */
39
+ export function parseGoogleSheetsA1Range(input) {
40
+ const { range, sheet } = splitSheetPrefix(input.trim());
41
+ const endpointTexts = range.split(":");
42
+ if (endpointTexts.length > 2)
43
+ return invalidA1Range(input);
44
+ if (endpointTexts.length === 1) {
45
+ const endpoint = parseEndpoint(endpointTexts[0] ?? "", input);
46
+ if (endpoint.column === undefined || endpoint.row === undefined) {
47
+ return invalidA1Range(input);
48
+ }
49
+ return {
50
+ endColumn: endpoint.column,
51
+ endRow: endpoint.row,
52
+ ...(sheet !== undefined && { sheet }),
53
+ startColumn: endpoint.column,
54
+ startRow: endpoint.row,
55
+ };
56
+ }
57
+ const start = parseEndpoint(endpointTexts[0] ?? "", input);
58
+ const end = parseEndpoint(endpointTexts[1] ?? "", input);
59
+ // Naming the endpoint mask keeps the preserved v5 case matrix legible.
60
+ const mask = (start.column === undefined ? 0 : 0b1000) |
61
+ (start.row === undefined ? 0 : 0b0100) |
62
+ (end.column === undefined ? 0 : 0b0010) |
63
+ (end.row === undefined ? 0 : 0b0001);
64
+ let parsed;
65
+ switch (mask) {
66
+ case 0b0101:
67
+ parsed = {
68
+ endRow: end.row,
69
+ startColumn: 1,
70
+ startRow: start.row,
71
+ };
72
+ break;
73
+ case 0b1101:
74
+ parsed = {
75
+ endRow: end.row,
76
+ startColumn: start.column,
77
+ startRow: start.row,
78
+ };
79
+ break;
80
+ case 0b0111:
81
+ parsed = {
82
+ endRow: end.row,
83
+ startColumn: end.column,
84
+ startRow: start.row,
85
+ };
86
+ break;
87
+ case 0b1010:
88
+ parsed = {
89
+ endColumn: end.column,
90
+ startColumn: start.column,
91
+ startRow: 1,
92
+ };
93
+ break;
94
+ case 0b1110:
95
+ parsed = {
96
+ endColumn: end.column,
97
+ startColumn: start.column,
98
+ startRow: start.row,
99
+ };
100
+ break;
101
+ case 0b1011:
102
+ parsed = {
103
+ endColumn: end.column,
104
+ startColumn: start.column,
105
+ startRow: end.row,
106
+ };
107
+ break;
108
+ case 0b1111:
109
+ parsed = {
110
+ endColumn: end.column,
111
+ endRow: end.row,
112
+ startColumn: start.column,
113
+ startRow: start.row,
114
+ };
115
+ break;
116
+ default:
117
+ return invalidA1Range(input);
118
+ }
119
+ if (parsed.endColumn !== undefined &&
120
+ parsed.startColumn !== undefined &&
121
+ parsed.endColumn < parsed.startColumn) {
122
+ return invalidA1Range(input);
123
+ }
124
+ if (parsed.endRow !== undefined &&
125
+ parsed.startRow !== undefined &&
126
+ parsed.endRow < parsed.startRow) {
127
+ return invalidA1Range(input);
128
+ }
129
+ return { ...parsed, ...(sheet !== undefined && { sheet }) };
130
+ }
131
+ /**
132
+ * Converts a one-based column number to its A1 label.
133
+ *
134
+ * @param column - One-based column number.
135
+ * @throws When the number is outside the Google Sheets column limit.
136
+ */
137
+ export function googleSheetsColumnToA1(column) {
138
+ if (!Number.isInteger(column) ||
139
+ column < 1 ||
140
+ column > GOOGLE_SHEETS_MAX_COLUMN) {
141
+ throw new Error(`Invalid Google Sheets column number: ${column}`);
142
+ }
143
+ let remaining = column;
144
+ let label = "";
145
+ while (remaining > 0) {
146
+ remaining--;
147
+ label = String.fromCharCode(65 + (remaining % 26)) + label;
148
+ remaining = Math.floor(remaining / 26);
149
+ }
150
+ return label;
151
+ }
152
+ /**
153
+ * Builds a canonical, quoted A1 cell reference.
154
+ *
155
+ * @param sheetTitle - Sheet title to quote.
156
+ * @param row - One-based row number.
157
+ * @param column - One-based column number.
158
+ */
159
+ export function buildGoogleSheetsCellA1(sheetTitle, row, column) {
160
+ return `'${sheetTitle.replaceAll("'", "''")}'!${googleSheetsColumnToA1(column)}${row}`;
161
+ }
162
+ /**
163
+ * Parses one possibly partial A1 endpoint.
164
+ *
165
+ * @param value - Endpoint text.
166
+ * @param input - Original range used in errors.
167
+ */
168
+ function parseEndpoint(value, input) {
169
+ const match = A1_ENDPOINT_PATTERN.exec(value);
170
+ if (!match || (!match[1] && !match[2]))
171
+ return invalidA1Range(input);
172
+ const row = match[2] ? Number(match[2].replace("$", "")) : undefined;
173
+ if (row !== undefined &&
174
+ (!Number.isSafeInteger(row) || row < 1 || row > GOOGLE_SHEETS_MAX_ROW)) {
175
+ return invalidA1Range(input);
176
+ }
177
+ return {
178
+ column: match[1]
179
+ ? googleSheetsA1ColumnToNumber(match[1].replace("$", ""))
180
+ : undefined,
181
+ row,
182
+ };
183
+ }
184
+ /**
185
+ * Converts an A1 column label to a one-based number.
186
+ *
187
+ * @param label - A1 column label.
188
+ */
189
+ function googleSheetsA1ColumnToNumber(label) {
190
+ let result = 0;
191
+ for (const character of label.toUpperCase()) {
192
+ result = result * 26 + character.charCodeAt(0) - 64;
193
+ }
194
+ if (result > GOOGLE_SHEETS_MAX_COLUMN)
195
+ return invalidA1Range(label);
196
+ return result;
197
+ }
198
+ /**
199
+ * Splits an optional quoted sheet prefix from an A1 range.
200
+ *
201
+ * @param value - Trimmed A1 input.
202
+ */
203
+ function splitSheetPrefix(value) {
204
+ if (!value)
205
+ return invalidA1Range(value);
206
+ let quoted = false;
207
+ let separator = -1;
208
+ for (let index = 0; index < value.length; index++) {
209
+ if (value[index] === "'") {
210
+ if (quoted && value[index + 1] === "'") {
211
+ index++;
212
+ }
213
+ else {
214
+ quoted = !quoted;
215
+ }
216
+ }
217
+ else if (value[index] === "!" && !quoted) {
218
+ if (separator !== -1)
219
+ return invalidA1Range(value);
220
+ separator = index;
221
+ }
222
+ }
223
+ if (quoted)
224
+ return invalidA1Range(value);
225
+ if (separator === -1)
226
+ return { range: value, sheet: undefined };
227
+ const rawSheet = value.slice(0, separator);
228
+ const range = value.slice(separator + 1);
229
+ if (!rawSheet || !range)
230
+ return invalidA1Range(value);
231
+ if (rawSheet.startsWith("'") || rawSheet.endsWith("'")) {
232
+ if (!(rawSheet.startsWith("'") && rawSheet.endsWith("'"))) {
233
+ return invalidA1Range(value);
234
+ }
235
+ const sheet = rawSheet.slice(1, -1).replaceAll("''", "'");
236
+ if (!sheet)
237
+ return invalidA1Range(value);
238
+ return { range, sheet };
239
+ }
240
+ if (rawSheet.includes("'"))
241
+ return invalidA1Range(value);
242
+ return { range, sheet: rawSheet };
243
+ }
244
+ /**
245
+ * Throws the canonical invalid-A1-range error.
246
+ *
247
+ * @param input - Invalid range input.
248
+ * @throws Always.
249
+ */
250
+ function invalidA1Range(input) {
251
+ throw new Error(`Invalid Google Sheets A1 range "${input}". Expected a cell or two-ended range such as A1, A1:B2, A:B, or 1:10, optionally prefixed by a sheet name.`);
252
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./google-sheets.js";
2
+ export * from "./schemas.js";
@@ -0,0 +1,2 @@
1
+ export * from "./google-sheets.js";
2
+ export * from "./schemas.js";
@@ -0,0 +1,165 @@
1
+ import * as z from "zod";
2
+ export declare const GOOGLE_SHEETS_MAX_COLUMN = 18278;
3
+ export declare const GOOGLE_SHEETS_MAX_ROW = 10000000;
4
+ export declare const GOOGLE_SHEETS_CELL_VALUE_SCHEMA: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
5
+ export declare const GOOGLE_SHEETS_GRID_RANGE_SCHEMA: z.ZodObject<{
6
+ endColumn: z.ZodOptional<z.ZodNumber>;
7
+ endRow: z.ZodOptional<z.ZodNumber>;
8
+ sheet: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
9
+ startColumn: z.ZodOptional<z.ZodNumber>;
10
+ startRow: z.ZodOptional<z.ZodNumber>;
11
+ }, z.core.$strip>;
12
+ /** A monitored range supplied as A1 notation or one-based grid bounds. */
13
+ export declare const GOOGLE_SHEETS_TRIGGER_RANGE_SCHEMA: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
14
+ endColumn: z.ZodOptional<z.ZodNumber>;
15
+ endRow: z.ZodOptional<z.ZodNumber>;
16
+ sheet: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
17
+ startColumn: z.ZodOptional<z.ZodNumber>;
18
+ startRow: z.ZodOptional<z.ZodNumber>;
19
+ }, z.core.$strip>]>;
20
+ /** Canonical subscription configuration shared by authoring and ingestion. */
21
+ export declare const GOOGLE_SHEETS_TRIGGER_CONFIG_SCHEMA: z.ZodObject<{
22
+ spreadsheetId: z.ZodString;
23
+ range: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
24
+ endColumn: z.ZodOptional<z.ZodNumber>;
25
+ endRow: z.ZodOptional<z.ZodNumber>;
26
+ sheet: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
27
+ startColumn: z.ZodOptional<z.ZodNumber>;
28
+ startRow: z.ZodOptional<z.ZodNumber>;
29
+ }, z.core.$strip>]>>;
30
+ }, z.core.$strip>;
31
+ export declare const GOOGLE_SHEETS_CELL_ERROR_SCHEMA: z.ZodObject<{
32
+ type: z.ZodString;
33
+ message: z.ZodOptional<z.ZodString>;
34
+ }, z.core.$strip>;
35
+ /** Formula and effective value observed for one cell. */
36
+ export declare const GOOGLE_SHEETS_CELL_STATE_SCHEMA: z.ZodObject<{
37
+ error: z.ZodOptional<z.ZodObject<{
38
+ type: z.ZodString;
39
+ message: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strip>>;
41
+ formula: z.ZodOptional<z.ZodString>;
42
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
43
+ }, z.core.$strip>;
44
+ export declare const GOOGLE_SHEETS_CELL_CHANGE_SCHEMA: z.ZodObject<{
45
+ before: z.ZodObject<{
46
+ error: z.ZodOptional<z.ZodObject<{
47
+ type: z.ZodString;
48
+ message: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$strip>>;
50
+ formula: z.ZodOptional<z.ZodString>;
51
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
52
+ }, z.core.$strip>;
53
+ cell: z.ZodObject<{
54
+ a1: z.ZodString;
55
+ column: z.ZodNumber;
56
+ row: z.ZodNumber;
57
+ }, z.core.$strip>;
58
+ after: z.ZodObject<{
59
+ error: z.ZodOptional<z.ZodObject<{
60
+ type: z.ZodString;
61
+ message: z.ZodOptional<z.ZodString>;
62
+ }, z.core.$strip>>;
63
+ formula: z.ZodOptional<z.ZodString>;
64
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
65
+ }, z.core.$strip>;
66
+ sheet: z.ZodObject<{
67
+ id: z.ZodNumber;
68
+ title: z.ZodString;
69
+ }, z.core.$strip>;
70
+ }, z.core.$strip>;
71
+ /** One bounded batch of net cell changes from a spreadsheet observation. */
72
+ export declare const GOOGLE_SHEETS_CELLS_CHANGED_EVENT_SCHEMA: z.ZodObject<{
73
+ changes: z.ZodArray<z.ZodObject<{
74
+ before: z.ZodObject<{
75
+ error: z.ZodOptional<z.ZodObject<{
76
+ type: z.ZodString;
77
+ message: z.ZodOptional<z.ZodString>;
78
+ }, z.core.$strip>>;
79
+ formula: z.ZodOptional<z.ZodString>;
80
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
81
+ }, z.core.$strip>;
82
+ cell: z.ZodObject<{
83
+ a1: z.ZodString;
84
+ column: z.ZodNumber;
85
+ row: z.ZodNumber;
86
+ }, z.core.$strip>;
87
+ after: z.ZodObject<{
88
+ error: z.ZodOptional<z.ZodObject<{
89
+ type: z.ZodString;
90
+ message: z.ZodOptional<z.ZodString>;
91
+ }, z.core.$strip>>;
92
+ formula: z.ZodOptional<z.ZodString>;
93
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
94
+ }, z.core.$strip>;
95
+ sheet: z.ZodObject<{
96
+ id: z.ZodNumber;
97
+ title: z.ZodString;
98
+ }, z.core.$strip>;
99
+ }, z.core.$strip>>;
100
+ chunk: z.ZodObject<{
101
+ index: z.ZodNumber;
102
+ total: z.ZodNumber;
103
+ }, z.core.$strip>;
104
+ observedAt: z.ZodISODateTime;
105
+ spreadsheet: z.ZodObject<{
106
+ id: z.ZodString;
107
+ title: z.ZodString;
108
+ url: z.ZodURL;
109
+ }, z.core.$strip>;
110
+ }, z.core.$strip>;
111
+ export declare const googleSheetsTriggerContracts: {
112
+ readonly "googleSheets.cells.changed": {
113
+ readonly configSchema: z.ZodObject<{
114
+ spreadsheetId: z.ZodString;
115
+ range: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
116
+ endColumn: z.ZodOptional<z.ZodNumber>;
117
+ endRow: z.ZodOptional<z.ZodNumber>;
118
+ sheet: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
119
+ startColumn: z.ZodOptional<z.ZodNumber>;
120
+ startRow: z.ZodOptional<z.ZodNumber>;
121
+ }, z.core.$strip>]>>;
122
+ }, z.core.$strip>;
123
+ readonly eventSchema: z.ZodObject<{
124
+ changes: z.ZodArray<z.ZodObject<{
125
+ before: z.ZodObject<{
126
+ error: z.ZodOptional<z.ZodObject<{
127
+ type: z.ZodString;
128
+ message: z.ZodOptional<z.ZodString>;
129
+ }, z.core.$strip>>;
130
+ formula: z.ZodOptional<z.ZodString>;
131
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
132
+ }, z.core.$strip>;
133
+ cell: z.ZodObject<{
134
+ a1: z.ZodString;
135
+ column: z.ZodNumber;
136
+ row: z.ZodNumber;
137
+ }, z.core.$strip>;
138
+ after: z.ZodObject<{
139
+ error: z.ZodOptional<z.ZodObject<{
140
+ type: z.ZodString;
141
+ message: z.ZodOptional<z.ZodString>;
142
+ }, z.core.$strip>>;
143
+ formula: z.ZodOptional<z.ZodString>;
144
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
145
+ }, z.core.$strip>;
146
+ sheet: z.ZodObject<{
147
+ id: z.ZodNumber;
148
+ title: z.ZodString;
149
+ }, z.core.$strip>;
150
+ }, z.core.$strip>>;
151
+ chunk: z.ZodObject<{
152
+ index: z.ZodNumber;
153
+ total: z.ZodNumber;
154
+ }, z.core.$strip>;
155
+ observedAt: z.ZodISODateTime;
156
+ spreadsheet: z.ZodObject<{
157
+ id: z.ZodString;
158
+ title: z.ZodString;
159
+ url: z.ZodURL;
160
+ }, z.core.$strip>;
161
+ }, z.core.$strip>;
162
+ };
163
+ };
164
+ export type GoogleSheetsGridRange = z.input<typeof GOOGLE_SHEETS_GRID_RANGE_SCHEMA>;
165
+ export type GoogleSheetsTriggerRange = z.input<typeof GOOGLE_SHEETS_TRIGGER_RANGE_SCHEMA>;
@@ -0,0 +1,100 @@
1
+ import * as z from "zod";
2
+ export const GOOGLE_SHEETS_MAX_COLUMN = 18_278;
3
+ export const GOOGLE_SHEETS_MAX_ROW = 10_000_000;
4
+ export const GOOGLE_SHEETS_CELL_VALUE_SCHEMA = z.union([
5
+ z.string(),
6
+ z.number(),
7
+ z.boolean(),
8
+ z.null(),
9
+ ]);
10
+ export const GOOGLE_SHEETS_GRID_RANGE_SCHEMA = z
11
+ .object({
12
+ /** Inclusive one-based ending column. Omit for an unbounded range. */
13
+ endColumn: z
14
+ .number()
15
+ .int()
16
+ .positive()
17
+ .max(GOOGLE_SHEETS_MAX_COLUMN)
18
+ .optional(),
19
+ /** Inclusive one-based ending row. Omit for an unbounded range. */
20
+ endRow: z.number().int().positive().max(GOOGLE_SHEETS_MAX_ROW).optional(),
21
+ /** Sheet title or immutable numeric sheet ID. */
22
+ sheet: z.union([z.string().trim().min(1), z.number().int().nonnegative()]),
23
+ /** Inclusive one-based starting column. */
24
+ startColumn: z
25
+ .number()
26
+ .int()
27
+ .positive()
28
+ .max(GOOGLE_SHEETS_MAX_COLUMN)
29
+ .optional(),
30
+ /** Inclusive one-based starting row. */
31
+ startRow: z.number().int().positive().max(GOOGLE_SHEETS_MAX_ROW).optional(),
32
+ })
33
+ .refine(({ endColumn, startColumn }) => endColumn === undefined ||
34
+ startColumn === undefined ||
35
+ endColumn >= startColumn, "The ending column must not precede the starting column.")
36
+ .refine(({ endRow, startRow }) => endRow === undefined || startRow === undefined || endRow >= startRow, "The ending row must not precede the starting row.");
37
+ /** A monitored range supplied as A1 notation or one-based grid bounds. */
38
+ export const GOOGLE_SHEETS_TRIGGER_RANGE_SCHEMA = z.union([
39
+ z.string().trim().min(1),
40
+ GOOGLE_SHEETS_GRID_RANGE_SCHEMA,
41
+ ]);
42
+ /** Canonical subscription configuration shared by authoring and ingestion. */
43
+ export const GOOGLE_SHEETS_TRIGGER_CONFIG_SCHEMA = z.object({
44
+ /** Immutable spreadsheet ID. */
45
+ spreadsheetId: z.string().trim().min(1),
46
+ /** Optional range. Omit to monitor every sheet in the spreadsheet. */
47
+ range: GOOGLE_SHEETS_TRIGGER_RANGE_SCHEMA.optional(),
48
+ });
49
+ export const GOOGLE_SHEETS_CELL_ERROR_SCHEMA = z.object({
50
+ /** Provider error category, such as `DIVIDE_BY_ZERO`. */
51
+ type: z.string().min(1),
52
+ /** Provider explanation when available. */
53
+ message: z.string().optional(),
54
+ });
55
+ /** Formula and effective value observed for one cell. */
56
+ export const GOOGLE_SHEETS_CELL_STATE_SCHEMA = z.object({
57
+ /** Provider error when the effective value cannot be calculated. */
58
+ error: GOOGLE_SHEETS_CELL_ERROR_SCHEMA.optional(),
59
+ /** User-entered formula, including its leading `=`. */
60
+ formula: z.string().optional(),
61
+ /** Effective unformatted value. Empty and error cells use `null`. */
62
+ value: GOOGLE_SHEETS_CELL_VALUE_SCHEMA,
63
+ });
64
+ export const GOOGLE_SHEETS_CELL_CHANGE_SCHEMA = z.object({
65
+ /** State from the preceding complete observation. */
66
+ before: GOOGLE_SHEETS_CELL_STATE_SCHEMA,
67
+ /** One-based cell coordinates and canonical A1 reference. */
68
+ cell: z.object({
69
+ a1: z.string().min(1),
70
+ column: z.number().int().positive(),
71
+ row: z.number().int().positive(),
72
+ }),
73
+ /** State from the current complete observation. */
74
+ after: GOOGLE_SHEETS_CELL_STATE_SCHEMA,
75
+ /** Immutable sheet identity and current or last-known title. */
76
+ sheet: z.object({
77
+ id: z.number().int().nonnegative(),
78
+ title: z.string().min(1),
79
+ }),
80
+ });
81
+ /** One bounded batch of net cell changes from a spreadsheet observation. */
82
+ export const GOOGLE_SHEETS_CELLS_CHANGED_EVENT_SCHEMA = z.object({
83
+ changes: GOOGLE_SHEETS_CELL_CHANGE_SCHEMA.array().min(1).max(500),
84
+ chunk: z.object({
85
+ index: z.number().int().nonnegative(),
86
+ total: z.number().int().positive(),
87
+ }),
88
+ observedAt: z.iso.datetime({ offset: true }),
89
+ spreadsheet: z.object({
90
+ id: z.string().min(1),
91
+ title: z.string().min(1),
92
+ url: z.url(),
93
+ }),
94
+ });
95
+ export const googleSheetsTriggerContracts = {
96
+ "googleSheets.cells.changed": {
97
+ configSchema: GOOGLE_SHEETS_TRIGGER_CONFIG_SCHEMA,
98
+ eventSchema: GOOGLE_SHEETS_CELLS_CHANGED_EVENT_SCHEMA,
99
+ },
100
+ };
@@ -10,6 +10,7 @@ import type { gmailTriggerContracts } from "./gmail/index.js";
10
10
  import type { googleCalendarTriggerContracts } from "./google-calendar/index.js";
11
11
  import type { googleFormsTriggerContracts } from "./google-forms/index.js";
12
12
  import type { googleMeetTriggerContracts } from "./google-meet/index.js";
13
+ import type { googleSheetsTriggerContracts } from "./google-sheets/index.js";
13
14
  import type { linearTriggerContracts } from "./linear/index.js";
14
15
  import type { notionTriggerContracts } from "./notion/index.js";
15
16
  import type { outlookTriggerContracts } from "./outlook/index.js";
@@ -22,7 +23,7 @@ import type { vercelTriggerContracts } from "./vercel/index.js";
22
23
  import type { webflowTriggerContracts } from "./webflow/index.js";
23
24
  import type { whatsappTriggerContracts } from "./whatsapp/index.js";
24
25
  import type { z } from "zod";
25
- export type TriggerContractMap = typeof airtableTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof convexTriggerContracts & typeof closeTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof linearTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
26
+ export type TriggerContractMap = typeof airtableTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof convexTriggerContracts & typeof closeTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof googleSheetsTriggerContracts & typeof linearTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
26
27
  export type IntegrationTriggerType = keyof TriggerContractMap;
27
28
  /** Canonical authoring configuration for one integration trigger type. */
28
29
  export type TriggerConfig<TType extends IntegrationTriggerType> = z.input<TriggerContractMap[TType]["configSchema"]> extends Record<string, never> ? object : z.input<TriggerContractMap[TType]["configSchema"]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/integration-contracts",
3
- "version": "0.112.10",
3
+ "version": "0.113.0",
4
4
  "description": "Shared integration payload contracts and provider primitives for Automate.ax.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,6 +29,7 @@
29
29
  "./google-calendar": "./src/google-calendar/index.ts",
30
30
  "./google-forms": "./src/google-forms/index.ts",
31
31
  "./google-meet": "./src/google-meet/index.ts",
32
+ "./google-sheets": "./src/google-sheets/index.ts",
32
33
  "./github": "./src/github/index.ts",
33
34
  "./jobnimbus": "./src/jobnimbus/index.ts",
34
35
  "./linear": "./src/linear/index.ts",
@@ -50,7 +51,7 @@
50
51
  }
51
52
  },
52
53
  "dependencies": {
53
- "@automate.ax/codec": "0.112.10",
54
+ "@automate.ax/codec": "0.113.0",
54
55
  "@cfworker/json-schema": "^4.1.1",
55
56
  "@googleapis/calendar": "^15.0.0",
56
57
  "@googleapis/forms": "^6.0.1",
@@ -156,6 +157,11 @@
156
157
  "types": "./dist/google-meet/index.d.ts",
157
158
  "default": "./dist/google-meet/index.js"
158
159
  },
160
+ "./google-sheets": {
161
+ "bun": "./src/google-sheets/index.ts",
162
+ "types": "./dist/google-sheets/index.d.ts",
163
+ "default": "./dist/google-sheets/index.js"
164
+ },
159
165
  "./github": {
160
166
  "bun": "./src/github/index.ts",
161
167
  "types": "./dist/github/index.d.ts",
@@ -0,0 +1,283 @@
1
+ import {
2
+ GOOGLE_SHEETS_MAX_COLUMN,
3
+ GOOGLE_SHEETS_MAX_ROW,
4
+ type GoogleSheetsGridRange,
5
+ } from "./schemas"
6
+
7
+ const GOOGLE_SHEETS_URL_HOSTS = new Set([
8
+ "docs.google.com",
9
+ "sheets.google.com",
10
+ ])
11
+ const A1_ENDPOINT_PATTERN = /^(\$?[A-Za-z]{1,3})?(\$?\d+)?$/
12
+
13
+ export interface ParsedGoogleSheetsA1Range
14
+ extends Omit<GoogleSheetsGridRange, "sheet"> {
15
+ /** Sheet title. Omit to use the spreadsheet's first visible sheet. */
16
+ sheet?: string
17
+ }
18
+
19
+ /**
20
+ * Accepts either a raw spreadsheet ID or a standard Google Sheets URL.
21
+ *
22
+ * @param spreadsheet - Spreadsheet ID or URL.
23
+ * @throws When a URL is not a standard Google Sheets URL.
24
+ */
25
+ export function normalizeGoogleSheetsSpreadsheetId(spreadsheet: string) {
26
+ const value = spreadsheet.trim()
27
+ if (!URL.canParse(value)) return value
28
+
29
+ const url = new URL(value)
30
+ if (!GOOGLE_SHEETS_URL_HOSTS.has(url.hostname)) {
31
+ throw new Error(`Expected a Google Sheets URL, received "${value}".`)
32
+ }
33
+ const parts = url.pathname.split("/")
34
+ const spreadsheetsIndex = parts.indexOf("spreadsheets")
35
+ const documentIndex =
36
+ spreadsheetsIndex === -1 ? -1 : parts.indexOf("d", spreadsheetsIndex + 1)
37
+ const spreadsheetId =
38
+ documentIndex === -1 ? undefined : parts[documentIndex + 1]
39
+ if (!spreadsheetId) {
40
+ throw new Error(`Could not find a spreadsheet ID in "${value}".`)
41
+ }
42
+ return spreadsheetId
43
+ }
44
+
45
+ /**
46
+ * Parses the practical A1 range forms accepted by Google Sheets.
47
+ *
48
+ * Partial two-ended ranges preserve the v5 parser's normalization: a missing
49
+ * coordinate is inferred from the opposite endpoint where needed, then left
50
+ * unbounded on the ending side.
51
+ *
52
+ * @param input - A1 cell or range with an optional sheet prefix.
53
+ */
54
+ export function parseGoogleSheetsA1Range(
55
+ input: string,
56
+ ): ParsedGoogleSheetsA1Range {
57
+ const { range, sheet } = splitSheetPrefix(input.trim())
58
+ const endpointTexts = range.split(":")
59
+ if (endpointTexts.length > 2) return invalidA1Range(input)
60
+
61
+ if (endpointTexts.length === 1) {
62
+ const endpoint = parseEndpoint(endpointTexts[0] ?? "", input)
63
+ if (endpoint.column === undefined || endpoint.row === undefined) {
64
+ return invalidA1Range(input)
65
+ }
66
+ return {
67
+ endColumn: endpoint.column,
68
+ endRow: endpoint.row,
69
+ ...(sheet !== undefined && { sheet }),
70
+ startColumn: endpoint.column,
71
+ startRow: endpoint.row,
72
+ }
73
+ }
74
+
75
+ const start = parseEndpoint(endpointTexts[0] ?? "", input)
76
+ const end = parseEndpoint(endpointTexts[1] ?? "", input)
77
+ // Naming the endpoint mask keeps the preserved v5 case matrix legible.
78
+ const mask =
79
+ (start.column === undefined ? 0 : 0b1000) |
80
+ (start.row === undefined ? 0 : 0b0100) |
81
+ (end.column === undefined ? 0 : 0b0010) |
82
+ (end.row === undefined ? 0 : 0b0001)
83
+
84
+ let parsed: Omit<ParsedGoogleSheetsA1Range, "sheet">
85
+ switch (mask) {
86
+ case 0b0101:
87
+ parsed = {
88
+ endRow: end.row,
89
+ startColumn: 1,
90
+ startRow: start.row,
91
+ }
92
+ break
93
+ case 0b1101:
94
+ parsed = {
95
+ endRow: end.row,
96
+ startColumn: start.column,
97
+ startRow: start.row,
98
+ }
99
+ break
100
+ case 0b0111:
101
+ parsed = {
102
+ endRow: end.row,
103
+ startColumn: end.column,
104
+ startRow: start.row,
105
+ }
106
+ break
107
+ case 0b1010:
108
+ parsed = {
109
+ endColumn: end.column,
110
+ startColumn: start.column,
111
+ startRow: 1,
112
+ }
113
+ break
114
+ case 0b1110:
115
+ parsed = {
116
+ endColumn: end.column,
117
+ startColumn: start.column,
118
+ startRow: start.row,
119
+ }
120
+ break
121
+ case 0b1011:
122
+ parsed = {
123
+ endColumn: end.column,
124
+ startColumn: start.column,
125
+ startRow: end.row,
126
+ }
127
+ break
128
+ case 0b1111:
129
+ parsed = {
130
+ endColumn: end.column,
131
+ endRow: end.row,
132
+ startColumn: start.column,
133
+ startRow: start.row,
134
+ }
135
+ break
136
+ default:
137
+ return invalidA1Range(input)
138
+ }
139
+
140
+ if (
141
+ parsed.endColumn !== undefined &&
142
+ parsed.startColumn !== undefined &&
143
+ parsed.endColumn < parsed.startColumn
144
+ ) {
145
+ return invalidA1Range(input)
146
+ }
147
+ if (
148
+ parsed.endRow !== undefined &&
149
+ parsed.startRow !== undefined &&
150
+ parsed.endRow < parsed.startRow
151
+ ) {
152
+ return invalidA1Range(input)
153
+ }
154
+ return { ...parsed, ...(sheet !== undefined && { sheet }) }
155
+ }
156
+
157
+ /**
158
+ * Converts a one-based column number to its A1 label.
159
+ *
160
+ * @param column - One-based column number.
161
+ * @throws When the number is outside the Google Sheets column limit.
162
+ */
163
+ export function googleSheetsColumnToA1(column: number) {
164
+ if (
165
+ !Number.isInteger(column) ||
166
+ column < 1 ||
167
+ column > GOOGLE_SHEETS_MAX_COLUMN
168
+ ) {
169
+ throw new Error(`Invalid Google Sheets column number: ${column}`)
170
+ }
171
+ let remaining = column
172
+ let label = ""
173
+ while (remaining > 0) {
174
+ remaining--
175
+ label = String.fromCharCode(65 + (remaining % 26)) + label
176
+ remaining = Math.floor(remaining / 26)
177
+ }
178
+ return label
179
+ }
180
+
181
+ /**
182
+ * Builds a canonical, quoted A1 cell reference.
183
+ *
184
+ * @param sheetTitle - Sheet title to quote.
185
+ * @param row - One-based row number.
186
+ * @param column - One-based column number.
187
+ */
188
+ export function buildGoogleSheetsCellA1(
189
+ sheetTitle: string,
190
+ row: number,
191
+ column: number,
192
+ ) {
193
+ return `'${sheetTitle.replaceAll("'", "''")}'!${googleSheetsColumnToA1(column)}${row}`
194
+ }
195
+
196
+ /**
197
+ * Parses one possibly partial A1 endpoint.
198
+ *
199
+ * @param value - Endpoint text.
200
+ * @param input - Original range used in errors.
201
+ */
202
+ function parseEndpoint(value: string, input: string) {
203
+ const match = A1_ENDPOINT_PATTERN.exec(value)
204
+ if (!match || (!match[1] && !match[2])) return invalidA1Range(input)
205
+ const row = match[2] ? Number(match[2].replace("$", "")) : undefined
206
+ if (
207
+ row !== undefined &&
208
+ (!Number.isSafeInteger(row) || row < 1 || row > GOOGLE_SHEETS_MAX_ROW)
209
+ ) {
210
+ return invalidA1Range(input)
211
+ }
212
+ return {
213
+ column: match[1]
214
+ ? googleSheetsA1ColumnToNumber(match[1].replace("$", ""))
215
+ : undefined,
216
+ row,
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Converts an A1 column label to a one-based number.
222
+ *
223
+ * @param label - A1 column label.
224
+ */
225
+ function googleSheetsA1ColumnToNumber(label: string) {
226
+ let result = 0
227
+ for (const character of label.toUpperCase()) {
228
+ result = result * 26 + character.charCodeAt(0) - 64
229
+ }
230
+ if (result > GOOGLE_SHEETS_MAX_COLUMN) return invalidA1Range(label)
231
+ return result
232
+ }
233
+
234
+ /**
235
+ * Splits an optional quoted sheet prefix from an A1 range.
236
+ *
237
+ * @param value - Trimmed A1 input.
238
+ */
239
+ function splitSheetPrefix(value: string) {
240
+ if (!value) return invalidA1Range(value)
241
+ let quoted = false
242
+ let separator = -1
243
+ for (let index = 0; index < value.length; index++) {
244
+ if (value[index] === "'") {
245
+ if (quoted && value[index + 1] === "'") {
246
+ index++
247
+ } else {
248
+ quoted = !quoted
249
+ }
250
+ } else if (value[index] === "!" && !quoted) {
251
+ if (separator !== -1) return invalidA1Range(value)
252
+ separator = index
253
+ }
254
+ }
255
+ if (quoted) return invalidA1Range(value)
256
+ if (separator === -1) return { range: value, sheet: undefined }
257
+
258
+ const rawSheet = value.slice(0, separator)
259
+ const range = value.slice(separator + 1)
260
+ if (!rawSheet || !range) return invalidA1Range(value)
261
+ if (rawSheet.startsWith("'") || rawSheet.endsWith("'")) {
262
+ if (!(rawSheet.startsWith("'") && rawSheet.endsWith("'"))) {
263
+ return invalidA1Range(value)
264
+ }
265
+ const sheet = rawSheet.slice(1, -1).replaceAll("''", "'")
266
+ if (!sheet) return invalidA1Range(value)
267
+ return { range, sheet }
268
+ }
269
+ if (rawSheet.includes("'")) return invalidA1Range(value)
270
+ return { range, sheet: rawSheet }
271
+ }
272
+
273
+ /**
274
+ * Throws the canonical invalid-A1-range error.
275
+ *
276
+ * @param input - Invalid range input.
277
+ * @throws Always.
278
+ */
279
+ function invalidA1Range(input: string): never {
280
+ throw new Error(
281
+ `Invalid Google Sheets A1 range "${input}". Expected a cell or two-ended range such as A1, A1:B2, A:B, or 1:10, optionally prefixed by a sheet name.`,
282
+ )
283
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./google-sheets"
2
+ export * from "./schemas"
@@ -0,0 +1,136 @@
1
+ import * as z from "zod"
2
+
3
+ export const GOOGLE_SHEETS_MAX_COLUMN = 18_278
4
+ export const GOOGLE_SHEETS_MAX_ROW = 10_000_000
5
+
6
+ export const GOOGLE_SHEETS_CELL_VALUE_SCHEMA = z.union([
7
+ z.string(),
8
+ z.number(),
9
+ z.boolean(),
10
+ z.null(),
11
+ ])
12
+
13
+ export const GOOGLE_SHEETS_GRID_RANGE_SCHEMA = z
14
+ .object({
15
+ /** Inclusive one-based ending column. Omit for an unbounded range. */
16
+ endColumn: z
17
+ .number()
18
+ .int()
19
+ .positive()
20
+ .max(GOOGLE_SHEETS_MAX_COLUMN)
21
+ .optional(),
22
+
23
+ /** Inclusive one-based ending row. Omit for an unbounded range. */
24
+ endRow: z.number().int().positive().max(GOOGLE_SHEETS_MAX_ROW).optional(),
25
+
26
+ /** Sheet title or immutable numeric sheet ID. */
27
+ sheet: z.union([z.string().trim().min(1), z.number().int().nonnegative()]),
28
+
29
+ /** Inclusive one-based starting column. */
30
+ startColumn: z
31
+ .number()
32
+ .int()
33
+ .positive()
34
+ .max(GOOGLE_SHEETS_MAX_COLUMN)
35
+ .optional(),
36
+
37
+ /** Inclusive one-based starting row. */
38
+ startRow: z.number().int().positive().max(GOOGLE_SHEETS_MAX_ROW).optional(),
39
+ })
40
+ .refine(
41
+ ({ endColumn, startColumn }) =>
42
+ endColumn === undefined ||
43
+ startColumn === undefined ||
44
+ endColumn >= startColumn,
45
+ "The ending column must not precede the starting column.",
46
+ )
47
+ .refine(
48
+ ({ endRow, startRow }) =>
49
+ endRow === undefined || startRow === undefined || endRow >= startRow,
50
+ "The ending row must not precede the starting row.",
51
+ )
52
+
53
+ /** A monitored range supplied as A1 notation or one-based grid bounds. */
54
+ export const GOOGLE_SHEETS_TRIGGER_RANGE_SCHEMA = z.union([
55
+ z.string().trim().min(1),
56
+ GOOGLE_SHEETS_GRID_RANGE_SCHEMA,
57
+ ])
58
+
59
+ /** Canonical subscription configuration shared by authoring and ingestion. */
60
+ export const GOOGLE_SHEETS_TRIGGER_CONFIG_SCHEMA = z.object({
61
+ /** Immutable spreadsheet ID. */
62
+ spreadsheetId: z.string().trim().min(1),
63
+
64
+ /** Optional range. Omit to monitor every sheet in the spreadsheet. */
65
+ range: GOOGLE_SHEETS_TRIGGER_RANGE_SCHEMA.optional(),
66
+ })
67
+
68
+ export const GOOGLE_SHEETS_CELL_ERROR_SCHEMA = z.object({
69
+ /** Provider error category, such as `DIVIDE_BY_ZERO`. */
70
+ type: z.string().min(1),
71
+
72
+ /** Provider explanation when available. */
73
+ message: z.string().optional(),
74
+ })
75
+
76
+ /** Formula and effective value observed for one cell. */
77
+ export const GOOGLE_SHEETS_CELL_STATE_SCHEMA = z.object({
78
+ /** Provider error when the effective value cannot be calculated. */
79
+ error: GOOGLE_SHEETS_CELL_ERROR_SCHEMA.optional(),
80
+
81
+ /** User-entered formula, including its leading `=`. */
82
+ formula: z.string().optional(),
83
+
84
+ /** Effective unformatted value. Empty and error cells use `null`. */
85
+ value: GOOGLE_SHEETS_CELL_VALUE_SCHEMA,
86
+ })
87
+
88
+ export const GOOGLE_SHEETS_CELL_CHANGE_SCHEMA = z.object({
89
+ /** State from the preceding complete observation. */
90
+ before: GOOGLE_SHEETS_CELL_STATE_SCHEMA,
91
+
92
+ /** One-based cell coordinates and canonical A1 reference. */
93
+ cell: z.object({
94
+ a1: z.string().min(1),
95
+ column: z.number().int().positive(),
96
+ row: z.number().int().positive(),
97
+ }),
98
+
99
+ /** State from the current complete observation. */
100
+ after: GOOGLE_SHEETS_CELL_STATE_SCHEMA,
101
+
102
+ /** Immutable sheet identity and current or last-known title. */
103
+ sheet: z.object({
104
+ id: z.number().int().nonnegative(),
105
+ title: z.string().min(1),
106
+ }),
107
+ })
108
+
109
+ /** One bounded batch of net cell changes from a spreadsheet observation. */
110
+ export const GOOGLE_SHEETS_CELLS_CHANGED_EVENT_SCHEMA = z.object({
111
+ changes: GOOGLE_SHEETS_CELL_CHANGE_SCHEMA.array().min(1).max(500),
112
+ chunk: z.object({
113
+ index: z.number().int().nonnegative(),
114
+ total: z.number().int().positive(),
115
+ }),
116
+ observedAt: z.iso.datetime({ offset: true }),
117
+ spreadsheet: z.object({
118
+ id: z.string().min(1),
119
+ title: z.string().min(1),
120
+ url: z.url(),
121
+ }),
122
+ })
123
+
124
+ export const googleSheetsTriggerContracts = {
125
+ "googleSheets.cells.changed": {
126
+ configSchema: GOOGLE_SHEETS_TRIGGER_CONFIG_SCHEMA,
127
+ eventSchema: GOOGLE_SHEETS_CELLS_CHANGED_EVENT_SCHEMA,
128
+ },
129
+ } as const
130
+
131
+ export type GoogleSheetsGridRange = z.input<
132
+ typeof GOOGLE_SHEETS_GRID_RANGE_SCHEMA
133
+ >
134
+ export type GoogleSheetsTriggerRange = z.input<
135
+ typeof GOOGLE_SHEETS_TRIGGER_RANGE_SCHEMA
136
+ >
package/src/triggers.ts CHANGED
@@ -10,6 +10,7 @@ import type { gmailTriggerContracts } from "./gmail"
10
10
  import type { googleCalendarTriggerContracts } from "./google-calendar"
11
11
  import type { googleFormsTriggerContracts } from "./google-forms"
12
12
  import type { googleMeetTriggerContracts } from "./google-meet"
13
+ import type { googleSheetsTriggerContracts } from "./google-sheets"
13
14
  import type { linearTriggerContracts } from "./linear"
14
15
  import type { notionTriggerContracts } from "./notion"
15
16
  import type { outlookTriggerContracts } from "./outlook"
@@ -35,6 +36,7 @@ export type TriggerContractMap = typeof airtableTriggerContracts &
35
36
  typeof googleCalendarTriggerContracts &
36
37
  typeof googleFormsTriggerContracts &
37
38
  typeof googleMeetTriggerContracts &
39
+ typeof googleSheetsTriggerContracts &
38
40
  typeof linearTriggerContracts &
39
41
  typeof notionTriggerContracts &
40
42
  typeof outlookTriggerContracts &