zitejs 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/index.d.ts +14 -35
- package/api/index.js +20 -11
- package/backend/index.d.ts +39 -0
- package/backend/index.js +13 -0
- package/package.json +9 -1
- package/sync/index.js +21 -155
- package/sync/lib.js +173 -0
package/api/index.d.ts
CHANGED
|
@@ -1,42 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* zitejs/api —
|
|
2
|
+
* zitejs/api — Client-side typed API caller.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* import {
|
|
4
|
+
* Used in generated .zite/api.ts files:
|
|
5
|
+
* import { createCaller } from 'zitejs/api';
|
|
6
|
+
* import myEndpoint from '../src/api/myEndpoint';
|
|
7
|
+
* export const api = { myEndpoint: createCaller(myEndpoint) };
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* outputSchema: z.object({ name: z.string() }),
|
|
11
|
-
* execute: async ({ input }) => {
|
|
12
|
-
* const user = await zite.users.findOne(input.id);
|
|
13
|
-
* return { name: user.name };
|
|
14
|
-
* },
|
|
15
|
-
* });
|
|
9
|
+
* Then consumed in app code:
|
|
10
|
+
* import { api } from 'zitejs/api'; // resolved via tsconfig to .zite/api.ts
|
|
11
|
+
* await api.myEndpoint({ ... });
|
|
16
12
|
*/
|
|
17
13
|
|
|
18
|
-
export interface
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
[key: string]: unknown;
|
|
14
|
+
export interface EndpointConfig<TInput = unknown, TOutput = unknown> {
|
|
15
|
+
_name?: string;
|
|
16
|
+
execute: (params: { input: TInput; context: unknown }) => Promise<TOutput> | TOutput;
|
|
22
17
|
}
|
|
23
18
|
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export declare class ZiteError extends Error {
|
|
29
|
-
constructor(message: string, options?: { statusCode?: number });
|
|
30
|
-
statusCode: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export declare function createEndpoint<TInput = unknown, TOutput = unknown>(config: {
|
|
34
|
-
description?: string;
|
|
35
|
-
inputSchema?: unknown;
|
|
36
|
-
outputSchema?: unknown;
|
|
37
|
-
stream?: boolean;
|
|
38
|
-
execute: (params: {
|
|
39
|
-
input: TInput;
|
|
40
|
-
context: ZiteRequestContext | ZiteScheduledContext;
|
|
41
|
-
}) => Promise<TOutput> | TOutput;
|
|
42
|
-
}): unknown;
|
|
19
|
+
export declare function createCaller<TInput, TOutput>(
|
|
20
|
+
endpoint: EndpointConfig<TInput, TOutput>,
|
|
21
|
+
): (input: TInput) => Promise<TOutput>;
|
package/api/index.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
// zitejs/api —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = 'ZiteError';
|
|
7
|
-
this.statusCode = options?.statusCode ?? 500;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
1
|
+
// zitejs/api — client-side typed API caller factory.
|
|
2
|
+
|
|
3
|
+
const BASE_URL = process.env.ZITE_API_URL ?? '';
|
|
4
|
+
const TOKEN = process.env.ZITE_DB_TOKEN ?? '';
|
|
10
5
|
|
|
11
|
-
export function
|
|
12
|
-
return
|
|
6
|
+
export function createCaller(endpoint) {
|
|
7
|
+
return async (input) => {
|
|
8
|
+
const res = await fetch(BASE_URL + '/api/' + endpoint._name, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${TOKEN}`,
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify(input),
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const text = await res.text().catch(() => '');
|
|
18
|
+
throw new Error(`API call failed (${res.status}): ${text}`);
|
|
19
|
+
}
|
|
20
|
+
return res.json();
|
|
21
|
+
};
|
|
13
22
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zitejs/backend — Server-side endpoint definition.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { createEndpoint, ZiteError } from 'zitejs/backend';
|
|
6
|
+
* export default createEndpoint({ ... });
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface ZiteRequestContext {
|
|
10
|
+
userId?: string;
|
|
11
|
+
organizationId?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ZiteScheduledContext extends ZiteRequestContext {
|
|
16
|
+
scheduledAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export declare class ZiteError extends Error {
|
|
20
|
+
constructor(message: string, options?: { statusCode?: number });
|
|
21
|
+
statusCode: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EndpointConfig<TInput = unknown, TOutput = unknown> {
|
|
25
|
+
description?: string;
|
|
26
|
+
inputSchema?: unknown;
|
|
27
|
+
outputSchema?: unknown;
|
|
28
|
+
stream?: boolean;
|
|
29
|
+
_name?: string;
|
|
30
|
+
execute: (params: {
|
|
31
|
+
input: TInput;
|
|
32
|
+
context: ZiteRequestContext | ZiteScheduledContext;
|
|
33
|
+
}) => Promise<TOutput> | TOutput;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export declare function createEndpoint<
|
|
37
|
+
TInput = unknown,
|
|
38
|
+
TOutput = unknown,
|
|
39
|
+
>(config: EndpointConfig<TInput, TOutput>): EndpointConfig<TInput, TOutput>;
|
package/backend/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// zitejs/backend — server-side endpoint definition.
|
|
2
|
+
|
|
3
|
+
export class ZiteError extends Error {
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'ZiteError';
|
|
7
|
+
this.statusCode = options?.statusCode ?? 500;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createEndpoint(config) {
|
|
12
|
+
return config;
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zitejs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The Zite framework — build apps on Zite Database",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"types": "./api/index.d.ts",
|
|
16
16
|
"default": "./api/index.js"
|
|
17
17
|
},
|
|
18
|
+
"./backend": {
|
|
19
|
+
"types": "./backend/index.d.ts",
|
|
20
|
+
"default": "./backend/index.js"
|
|
21
|
+
},
|
|
18
22
|
"./auth": {
|
|
19
23
|
"types": "./auth/index.d.ts",
|
|
20
24
|
"default": "./auth/index.js"
|
|
@@ -30,11 +34,15 @@
|
|
|
30
34
|
"./schedules": {
|
|
31
35
|
"types": "./schedules/index.d.ts",
|
|
32
36
|
"default": "./schedules/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./sync": {
|
|
39
|
+
"default": "./sync/lib.js"
|
|
33
40
|
}
|
|
34
41
|
},
|
|
35
42
|
"files": [
|
|
36
43
|
"db",
|
|
37
44
|
"api",
|
|
45
|
+
"backend",
|
|
38
46
|
"auth",
|
|
39
47
|
"upload",
|
|
40
48
|
"pdf",
|
package/sync/index.js
CHANGED
|
@@ -1,68 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* zitejs sync —
|
|
5
|
-
*
|
|
4
|
+
* zitejs sync — CLI entry point.
|
|
5
|
+
* Fetches database schema, generates .zite/db.ts and .zite/api.ts.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* ZITE_DB_TOKEN=sk_prod_... npx zitejs sync
|
|
9
|
-
*
|
|
10
|
-
* Environment:
|
|
11
|
-
* ZITE_DB_TOKEN — API token (required)
|
|
12
|
-
* ZITE_DB_URL — Base URL (default: https://tables.fillout.com/api/v1)
|
|
13
|
-
* ZITE_BASE_ID — Database ID (auto-detected from zite.config.json if omitted)
|
|
14
9
|
*/
|
|
15
10
|
|
|
16
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'fs';
|
|
17
12
|
import { join, dirname } from 'path';
|
|
13
|
+
import { generateSchema, generateDbTs, generateApiTs } from './lib.js';
|
|
18
14
|
|
|
19
15
|
const BASE_URL =
|
|
20
16
|
process.env.ZITE_DB_URL ?? 'https://tables.fillout.com/api/v1';
|
|
21
17
|
const TOKEN = process.env.ZITE_DB_TOKEN ?? '';
|
|
22
18
|
|
|
23
|
-
// Field type → TypeScript type mapping
|
|
24
|
-
const FIELD_TYPE_MAP = {
|
|
25
|
-
single_line_text: 'string',
|
|
26
|
-
long_text: 'string',
|
|
27
|
-
email: 'string',
|
|
28
|
-
url: 'string',
|
|
29
|
-
phone_number: 'string',
|
|
30
|
-
number: 'number',
|
|
31
|
-
currency: 'number',
|
|
32
|
-
percent: 'number',
|
|
33
|
-
rating: 'number',
|
|
34
|
-
duration: 'number',
|
|
35
|
-
single_select: 'string',
|
|
36
|
-
multiple_select: 'string[]',
|
|
37
|
-
checkbox: 'boolean',
|
|
38
|
-
date: 'string',
|
|
39
|
-
datetime: 'string',
|
|
40
|
-
attachments: 'Array<{ url: string; name?: string }>',
|
|
41
|
-
linked_record: 'string[]',
|
|
42
|
-
lookup: 'unknown',
|
|
43
|
-
autonumber: 'number',
|
|
44
|
-
source: 'string',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
function toLowerCamel(name) {
|
|
48
|
-
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function toPascalCase(name) {
|
|
52
|
-
return name
|
|
53
|
-
.replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
|
|
54
|
-
.replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function toCamelCase(name) {
|
|
58
|
-
const pascal = toPascalCase(name);
|
|
59
|
-
return toLowerCamel(pascal);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function tsTypeForField(field) {
|
|
63
|
-
return FIELD_TYPE_MAP[field.type] ?? 'unknown';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
19
|
async function fetchBaseMetadata(baseId) {
|
|
67
20
|
const url = `${BASE_URL}/bases/${encodeURIComponent(baseId)}`;
|
|
68
21
|
const res = await fetch(url, {
|
|
@@ -76,121 +29,22 @@ async function fetchBaseMetadata(baseId) {
|
|
|
76
29
|
return res.json();
|
|
77
30
|
}
|
|
78
31
|
|
|
79
|
-
function generateSchema(base) {
|
|
80
|
-
const schema = { tables: {} };
|
|
81
|
-
for (const table of base.tables ?? []) {
|
|
82
|
-
const tableName = toCamelCase(table.name);
|
|
83
|
-
const fields = {};
|
|
84
|
-
for (const field of table.fields ?? []) {
|
|
85
|
-
fields[toCamelCase(field.name)] = { id: field.id };
|
|
86
|
-
}
|
|
87
|
-
schema.tables[tableName] = { id: table.id, fields };
|
|
88
|
-
}
|
|
89
|
-
return schema;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function generateDbTs(base, schema, baseId) {
|
|
93
|
-
const lines = [];
|
|
94
|
-
|
|
95
|
-
lines.push('// Auto-generated by zitejs sync. Do not edit manually.');
|
|
96
|
-
lines.push('');
|
|
97
|
-
|
|
98
|
-
// Runtime
|
|
99
|
-
lines.push(`const BASE_URL = process.env.ZITE_DB_URL ?? 'https://tables.fillout.com/api/v1';`);
|
|
100
|
-
lines.push(`const TOKEN = process.env.ZITE_DB_TOKEN ?? '';`);
|
|
101
|
-
lines.push('');
|
|
102
|
-
lines.push('async function dbFetch(method: string, path: string, body?: unknown) {');
|
|
103
|
-
lines.push(' const res = await fetch(BASE_URL + path, {');
|
|
104
|
-
lines.push(' method,');
|
|
105
|
-
lines.push(' headers: {');
|
|
106
|
-
lines.push(' Authorization: `Bearer ${TOKEN}`,');
|
|
107
|
-
lines.push(" ...(body ? { 'Content-Type': 'application/json' } : {}),");
|
|
108
|
-
lines.push(' },');
|
|
109
|
-
lines.push(' body: body ? JSON.stringify(body) : undefined,');
|
|
110
|
-
lines.push(' });');
|
|
111
|
-
lines.push(' if (!res.ok) {');
|
|
112
|
-
lines.push(" const text = await res.text().catch(() => '');");
|
|
113
|
-
lines.push(' throw new Error(`Zite DB request failed (${res.status}): ${text}`);');
|
|
114
|
-
lines.push(' }');
|
|
115
|
-
lines.push(' return res.json();');
|
|
116
|
-
lines.push('}');
|
|
117
|
-
lines.push('');
|
|
118
|
-
|
|
119
|
-
// Per-table types + client
|
|
120
|
-
for (const table of base.tables ?? []) {
|
|
121
|
-
const className = toPascalCase(table.name);
|
|
122
|
-
const propName = toCamelCase(table.name);
|
|
123
|
-
const recordType = `${className}RecordType`;
|
|
124
|
-
|
|
125
|
-
// Record type
|
|
126
|
-
lines.push(`export type ${recordType} = {`);
|
|
127
|
-
lines.push(' id: string;');
|
|
128
|
-
for (const field of table.fields ?? []) {
|
|
129
|
-
const fieldName = toCamelCase(field.name);
|
|
130
|
-
const tsType = tsTypeForField(field);
|
|
131
|
-
lines.push(` ${fieldName}: ${tsType};`);
|
|
132
|
-
}
|
|
133
|
-
lines.push(' createdAt: string;');
|
|
134
|
-
lines.push(' updatedAt: string;');
|
|
135
|
-
lines.push('};');
|
|
136
|
-
lines.push('');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// createTableClient helper
|
|
140
|
-
lines.push('function createTableClient<T>(baseId: string, tableId: string) {');
|
|
141
|
-
lines.push(' const base = `/bases/${encodeURIComponent(baseId)}/tables/${encodeURIComponent(tableId)}`;');
|
|
142
|
-
lines.push(' return {');
|
|
143
|
-
lines.push(' findMany: (options?: { limit?: number; offset?: number; sort?: unknown[]; filter?: unknown }): Promise<{ records: T[]; total: number; hasMore: boolean }> =>');
|
|
144
|
-
lines.push(' dbFetch("POST", `${base}/records/list`, options),');
|
|
145
|
-
lines.push(' findOne: (recordId: string): Promise<T> =>');
|
|
146
|
-
lines.push(' dbFetch("GET", `${base}/records/${encodeURIComponent(recordId)}`),');
|
|
147
|
-
lines.push(' create: (data: Partial<T>): Promise<T> =>');
|
|
148
|
-
lines.push(' dbFetch("POST", `${base}/records`, { record: data }),');
|
|
149
|
-
lines.push(' update: (recordId: string, data: Partial<T>): Promise<T> =>');
|
|
150
|
-
lines.push(' dbFetch("PATCH", `${base}/records/${encodeURIComponent(recordId)}`, { record: data }),');
|
|
151
|
-
lines.push(' delete: (recordId: string): Promise<{ deleted: true }> =>');
|
|
152
|
-
lines.push(' dbFetch("DELETE", `${base}/records/${encodeURIComponent(recordId)}`),');
|
|
153
|
-
lines.push(' bulkCreate: (records: Partial<T>[]): Promise<T[]> =>');
|
|
154
|
-
lines.push(' dbFetch("POST", `${base}/records/bulk`, { records }),');
|
|
155
|
-
lines.push(' };');
|
|
156
|
-
lines.push('}');
|
|
157
|
-
lines.push('');
|
|
158
|
-
|
|
159
|
-
// Singleton
|
|
160
|
-
lines.push(`const BASE_ID = '${baseId}';`);
|
|
161
|
-
lines.push('');
|
|
162
|
-
lines.push('export const zite = {');
|
|
163
|
-
for (const table of base.tables ?? []) {
|
|
164
|
-
const className = toPascalCase(table.name);
|
|
165
|
-
const propName = toCamelCase(table.name);
|
|
166
|
-
const tableId = schema.tables[propName]?.id ?? table.id;
|
|
167
|
-
lines.push(` ${propName}: createTableClient<${className}RecordType>(BASE_ID, '${tableId}'),`);
|
|
168
|
-
}
|
|
169
|
-
lines.push('};');
|
|
170
|
-
lines.push('');
|
|
171
|
-
|
|
172
|
-
return lines.join('\n');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
32
|
async function main() {
|
|
176
33
|
if (!TOKEN) {
|
|
177
|
-
console.error('Error: ZITE_DB_TOKEN is required.
|
|
34
|
+
console.error('Error: ZITE_DB_TOKEN is required.');
|
|
178
35
|
process.exit(1);
|
|
179
36
|
}
|
|
180
37
|
|
|
181
|
-
// Find base ID from zite.config.json or env
|
|
182
38
|
let baseId = process.env.ZITE_BASE_ID;
|
|
183
39
|
if (!baseId) {
|
|
184
40
|
try {
|
|
185
41
|
const config = JSON.parse(readFileSync('zite.config.json', 'utf-8'));
|
|
186
42
|
baseId = config.project?.basePublicIdentifier;
|
|
187
|
-
} catch {
|
|
188
|
-
// ignore
|
|
189
|
-
}
|
|
43
|
+
} catch { /* ignore */ }
|
|
190
44
|
}
|
|
191
45
|
|
|
192
46
|
if (!baseId) {
|
|
193
|
-
console.error('Error: Could not determine base ID.
|
|
47
|
+
console.error('Error: Could not determine base ID.');
|
|
194
48
|
process.exit(1);
|
|
195
49
|
}
|
|
196
50
|
|
|
@@ -201,17 +55,29 @@ async function main() {
|
|
|
201
55
|
|
|
202
56
|
const schema = generateSchema(base);
|
|
203
57
|
|
|
204
|
-
// Write zite.schema.json
|
|
205
58
|
writeFileSync('zite.schema.json', JSON.stringify(schema, null, 2));
|
|
206
59
|
console.log('Wrote zite.schema.json');
|
|
207
60
|
|
|
208
|
-
// Write .zite/db.ts
|
|
209
61
|
const dbTs = generateDbTs(base, schema, baseId);
|
|
210
62
|
const dbPath = join('.zite', 'db.ts');
|
|
211
63
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
212
64
|
writeFileSync(dbPath, dbTs);
|
|
213
65
|
console.log(`Wrote ${dbPath}`);
|
|
214
66
|
|
|
67
|
+
// Generate .zite/api.ts from src/api/*.ts
|
|
68
|
+
const apiDir = join('src', 'api');
|
|
69
|
+
if (existsSync(apiDir)) {
|
|
70
|
+
const endpointFiles = readdirSync(apiDir).filter(
|
|
71
|
+
f => f.endsWith('.ts') || f.endsWith('.js'),
|
|
72
|
+
);
|
|
73
|
+
const apiTs = generateApiTs(endpointFiles);
|
|
74
|
+
if (apiTs) {
|
|
75
|
+
const apiPath = join('.zite', 'api.ts');
|
|
76
|
+
writeFileSync(apiPath, apiTs);
|
|
77
|
+
console.log(`Wrote ${apiPath}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
215
81
|
console.log('Done!');
|
|
216
82
|
}
|
|
217
83
|
|
package/sync/lib.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zitejs/sync — pure generator functions.
|
|
3
|
+
*
|
|
4
|
+
* These can be imported as a library (by our backend) or used by the
|
|
5
|
+
* CLI entry point. No fs/network dependencies — just string generation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Field type → TypeScript type mapping
|
|
9
|
+
const FIELD_TYPE_MAP = {
|
|
10
|
+
single_line_text: 'string',
|
|
11
|
+
long_text: 'string',
|
|
12
|
+
email: 'string',
|
|
13
|
+
url: 'string',
|
|
14
|
+
phone_number: 'string',
|
|
15
|
+
number: 'number',
|
|
16
|
+
currency: 'number',
|
|
17
|
+
percent: 'number',
|
|
18
|
+
rating: 'number',
|
|
19
|
+
duration: 'number',
|
|
20
|
+
single_select: 'string',
|
|
21
|
+
multiple_select: 'string[]',
|
|
22
|
+
checkbox: 'boolean',
|
|
23
|
+
date: 'string',
|
|
24
|
+
datetime: 'string',
|
|
25
|
+
attachments: 'Array<{ url: string; name?: string }>',
|
|
26
|
+
linked_record: 'string[]',
|
|
27
|
+
lookup: 'unknown',
|
|
28
|
+
autonumber: 'number',
|
|
29
|
+
source: 'string',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function toPascalCase(name) {
|
|
33
|
+
return name
|
|
34
|
+
.replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
|
|
35
|
+
.replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function toCamelCase(name) {
|
|
39
|
+
const pascal = toPascalCase(name);
|
|
40
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function tsTypeForField(field) {
|
|
44
|
+
return FIELD_TYPE_MAP[field.type] ?? 'unknown';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate zite.schema.json content from base metadata.
|
|
49
|
+
*/
|
|
50
|
+
export function generateSchema(base) {
|
|
51
|
+
const schema = { tables: {} };
|
|
52
|
+
for (const table of base.tables ?? []) {
|
|
53
|
+
const tableName = toCamelCase(table.name);
|
|
54
|
+
const fields = {};
|
|
55
|
+
for (const field of table.fields ?? []) {
|
|
56
|
+
fields[toCamelCase(field.name)] = { id: field.id };
|
|
57
|
+
}
|
|
58
|
+
schema.tables[tableName] = { id: table.id, fields };
|
|
59
|
+
}
|
|
60
|
+
return schema;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate .zite/db.ts content from base metadata + schema.
|
|
65
|
+
*/
|
|
66
|
+
export function generateDbTs(base, schema, baseId) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
|
|
69
|
+
lines.push('// Auto-generated by zitejs sync. Do not edit manually.');
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push(`const BASE_URL = process.env.ZITE_DB_URL ?? 'https://tables.fillout.com/api/v1';`);
|
|
72
|
+
lines.push(`const TOKEN = process.env.ZITE_DB_TOKEN ?? '';`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push('async function dbFetch(method: string, path: string, body?: unknown) {');
|
|
75
|
+
lines.push(' const res = await fetch(BASE_URL + path, {');
|
|
76
|
+
lines.push(' method,');
|
|
77
|
+
lines.push(' headers: {');
|
|
78
|
+
lines.push(' Authorization: `Bearer ${TOKEN}`,');
|
|
79
|
+
lines.push(" ...(body ? { 'Content-Type': 'application/json' } : {}),");
|
|
80
|
+
lines.push(' },');
|
|
81
|
+
lines.push(' body: body ? JSON.stringify(body) : undefined,');
|
|
82
|
+
lines.push(' });');
|
|
83
|
+
lines.push(' if (!res.ok) {');
|
|
84
|
+
lines.push(" const text = await res.text().catch(() => '');");
|
|
85
|
+
lines.push(' throw new Error(`Zite DB request failed (${res.status}): ${text}`);');
|
|
86
|
+
lines.push(' }');
|
|
87
|
+
lines.push(' return res.json();');
|
|
88
|
+
lines.push('}');
|
|
89
|
+
lines.push('');
|
|
90
|
+
|
|
91
|
+
for (const table of base.tables ?? []) {
|
|
92
|
+
const className = toPascalCase(table.name);
|
|
93
|
+
const recordType = `${className}RecordType`;
|
|
94
|
+
|
|
95
|
+
lines.push(`export type ${recordType} = {`);
|
|
96
|
+
lines.push(' id: string;');
|
|
97
|
+
for (const field of table.fields ?? []) {
|
|
98
|
+
const fieldName = toCamelCase(field.name);
|
|
99
|
+
const tsType = tsTypeForField(field);
|
|
100
|
+
lines.push(` ${fieldName}: ${tsType};`);
|
|
101
|
+
}
|
|
102
|
+
lines.push(' createdAt: string;');
|
|
103
|
+
lines.push(' updatedAt: string;');
|
|
104
|
+
lines.push('};');
|
|
105
|
+
lines.push('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
lines.push('function createTableClient<T>(baseId: string, tableId: string) {');
|
|
109
|
+
lines.push(' const base = `/bases/${encodeURIComponent(baseId)}/tables/${encodeURIComponent(tableId)}`;');
|
|
110
|
+
lines.push(' return {');
|
|
111
|
+
lines.push(' findMany: (options?: { limit?: number; offset?: number; sort?: unknown[]; filter?: unknown }): Promise<{ records: T[]; total: number; hasMore: boolean }> =>');
|
|
112
|
+
lines.push(' dbFetch("POST", `${base}/records/list`, options),');
|
|
113
|
+
lines.push(' findOne: (recordId: string): Promise<T> =>');
|
|
114
|
+
lines.push(' dbFetch("GET", `${base}/records/${encodeURIComponent(recordId)}`),');
|
|
115
|
+
lines.push(' create: (data: Partial<T>): Promise<T> =>');
|
|
116
|
+
lines.push(' dbFetch("POST", `${base}/records`, { record: data }),');
|
|
117
|
+
lines.push(' update: (recordId: string, data: Partial<T>): Promise<T> =>');
|
|
118
|
+
lines.push(' dbFetch("PATCH", `${base}/records/${encodeURIComponent(recordId)}`, { record: data }),');
|
|
119
|
+
lines.push(' delete: (recordId: string): Promise<{ deleted: true }> =>');
|
|
120
|
+
lines.push(' dbFetch("DELETE", `${base}/records/${encodeURIComponent(recordId)}`),');
|
|
121
|
+
lines.push(' bulkCreate: (records: Partial<T>[]): Promise<T[]> =>');
|
|
122
|
+
lines.push(' dbFetch("POST", `${base}/records/bulk`, { records }),');
|
|
123
|
+
lines.push(' };');
|
|
124
|
+
lines.push('}');
|
|
125
|
+
lines.push('');
|
|
126
|
+
|
|
127
|
+
lines.push(`const BASE_ID = '${baseId}';`);
|
|
128
|
+
lines.push('');
|
|
129
|
+
lines.push('export const zite = {');
|
|
130
|
+
for (const table of base.tables ?? []) {
|
|
131
|
+
const className = toPascalCase(table.name);
|
|
132
|
+
const propName = toCamelCase(table.name);
|
|
133
|
+
const tableId = schema.tables[propName]?.id ?? table.id;
|
|
134
|
+
lines.push(` ${propName}: createTableClient<${className}RecordType>(BASE_ID, '${tableId}'),`);
|
|
135
|
+
}
|
|
136
|
+
lines.push('};');
|
|
137
|
+
lines.push('');
|
|
138
|
+
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generate .zite/api.ts content from a list of endpoint file names.
|
|
144
|
+
* @param endpointFiles — array of filenames like ['deleteRecord.ts', 'getRecords.ts']
|
|
145
|
+
*/
|
|
146
|
+
export function generateApiTs(endpointFiles) {
|
|
147
|
+
if (!endpointFiles || endpointFiles.length === 0) return null;
|
|
148
|
+
|
|
149
|
+
const lines = [
|
|
150
|
+
'// Auto-generated by zitejs sync. Do not edit manually.',
|
|
151
|
+
'',
|
|
152
|
+
"import { createCaller } from 'zitejs/api';",
|
|
153
|
+
'',
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
const endpointNames = [];
|
|
157
|
+
for (const file of endpointFiles) {
|
|
158
|
+
const name = file.replace(/\.(ts|js)$/, '');
|
|
159
|
+
const camelName = toCamelCase(name);
|
|
160
|
+
lines.push(`import ${camelName}Endpoint from '../src/api/${name}';`);
|
|
161
|
+
endpointNames.push(camelName);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push('export const api = {');
|
|
166
|
+
for (const name of endpointNames) {
|
|
167
|
+
lines.push(` ${name}: createCaller(${name}Endpoint),`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('};');
|
|
170
|
+
lines.push('');
|
|
171
|
+
|
|
172
|
+
return lines.join('\n');
|
|
173
|
+
}
|