zitejs 0.10.0-start.7 → 0.10.0-start.8
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/sync/lib.js +4 -7
- package/dist/cjs/vite/index.js +8 -3
- package/dist/cjs/vite/index.test.js +10 -2
- package/dist/esm/start/airtable/arrayFields.d.ts +10 -0
- package/dist/esm/start/airtable/arrayFields.js +30 -0
- package/dist/esm/start/airtable/dispatch.d.ts +54 -0
- package/dist/esm/start/airtable/dispatch.js +465 -0
- package/dist/esm/start/airtable/formulas.d.ts +69 -0
- package/dist/esm/start/airtable/formulas.js +235 -0
- package/dist/esm/start/airtable/index.d.ts +11 -0
- package/dist/esm/start/airtable/index.js +70 -0
- package/dist/esm/start/data/baseRunner.d.ts +92 -0
- package/dist/esm/start/data/baseRunner.js +83 -0
- package/dist/esm/start/data/dispatch.d.ts +16 -0
- package/dist/esm/start/data/dispatch.js +198 -0
- package/dist/esm/start/data/index.d.ts +18 -0
- package/dist/esm/start/data/index.js +76 -0
- package/dist/esm/start/data/sqlNameMappings.d.ts +58 -0
- package/dist/esm/start/data/sqlNameMappings.js +92 -0
- package/dist/esm/start/data/transformations.d.ts +128 -0
- package/dist/esm/start/data/transformations.js +318 -0
- package/dist/esm/start/data/types.d.ts +91 -0
- package/dist/esm/start/data/types.js +14 -0
- package/dist/esm/start/server.js +54 -5
- package/dist/esm/start/types.d.ts +40 -0
- package/dist/esm/start/version.d.ts +1 -1
- package/dist/esm/start/version.js +1 -1
- package/dist/esm/start/vite.js +2 -1
- package/dist/esm/sync/lib.js +4 -7
- package/dist/esm/vite/index.js +8 -3
- package/dist/esm/vite/index.test.js +10 -2
- package/package.json +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Record Transformation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared transformation functions for converting between:
|
|
5
|
+
* - SDK field names ↔ database field IDs
|
|
6
|
+
* - LLM-friendly formats ↔ database formats
|
|
7
|
+
*
|
|
8
|
+
* Used by both workflow-runner and cloudflare-lambda.
|
|
9
|
+
*/
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Reverse Lookup Helpers
|
|
12
|
+
// =============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Build reverse lookup map: sdkName → fieldId
|
|
15
|
+
*/
|
|
16
|
+
export function buildSdkNameToFieldIdMap(fieldMappings) {
|
|
17
|
+
const map = {};
|
|
18
|
+
if (!fieldMappings)
|
|
19
|
+
return map;
|
|
20
|
+
for (const [fieldId, fieldInfo] of Object.entries(fieldMappings)) {
|
|
21
|
+
if (fieldInfo?.mapping?.sdkName) {
|
|
22
|
+
map[fieldInfo.mapping.sdkName] = fieldId;
|
|
23
|
+
// Also add lowercase version for case-insensitive lookup
|
|
24
|
+
map[fieldInfo.mapping.sdkName.toLowerCase()] = fieldId;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return map;
|
|
28
|
+
}
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Filter Transformations
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Transform SDK field names to database field IDs in filters
|
|
34
|
+
*/
|
|
35
|
+
export function transformFiltersToFieldIds(filters, fieldMappings) {
|
|
36
|
+
if (!fieldMappings)
|
|
37
|
+
return filters;
|
|
38
|
+
const sdkNameToFieldId = buildSdkNameToFieldIdMap(fieldMappings);
|
|
39
|
+
const transformed = {};
|
|
40
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
41
|
+
const fieldId = sdkNameToFieldId[key] || sdkNameToFieldId[key.toLowerCase()] || key;
|
|
42
|
+
transformed[fieldId] = value;
|
|
43
|
+
}
|
|
44
|
+
return transformed;
|
|
45
|
+
}
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Formula Expression Transformations
|
|
48
|
+
// =============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Transform SDK field names to database field IDs inside a formula expression.
|
|
51
|
+
*
|
|
52
|
+
* The base-runner formula evaluator only accepts display names or field IDs
|
|
53
|
+
* in `{fieldRef}` placeholders — never SDK names. The LLM, however, sees the
|
|
54
|
+
* codebase through the SDK (camelCase fields like `count`), so it naturally
|
|
55
|
+
* writes `{count}` in expressions. This rewrite normalizes those references
|
|
56
|
+
* to field IDs at the SDK boundary, mirroring how `transformFiltersToFieldIds`
|
|
57
|
+
* normalizes filter keys.
|
|
58
|
+
*
|
|
59
|
+
* Behavior:
|
|
60
|
+
* - `{sdkName}` → `{fieldId}` when sdkName matches a field in `fieldMappings`
|
|
61
|
+
* - `{fieldId}` → unchanged (already a valid id)
|
|
62
|
+
* - `{anything else}` → unchanged (let the formula validator reject with a
|
|
63
|
+
* clear, table-specific error message)
|
|
64
|
+
* - `{linkField.targetField}` (cross-table) → unchanged (target field lives
|
|
65
|
+
* in another table; resolving it requires a separate per-link mapping
|
|
66
|
+
* that the SDK boundary doesn't have)
|
|
67
|
+
*/
|
|
68
|
+
export function transformFormulaExpressionToFieldIds(expression, fieldMappings) {
|
|
69
|
+
if (!fieldMappings)
|
|
70
|
+
return expression;
|
|
71
|
+
const sdkNameToFieldId = buildSdkNameToFieldIdMap(fieldMappings);
|
|
72
|
+
const knownFieldIds = new Set(Object.keys(fieldMappings));
|
|
73
|
+
return expression.replace(/\{([^}]+)\}/g, (match, ref) => {
|
|
74
|
+
if (ref.includes('.'))
|
|
75
|
+
return match;
|
|
76
|
+
if (knownFieldIds.has(ref))
|
|
77
|
+
return match;
|
|
78
|
+
const fieldId = sdkNameToFieldId[ref] ?? sdkNameToFieldId[ref.toLowerCase()];
|
|
79
|
+
return fieldId ? `{${fieldId}}` : match;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Record Input Transformations (SDK → Database)
|
|
84
|
+
// =============================================================================
|
|
85
|
+
/**
|
|
86
|
+
* Transform record data to use field IDs instead of field names.
|
|
87
|
+
*
|
|
88
|
+
* LLMs often use human-readable field names, but the database expects field IDs.
|
|
89
|
+
* Uses a three-step fallback:
|
|
90
|
+
* 1. Check if key is already a valid field ID
|
|
91
|
+
* 2. Match by field name (case-insensitive)
|
|
92
|
+
* 3. Match by SDK name from field mappings
|
|
93
|
+
*
|
|
94
|
+
* @param recordData - The record data with potentially mixed keys
|
|
95
|
+
* @param table - Table definition for field name lookups (optional)
|
|
96
|
+
* @param fieldMappings - SDK name mappings (optional)
|
|
97
|
+
*/
|
|
98
|
+
export function transformFieldNamesToIds(recordData, table, fieldMappings) {
|
|
99
|
+
if (!recordData)
|
|
100
|
+
return recordData;
|
|
101
|
+
// If no table definition, fall back to simple SDK name mapping
|
|
102
|
+
if (!table) {
|
|
103
|
+
return transformRecordToFieldIds(recordData, fieldMappings);
|
|
104
|
+
}
|
|
105
|
+
const transformedRecord = {};
|
|
106
|
+
for (const [key, value] of Object.entries(recordData)) {
|
|
107
|
+
// Step 1: Check if it's already a field ID
|
|
108
|
+
const existsAsFieldId = table.fields.some(f => f.id === key);
|
|
109
|
+
if (existsAsFieldId) {
|
|
110
|
+
transformedRecord[key] = value;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Step 2: Try to find field by name (case-insensitive)
|
|
114
|
+
const fieldByName = table.fields.find(f => f.name.toLowerCase() === key.toLowerCase());
|
|
115
|
+
if (fieldByName) {
|
|
116
|
+
transformedRecord[fieldByName.id] = value;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Step 3: Try using field mappings (SDK name to field ID)
|
|
120
|
+
if (fieldMappings) {
|
|
121
|
+
const fieldId = Object.keys(fieldMappings).find(id => fieldMappings[id]?.mapping?.sdkName?.toLowerCase() ===
|
|
122
|
+
key.toLowerCase());
|
|
123
|
+
if (fieldId) {
|
|
124
|
+
transformedRecord[fieldId] = value;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// If no mapping found, keep the original key (might be a valid field ID)
|
|
129
|
+
transformedRecord[key] = value;
|
|
130
|
+
}
|
|
131
|
+
return transformedRecord;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Simple transformation using only field mappings (no table definition).
|
|
135
|
+
* Used when table metadata is not available.
|
|
136
|
+
*/
|
|
137
|
+
export function transformRecordToFieldIds(recordData, fieldMappings) {
|
|
138
|
+
if (!recordData || !fieldMappings)
|
|
139
|
+
return recordData;
|
|
140
|
+
const sdkNameToFieldId = buildSdkNameToFieldIdMap(fieldMappings);
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const [key, value] of Object.entries(recordData)) {
|
|
143
|
+
// Skip id field
|
|
144
|
+
if (key === 'id' || key === 'recordID') {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const fieldId = sdkNameToFieldId[key] || sdkNameToFieldId[key.toLowerCase()] || key;
|
|
148
|
+
result[fieldId] = value;
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Transform linked record fields to ensure they are arrays.
|
|
154
|
+
*
|
|
155
|
+
* Handles common LLM mistakes:
|
|
156
|
+
* - Passing a string instead of an array of record IDs
|
|
157
|
+
* - Passing null/undefined instead of an empty array
|
|
158
|
+
* - Passing literal strings like "null" or "undefined"
|
|
159
|
+
*
|
|
160
|
+
* @param recordData - Record data (should already have field IDs as keys)
|
|
161
|
+
* @param table - Table definition for field type lookups
|
|
162
|
+
*/
|
|
163
|
+
export function transformLinkedRecordFields(recordData, table) {
|
|
164
|
+
if (!recordData || !table)
|
|
165
|
+
return recordData;
|
|
166
|
+
const transformedRecord = {};
|
|
167
|
+
for (const [key, value] of Object.entries(recordData)) {
|
|
168
|
+
const field = table.fields.find(f => f.id === key);
|
|
169
|
+
if (field?.type === 'linked_record') {
|
|
170
|
+
if (typeof value === 'string') {
|
|
171
|
+
// Filter out LLM-generated placeholder strings
|
|
172
|
+
const cleanValue = value.trim();
|
|
173
|
+
if (cleanValue === '' ||
|
|
174
|
+
cleanValue === 'null' ||
|
|
175
|
+
cleanValue === 'undefined') {
|
|
176
|
+
transformedRecord[key] = [];
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
transformedRecord[key] = [cleanValue];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (value == null) {
|
|
183
|
+
// Convert null/undefined to empty array
|
|
184
|
+
transformedRecord[key] = [];
|
|
185
|
+
}
|
|
186
|
+
else if (Array.isArray(value)) {
|
|
187
|
+
// Filter out any null/undefined/"null"/"undefined" entries
|
|
188
|
+
transformedRecord[key] = value.filter(v => v != null && v !== 'null' && v !== 'undefined');
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
transformedRecord[key] = value;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
transformedRecord[key] = value;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return transformedRecord;
|
|
199
|
+
}
|
|
200
|
+
// =============================================================================
|
|
201
|
+
// Record Output Transformations (Database → SDK)
|
|
202
|
+
// =============================================================================
|
|
203
|
+
/**
|
|
204
|
+
* Transform database record to use SDK field names
|
|
205
|
+
*
|
|
206
|
+
* @param record - Raw database record with field IDs
|
|
207
|
+
* @param fieldMappings - SDK name mappings
|
|
208
|
+
*/
|
|
209
|
+
export function transformRecordToSdkNames(record, fieldMappings) {
|
|
210
|
+
if (!record)
|
|
211
|
+
return undefined;
|
|
212
|
+
const result = { id: record.id };
|
|
213
|
+
if (!fieldMappings) {
|
|
214
|
+
const { id: _ignoredId, ...dataWithoutId } = record.data;
|
|
215
|
+
return { ...result, ...dataWithoutId };
|
|
216
|
+
}
|
|
217
|
+
for (const [fieldId, value] of Object.entries(record.data)) {
|
|
218
|
+
const mapping = fieldMappings[fieldId];
|
|
219
|
+
const sdkName = mapping?.mapping?.sdkName || fieldId;
|
|
220
|
+
if (sdkName === 'id') {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
result[sdkName] = value;
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
// =============================================================================
|
|
228
|
+
// Batch Transformations
|
|
229
|
+
// =============================================================================
|
|
230
|
+
/**
|
|
231
|
+
* Transform multiple records for input (SDK → Database)
|
|
232
|
+
*/
|
|
233
|
+
export function transformRecordsForInput(records, table, fieldMappings) {
|
|
234
|
+
return records.map(record => {
|
|
235
|
+
const withFieldIds = transformFieldNamesToIds(record, table, fieldMappings);
|
|
236
|
+
return transformLinkedRecordFields(withFieldIds, table);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Transform multiple records for output (Database → SDK)
|
|
241
|
+
*/
|
|
242
|
+
export function transformRecordsForOutput(records, fieldMappings) {
|
|
243
|
+
return records
|
|
244
|
+
.map(r => transformRecordToSdkNames(r, fieldMappings))
|
|
245
|
+
.filter((r) => r !== undefined);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Transform an array of SDK field names to field IDs.
|
|
249
|
+
*
|
|
250
|
+
* Used for the `fields` parameter in findOne/findAll to select which fields to return.
|
|
251
|
+
* Uses the same three-step fallback as transformFieldNamesToIds:
|
|
252
|
+
* 1. Check if name is already a valid field ID
|
|
253
|
+
* 2. Match by field name (case-insensitive)
|
|
254
|
+
* 3. Match by SDK name from field mappings
|
|
255
|
+
*
|
|
256
|
+
* @param fields - Array of SDK field names (e.g. ['customerName', 'email'])
|
|
257
|
+
* @param table - Table definition for field name lookups (optional)
|
|
258
|
+
* @param fieldMappings - SDK name mappings (optional)
|
|
259
|
+
* @returns Array of field IDs, or undefined if input is undefined
|
|
260
|
+
*/
|
|
261
|
+
export function transformFieldsToFieldIds(fields, table, fieldMappings) {
|
|
262
|
+
if (!fields)
|
|
263
|
+
return undefined;
|
|
264
|
+
const sdkNameToFieldId = buildSdkNameToFieldIdMap(fieldMappings);
|
|
265
|
+
return fields.map(name => {
|
|
266
|
+
// Step 1: Check if it's already a field ID
|
|
267
|
+
if (table?.fields.some(f => f.id === name)) {
|
|
268
|
+
return name;
|
|
269
|
+
}
|
|
270
|
+
// Step 2: Match by human-readable field name (case-insensitive)
|
|
271
|
+
if (table) {
|
|
272
|
+
const fieldByName = table.fields.find(f => f.name.toLowerCase() === name.toLowerCase());
|
|
273
|
+
if (fieldByName)
|
|
274
|
+
return fieldByName.id;
|
|
275
|
+
}
|
|
276
|
+
// Step 3: Match by SDK name from field mappings
|
|
277
|
+
return (sdkNameToFieldId[name] || sdkNameToFieldId[name.toLowerCase()] || name);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Resolve the field references in a sort spec to field IDs.
|
|
282
|
+
*
|
|
283
|
+
* Sort arrives from app code using SDK names (`closeDate`), but base-runner
|
|
284
|
+
* resolves sort fields by field ID or display name only, and silently skips one
|
|
285
|
+
* it cannot match. Running the references through the same resolution order as
|
|
286
|
+
* `fields` is what makes a sort on a multi-word field ("Close date" ->
|
|
287
|
+
* `closeDate`) actually apply instead of being dropped without an error.
|
|
288
|
+
*
|
|
289
|
+
* System sort fields (`id`, `createdAt`, `updatedAt`) have no mapping and fall
|
|
290
|
+
* through unchanged, which is what base-runner expects.
|
|
291
|
+
*/
|
|
292
|
+
export function transformSortToFieldIds(sort, table, fieldMappings) {
|
|
293
|
+
if (!sort)
|
|
294
|
+
return undefined;
|
|
295
|
+
const entries = typeof sort === 'string'
|
|
296
|
+
? [{ field: sort, direction: 'asc' }]
|
|
297
|
+
: sort;
|
|
298
|
+
if (entries.length === 0)
|
|
299
|
+
return undefined;
|
|
300
|
+
const resolved = transformFieldsToFieldIds(entries.map(entry => entry.field), table, fieldMappings);
|
|
301
|
+
return entries.map((entry, index) => ({
|
|
302
|
+
field: resolved?.[index] ?? entry.field,
|
|
303
|
+
direction: entry.direction ?? 'asc',
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Transform matchOn field names to field IDs
|
|
308
|
+
*/
|
|
309
|
+
export function transformMatchOnFields(matchOn, fieldMappings) {
|
|
310
|
+
if (!matchOn || !fieldMappings)
|
|
311
|
+
return matchOn;
|
|
312
|
+
const sdkNameToFieldId = buildSdkNameToFieldIdMap(fieldMappings);
|
|
313
|
+
return matchOn.map(fieldName => {
|
|
314
|
+
return (sdkNameToFieldId[fieldName] ||
|
|
315
|
+
sdkNameToFieldId[fieldName.toLowerCase()] ||
|
|
316
|
+
fieldName);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export type BaseTable = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
deletedAt?: string | null;
|
|
5
|
+
fields: Array<{
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
deletedAt?: string | null;
|
|
10
|
+
template?: {
|
|
11
|
+
tableId?: string;
|
|
12
|
+
isInverse?: boolean;
|
|
13
|
+
} & Record<string, unknown>;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
export type GetBaseResponseType = {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
tables: BaseTable[];
|
|
20
|
+
};
|
|
21
|
+
export interface FindOneResponseType {
|
|
22
|
+
id: string;
|
|
23
|
+
data: Record<string, unknown>;
|
|
24
|
+
fields?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
export interface FindAllResponseType {
|
|
27
|
+
records: FindOneResponseType[];
|
|
28
|
+
offset?: string;
|
|
29
|
+
hasMore: boolean;
|
|
30
|
+
total?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface BulkCreateResponseType {
|
|
33
|
+
success?: boolean;
|
|
34
|
+
records: FindOneResponseType[];
|
|
35
|
+
}
|
|
36
|
+
export type LegacyRecordsFilterClauseType = Record<string, unknown>;
|
|
37
|
+
/** Simplified table definition for SDK transformations. */
|
|
38
|
+
export interface SimpleTableDefinition {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
fields: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
type: string;
|
|
45
|
+
template?: unknown;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/** Field mappings for transforming between SDK names and field IDs */
|
|
49
|
+
export type FieldMappings = Record<string, {
|
|
50
|
+
mapping: {
|
|
51
|
+
sdkName: string;
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
/** A record after transformation. */
|
|
55
|
+
export interface DatabaseRecord {
|
|
56
|
+
id: string;
|
|
57
|
+
[key: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
/** The build's tables' and fields' SDK names, by id (`projectConfig.nameMappings`). */
|
|
60
|
+
export type DataNameMappings = {
|
|
61
|
+
tables: Record<string, {
|
|
62
|
+
mapping: {
|
|
63
|
+
sdkName: string;
|
|
64
|
+
};
|
|
65
|
+
fields: Record<string, {
|
|
66
|
+
mapping: {
|
|
67
|
+
sdkName: string;
|
|
68
|
+
};
|
|
69
|
+
}>;
|
|
70
|
+
}>;
|
|
71
|
+
};
|
|
72
|
+
/** base-runner's rename map for SQL, which `buildSdkNameMappingsForBase` builds. */
|
|
73
|
+
export interface NameMappings {
|
|
74
|
+
tables: Record<string, {
|
|
75
|
+
displayName: string;
|
|
76
|
+
fields: Record<string, string>;
|
|
77
|
+
}>;
|
|
78
|
+
linkTables: Record<string, {
|
|
79
|
+
displayName: string;
|
|
80
|
+
sourceIdColumnAlias: string;
|
|
81
|
+
targetIdColumnAlias: string;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
/** Marks an error as our fault rather than the app's. */
|
|
85
|
+
export declare const ZITE_PLATFORM_FAULT_FLAG = "zitePlatformFault";
|
|
86
|
+
/** base-runner's refusal, with its status. */
|
|
87
|
+
export declare class DatabasesApiError extends Error {
|
|
88
|
+
readonly statusCode: number;
|
|
89
|
+
readonly statusText: string;
|
|
90
|
+
constructor(message: string, statusCode: number, statusText: string);
|
|
91
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Ported from restly's packages/workflow-edge/src/databases/types.ts: the shapes base-runner answers with.
|
|
2
|
+
/** Marks an error as our fault rather than the app's. */
|
|
3
|
+
export const ZITE_PLATFORM_FAULT_FLAG = "zitePlatformFault";
|
|
4
|
+
/** base-runner's refusal, with its status. */
|
|
5
|
+
export class DatabasesApiError extends Error {
|
|
6
|
+
statusCode;
|
|
7
|
+
statusText;
|
|
8
|
+
constructor(message, statusCode, statusText) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "DatabasesApiError";
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.statusText = statusText;
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/esm/start/server.js
CHANGED
|
@@ -3,6 +3,8 @@ import { describeApp } from "./describe.js";
|
|
|
3
3
|
import { runRegistration } from "./registration.js";
|
|
4
4
|
import { isAuthHookName, loadAuthHooks } from "./authHooks.js";
|
|
5
5
|
import { errorOf, preview } from "./runLog.js";
|
|
6
|
+
import { DATA_INTEGRATION_ID, dataContextOf, runDataCall } from "./data/index.js";
|
|
7
|
+
import { airtableSettingsOf, runAirtableCall } from "./airtable/index.js";
|
|
6
8
|
import { getHost, requireScope, runInScope, runWithHost } from "./scope.js";
|
|
7
9
|
import { ZITEJS_VERSION } from "./version.js";
|
|
8
10
|
import { loadWorkflows, loggedInput, runWorkflow } from "./workflowRun.js";
|
|
@@ -22,7 +24,20 @@ export function installPlatformTransport() {
|
|
|
22
24
|
if (!host?.platform)
|
|
23
25
|
throw new Error("The Zite platform binding is missing");
|
|
24
26
|
host.session ??= Promise.resolve(host.watch ? host.platform.session(host.watch) : host.platform.session());
|
|
25
|
-
const
|
|
27
|
+
const session = await host.session;
|
|
28
|
+
if (integrationId === DATA_INTEGRATION_ID) {
|
|
29
|
+
const found = await dataContextOf(session);
|
|
30
|
+
if (found) {
|
|
31
|
+
return recordCall(host, { integrationId, className, methodName, params }, () => runDataCall(session, found, host.mode, methodName, params));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (!integrationId.startsWith("__")) {
|
|
35
|
+
const airtable = await airtableSettingsOf(session, integrationId);
|
|
36
|
+
if (airtable) {
|
|
37
|
+
return recordCall(host, { integrationId, className, methodName, params }, () => runAirtableCall(host, session, integrationId, airtable, methodName, params, scope.kind));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const result = await session.call({ integrationId, className, methodName, params, access: scope.kind });
|
|
26
41
|
if ("value" in result)
|
|
27
42
|
return result.value;
|
|
28
43
|
const { code, message, userFacingMessage } = result.error;
|
|
@@ -31,6 +46,27 @@ export function installPlatformTransport() {
|
|
|
31
46
|
globalThis.__wrapSdkCall = sdkCall;
|
|
32
47
|
watchOutboundFetches();
|
|
33
48
|
}
|
|
49
|
+
/** A data call the app made itself, recorded as the host recorded the ones it made. */
|
|
50
|
+
const recordCall = async (host, call, run) => {
|
|
51
|
+
const at = Date.now();
|
|
52
|
+
const record = {
|
|
53
|
+
integrationId: call.integrationId,
|
|
54
|
+
className: call.className,
|
|
55
|
+
methodName: call.methodName,
|
|
56
|
+
at,
|
|
57
|
+
inputs: preview(call.params),
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
const value = await run();
|
|
61
|
+
(host.calls ??= []).push({ ...record, durationMs: Date.now() - at, outputs: preview(value) });
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const { code, message } = errorOf(error);
|
|
66
|
+
(host.calls ??= []).push({ ...record, durationMs: Date.now() - at, error: { code, message } });
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
34
70
|
let fetchesWatched = false;
|
|
35
71
|
const fetchedHost = (input) => {
|
|
36
72
|
try {
|
|
@@ -69,13 +105,16 @@ export function hostContextFromProps(request, props) {
|
|
|
69
105
|
appUrl: props.appUrl,
|
|
70
106
|
baseId: props.baseId,
|
|
71
107
|
previewClaim: props.previewClaim,
|
|
108
|
+
env: props.env,
|
|
72
109
|
runs: [],
|
|
73
110
|
platform: props.platform,
|
|
74
111
|
};
|
|
75
112
|
}
|
|
76
113
|
// Classic sets these per run. Node and workerd both refuse getters on process.env, so each request writes what the
|
|
77
114
|
// host sent, which is the same for every request a build serves: the live URL even on a demo.
|
|
78
|
-
|
|
115
|
+
// The connection vars the last request set, so one gone since (an integration removed) doesn't linger.
|
|
116
|
+
let connectionEnvKeys = new Set();
|
|
117
|
+
const setRequestEnv = ({ appUrl, baseId, env: connectionEnv }) => {
|
|
79
118
|
const env = globalThis.process?.env;
|
|
80
119
|
if (!env)
|
|
81
120
|
return;
|
|
@@ -83,6 +122,16 @@ const setRequestEnv = ({ appUrl, baseId }) => {
|
|
|
83
122
|
env.ZITE_APP_URL = appUrl;
|
|
84
123
|
if (baseId)
|
|
85
124
|
env.ZITE_BASE_ID = baseId;
|
|
125
|
+
if (!connectionEnv)
|
|
126
|
+
return;
|
|
127
|
+
for (const key of connectionEnvKeys)
|
|
128
|
+
if (!Object.hasOwn(connectionEnv, key))
|
|
129
|
+
delete env[key];
|
|
130
|
+
for (const [key, value] of Object.entries(connectionEnv)) {
|
|
131
|
+
if (key.startsWith("ZITE_"))
|
|
132
|
+
env[key] = value;
|
|
133
|
+
}
|
|
134
|
+
connectionEnvKeys = new Set(Object.keys(connectionEnv));
|
|
86
135
|
};
|
|
87
136
|
/**
|
|
88
137
|
* A request the host didn't dispatch: the host's warm-up, `zitejs describe` under `vite preview`, and tests. Only the
|
|
@@ -241,14 +290,14 @@ export function createZiteHandlers(options) {
|
|
|
241
290
|
response = new Response(response.body, { status, headers: response.headers });
|
|
242
291
|
return onBodyEnd(response, () => {
|
|
243
292
|
const { platform } = host;
|
|
244
|
-
if ((!host.runs.length && !host.pending?.length) || !platform)
|
|
293
|
+
if ((!host.runs.length && !host.calls?.length && !host.pending?.length) || !platform)
|
|
245
294
|
return;
|
|
246
295
|
// On the request's session when it has one: one call, not another binding round trip.
|
|
247
296
|
const flushed = (async () => {
|
|
248
297
|
await Promise.all(host.pending ?? []);
|
|
249
|
-
if (!host.runs.length)
|
|
298
|
+
if (!host.runs.length && !host.calls?.length)
|
|
250
299
|
return;
|
|
251
|
-
const batch = { runs: host.runs };
|
|
300
|
+
const batch = { runs: host.runs, ...(host.calls?.length && { calls: host.calls }) };
|
|
252
301
|
const session = host.session ? await host.session : undefined;
|
|
253
302
|
await (session?.runs ? session.runs(batch) : platform.runs(batch));
|
|
254
303
|
})().catch((error) => {
|
|
@@ -53,12 +53,44 @@ export type PlatformCallResult = {
|
|
|
53
53
|
userFacingMessage?: string;
|
|
54
54
|
};
|
|
55
55
|
};
|
|
56
|
+
/** One data call the app's own dispatch made, for its run's detail. */
|
|
57
|
+
export type CallRecord = {
|
|
58
|
+
integrationId: string;
|
|
59
|
+
className: string;
|
|
60
|
+
methodName: string;
|
|
61
|
+
at: number;
|
|
62
|
+
durationMs: number;
|
|
63
|
+
inputs?: string;
|
|
64
|
+
outputs?: string;
|
|
65
|
+
error?: {
|
|
66
|
+
code: string;
|
|
67
|
+
message: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
56
70
|
export type PlatformSession = {
|
|
57
71
|
call(call: PlatformCallInput): Promise<PlatformCallResult>;
|
|
58
72
|
/** As `PlatformBinding.runs`, on a session the request already holds. Hosts before 0.10.0-start.4 lack it. */
|
|
59
73
|
runs?(batch: {
|
|
60
74
|
runs: RunRecord[];
|
|
75
|
+
calls?: CallRecord[];
|
|
61
76
|
}): Promise<void>;
|
|
77
|
+
/** The build's base and SDK names, which the app's data dispatch translates against. */
|
|
78
|
+
dataContext?(): Promise<{
|
|
79
|
+
baseId: string | null;
|
|
80
|
+
nameMappings: unknown;
|
|
81
|
+
}>;
|
|
82
|
+
/** One base-runner request; the host holds it to the build's base and adds the key and acting user. */
|
|
83
|
+
dataFetch?(request: {
|
|
84
|
+
method: string;
|
|
85
|
+
path: string;
|
|
86
|
+
body?: string;
|
|
87
|
+
}): Promise<Response>;
|
|
88
|
+
/** An integration's settings as the build declares them; null for one it doesn't, or that runs on the platform. */
|
|
89
|
+
integrationSettings?(integrationId: string): Promise<{
|
|
90
|
+
serviceType: string;
|
|
91
|
+
baseId?: string;
|
|
92
|
+
nameMappings?: unknown;
|
|
93
|
+
} | null>;
|
|
62
94
|
};
|
|
63
95
|
/**
|
|
64
96
|
* The host's ear on a page render it may cache, from `render()`. Reports can
|
|
@@ -95,6 +127,8 @@ export type HostProps = {
|
|
|
95
127
|
baseId?: string;
|
|
96
128
|
/** An editor preview's access, which zitejs keeps on the app's links and calls. */
|
|
97
129
|
previewClaim?: string;
|
|
130
|
+
/** `process.env` as classic sets it: connections' vars, secrets as placeholders egress swaps but S3's, then the user's own secrets. */
|
|
131
|
+
env?: Record<string, string>;
|
|
98
132
|
};
|
|
99
133
|
/** What the host resolved for this request. Set by the generated server entry, never by the browser. */
|
|
100
134
|
export type ZiteRequestContext = {
|
|
@@ -106,8 +140,14 @@ export type ZiteRequestContext = {
|
|
|
106
140
|
appUrl?: string;
|
|
107
141
|
baseId?: string;
|
|
108
142
|
previewClaim?: string;
|
|
143
|
+
/** Classic's connection env vars the host sent, set into `process.env` for the request. */
|
|
144
|
+
env?: Record<string, string>;
|
|
109
145
|
/** Filled by run capture, flushed to the platform once the response ends. */
|
|
110
146
|
runs: RunRecord[];
|
|
147
|
+
/** The data calls this request made, flushed with its runs. */
|
|
148
|
+
calls?: CallRecord[];
|
|
149
|
+
/** This request's Airtable reads, so the same one twice goes out once. */
|
|
150
|
+
airtableReads?: Map<string, Promise<unknown>>;
|
|
111
151
|
/** Records still reading a copy of a body; the flush waits for them. */
|
|
112
152
|
pending?: Promise<void>[];
|
|
113
153
|
platform?: PlatformBinding;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const ZITEJS_VERSION = "0.10.0-start.
|
|
1
|
+
export declare const ZITEJS_VERSION = "0.10.0-start.8";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Written by scripts/write-version.mjs from package.json.
|
|
2
|
-
export const ZITEJS_VERSION = "0.10.0-start.
|
|
2
|
+
export const ZITEJS_VERSION = "0.10.0-start.8";
|
package/dist/esm/start/vite.js
CHANGED
|
@@ -131,6 +131,8 @@ export function zite(options = {}) {
|
|
|
131
131
|
},
|
|
132
132
|
};
|
|
133
133
|
return [
|
|
134
|
+
// First, so ids name lines in the source, before TanStack splits a route's component out of it.
|
|
135
|
+
ziteId(),
|
|
134
136
|
tanstackStart({
|
|
135
137
|
// TanStack resolves these from `src/`.
|
|
136
138
|
server: { entry: `../${serverEntry.replace(/\.ts$/, "")}` },
|
|
@@ -163,7 +165,6 @@ export function zite(options = {}) {
|
|
|
163
165
|
}),
|
|
164
166
|
options.cloudflare !== false ? loadCloudflare(serverEntry) : null,
|
|
165
167
|
viteReact(),
|
|
166
|
-
ziteId(),
|
|
167
168
|
compileZitejs,
|
|
168
169
|
functionIds,
|
|
169
170
|
workflowRegistry,
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -71,6 +71,7 @@ const FIELD_TYPE_MAP = {
|
|
|
71
71
|
formula: "any",
|
|
72
72
|
created_at: "string",
|
|
73
73
|
updated_at: "string",
|
|
74
|
+
created_by: "string",
|
|
74
75
|
updated_by: "string",
|
|
75
76
|
};
|
|
76
77
|
/**
|
|
@@ -96,7 +97,7 @@ const FIELD_INPUT_TYPE_MAP = {
|
|
|
96
97
|
* rather than typed and then refused at runtime.
|
|
97
98
|
*
|
|
98
99
|
* Mirrors `COMPUTED_FIELD_TYPES` in the product's own `shared/bases/fields`.
|
|
99
|
-
* The first
|
|
100
|
+
* The first seven are rejected by `createRecordDataSchema` — each has a
|
|
100
101
|
* `refine(val => val == null)`, so a non-null write errors (null is a no-op
|
|
101
102
|
* clear). `created_at` / `updated_at` are refused differently: they have no
|
|
102
103
|
* column at all, so a write is silently dropped, which is the worse outcome to
|
|
@@ -108,6 +109,7 @@ const READ_ONLY_FIELD_TYPES = new Set([
|
|
|
108
109
|
"source",
|
|
109
110
|
"formula",
|
|
110
111
|
"autonumber",
|
|
112
|
+
"created_by",
|
|
111
113
|
"updated_by",
|
|
112
114
|
"created_at",
|
|
113
115
|
"updated_at",
|
|
@@ -245,13 +247,8 @@ function fieldJsdoc(schemaField, table, schema) {
|
|
|
245
247
|
const parts = [];
|
|
246
248
|
if (table.primaryFieldId === schemaField.id)
|
|
247
249
|
parts.push("Primary field");
|
|
248
|
-
if (def.type
|
|
249
|
-
def.type === "updated_at" ||
|
|
250
|
-
def.type === "lookup" ||
|
|
251
|
-
def.type === "formula" ||
|
|
252
|
-
def.type === "autonumber") {
|
|
250
|
+
if (READ_ONLY_FIELD_TYPES.has(def.type))
|
|
253
251
|
parts.push("Read-only");
|
|
254
|
-
}
|
|
255
252
|
if (def.type === "linked_record") {
|
|
256
253
|
const tpl = def.template;
|
|
257
254
|
if (tpl.tableId) {
|