zitejs 0.9.41 → 0.9.43
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/bundle/index.js +9 -0
- package/dist/cjs/dev/index.js +37 -0
- package/dist/cjs/runtime/index.js +30 -0
- package/dist/cjs/sync/index.js +18 -6
- package/dist/cjs/sync/lib.js +263 -33
- package/dist/esm/bundle/index.js +9 -0
- package/dist/esm/client/index.d.ts +18 -18
- package/dist/esm/dev/index.js +38 -1
- package/dist/esm/runtime/index.d.ts +26 -0
- package/dist/esm/runtime/index.js +29 -0
- package/dist/esm/sync/index.js +19 -7
- package/dist/esm/sync/lib.d.ts +43 -18
- package/dist/esm/sync/lib.js +262 -33
- package/dist/esm/types/fields.d.ts +17 -17
- package/dist/esm/types/tables.d.ts +12 -12
- package/package.json +1 -1
package/dist/esm/dev/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { watch } from 'fs';
|
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { runSync } from '../sync/index.js';
|
|
5
|
-
import { generateApiTs, generateUserTs, generateAuthWrapperTs, generateBackendWrapperTs, } from '../sync/lib.js';
|
|
5
|
+
import { generateApiTs, generateUserTs, generateAuthWrapperTs, generateBackendWrapperTs, generateAirtableTs, } from '../sync/lib.js';
|
|
6
6
|
const debounceTimers = new Map();
|
|
7
7
|
function debounce(key, fn, ms) {
|
|
8
8
|
const existing = debounceTimers.get(key);
|
|
@@ -49,6 +49,42 @@ function regenerateAppApiTs(appDir) {
|
|
|
49
49
|
console.log(`Updated apps/${appDir}/.zite/api.ts`);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
function regenerateAppAirtableSdk(appDir) {
|
|
53
|
+
const lockPath = join('apps', appDir, 'zite.lock');
|
|
54
|
+
if (!existsSync(lockPath))
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
const lockContent = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
58
|
+
const integrations = lockContent.integrations ?? {};
|
|
59
|
+
for (const [integrationId, integration] of Object.entries(integrations)) {
|
|
60
|
+
const int = integration;
|
|
61
|
+
if (!int.idMappings?.tables)
|
|
62
|
+
continue;
|
|
63
|
+
const airtableLock = {
|
|
64
|
+
integrationId,
|
|
65
|
+
tables: Object.entries(int.idMappings.tables).map(([sdkName, tableId]) => ({
|
|
66
|
+
id: tableId,
|
|
67
|
+
sdkName,
|
|
68
|
+
fields: Object.entries(int.idMappings?.fields?.[sdkName] ?? {}).map(([fieldSdkName, fieldId]) => ({
|
|
69
|
+
id: fieldId,
|
|
70
|
+
sdkName: fieldSdkName,
|
|
71
|
+
type: 'singleLineText',
|
|
72
|
+
})),
|
|
73
|
+
})),
|
|
74
|
+
};
|
|
75
|
+
const content = generateAirtableTs(airtableLock);
|
|
76
|
+
if (content) {
|
|
77
|
+
const outDir = join('apps', appDir, '.zite', 'integrations');
|
|
78
|
+
mkdirSync(outDir, { recursive: true });
|
|
79
|
+
writeFileSync(join(outDir, 'airtable.ts'), content);
|
|
80
|
+
console.log(`Updated apps/${appDir}/.zite/integrations/airtable.ts`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Lock file invalid or missing — skip
|
|
86
|
+
}
|
|
87
|
+
}
|
|
52
88
|
export async function runGenerate() {
|
|
53
89
|
// 1. Run sync (generates root .zite/db.ts)
|
|
54
90
|
try {
|
|
@@ -62,6 +98,7 @@ export async function runGenerate() {
|
|
|
62
98
|
for (const app of appDirs) {
|
|
63
99
|
regenerateAppApiTs(app);
|
|
64
100
|
regenerateAppTypedWrappers(app);
|
|
101
|
+
regenerateAppAirtableSdk(app);
|
|
65
102
|
}
|
|
66
103
|
console.log('Done!');
|
|
67
104
|
}
|
|
@@ -39,5 +39,31 @@ export declare function createSqlClient(): (params: {
|
|
|
39
39
|
query: string;
|
|
40
40
|
params?: unknown[];
|
|
41
41
|
}) => Promise<SqlResult>;
|
|
42
|
+
export interface AirtableTableClient<T> {
|
|
43
|
+
findAll(options?: {
|
|
44
|
+
offset?: string;
|
|
45
|
+
limit?: number;
|
|
46
|
+
filters?: unknown;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
records: T[];
|
|
49
|
+
offset: string | undefined;
|
|
50
|
+
hasMore: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
findOne(params: {
|
|
53
|
+
id?: string;
|
|
54
|
+
filters?: unknown;
|
|
55
|
+
}): Promise<T | undefined>;
|
|
56
|
+
create(data: {
|
|
57
|
+
record: Partial<T>;
|
|
58
|
+
}): Promise<T>;
|
|
59
|
+
bulkCreate(records: Partial<T>[]): Promise<T[]>;
|
|
60
|
+
update(id: string, data: {
|
|
61
|
+
record: Partial<T>;
|
|
62
|
+
}): Promise<T>;
|
|
63
|
+
delete(id: string): Promise<{
|
|
64
|
+
deleted: true;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
export declare function createAirtableClient<T>(integrationId: string, className: string, implicitParams: Record<string, unknown>): AirtableTableClient<T>;
|
|
42
68
|
export { createCaller } from "../caller/index.js";
|
|
43
69
|
export type { EndpointConfig } from "../caller/index.js";
|
|
@@ -71,4 +71,33 @@ export function createSqlClient() {
|
|
|
71
71
|
...params,
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
+
export function createAirtableClient(integrationId, className, implicitParams) {
|
|
75
|
+
return {
|
|
76
|
+
findAll: (options) => getSdkCall()(integrationId, className, "findAll", {
|
|
77
|
+
...implicitParams,
|
|
78
|
+
...options,
|
|
79
|
+
}),
|
|
80
|
+
findOne: (params) => getSdkCall()(integrationId, className, "findOne", {
|
|
81
|
+
...implicitParams,
|
|
82
|
+
...params,
|
|
83
|
+
}),
|
|
84
|
+
create: (data) => getSdkCall()(integrationId, className, "create", {
|
|
85
|
+
...implicitParams,
|
|
86
|
+
...data,
|
|
87
|
+
}),
|
|
88
|
+
bulkCreate: (records) => getSdkCall()(integrationId, className, "bulkCreate", {
|
|
89
|
+
...implicitParams,
|
|
90
|
+
records,
|
|
91
|
+
}),
|
|
92
|
+
update: (id, data) => getSdkCall()(integrationId, className, "update", {
|
|
93
|
+
...implicitParams,
|
|
94
|
+
id,
|
|
95
|
+
...data,
|
|
96
|
+
}),
|
|
97
|
+
delete: (id) => getSdkCall()(integrationId, className, "delete", {
|
|
98
|
+
...implicitParams,
|
|
99
|
+
id,
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
74
103
|
export { createCaller } from "../caller/index.js";
|
package/dist/esm/sync/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { generateSchema, generateDbTs, generateApiTs } from "./lib.js";
|
|
4
|
+
import { generateSchema, generateDbTs, generateApiTs, } from "./lib.js";
|
|
5
5
|
const DB_ENVIRONMENTS = {
|
|
6
6
|
production: "https://tables.fillout.com/api/v1",
|
|
7
7
|
staging: "https://tables.filloutstaging.com/api/v1",
|
|
8
8
|
local: "http://localhost:2507/api/v1",
|
|
9
9
|
};
|
|
10
10
|
const env = process.env.ZITE_ENV ?? "production";
|
|
11
|
-
const BASE_URL = process.env.ZITE_DB_URL ??
|
|
11
|
+
const BASE_URL = process.env.ZITE_DB_URL ??
|
|
12
|
+
DB_ENVIRONMENTS[env] ??
|
|
13
|
+
DB_ENVIRONMENTS.production;
|
|
12
14
|
const TOKEN = process.env.ZITE_DB_TOKEN ?? "";
|
|
13
|
-
async function
|
|
15
|
+
async function fetchDatabase(baseId) {
|
|
14
16
|
const url = `${BASE_URL}/bases/${encodeURIComponent(baseId)}`;
|
|
15
17
|
const res = await fetch(url, {
|
|
16
18
|
headers: { Authorization: `Bearer ${TOKEN}` },
|
|
@@ -31,6 +33,15 @@ function resolveBaseId() {
|
|
|
31
33
|
return undefined;
|
|
32
34
|
}
|
|
33
35
|
}
|
|
36
|
+
function readExistingSchema() {
|
|
37
|
+
try {
|
|
38
|
+
if (existsSync("zite.schema.json")) {
|
|
39
|
+
return JSON.parse(readFileSync("zite.schema.json", "utf-8"));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
34
45
|
export async function runSync() {
|
|
35
46
|
if (!TOKEN) {
|
|
36
47
|
throw new Error("ZITE_DB_TOKEN is required.");
|
|
@@ -40,12 +51,13 @@ export async function runSync() {
|
|
|
40
51
|
throw new Error("Could not determine base ID.");
|
|
41
52
|
}
|
|
42
53
|
console.log(`Syncing database ${baseId}...`);
|
|
43
|
-
const
|
|
44
|
-
console.log(`Found ${
|
|
45
|
-
const
|
|
54
|
+
const database = await fetchDatabase(baseId);
|
|
55
|
+
console.log(`Found ${database.tables?.length ?? 0} tables`);
|
|
56
|
+
const existingSchema = readExistingSchema();
|
|
57
|
+
const schema = generateSchema(database, existingSchema);
|
|
46
58
|
writeFileSync("zite.schema.json", JSON.stringify(schema, null, 2));
|
|
47
59
|
console.log("Wrote zite.schema.json");
|
|
48
|
-
const dbTs = generateDbTs(
|
|
60
|
+
const dbTs = generateDbTs(schema);
|
|
49
61
|
const dotZite = ".zite";
|
|
50
62
|
mkdirSync(dotZite, { recursive: true });
|
|
51
63
|
writeFileSync(join(dotZite, "db.ts"), dbTs);
|
package/dist/esm/sync/lib.d.ts
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import type { PublicFieldDefinition } from "../types/fields.js";
|
|
2
|
+
import type { Database } from "../types/tables.js";
|
|
3
|
+
export type ZiteSchemaField = {
|
|
4
|
+
id: string;
|
|
5
|
+
sdkName: string;
|
|
6
|
+
definition: PublicFieldDefinition;
|
|
7
|
+
};
|
|
8
|
+
export type ZiteSchemaTable = {
|
|
9
|
+
id: string;
|
|
10
|
+
sdkName: string;
|
|
11
|
+
primaryFieldId?: string;
|
|
12
|
+
fields: ZiteSchemaField[];
|
|
11
13
|
};
|
|
12
14
|
export type ZiteSchema = {
|
|
13
|
-
tables:
|
|
14
|
-
id: string;
|
|
15
|
-
fields: Record<string, {
|
|
16
|
-
id: string;
|
|
17
|
-
}>;
|
|
18
|
-
}>;
|
|
15
|
+
tables: ZiteSchemaTable[];
|
|
19
16
|
};
|
|
20
17
|
export declare function toPascalCase(name: string): string;
|
|
21
18
|
export declare function toCamelCase(name: string): string;
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Build a ZiteSchema from a Database API response.
|
|
21
|
+
*
|
|
22
|
+
* If `existingSchema` is provided, locked SDK names are preserved for
|
|
23
|
+
* tables/fields that already exist (matched by ID). New tables/fields
|
|
24
|
+
* get fresh camelCase SDK names. This is how `zitejs sync` preserves
|
|
25
|
+
* name stability across schema changes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateSchema(database: Database, existingSchema?: ZiteSchema): ZiteSchema;
|
|
28
|
+
/**
|
|
29
|
+
* Generate .zite/db.ts purely from ZiteSchema.
|
|
30
|
+
* Pure function — no external data needed beyond what's in the schema file.
|
|
31
|
+
*/
|
|
32
|
+
export declare function generateDbTs(schema: ZiteSchema): string;
|
|
24
33
|
export declare function generateApiTs(endpointFiles: string[]): string | null;
|
|
25
34
|
export declare function generateUserTs(usersTableFields?: Array<{
|
|
26
35
|
name: string;
|
|
27
36
|
type: string;
|
|
28
37
|
}>): string;
|
|
29
38
|
export declare function generateAuthWrapperTs(): string;
|
|
39
|
+
export type AirtableLockField = {
|
|
40
|
+
id: string;
|
|
41
|
+
sdkName: string;
|
|
42
|
+
type: string;
|
|
43
|
+
options?: string[];
|
|
44
|
+
};
|
|
45
|
+
export type AirtableLockTable = {
|
|
46
|
+
id: string;
|
|
47
|
+
sdkName: string;
|
|
48
|
+
fields: AirtableLockField[];
|
|
49
|
+
};
|
|
50
|
+
export type AirtableLock = {
|
|
51
|
+
integrationId: string;
|
|
52
|
+
tables: AirtableLockTable[];
|
|
53
|
+
};
|
|
54
|
+
export declare function generateAirtableTs(lock: AirtableLock): string | null;
|
|
30
55
|
export declare function generateBackendWrapperTs(): string;
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const FIELD_TYPE_MAP = {
|
|
2
2
|
single_line_text: "string",
|
|
3
3
|
long_text: "string",
|
|
4
|
+
rich_text: "string",
|
|
4
5
|
email: "string",
|
|
5
6
|
url: "string",
|
|
6
7
|
phone_number: "string",
|
|
@@ -19,6 +20,25 @@ const FIELD_TYPE_MAP = {
|
|
|
19
20
|
lookup: "unknown",
|
|
20
21
|
autonumber: "number",
|
|
21
22
|
source: "string",
|
|
23
|
+
formula: "unknown",
|
|
24
|
+
created_at: "string",
|
|
25
|
+
updated_at: "string",
|
|
26
|
+
};
|
|
27
|
+
const MAX_SELECT_OPTIONS = 100;
|
|
28
|
+
const NUMBER_FORMAT_EXAMPLES = {
|
|
29
|
+
local: "1,000,000.50",
|
|
30
|
+
comma_period: "1,000,000.50",
|
|
31
|
+
period_comma: "1.000.000,50",
|
|
32
|
+
space_comma: "1 000 000,50",
|
|
33
|
+
space_period: "1 000 000.50",
|
|
34
|
+
no_separator: "1000000.50",
|
|
35
|
+
};
|
|
36
|
+
const DURATION_FORMAT_EXAMPLES = {
|
|
37
|
+
"h:mm": "1:23",
|
|
38
|
+
"h:mm:ss": "1:23:03",
|
|
39
|
+
"h:mm:ss.s": "1:23:03.0",
|
|
40
|
+
"h:mm:ss.ss": "1:23:03.00",
|
|
41
|
+
"h:mm:ss.sss": "1:23:03.000",
|
|
22
42
|
};
|
|
23
43
|
export function toPascalCase(name) {
|
|
24
44
|
return name
|
|
@@ -29,33 +49,157 @@ export function toCamelCase(name) {
|
|
|
29
49
|
const pascal = toPascalCase(name);
|
|
30
50
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
31
51
|
}
|
|
32
|
-
function
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
function tsTypeForSchemaField(def) {
|
|
53
|
+
if (def.type === "single_select" || def.type === "multiple_select") {
|
|
54
|
+
const options = def.template
|
|
55
|
+
?.options;
|
|
56
|
+
if (options && options.length > 0) {
|
|
57
|
+
const literals = options
|
|
58
|
+
.slice(0, MAX_SELECT_OPTIONS)
|
|
59
|
+
.map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
|
|
60
|
+
.join(" | ");
|
|
61
|
+
const union = `${literals} | string`;
|
|
62
|
+
if (def.type === "multiple_select")
|
|
63
|
+
return `(${union})[]`;
|
|
64
|
+
return union;
|
|
65
|
+
}
|
|
41
66
|
}
|
|
67
|
+
if (FIELD_TYPE_MAP[def.type])
|
|
68
|
+
return FIELD_TYPE_MAP[def.type];
|
|
42
69
|
return "string";
|
|
43
70
|
}
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
function fieldJsdoc(schemaField, table) {
|
|
72
|
+
const def = schemaField.definition;
|
|
73
|
+
const parts = [];
|
|
74
|
+
if (table.primaryFieldId === schemaField.id)
|
|
75
|
+
parts.push("Primary field");
|
|
76
|
+
if (def.type === "created_at" ||
|
|
77
|
+
def.type === "updated_at" ||
|
|
78
|
+
def.type === "lookup" ||
|
|
79
|
+
def.type === "formula" ||
|
|
80
|
+
def.type === "autonumber") {
|
|
81
|
+
parts.push("Read-only");
|
|
82
|
+
}
|
|
83
|
+
if (def.type === "linked_record") {
|
|
84
|
+
const tpl = def.template;
|
|
85
|
+
if (tpl.tableId)
|
|
86
|
+
parts.push(`Links to table ${tpl.tableId}`);
|
|
87
|
+
if (tpl.allowMultiple === false)
|
|
88
|
+
parts.push("Single record only");
|
|
89
|
+
}
|
|
90
|
+
parts.push(`"${def.name}"`);
|
|
91
|
+
if (def.type === "date") {
|
|
92
|
+
const tpl = def.template;
|
|
93
|
+
if (tpl.dateFormat)
|
|
94
|
+
parts.push(`Date-only field (YYYY-MM-DD string), display as "${tpl.dateFormat}" format`);
|
|
95
|
+
}
|
|
96
|
+
if (def.type === "datetime") {
|
|
97
|
+
const tpl = def.template;
|
|
98
|
+
const timeParts = [
|
|
99
|
+
"Date+time field (ISO 8601 timestamp)",
|
|
100
|
+
];
|
|
101
|
+
if (tpl.dateFormat)
|
|
102
|
+
timeParts.push(`date: ${tpl.dateFormat}`);
|
|
103
|
+
if (tpl.timeFormat)
|
|
104
|
+
timeParts.push(`time: ${tpl.timeFormat}`);
|
|
105
|
+
if (tpl.timezone)
|
|
106
|
+
timeParts.push(`tz: ${tpl.timezone}`);
|
|
107
|
+
if (tpl.displayTimeZone)
|
|
108
|
+
timeParts.push("timezone displayed to user");
|
|
109
|
+
parts.push(timeParts.join(", "));
|
|
110
|
+
}
|
|
111
|
+
if (def.type === "currency") {
|
|
112
|
+
const tpl = def.template;
|
|
113
|
+
if (tpl.currencySymbol) {
|
|
114
|
+
const formatHint = NUMBER_FORMAT_EXAMPLES[tpl.numberFormat ?? "local"] ?? "";
|
|
115
|
+
parts.push(`Value is a raw number; display as currency with symbol "${tpl.currencySymbol}", ${tpl.decimalPlaces ?? 2} decimal places, format: ${tpl.numberFormat ?? "local"} (e.g. ${formatHint})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (def.type === "number") {
|
|
119
|
+
const tpl = def.template;
|
|
120
|
+
const formatHint = NUMBER_FORMAT_EXAMPLES[tpl.numberFormat ?? "local"] ?? "";
|
|
121
|
+
parts.push(`${tpl.decimalPlaces ?? 0} decimal places, format: ${tpl.numberFormat ?? "local"} (e.g. ${formatHint})`);
|
|
122
|
+
}
|
|
123
|
+
if (def.type === "percent") {
|
|
124
|
+
const tpl = def.template;
|
|
125
|
+
if (tpl.decimalPlaces !== undefined)
|
|
126
|
+
parts.push(`${tpl.decimalPlaces} decimal places`);
|
|
127
|
+
}
|
|
128
|
+
if (def.type === "duration") {
|
|
129
|
+
const tpl = def.template;
|
|
130
|
+
if (tpl.format) {
|
|
131
|
+
const example = DURATION_FORMAT_EXAMPLES[tpl.format] ?? "";
|
|
132
|
+
parts.push(`Value is in seconds; display in "${tpl.format}" format (${example})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (def.type === "rating") {
|
|
136
|
+
const tpl = def.template;
|
|
137
|
+
if (tpl.maxRating)
|
|
138
|
+
parts.push(`Max rating: ${tpl.maxRating}`);
|
|
139
|
+
}
|
|
140
|
+
if (def.type === "created_at") {
|
|
141
|
+
parts.push("Auto-set when record was created (ISO 8601)");
|
|
142
|
+
}
|
|
143
|
+
if (def.type === "updated_at") {
|
|
144
|
+
parts.push("Auto-set when record was last modified (ISO 8601)");
|
|
145
|
+
}
|
|
146
|
+
if ((def.type === "single_select" || def.type === "multiple_select") &&
|
|
147
|
+
def.template?.options) {
|
|
148
|
+
const count = (def.template.options ?? []).length;
|
|
149
|
+
if (count > MAX_SELECT_OPTIONS) {
|
|
150
|
+
parts.push(`${count - MAX_SELECT_OPTIONS} more options omitted`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (parts.length <= 1)
|
|
154
|
+
return undefined;
|
|
155
|
+
return parts.join(". ");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Build a ZiteSchema from a Database API response.
|
|
159
|
+
*
|
|
160
|
+
* If `existingSchema` is provided, locked SDK names are preserved for
|
|
161
|
+
* tables/fields that already exist (matched by ID). New tables/fields
|
|
162
|
+
* get fresh camelCase SDK names. This is how `zitejs sync` preserves
|
|
163
|
+
* name stability across schema changes.
|
|
164
|
+
*/
|
|
165
|
+
export function generateSchema(database, existingSchema) {
|
|
166
|
+
const existingTableById = new Map();
|
|
167
|
+
for (const t of existingSchema?.tables ?? []) {
|
|
168
|
+
existingTableById.set(t.id, t);
|
|
169
|
+
}
|
|
170
|
+
const tables = [];
|
|
171
|
+
for (const table of database.tables) {
|
|
172
|
+
const existingTable = existingTableById.get(table.id);
|
|
173
|
+
const existingFieldById = new Map();
|
|
174
|
+
for (const f of existingTable?.fields ?? []) {
|
|
175
|
+
existingFieldById.set(f.id, f);
|
|
51
176
|
}
|
|
52
|
-
|
|
177
|
+
const fields = [];
|
|
178
|
+
for (const field of table.fields) {
|
|
179
|
+
const existing = existingFieldById.get(field.id);
|
|
180
|
+
const { id: _id, order: _order, ...definition } = field;
|
|
181
|
+
fields.push({
|
|
182
|
+
id: field.id,
|
|
183
|
+
sdkName: existing?.sdkName ?? toCamelCase(field.name),
|
|
184
|
+
definition,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
tables.push({
|
|
188
|
+
id: table.id,
|
|
189
|
+
sdkName: existingTable?.sdkName ?? toCamelCase(table.name),
|
|
190
|
+
primaryFieldId: table.primaryFieldId,
|
|
191
|
+
fields,
|
|
192
|
+
});
|
|
53
193
|
}
|
|
54
|
-
return
|
|
194
|
+
return { tables };
|
|
55
195
|
}
|
|
56
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Generate .zite/db.ts purely from ZiteSchema.
|
|
198
|
+
* Pure function — no external data needed beyond what's in the schema file.
|
|
199
|
+
*/
|
|
200
|
+
export function generateDbTs(schema) {
|
|
57
201
|
const lines = [];
|
|
58
|
-
lines.push("// Auto-generated by zitejs
|
|
202
|
+
lines.push("// Auto-generated by zitejs generate. Do not edit manually.");
|
|
59
203
|
lines.push("//");
|
|
60
204
|
lines.push("// Usage in endpoint files (src/api/*.ts):");
|
|
61
205
|
lines.push('// import { zite } from "zitejs/db";');
|
|
@@ -87,26 +231,28 @@ export function generateDbTs(base, schema) {
|
|
|
87
231
|
lines.push('// - Load the "zite:sql" skill for full SQL reference (formulas, lookups, etc.)');
|
|
88
232
|
lines.push("import { createTableClient, createSqlClient } from 'zitejs/runtime';");
|
|
89
233
|
lines.push("");
|
|
90
|
-
for (const table of
|
|
91
|
-
const className = toPascalCase(table.
|
|
234
|
+
for (const table of schema.tables) {
|
|
235
|
+
const className = toPascalCase(table.sdkName);
|
|
92
236
|
const recordType = `${className}RecordType`;
|
|
93
237
|
lines.push(`export type ${recordType} = {`);
|
|
94
238
|
lines.push(" id: string;");
|
|
95
|
-
for (const field of table.fields
|
|
96
|
-
|
|
97
|
-
if (fieldName === "id")
|
|
239
|
+
for (const field of table.fields) {
|
|
240
|
+
if (field.sdkName === "id")
|
|
98
241
|
continue;
|
|
99
|
-
const tsType =
|
|
100
|
-
|
|
242
|
+
const tsType = tsTypeForSchemaField(field.definition);
|
|
243
|
+
const jsdoc = fieldJsdoc(field, table);
|
|
244
|
+
if (jsdoc) {
|
|
245
|
+
lines.push(` /** ${jsdoc} */`);
|
|
246
|
+
}
|
|
247
|
+
lines.push(` ${field.sdkName}: ${tsType};`);
|
|
101
248
|
}
|
|
102
249
|
lines.push("};");
|
|
103
250
|
lines.push("");
|
|
104
251
|
}
|
|
105
252
|
lines.push("export const zite = {");
|
|
106
|
-
for (const table of
|
|
107
|
-
const className = toPascalCase(table.
|
|
108
|
-
|
|
109
|
-
lines.push(` ${propName}: createTableClient<${className}RecordType>('${className}'),`);
|
|
253
|
+
for (const table of schema.tables) {
|
|
254
|
+
const className = toPascalCase(table.sdkName);
|
|
255
|
+
lines.push(` ${table.sdkName}: createTableClient<${className}RecordType>('${className}'),`);
|
|
110
256
|
}
|
|
111
257
|
lines.push(` sql: createSqlClient(),`);
|
|
112
258
|
lines.push("};");
|
|
@@ -134,14 +280,12 @@ export function generateApiTs(endpointFiles) {
|
|
|
134
280
|
lines.push(`export const ${name} = createCaller(${name}Endpoint, '${name}');`);
|
|
135
281
|
}
|
|
136
282
|
lines.push("");
|
|
137
|
-
// Inferred input/output types per endpoint (e.g. GetDashboardInputType, GetDashboardOutputType)
|
|
138
283
|
for (const name of endpointNames) {
|
|
139
284
|
const pascal = toPascalCase(name);
|
|
140
285
|
lines.push(`export type ${pascal}InputType = Parameters<typeof ${name}Endpoint.execute>[0]['input'];`);
|
|
141
286
|
lines.push(`export type ${pascal}OutputType = Awaited<ReturnType<typeof ${name}Endpoint.execute>>;`);
|
|
142
287
|
}
|
|
143
288
|
lines.push("");
|
|
144
|
-
// Also export as a single api object
|
|
145
289
|
lines.push("export const api = {");
|
|
146
290
|
for (const name of endpointNames) {
|
|
147
291
|
lines.push(` ${name},`);
|
|
@@ -150,6 +294,11 @@ export function generateApiTs(endpointFiles) {
|
|
|
150
294
|
lines.push("");
|
|
151
295
|
return lines.join("\n");
|
|
152
296
|
}
|
|
297
|
+
function tsTypeForField(field) {
|
|
298
|
+
if (FIELD_TYPE_MAP[field.type])
|
|
299
|
+
return FIELD_TYPE_MAP[field.type];
|
|
300
|
+
return "string";
|
|
301
|
+
}
|
|
153
302
|
export function generateUserTs(usersTableFields) {
|
|
154
303
|
const lines = [
|
|
155
304
|
"// Auto-generated by zitejs sync. Do not edit manually.",
|
|
@@ -198,6 +347,86 @@ export function generateAuthWrapperTs() {
|
|
|
198
347
|
"",
|
|
199
348
|
].join("\n");
|
|
200
349
|
}
|
|
350
|
+
const AIRTABLE_FIELD_TYPE_MAP = {
|
|
351
|
+
singleLineText: "string",
|
|
352
|
+
multilineText: "string",
|
|
353
|
+
richText: "string",
|
|
354
|
+
email: "string",
|
|
355
|
+
url: "string",
|
|
356
|
+
phoneNumber: "string",
|
|
357
|
+
number: "number",
|
|
358
|
+
currency: "number",
|
|
359
|
+
percent: "number",
|
|
360
|
+
rating: "number",
|
|
361
|
+
duration: "number",
|
|
362
|
+
singleSelect: "string",
|
|
363
|
+
multipleSelects: "string[]",
|
|
364
|
+
checkbox: "boolean",
|
|
365
|
+
date: "string",
|
|
366
|
+
dateTime: "string",
|
|
367
|
+
attachment: "Array<{ url: string; filename?: string }>",
|
|
368
|
+
multipleRecordLinks: "string | string[]",
|
|
369
|
+
formula: "unknown",
|
|
370
|
+
rollup: "unknown",
|
|
371
|
+
lookup: "unknown",
|
|
372
|
+
count: "number",
|
|
373
|
+
autoNumber: "number",
|
|
374
|
+
barcode: "string",
|
|
375
|
+
button: "unknown",
|
|
376
|
+
createdTime: "string",
|
|
377
|
+
lastModifiedTime: "string",
|
|
378
|
+
createdBy: "unknown",
|
|
379
|
+
lastModifiedBy: "unknown",
|
|
380
|
+
externalSyncSource: "unknown",
|
|
381
|
+
aiText: "string",
|
|
382
|
+
};
|
|
383
|
+
function airtableTsType(field) {
|
|
384
|
+
if ((field.type === "singleSelect" || field.type === "multipleSelects") &&
|
|
385
|
+
field.options &&
|
|
386
|
+
field.options.length > 0) {
|
|
387
|
+
const literals = field.options
|
|
388
|
+
.slice(0, MAX_SELECT_OPTIONS)
|
|
389
|
+
.map((o) => `"${o.replace(/"/g, '\\"')}"`)
|
|
390
|
+
.join(" | ");
|
|
391
|
+
const union = `${literals} | string`;
|
|
392
|
+
if (field.type === "multipleSelects")
|
|
393
|
+
return `(${union})[]`;
|
|
394
|
+
return union;
|
|
395
|
+
}
|
|
396
|
+
return AIRTABLE_FIELD_TYPE_MAP[field.type] ?? "unknown";
|
|
397
|
+
}
|
|
398
|
+
export function generateAirtableTs(lock) {
|
|
399
|
+
if (lock.tables.length === 0)
|
|
400
|
+
return null;
|
|
401
|
+
const lines = [
|
|
402
|
+
"// Auto-generated by zitejs generate from zite.lock. Do not edit manually.",
|
|
403
|
+
"// Airtable SDK — uses createAirtableClient from zitejs/runtime.",
|
|
404
|
+
"// The airtable package is externalized (not bundled per-endpoint).",
|
|
405
|
+
"",
|
|
406
|
+
"import { createAirtableClient } from 'zitejs/runtime';",
|
|
407
|
+
"",
|
|
408
|
+
];
|
|
409
|
+
for (const table of lock.tables) {
|
|
410
|
+
const recordType = `${table.sdkName}RecordType`;
|
|
411
|
+
lines.push(`export type ${recordType} = {`);
|
|
412
|
+
lines.push(" id: string;");
|
|
413
|
+
for (const field of table.fields) {
|
|
414
|
+
if (field.sdkName === "id")
|
|
415
|
+
continue;
|
|
416
|
+
const tsType = airtableTsType(field);
|
|
417
|
+
lines.push(` ${field.sdkName}: ${tsType};`);
|
|
418
|
+
}
|
|
419
|
+
lines.push("};");
|
|
420
|
+
lines.push("");
|
|
421
|
+
lines.push(`export const ${table.sdkName} = createAirtableClient<${recordType}>(`);
|
|
422
|
+
lines.push(` '${lock.integrationId}',`);
|
|
423
|
+
lines.push(` '${table.sdkName}',`);
|
|
424
|
+
lines.push(` { tableId: '${table.id}' },`);
|
|
425
|
+
lines.push(`);`);
|
|
426
|
+
lines.push("");
|
|
427
|
+
}
|
|
428
|
+
return lines.join("\n");
|
|
429
|
+
}
|
|
201
430
|
export function generateBackendWrapperTs() {
|
|
202
431
|
return [
|
|
203
432
|
"// Auto-generated type-narrowing wrapper. Do not edit manually.",
|