zitejs 0.9.68 → 0.9.70

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.
@@ -259,6 +259,15 @@ function createAliasPlugin(opts) {
259
259
  }
260
260
  return { path: 'zitejs/integrations', external: true };
261
261
  });
262
+ // Resolve zitejs/email to .zite/integrations/email.ts
263
+ build.onResolve({ filter: /^zitejs\/email$/ }, () => {
264
+ if (opts.baseDir) {
265
+ const intPath = path.resolve(opts.baseDir, '.zite/integrations/email.ts');
266
+ if (fs.existsSync(intPath))
267
+ return { path: intPath };
268
+ }
269
+ return { path: 'zitejs/email', external: true };
270
+ });
262
271
  // zitejs/runtime is NOT in PREBUNDLED_LIBS — it's a thin fetch()
263
272
  // wrapper that gets bundled inline by esbuild (no special handling).
264
273
  for (const [pkgName, modulePath] of Object.entries(PREBUNDLED_LIBS)) {
@@ -33,11 +33,39 @@ function getFlowId(appDir) {
33
33
  catch { }
34
34
  return undefined;
35
35
  }
36
+ /**
37
+ * Find the connected email integration's key in an app's zite.config.json,
38
+ * if any. The key is the integrationId used by the runtime SDK bridge.
39
+ */
40
+ function getEmailIntegrationId(appDir) {
41
+ try {
42
+ const configPath = (0, path_1.join)("apps", appDir, "zite.config.json");
43
+ if (!(0, fs_2.existsSync)(configPath))
44
+ return undefined;
45
+ const config = JSON.parse((0, fs_2.readFileSync)(configPath, "utf-8"));
46
+ const integrations = config.integrations ?? {};
47
+ for (const [id, int] of Object.entries(integrations)) {
48
+ if (int?.type === "email")
49
+ return id;
50
+ }
51
+ }
52
+ catch { }
53
+ return undefined;
54
+ }
36
55
  function regenerateAppTypedWrappers(appDir) {
37
56
  const outDir = (0, path_1.join)("apps", appDir, ".zite");
38
57
  (0, fs_2.mkdirSync)(outDir, { recursive: true });
39
58
  (0, fs_2.writeFileSync)((0, path_1.join)(outDir, "user.ts"), (0, lib_js_1.generateUserTs)());
40
59
  (0, fs_2.writeFileSync)((0, path_1.join)(outDir, "auth.ts"), (0, lib_js_1.generateAuthWrapperTs)());
60
+ // Email integration: generate the Email client at .zite/integrations/email.ts.
61
+ // Resolved by the `zitejs/email` bundler/tsconfig alias (mirrors airtable's
62
+ // `zitejs/integrations`), so endpoint code uses `import { Email } from 'zitejs/email'`.
63
+ const emailIntegrationId = getEmailIntegrationId(appDir);
64
+ if (emailIntegrationId) {
65
+ const intDir = (0, path_1.join)(outDir, "integrations");
66
+ (0, fs_2.mkdirSync)(intDir, { recursive: true });
67
+ (0, fs_2.writeFileSync)((0, path_1.join)(intDir, "email.ts"), (0, lib_js_1.generateEmailSdk)(emailIntegrationId));
68
+ }
41
69
  (0, fs_2.writeFileSync)((0, path_1.join)(outDir, "backend.ts"), (0, lib_js_1.generateBackendWrapperTs)());
42
70
  }
43
71
  function regenerateAppApiTs(appDir) {
@@ -73,7 +73,7 @@ export interface AirtableTableClient<T> {
73
73
  }): Promise<T | undefined>;
74
74
  create(params: {
75
75
  record: Partial<T>;
76
- }): Promise<T>;
76
+ }): Promise<T | undefined>;
77
77
  bulkCreate(params: {
78
78
  records: Partial<T>[];
79
79
  }): Promise<T[]>;
@@ -82,13 +82,75 @@ export interface AirtableTableClient<T> {
82
82
  record: Partial<T>;
83
83
  }): Promise<{
84
84
  id: string;
85
- fields: Partial<T>;
86
- }>;
85
+ fields: T;
86
+ } | undefined>;
87
87
  delete(params: {
88
88
  id: string;
89
89
  }): Promise<DeleteResult>;
90
90
  }
91
91
  export declare function createAirtableClient<T>(integrationId: string, className: string, implicitParams: Record<string, unknown>): AirtableTableClient<T>;
92
+ /** A block of content in an email body. */
93
+ export type EmailBlock = {
94
+ type: "text";
95
+ content: string;
96
+ } | {
97
+ type: "button";
98
+ label: string;
99
+ href: string;
100
+ alignment?: "left" | "center" | "right";
101
+ } | {
102
+ type: "image";
103
+ src: string;
104
+ alt?: string;
105
+ alignment?: "left" | "center" | "right";
106
+ width: number;
107
+ height: number;
108
+ } | {
109
+ type: "spacer";
110
+ height: number;
111
+ } | {
112
+ type: "divider";
113
+ borderColor?: string;
114
+ };
115
+ export interface SendEmailParams {
116
+ /** Recipient address(es). Must be valid emails — never placeholders like "me". */
117
+ to: string | string[];
118
+ subject: string;
119
+ /** Email body as an array of content blocks. */
120
+ body: EmailBlock[];
121
+ cc?: string[];
122
+ bcc?: string[];
123
+ replyTo?: string;
124
+ /** 'formatted' (default) for styled HTML, 'plain' for text-only. */
125
+ layout?: "formatted" | "plain";
126
+ /** Custom logo shown above the email content (header area), for branding. */
127
+ logo?: {
128
+ url: string;
129
+ width?: number;
130
+ height?: number;
131
+ };
132
+ /** File attachments, each fetched from its URL at send time. */
133
+ attachments?: {
134
+ filename: string;
135
+ url: string;
136
+ contentType?: string;
137
+ }[];
138
+ }
139
+ export interface SendEmailResult {
140
+ success: boolean;
141
+ messageId: string;
142
+ }
143
+ export interface EmailClient {
144
+ send(params: SendEmailParams): Promise<SendEmailResult>;
145
+ }
146
+ /**
147
+ * Email client — sends through the Zite email gateway via the runtime SDK
148
+ * bridge. Email cannot run in-worker (provider auth, image processing, HTML
149
+ * inlining all live in the gateway), so every call dispatches through
150
+ * getSdkCall(), which routes to the email handler. The integrationId is the
151
+ * app's connected email integration key.
152
+ */
153
+ export declare function createEmailClient(integrationId: string): EmailClient;
92
154
  export declare function createNotificationsClient(): {
93
155
  create(params: NotificationsCreateParams): Promise<NotificationsCreateResult>;
94
156
  };
@@ -4,6 +4,7 @@ exports.createCaller = void 0;
4
4
  exports.createTableClient = createTableClient;
5
5
  exports.createSqlClient = createSqlClient;
6
6
  exports.createAirtableClient = createAirtableClient;
7
+ exports.createEmailClient = createEmailClient;
7
8
  exports.createNotificationsClient = createNotificationsClient;
8
9
  exports.createMetaClient = createMetaClient;
9
10
  const sdkCall_js_1 = require("../internal/sdkCall.js");
@@ -100,6 +101,18 @@ function createAirtableClient(integrationId, className, implicitParams) {
100
101
  }),
101
102
  };
102
103
  }
104
+ /**
105
+ * Email client — sends through the Zite email gateway via the runtime SDK
106
+ * bridge. Email cannot run in-worker (provider auth, image processing, HTML
107
+ * inlining all live in the gateway), so every call dispatches through
108
+ * getSdkCall(), which routes to the email handler. The integrationId is the
109
+ * app's connected email integration key.
110
+ */
111
+ function createEmailClient(integrationId) {
112
+ return {
113
+ send: (params) => (0, sdkCall_js_1.getSdkCall)()(integrationId, "Email", "send", params),
114
+ };
115
+ }
103
116
  const NOTIFICATIONS_SDK_INTEGRATION_ID = "__notifications__";
104
117
  const META_SDK_INTEGRATION_ID = "__meta__";
105
118
  function createNotificationsClient() {
@@ -59,3 +59,11 @@ export type AirtableLock = {
59
59
  };
60
60
  export declare function generateAirtableTs(lock: AirtableLock): string | null;
61
61
  export declare function generateBackendWrapperTs(): string;
62
+ /**
63
+ * Generate `.zite/integrations/email.ts` — the `Email` client for an app with
64
+ * an email integration connected. Mirrors the airtable SDK generation: a thin
65
+ * wrapper over a `zitejs/runtime` factory that dispatches through the runtime
66
+ * SDK bridge, resolved via the `zitejs/email` alias. `integrationId` is the
67
+ * connected integration's key.
68
+ */
69
+ export declare function generateEmailSdk(integrationId: string): string;
@@ -9,6 +9,7 @@ exports.generateUserTs = generateUserTs;
9
9
  exports.generateAuthWrapperTs = generateAuthWrapperTs;
10
10
  exports.generateAirtableTs = generateAirtableTs;
11
11
  exports.generateBackendWrapperTs = generateBackendWrapperTs;
12
+ exports.generateEmailSdk = generateEmailSdk;
12
13
  const parser_1 = require("@babel/parser");
13
14
  const FIELD_TYPE_MAP = {
14
15
  single_line_text: "string",
@@ -25,8 +26,8 @@ const FIELD_TYPE_MAP = {
25
26
  single_select: "string",
26
27
  multiple_select: "string[]",
27
28
  checkbox: "boolean",
28
- date: "string",
29
- datetime: "string",
29
+ date: "string | null",
30
+ datetime: "string | null",
30
31
  attachments: "Array<{ url: string; name?: string }>",
31
32
  linked_record: "string | string[]",
32
33
  lookup: "unknown",
@@ -53,14 +54,31 @@ const DURATION_FORMAT_EXAMPLES = {
53
54
  "h:mm:ss.sss": "1:23:03.000",
54
55
  };
55
56
  function toPascalCase(name) {
56
- return name
57
+ const pascal = name
57
58
  .replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
58
- .replace(/^(.)/, (_, c) => c.toUpperCase());
59
+ .replace(/^(.)/, (_, c) => c.toUpperCase())
60
+ // Drop leftover non-alphanumerics (e.g. a trailing ">" from "</p>") so the
61
+ // sdkName is always a valid identifier in .zite/db.ts.
62
+ .replace(/[^a-zA-Z0-9]+/g, "");
63
+ if (!pascal)
64
+ return "_";
65
+ return /^[0-9]/.test(pascal) ? `_${pascal}` : pascal; // can't start with a digit
59
66
  }
60
67
  function toCamelCase(name) {
61
68
  const pascal = toPascalCase(name);
62
69
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
63
70
  }
71
+ /**
72
+ * Existing sdkNames are preserved across syncs so user code keeps compiling,
73
+ * but schemas written before the sanitizer stripped trailing symbols can carry
74
+ * invalid identifiers (e.g. "timeSeconds)"). Keep an existing name only when
75
+ * it's a valid identifier; otherwise fall through to recomputing it.
76
+ */
77
+ function keepValidSdkName(sdkName) {
78
+ if (!sdkName)
79
+ return undefined;
80
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(sdkName) ? sdkName : undefined;
81
+ }
64
82
  function tsTypeForSchemaField(def) {
65
83
  if (def.type === "single_select" || def.type === "multiple_select") {
66
84
  const options = def.template
@@ -103,11 +121,11 @@ function fieldJsdoc(schemaField, table) {
103
121
  if (def.type === "date") {
104
122
  const tpl = def.template;
105
123
  if (tpl.dateFormat)
106
- parts.push(`Date-only field (YYYY-MM-DD string), display as "${tpl.dateFormat}" format`);
124
+ parts.push(`Date-only field (YYYY-MM-DD string), null when unset, display as "${tpl.dateFormat}" format`);
107
125
  }
108
126
  if (def.type === "datetime") {
109
127
  const tpl = def.template;
110
- const timeParts = ["Date+time field (ISO 8601 timestamp)"];
128
+ const timeParts = ["Date+time field (ISO 8601 timestamp), null when unset"];
111
129
  if (tpl.dateFormat)
112
130
  timeParts.push(`date: ${tpl.dateFormat}`);
113
131
  if (tpl.timeFormat)
@@ -191,13 +209,13 @@ function generateSchema(database, existingSchema) {
191
209
  const { id: _id, order: _order, ...definition } = field;
192
210
  fields.push({
193
211
  id: field.id,
194
- sdkName: existing?.sdkName ?? toCamelCase(field.name),
212
+ sdkName: keepValidSdkName(existing?.sdkName) ?? toCamelCase(field.name),
195
213
  definition,
196
214
  });
197
215
  }
198
216
  tables.push({
199
217
  id: table.id,
200
- sdkName: existingTable?.sdkName ?? toCamelCase(table.name),
218
+ sdkName: keepValidSdkName(existingTable?.sdkName) ?? toCamelCase(table.name),
201
219
  primaryFieldId: table.primaryFieldId,
202
220
  fields,
203
221
  });
@@ -725,3 +743,30 @@ function generateBackendWrapperTs() {
725
743
  "",
726
744
  ].join("\n");
727
745
  }
746
+ /**
747
+ * Generate `.zite/integrations/email.ts` — the `Email` client for an app with
748
+ * an email integration connected. Mirrors the airtable SDK generation: a thin
749
+ * wrapper over a `zitejs/runtime` factory that dispatches through the runtime
750
+ * SDK bridge, resolved via the `zitejs/email` alias. `integrationId` is the
751
+ * connected integration's key.
752
+ */
753
+ function generateEmailSdk(integrationId) {
754
+ return [
755
+ "// Auto-generated by zitejs generate from zite.config.json. Do not edit manually.",
756
+ "// Email SDK — sends through the Zite email gateway via the runtime bridge.",
757
+ "//",
758
+ "// await Email.send({ to, subject, body: [{ type: 'text', content: '...' }] })",
759
+ "// => { success: boolean; messageId: string }",
760
+ "",
761
+ "import { createEmailClient } from 'zitejs/runtime';",
762
+ "",
763
+ "export type {",
764
+ " EmailBlock,",
765
+ " SendEmailParams,",
766
+ " SendEmailResult,",
767
+ "} from 'zitejs/runtime';",
768
+ "",
769
+ `export const Email = createEmailClient('${integrationId}');`,
770
+ "",
771
+ ].join("\n");
772
+ }
@@ -223,6 +223,15 @@ function createAliasPlugin(opts) {
223
223
  }
224
224
  return { path: 'zitejs/integrations', external: true };
225
225
  });
226
+ // Resolve zitejs/email to .zite/integrations/email.ts
227
+ build.onResolve({ filter: /^zitejs\/email$/ }, () => {
228
+ if (opts.baseDir) {
229
+ const intPath = path.resolve(opts.baseDir, '.zite/integrations/email.ts');
230
+ if (fs.existsSync(intPath))
231
+ return { path: intPath };
232
+ }
233
+ return { path: 'zitejs/email', external: true };
234
+ });
226
235
  // zitejs/runtime is NOT in PREBUNDLED_LIBS — it's a thin fetch()
227
236
  // wrapper that gets bundled inline by esbuild (no special handling).
228
237
  for (const [pkgName, modulePath] of Object.entries(PREBUNDLED_LIBS)) {
package/dist/esm/cli.js CHANGED
File without changes
@@ -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 { generateDbTs, generateApiTs, generateUserTs, generateAuthWrapperTs, generateBackendWrapperTs, generateAirtableTs, } from "../sync/lib.js";
5
+ import { generateDbTs, generateApiTs, generateUserTs, generateAuthWrapperTs, generateBackendWrapperTs, generateAirtableTs, generateEmailSdk, } from "../sync/lib.js";
6
6
  const debounceTimers = new Map();
7
7
  function debounce(key, fn, ms) {
8
8
  const existing = debounceTimers.get(key);
@@ -29,11 +29,39 @@ function getFlowId(appDir) {
29
29
  catch { }
30
30
  return undefined;
31
31
  }
32
+ /**
33
+ * Find the connected email integration's key in an app's zite.config.json,
34
+ * if any. The key is the integrationId used by the runtime SDK bridge.
35
+ */
36
+ function getEmailIntegrationId(appDir) {
37
+ try {
38
+ const configPath = join("apps", appDir, "zite.config.json");
39
+ if (!existsSync(configPath))
40
+ return undefined;
41
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
42
+ const integrations = config.integrations ?? {};
43
+ for (const [id, int] of Object.entries(integrations)) {
44
+ if (int?.type === "email")
45
+ return id;
46
+ }
47
+ }
48
+ catch { }
49
+ return undefined;
50
+ }
32
51
  function regenerateAppTypedWrappers(appDir) {
33
52
  const outDir = join("apps", appDir, ".zite");
34
53
  mkdirSync(outDir, { recursive: true });
35
54
  writeFileSync(join(outDir, "user.ts"), generateUserTs());
36
55
  writeFileSync(join(outDir, "auth.ts"), generateAuthWrapperTs());
56
+ // Email integration: generate the Email client at .zite/integrations/email.ts.
57
+ // Resolved by the `zitejs/email` bundler/tsconfig alias (mirrors airtable's
58
+ // `zitejs/integrations`), so endpoint code uses `import { Email } from 'zitejs/email'`.
59
+ const emailIntegrationId = getEmailIntegrationId(appDir);
60
+ if (emailIntegrationId) {
61
+ const intDir = join(outDir, "integrations");
62
+ mkdirSync(intDir, { recursive: true });
63
+ writeFileSync(join(intDir, "email.ts"), generateEmailSdk(emailIntegrationId));
64
+ }
37
65
  writeFileSync(join(outDir, "backend.ts"), generateBackendWrapperTs());
38
66
  }
39
67
  function regenerateAppApiTs(appDir) {
@@ -73,7 +73,7 @@ export interface AirtableTableClient<T> {
73
73
  }): Promise<T | undefined>;
74
74
  create(params: {
75
75
  record: Partial<T>;
76
- }): Promise<T>;
76
+ }): Promise<T | undefined>;
77
77
  bulkCreate(params: {
78
78
  records: Partial<T>[];
79
79
  }): Promise<T[]>;
@@ -82,13 +82,75 @@ export interface AirtableTableClient<T> {
82
82
  record: Partial<T>;
83
83
  }): Promise<{
84
84
  id: string;
85
- fields: Partial<T>;
86
- }>;
85
+ fields: T;
86
+ } | undefined>;
87
87
  delete(params: {
88
88
  id: string;
89
89
  }): Promise<DeleteResult>;
90
90
  }
91
91
  export declare function createAirtableClient<T>(integrationId: string, className: string, implicitParams: Record<string, unknown>): AirtableTableClient<T>;
92
+ /** A block of content in an email body. */
93
+ export type EmailBlock = {
94
+ type: "text";
95
+ content: string;
96
+ } | {
97
+ type: "button";
98
+ label: string;
99
+ href: string;
100
+ alignment?: "left" | "center" | "right";
101
+ } | {
102
+ type: "image";
103
+ src: string;
104
+ alt?: string;
105
+ alignment?: "left" | "center" | "right";
106
+ width: number;
107
+ height: number;
108
+ } | {
109
+ type: "spacer";
110
+ height: number;
111
+ } | {
112
+ type: "divider";
113
+ borderColor?: string;
114
+ };
115
+ export interface SendEmailParams {
116
+ /** Recipient address(es). Must be valid emails — never placeholders like "me". */
117
+ to: string | string[];
118
+ subject: string;
119
+ /** Email body as an array of content blocks. */
120
+ body: EmailBlock[];
121
+ cc?: string[];
122
+ bcc?: string[];
123
+ replyTo?: string;
124
+ /** 'formatted' (default) for styled HTML, 'plain' for text-only. */
125
+ layout?: "formatted" | "plain";
126
+ /** Custom logo shown above the email content (header area), for branding. */
127
+ logo?: {
128
+ url: string;
129
+ width?: number;
130
+ height?: number;
131
+ };
132
+ /** File attachments, each fetched from its URL at send time. */
133
+ attachments?: {
134
+ filename: string;
135
+ url: string;
136
+ contentType?: string;
137
+ }[];
138
+ }
139
+ export interface SendEmailResult {
140
+ success: boolean;
141
+ messageId: string;
142
+ }
143
+ export interface EmailClient {
144
+ send(params: SendEmailParams): Promise<SendEmailResult>;
145
+ }
146
+ /**
147
+ * Email client — sends through the Zite email gateway via the runtime SDK
148
+ * bridge. Email cannot run in-worker (provider auth, image processing, HTML
149
+ * inlining all live in the gateway), so every call dispatches through
150
+ * getSdkCall(), which routes to the email handler. The integrationId is the
151
+ * app's connected email integration key.
152
+ */
153
+ export declare function createEmailClient(integrationId: string): EmailClient;
92
154
  export declare function createNotificationsClient(): {
93
155
  create(params: NotificationsCreateParams): Promise<NotificationsCreateResult>;
94
156
  };
@@ -92,6 +92,18 @@ export function createAirtableClient(integrationId, className, implicitParams) {
92
92
  }),
93
93
  };
94
94
  }
95
+ /**
96
+ * Email client — sends through the Zite email gateway via the runtime SDK
97
+ * bridge. Email cannot run in-worker (provider auth, image processing, HTML
98
+ * inlining all live in the gateway), so every call dispatches through
99
+ * getSdkCall(), which routes to the email handler. The integrationId is the
100
+ * app's connected email integration key.
101
+ */
102
+ export function createEmailClient(integrationId) {
103
+ return {
104
+ send: (params) => getSdkCall()(integrationId, "Email", "send", params),
105
+ };
106
+ }
95
107
  const NOTIFICATIONS_SDK_INTEGRATION_ID = "__notifications__";
96
108
  const META_SDK_INTEGRATION_ID = "__meta__";
97
109
  export function createNotificationsClient() {
@@ -59,3 +59,11 @@ export type AirtableLock = {
59
59
  };
60
60
  export declare function generateAirtableTs(lock: AirtableLock): string | null;
61
61
  export declare function generateBackendWrapperTs(): string;
62
+ /**
63
+ * Generate `.zite/integrations/email.ts` — the `Email` client for an app with
64
+ * an email integration connected. Mirrors the airtable SDK generation: a thin
65
+ * wrapper over a `zitejs/runtime` factory that dispatches through the runtime
66
+ * SDK bridge, resolved via the `zitejs/email` alias. `integrationId` is the
67
+ * connected integration's key.
68
+ */
69
+ export declare function generateEmailSdk(integrationId: string): string;
@@ -14,8 +14,8 @@ const FIELD_TYPE_MAP = {
14
14
  single_select: "string",
15
15
  multiple_select: "string[]",
16
16
  checkbox: "boolean",
17
- date: "string",
18
- datetime: "string",
17
+ date: "string | null",
18
+ datetime: "string | null",
19
19
  attachments: "Array<{ url: string; name?: string }>",
20
20
  linked_record: "string | string[]",
21
21
  lookup: "unknown",
@@ -42,14 +42,31 @@ const DURATION_FORMAT_EXAMPLES = {
42
42
  "h:mm:ss.sss": "1:23:03.000",
43
43
  };
44
44
  export function toPascalCase(name) {
45
- return name
45
+ const pascal = name
46
46
  .replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
47
- .replace(/^(.)/, (_, c) => c.toUpperCase());
47
+ .replace(/^(.)/, (_, c) => c.toUpperCase())
48
+ // Drop leftover non-alphanumerics (e.g. a trailing ">" from "</p>") so the
49
+ // sdkName is always a valid identifier in .zite/db.ts.
50
+ .replace(/[^a-zA-Z0-9]+/g, "");
51
+ if (!pascal)
52
+ return "_";
53
+ return /^[0-9]/.test(pascal) ? `_${pascal}` : pascal; // can't start with a digit
48
54
  }
49
55
  export function toCamelCase(name) {
50
56
  const pascal = toPascalCase(name);
51
57
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
52
58
  }
59
+ /**
60
+ * Existing sdkNames are preserved across syncs so user code keeps compiling,
61
+ * but schemas written before the sanitizer stripped trailing symbols can carry
62
+ * invalid identifiers (e.g. "timeSeconds)"). Keep an existing name only when
63
+ * it's a valid identifier; otherwise fall through to recomputing it.
64
+ */
65
+ function keepValidSdkName(sdkName) {
66
+ if (!sdkName)
67
+ return undefined;
68
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(sdkName) ? sdkName : undefined;
69
+ }
53
70
  function tsTypeForSchemaField(def) {
54
71
  if (def.type === "single_select" || def.type === "multiple_select") {
55
72
  const options = def.template
@@ -92,11 +109,11 @@ function fieldJsdoc(schemaField, table) {
92
109
  if (def.type === "date") {
93
110
  const tpl = def.template;
94
111
  if (tpl.dateFormat)
95
- parts.push(`Date-only field (YYYY-MM-DD string), display as "${tpl.dateFormat}" format`);
112
+ parts.push(`Date-only field (YYYY-MM-DD string), null when unset, display as "${tpl.dateFormat}" format`);
96
113
  }
97
114
  if (def.type === "datetime") {
98
115
  const tpl = def.template;
99
- const timeParts = ["Date+time field (ISO 8601 timestamp)"];
116
+ const timeParts = ["Date+time field (ISO 8601 timestamp), null when unset"];
100
117
  if (tpl.dateFormat)
101
118
  timeParts.push(`date: ${tpl.dateFormat}`);
102
119
  if (tpl.timeFormat)
@@ -180,13 +197,13 @@ export function generateSchema(database, existingSchema) {
180
197
  const { id: _id, order: _order, ...definition } = field;
181
198
  fields.push({
182
199
  id: field.id,
183
- sdkName: existing?.sdkName ?? toCamelCase(field.name),
200
+ sdkName: keepValidSdkName(existing?.sdkName) ?? toCamelCase(field.name),
184
201
  definition,
185
202
  });
186
203
  }
187
204
  tables.push({
188
205
  id: table.id,
189
- sdkName: existingTable?.sdkName ?? toCamelCase(table.name),
206
+ sdkName: keepValidSdkName(existingTable?.sdkName) ?? toCamelCase(table.name),
190
207
  primaryFieldId: table.primaryFieldId,
191
208
  fields,
192
209
  });
@@ -714,3 +731,30 @@ export function generateBackendWrapperTs() {
714
731
  "",
715
732
  ].join("\n");
716
733
  }
734
+ /**
735
+ * Generate `.zite/integrations/email.ts` — the `Email` client for an app with
736
+ * an email integration connected. Mirrors the airtable SDK generation: a thin
737
+ * wrapper over a `zitejs/runtime` factory that dispatches through the runtime
738
+ * SDK bridge, resolved via the `zitejs/email` alias. `integrationId` is the
739
+ * connected integration's key.
740
+ */
741
+ export function generateEmailSdk(integrationId) {
742
+ return [
743
+ "// Auto-generated by zitejs generate from zite.config.json. Do not edit manually.",
744
+ "// Email SDK — sends through the Zite email gateway via the runtime bridge.",
745
+ "//",
746
+ "// await Email.send({ to, subject, body: [{ type: 'text', content: '...' }] })",
747
+ "// => { success: boolean; messageId: string }",
748
+ "",
749
+ "import { createEmailClient } from 'zitejs/runtime';",
750
+ "",
751
+ "export type {",
752
+ " EmailBlock,",
753
+ " SendEmailParams,",
754
+ " SendEmailResult,",
755
+ "} from 'zitejs/runtime';",
756
+ "",
757
+ `export const Email = createEmailClient('${integrationId}');`,
758
+ "",
759
+ ].join("\n");
760
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zitejs",
3
- "version": "0.9.68",
3
+ "version": "0.9.70",
4
4
  "description": "The Zite framework — build apps on Zite Database",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createCaller = void 0;
4
- var index_js_1 = require("../caller/index.js");
5
- Object.defineProperty(exports, "createCaller", { enumerable: true, get: function () { return index_js_1.createCaller; } });
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createTableClient = void 0;
4
- var index_js_1 = require("../runtime/index.js");
5
- Object.defineProperty(exports, "createTableClient", { enumerable: true, get: function () { return index_js_1.createTableClient; } });
@@ -1,2 +0,0 @@
1
- export { createCaller } from '../caller/index.js';
2
- export type { EndpointConfig } from '../caller/index.js';
@@ -1 +0,0 @@
1
- export { createCaller } from '../caller/index.js';
@@ -1,2 +0,0 @@
1
- export { createTableClient } from '../runtime/index.js';
2
- export type { TableClient } from '../runtime/index.js';
@@ -1 +0,0 @@
1
- export { createTableClient } from '../runtime/index.js';