zitejs 0.9.123 → 0.9.124

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.
Files changed (58) hide show
  1. package/README.md +4 -0
  2. package/dist/cjs/auth/index.d.ts +2 -2
  3. package/dist/cjs/bundle/index.d.ts +4 -0
  4. package/dist/cjs/bundle/index.js +28 -9
  5. package/dist/cjs/dev/index.d.ts +5 -0
  6. package/dist/cjs/dev/index.js +46 -0
  7. package/dist/cjs/index.d.ts +2 -0
  8. package/dist/cjs/index.js +7 -1
  9. package/dist/cjs/integrations/client.d.ts +25 -0
  10. package/dist/cjs/integrations/client.js +21 -0
  11. package/dist/cjs/integrations/client.test-d.d.ts +1 -0
  12. package/dist/cjs/integrations/client.test-d.js +50 -0
  13. package/dist/cjs/integrations/index.d.ts +3 -0
  14. package/dist/cjs/integrations/index.js +6 -0
  15. package/dist/cjs/integrations/rules.d.ts +67 -0
  16. package/dist/cjs/integrations/rules.js +104 -0
  17. package/dist/cjs/integrations/rules.test.d.ts +1 -0
  18. package/dist/cjs/integrations/rules.test.js +86 -0
  19. package/dist/cjs/internal/sdkCall.d.ts +1 -0
  20. package/dist/cjs/internal/sdkCall.js +3 -1
  21. package/dist/cjs/runtime/index.d.ts +1 -0
  22. package/dist/cjs/runtime/index.js +3 -1
  23. package/dist/cjs/sync/lib.d.ts +9 -0
  24. package/dist/cjs/sync/lib.js +53 -0
  25. package/dist/cjs/sync/lib.test.js +119 -0
  26. package/dist/esm/auth/index.d.ts +2 -2
  27. package/dist/esm/bundle/index.d.ts +4 -0
  28. package/dist/esm/bundle/index.js +28 -10
  29. package/dist/esm/dev/index.d.ts +5 -0
  30. package/dist/esm/dev/index.js +47 -2
  31. package/dist/esm/index.d.ts +2 -0
  32. package/dist/esm/index.js +1 -0
  33. package/dist/esm/integrations/client.d.ts +25 -0
  34. package/dist/esm/integrations/client.js +17 -0
  35. package/dist/esm/integrations/client.test-d.d.ts +1 -0
  36. package/dist/esm/integrations/client.test-d.js +48 -0
  37. package/dist/esm/integrations/index.d.ts +3 -0
  38. package/dist/esm/integrations/index.js +3 -0
  39. package/dist/esm/integrations/rules.d.ts +67 -0
  40. package/dist/esm/integrations/rules.js +98 -0
  41. package/dist/esm/integrations/rules.test.d.ts +1 -0
  42. package/dist/esm/integrations/rules.test.js +84 -0
  43. package/dist/esm/internal/sdkCall.d.ts +1 -0
  44. package/dist/esm/internal/sdkCall.js +2 -1
  45. package/dist/esm/runtime/index.d.ts +1 -0
  46. package/dist/esm/runtime/index.js +1 -0
  47. package/dist/esm/sync/lib.d.ts +9 -0
  48. package/dist/esm/sync/lib.js +52 -0
  49. package/dist/esm/sync/lib.test.js +120 -1
  50. package/package.json +10 -1
  51. package/dist/cjs/meta/index.d.ts +0 -14
  52. package/dist/cjs/meta/index.js +0 -12
  53. package/dist/cjs/notifications/index.d.ts +0 -25
  54. package/dist/cjs/notifications/index.js +0 -12
  55. package/dist/esm/meta/index.d.ts +0 -14
  56. package/dist/esm/meta/index.js +0 -8
  57. package/dist/esm/notifications/index.d.ts +0 -25
  58. package/dist/esm/notifications/index.js +0 -8
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createCaller = void 0;
3
+ exports.createCaller = exports.createIntegrationsClient = void 0;
4
4
  exports.createTableClient = createTableClient;
5
5
  exports.createSqlClient = createSqlClient;
6
6
  exports.createAuthClient = createAuthClient;
@@ -211,5 +211,7 @@ function createEmailClient(integrationId) {
211
211
  send: (params) => (0, sdkCall_js_1.getSdkCall)()(integrationId, "Email", "send", params),
212
212
  };
213
213
  }
214
+ var client_js_1 = require("../integrations/client.js");
215
+ Object.defineProperty(exports, "createIntegrationsClient", { enumerable: true, get: function () { return client_js_1.createIntegrationsClient; } });
214
216
  var index_js_1 = require("../caller/index.js");
215
217
  Object.defineProperty(exports, "createCaller", { enumerable: true, get: function () { return index_js_1.createCaller; } });
@@ -1,6 +1,7 @@
1
1
  import type { PublicFieldDefinition } from "../types/fields.js";
2
2
  import type { Database } from "../types/tables.js";
3
3
  import { type SdkNameRename } from "./sdkNames.js";
4
+ import { type GettableIntegration } from "../integrations/rules.js";
4
5
  export type ZiteSchemaField = {
5
6
  id: string;
6
7
  sdkName: string;
@@ -96,3 +97,11 @@ export declare function findEmailIntegrationId(appConfig: unknown, workspaceConf
96
97
  * connected integration's key.
97
98
  */
98
99
  export declare function generateEmailSdk(integrationId: string): string;
100
+ /**
101
+ * `.zite/integrations/index.ts`: `zitejs/integrations` for one source dir. The
102
+ * `integrations` client typed by what the dir can see, plus the generated
103
+ * Airtable clients when it has them.
104
+ */
105
+ export declare function generateIntegrationsIndex(entries: GettableIntegration[], opts: {
106
+ reexportAirtable: boolean;
107
+ }): string;
@@ -9,8 +9,10 @@ exports.generateAirtableTs = generateAirtableTs;
9
9
  exports.generateBackendWrapperTs = generateBackendWrapperTs;
10
10
  exports.findEmailIntegrationId = findEmailIntegrationId;
11
11
  exports.generateEmailSdk = generateEmailSdk;
12
+ exports.generateIntegrationsIndex = generateIntegrationsIndex;
12
13
  const parser_1 = require("@babel/parser");
13
14
  const sdkNames_js_1 = require("./sdkNames.js");
15
+ const rules_js_1 = require("../integrations/rules.js");
14
16
  const AUTH_USERS_TABLE_ID = "zite_user";
15
17
  /**
16
18
  * What a field's value looks like when a record is READ back.
@@ -1397,3 +1399,54 @@ function generateEmailSdk(integrationId) {
1397
1399
  "",
1398
1400
  ].join("\n");
1399
1401
  }
1402
+ const connectedTypeFor = (serviceType) => {
1403
+ const extraLabels = rules_js_1.CREDENTIAL_FIELDS[serviceType].slice(1);
1404
+ return extraLabels.length === 0
1405
+ ? "Connected"
1406
+ : `Connected<{ ${extraLabels.map((label) => `${label}: string`).join("; ")} }>`;
1407
+ };
1408
+ /**
1409
+ * `.zite/integrations/index.ts`: `zitejs/integrations` for one source dir. The
1410
+ * `integrations` client typed by what the dir can see, plus the generated
1411
+ * Airtable clients when it has them.
1412
+ */
1413
+ function generateIntegrationsIndex(entries, opts) {
1414
+ const sorted = [...entries].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
1415
+ const nameUnion = sorted.length === 0
1416
+ ? "never"
1417
+ : sorted.map(({ name }) => tsSingleQuoted(name)).join(" | ");
1418
+ const byName = sorted.length === 0
1419
+ ? ["export type IntegrationByName = {};"]
1420
+ : [
1421
+ "export type IntegrationByName = {",
1422
+ ...sorted.map(({ name, serviceType }) => ` ${tsSingleQuoted(name)}: ${connectedTypeFor(serviceType)} | NeedsConnect;`),
1423
+ "};",
1424
+ ];
1425
+ return [
1426
+ "// Auto-generated by zitejs generate from zite.config.json. Do not edit manually.",
1427
+ "// zitejs/integrations — an integration's credentials by its zite.config.json entry name.",
1428
+ "//",
1429
+ "// Usage in endpoints (src/api/*.ts) and automations (src/workflow.ts):",
1430
+ "// import { integrations } from 'zitejs/integrations';",
1431
+ "//",
1432
+ "// const crm = await integrations.get('<entry name>');",
1433
+ "// if (crm.status !== 'connected') { /* crm.connectUrl is where a person reconnects */ }",
1434
+ "//",
1435
+ "// Call get where the value is used, not once at module scope: a token refreshed",
1436
+ "// mid-run is what the next call returns.",
1437
+ "",
1438
+ // Not 'zitejs/integrations': apps alias that specifier to this very file.
1439
+ "import { createIntegrationsClient } from 'zitejs/runtime';",
1440
+ "import type { Connected, NeedsConnect } from 'zitejs/runtime';",
1441
+ "",
1442
+ ...(opts.reexportAirtable ? ["export * from './airtable';", ""] : []),
1443
+ "export type { Connected, NeedsConnect };",
1444
+ "",
1445
+ `export type IntegrationName = ${nameUnion};`,
1446
+ "",
1447
+ ...byName,
1448
+ "",
1449
+ "export const integrations = createIntegrationsClient<IntegrationByName>();",
1450
+ "",
1451
+ ].join("\n");
1452
+ }
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const vitest_1 = require("vitest");
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
+ const path_1 = require("path");
8
9
  const lib_js_1 = require("./lib.js");
9
10
  /**
10
11
  * Syntax errors in the emitted source. `.zite/db.ts` sits at the repo root and
@@ -418,3 +419,121 @@ const duplicateIdentifierErrorsIn = (source) => {
418
419
  (0, vitest_1.expect)((0, lib_js_1.findEmailIntegrationId)(undefined, undefined)).toBeUndefined();
419
420
  });
420
421
  });
422
+ (0, vitest_1.describe)('generateIntegrationsIndex', () => {
423
+ // Not import.meta: this file also compiles to CommonJS. Vitest runs from the repo root.
424
+ const runtimePath = (0, path_1.resolve)('src/runtime/index.ts');
425
+ /** Type errors in `usage` compiled beside the generated file, `zitejs/runtime` mapped to this source. */
426
+ const typeErrorsIn = (generated, usage, airtable) => {
427
+ const files = {
428
+ '/app/.zite/integrations/index.ts': generated,
429
+ ...(airtable ? { '/app/.zite/integrations/airtable.ts': airtable } : {}),
430
+ '/app/src/usage.ts': usage,
431
+ };
432
+ const options = {
433
+ strict: true,
434
+ noEmit: true,
435
+ target: typescript_1.default.ScriptTarget.ES2022,
436
+ module: typescript_1.default.ModuleKind.ESNext,
437
+ moduleResolution: typescript_1.default.ModuleResolutionKind.Bundler,
438
+ skipLibCheck: true,
439
+ baseUrl: '/app',
440
+ paths: {
441
+ 'zitejs/runtime': [runtimePath],
442
+ 'zitejs/integrations': ['/app/.zite/integrations/index.ts'],
443
+ },
444
+ };
445
+ const host = typescript_1.default.createCompilerHost(options);
446
+ const { getSourceFile, fileExists, readFile, directoryExists } = host;
447
+ host.fileExists = f => f in files || fileExists(f);
448
+ host.directoryExists = d => Object.keys(files).some(f => f.startsWith(`${d}/`)) ||
449
+ (directoryExists?.(d) ?? false);
450
+ host.readFile = f => files[f] ?? readFile(f);
451
+ host.getSourceFile = (f, lang, ...rest) => f in files
452
+ ? typescript_1.default.createSourceFile(f, files[f], lang)
453
+ : getSourceFile(f, lang, ...rest);
454
+ const program = typescript_1.default.createProgram(Object.keys(files), options, host);
455
+ return typescript_1.default
456
+ .getPreEmitDiagnostics(program)
457
+ .filter(d => d.file?.fileName.startsWith('/app/'))
458
+ .map(d => typescript_1.default.flattenDiagnosticMessageText(d.messageText, ' '));
459
+ };
460
+ (0, vitest_1.it)('emits the client typed by name', () => {
461
+ (0, vitest_1.expect)((0, lib_js_1.generateIntegrationsIndex)([
462
+ { name: 'team', serviceType: 'slack' },
463
+ { name: 'crm', serviceType: 'salesforce' },
464
+ ], { reexportAirtable: false })).toMatchInlineSnapshot(`
465
+ "// Auto-generated by zitejs generate from zite.config.json. Do not edit manually.
466
+ // zitejs/integrations — an integration's credentials by its zite.config.json entry name.
467
+ //
468
+ // Usage in endpoints (src/api/*.ts) and automations (src/workflow.ts):
469
+ // import { integrations } from 'zitejs/integrations';
470
+ //
471
+ // const crm = await integrations.get('<entry name>');
472
+ // if (crm.status !== 'connected') { /* crm.connectUrl is where a person reconnects */ }
473
+ //
474
+ // Call get where the value is used, not once at module scope: a token refreshed
475
+ // mid-run is what the next call returns.
476
+
477
+ import { createIntegrationsClient } from 'zitejs/runtime';
478
+ import type { Connected, NeedsConnect } from 'zitejs/runtime';
479
+
480
+ export type { Connected, NeedsConnect };
481
+
482
+ export type IntegrationName = 'crm' | 'team';
483
+
484
+ export type IntegrationByName = {
485
+ 'crm': Connected<{ instanceUrl: string }> | NeedsConnect;
486
+ 'team': Connected | NeedsConnect;
487
+ };
488
+
489
+ export const integrations = createIntegrationsClient<IntegrationByName>();
490
+ "
491
+ `);
492
+ });
493
+ (0, vitest_1.it)('types get per name in app code', () => {
494
+ const generated = (0, lib_js_1.generateIntegrationsIndex)([
495
+ { name: 'crm', serviceType: 'salesforce' },
496
+ { name: 'warehouse', serviceType: 'clickhouse' },
497
+ ], { reexportAirtable: false });
498
+ (0, vitest_1.expect)(typeErrorsIn(generated, [
499
+ "import { integrations } from 'zitejs/integrations';",
500
+ 'export const run = async () => {',
501
+ " const crm = await integrations.get('crm');",
502
+ " if (crm.status === 'connected') return crm.instanceUrl + crm.accessToken;",
503
+ " const warehouse = await integrations.get('warehouse');",
504
+ " if (warehouse.status === 'connected') return warehouse.url + warehouse.username;",
505
+ ' return crm.connectUrl;',
506
+ '};',
507
+ ].join('\n'))).toEqual([]);
508
+ (0, vitest_1.expect)(typeErrorsIn(generated, "import { integrations } from 'zitejs/integrations';\nvoid integrations.get('nope');")).toHaveLength(1);
509
+ });
510
+ (0, vitest_1.it)('compiles with nothing visible, naming nothing', () => {
511
+ const generated = (0, lib_js_1.generateIntegrationsIndex)([], {
512
+ reexportAirtable: false,
513
+ });
514
+ (0, vitest_1.expect)(generated).toContain('export type IntegrationName = never;');
515
+ (0, vitest_1.expect)(typeErrorsIn(generated, "import { integrations, type IntegrationName } from 'zitejs/integrations';\nexport const f = (n: IntegrationName) => n;\nexport const g = integrations;")).toEqual([]);
516
+ });
517
+ vitest_1.it.each([
518
+ ['a double quote', 'crm"prod'],
519
+ ['a single quote', "crm'prod"],
520
+ ['a newline', 'crm\nprod'],
521
+ ['a trailing backslash', 'crm\\'],
522
+ ])('emits parseable TypeScript for a name with %s', (_what, name) => {
523
+ (0, vitest_1.expect)(syntaxErrorsIn((0, lib_js_1.generateIntegrationsIndex)([{ name, serviceType: 'slack' }], {
524
+ reexportAirtable: false,
525
+ }))).toEqual([]);
526
+ });
527
+ // One module for everything integration: the Airtable clients keep resolving.
528
+ (0, vitest_1.it)('re-exports the generated Airtable clients beside the integrations client', () => {
529
+ const generated = (0, lib_js_1.generateIntegrationsIndex)([{ name: 'team', serviceType: 'slack' }], { reexportAirtable: true });
530
+ (0, vitest_1.expect)(generated).toContain("export * from './airtable';");
531
+ (0, vitest_1.expect)(typeErrorsIn(generated, [
532
+ "import { Customers, integrations } from 'zitejs/integrations';",
533
+ 'export const run = async () => {',
534
+ ' await Customers.findMany();',
535
+ " return integrations.get('team');",
536
+ '};',
537
+ ].join('\n'), 'export const Customers = { findMany: async () => [] };')).toEqual([]);
538
+ });
539
+ });
@@ -67,7 +67,7 @@ export declare const signIn: {
67
67
  }, FetchOptions["throw"] extends true ? true : false>>;
68
68
  } & {
69
69
  social: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
70
- provider: (string & {}) | "linear" | "huggingface" | "github" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
70
+ provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
71
71
  callbackURL?: string | undefined;
72
72
  newUserCallbackURL?: string | undefined;
73
73
  errorCallbackURL?: string | undefined;
@@ -91,7 +91,7 @@ export declare const signIn: {
91
91
  loginHint?: string | undefined;
92
92
  additionalData?: Record<string, any> | undefined;
93
93
  }> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, any> | undefined>>(data_0: import("better-auth/react").Prettify<{
94
- provider: (string & {}) | "linear" | "huggingface" | "github" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
94
+ provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
95
95
  callbackURL?: string | undefined;
96
96
  newUserCallbackURL?: string | undefined;
97
97
  errorCallbackURL?: string | undefined;
@@ -22,4 +22,8 @@
22
22
  import * as esbuild from 'esbuild';
23
23
  export declare const UNSUPPORTED_IN_WORKERD: Set<string>;
24
24
  export declare const BASE_BUILD_OPTIONS: esbuild.BuildOptions;
25
+ export declare function createAliasPlugin(opts: {
26
+ baseDir?: string;
27
+ sdkPath?: string;
28
+ }): esbuild.Plugin;
25
29
  export declare function runBundle(): Promise<void>;
@@ -24,6 +24,8 @@ import * as path from 'path';
24
24
  import * as fs from 'fs';
25
25
  import { parse } from '@babel/parser';
26
26
  import { builtinModules } from 'node:module';
27
+ import { INTEGRATIONS_CLASS_NAME, INTEGRATIONS_SDK_INTEGRATION_ID, } from '../integrations/client.js';
28
+ import { SDK_NOT_INITIALIZED_MESSAGE } from '../internal/sdkCall.js';
27
29
  import { AUTOMATION_ENDPOINT_NAME, endpointEntryFile, findSourceDir, listEndpointNames, sourceRootOf, } from '../sourceRoots.js';
28
30
  // workerd's `nodejs_compat` does not provide these. Left external they bundle
29
31
  // fine and then kill the worker at load time with `No such module`; excluded,
@@ -192,7 +194,7 @@ function getSdkImportSource(baseDir) {
192
194
  return 'zite-integrations-backend-sdk';
193
195
  }
194
196
  /**
195
- * `zitejs/db`, `zitejs/api`, `zitejs/integrations` and `zitejs/email` are
197
+ * `zitejs/db`, `zitejs/api` and `zitejs/email` are
196
198
  * tsconfig aliases onto generated files, not real package subpaths — they are
197
199
  * absent from the `exports` map. `zitejs/backend` IS a real subpath export, but
198
200
  * it lands here too and `zitejs` is not in PREBUNDLED_LIBS, so nothing would
@@ -205,7 +207,19 @@ const unresolvedAlias = (specifier, expectedPath) => {
205
207
  throw new Error(`Cannot resolve '${specifier}': expected generated file at ${expectedPath}. ` +
206
208
  `Run \`npx zitejs generate\` before bundling.`);
207
209
  };
208
- function createAliasPlugin(opts) {
210
+ const AUTH_SERVER_SHIM_PATH = 'zitejs-auth-server-shim';
211
+ const INTEGRATIONS_SHIM_PATH = 'zitejs-integrations-shim';
212
+ const VIRTUAL_MODULES = {
213
+ [AUTH_SERVER_SHIM_PATH]: 'export const ziteAuth = (config) => config;',
214
+ [INTEGRATIONS_SHIM_PATH]: `export const integrations = {
215
+ get(name) {
216
+ const sdkCall = globalThis.__wrapSdkCall;
217
+ if (!sdkCall) throw new Error(${JSON.stringify(SDK_NOT_INITIALIZED_MESSAGE)});
218
+ return sdkCall(${JSON.stringify(INTEGRATIONS_SDK_INTEGRATION_ID)}, ${JSON.stringify(INTEGRATIONS_CLASS_NAME)}, 'get', { name });
219
+ },
220
+ };`,
221
+ };
222
+ export function createAliasPlugin(opts) {
209
223
  return {
210
224
  name: 'zite-alias',
211
225
  setup(build) {
@@ -256,14 +270,18 @@ function createAliasPlugin(opts) {
256
270
  }
257
271
  return unresolvedAlias('zitejs/api', `${opts.baseDir}/.zite/api.ts`);
258
272
  });
259
- // Resolve zitejs/integrations to .zite/integrations/airtable.ts
273
+ // zitejs/integrations: the generated index (the integrations client plus
274
+ // any Airtable clients), else Airtable alone from an older generate, else
275
+ // this bundler's own client, whatever zitejs the project pins.
260
276
  build.onResolve({ filter: /^zitejs\/integrations$/ }, () => {
261
277
  if (opts.baseDir) {
262
- const intPath = path.resolve(opts.baseDir, '.zite/integrations/airtable.ts');
263
- if (fs.existsSync(intPath))
264
- return { path: intPath };
278
+ for (const file of ['index.ts', 'airtable.ts']) {
279
+ const intPath = path.resolve(opts.baseDir, '.zite/integrations', file);
280
+ if (fs.existsSync(intPath))
281
+ return { path: intPath };
282
+ }
265
283
  }
266
- return unresolvedAlias('zitejs/integrations', `${opts.baseDir}/.zite/integrations/airtable.ts`);
284
+ return { path: INTEGRATIONS_SHIM_PATH, namespace: 'zite-virtual' };
267
285
  });
268
286
  // Resolve zitejs/email to .zite/integrations/email.ts
269
287
  build.onResolve({ filter: /^zitejs\/email$/ }, () => {
@@ -277,11 +295,11 @@ function createAliasPlugin(opts) {
277
295
  // zitejs/auth/server is types + an identity wrapper — inline a shim so
278
296
  // zite.auth.ts bundles without resolving the installed package.
279
297
  build.onResolve({ filter: /^zitejs\/auth\/server$/ }, () => ({
280
- path: 'zitejs-auth-server-shim',
298
+ path: AUTH_SERVER_SHIM_PATH,
281
299
  namespace: 'zite-virtual',
282
300
  }));
283
- build.onLoad({ filter: /.*/, namespace: 'zite-virtual' }, () => ({
284
- contents: 'export const ziteAuth = (config) => config;',
301
+ build.onLoad({ filter: /.*/, namespace: 'zite-virtual' }, (args) => ({
302
+ contents: VIRTUAL_MODULES[args.path],
285
303
  loader: 'js',
286
304
  }));
287
305
  // zitejs/runtime is NOT in PREBUNDLED_LIBS — it's a thin fetch()
@@ -1,2 +1,7 @@
1
+ /**
2
+ * Whether the zitejs that `fromDir` resolves, nearest first up to the project root,
3
+ * has the integrations client: the CLI running generate can be newer than that pin.
4
+ */
5
+ export declare function projectRuntimeHasIntegrationsClient(fromDir: string, projectRoot?: string): boolean;
1
6
  export declare function runGenerate(): Promise<void>;
2
7
  export declare function runDev(): Promise<void>;
@@ -1,9 +1,10 @@
1
1
  import { watch } from "fs";
2
2
  import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, } from "fs";
3
- import { join } from "path";
3
+ import { dirname, join, resolve } from "path";
4
4
  import { listSourceDirs } from "../sourceRoots.js";
5
5
  import { runSync } from "../sync/index.js";
6
- import { generateDbTs, generateApiTs, generateBackendWrapperTs, generateAirtableTs, findEmailIntegrationId, generateEmailSdk, } from "../sync/lib.js";
6
+ import { generateDbTs, generateApiTs, generateBackendWrapperTs, generateAirtableTs, findEmailIntegrationId, generateEmailSdk, generateIntegrationsIndex, } from "../sync/lib.js";
7
+ import { gettableIntegrations, } from "../integrations/rules.js";
7
8
  const debounceTimers = new Map();
8
9
  function debounce(key, fn, ms) {
9
10
  const existing = debounceTimers.get(key);
@@ -35,6 +36,48 @@ const readJsonFile = (path) => {
35
36
  function getEmailIntegrationId(appDir) {
36
37
  return findEmailIntegrationId(readJsonFile(join(appDir.path, "zite.config.json")), readJsonFile("zite.config.json"));
37
38
  }
39
+ // `{}` with no file; undefined when unreadable, so a failed read keeps the old file.
40
+ const readConfigIntegrations = (path) => {
41
+ if (!existsSync(path))
42
+ return {};
43
+ const config = readJsonFile(path);
44
+ if (typeof config !== "object" || config === null)
45
+ return undefined;
46
+ const { integrations } = config;
47
+ if (integrations == null)
48
+ return {};
49
+ return typeof integrations === "object" && !Array.isArray(integrations)
50
+ ? integrations
51
+ : undefined;
52
+ };
53
+ /**
54
+ * Whether the zitejs that `fromDir` resolves, nearest first up to the project root,
55
+ * has the integrations client: the CLI running generate can be newer than that pin.
56
+ */
57
+ export function projectRuntimeHasIntegrationsClient(fromDir, projectRoot = process.cwd()) {
58
+ const root = resolve(projectRoot);
59
+ for (let dir = resolve(fromDir);; dir = dirname(dir)) {
60
+ const pkg = readJsonFile(join(dir, "node_modules", "zitejs", "package.json"));
61
+ if (pkg)
62
+ return pkg.exports?.["./integrations"] !== undefined;
63
+ if (dir === root || dirname(dir) === dir)
64
+ return false;
65
+ }
66
+ }
67
+ // Runs after the Airtable step, so the index re-exports its clients when they exist.
68
+ function regenerateAppIntegrationsIndex(appDir) {
69
+ if (!projectRuntimeHasIntegrationsClient(appDir.path))
70
+ return;
71
+ const root = readConfigIntegrations("zite.config.json");
72
+ const own = readConfigIntegrations(join(appDir.path, "zite.config.json"));
73
+ if (!root || !own)
74
+ return;
75
+ const intDir = join(appDir.path, ".zite", "integrations");
76
+ mkdirSync(intDir, { recursive: true });
77
+ writeFileSync(join(intDir, "index.ts"), generateIntegrationsIndex(gettableIntegrations(root, own), {
78
+ reexportAirtable: existsSync(join(intDir, "airtable.ts")),
79
+ }));
80
+ }
38
81
  function getDeclaredEnvVarNames(appDir) {
39
82
  try {
40
83
  const configPath = join(appDir.path, "zite.config.json");
@@ -149,6 +192,7 @@ export async function runGenerate() {
149
192
  regenerateAppApiTs(app);
150
193
  regenerateAppTypedWrappers(app);
151
194
  regenerateAppAirtableSdk(app);
195
+ regenerateAppIntegrationsIndex(app);
152
196
  }
153
197
  console.log("Done!");
154
198
  }
@@ -169,6 +213,7 @@ export async function runDev() {
169
213
  for (const app of appDirs) {
170
214
  regenerateAppApiTs(app);
171
215
  regenerateAppTypedWrappers(app);
216
+ regenerateAppIntegrationsIndex(app);
172
217
  }
173
218
  // 3. Watch each dir's src/api/ for endpoint changes
174
219
  let watchingAny = false;
@@ -1,3 +1,5 @@
1
1
  export { Zite } from "./client/index.js";
2
2
  export type { Record, RecordListOutput, RecordListOptions, FieldCreateInput, FieldUpdateInput, TableCreateInput, TableUpdateInput, DatabaseCreateInput, DatabaseListOutput, } from "./client/index.js";
3
3
  export * from "./types/index.js";
4
+ export { CREDENTIAL_FIELDS, NON_CREDENTIAL_SERVICE_TYPES, gettableIntegrations, isCredentialServiceType, normalizeIntegrationServiceType, } from "./integrations/rules.js";
5
+ export type { ConfigIntegrations, CredentialServiceType, GettableIntegration, } from "./integrations/rules.js";
package/dist/esm/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Zite } from "./client/index.js";
2
2
  export * from "./types/index.js";
3
+ export { CREDENTIAL_FIELDS, NON_CREDENTIAL_SERVICE_TYPES, gettableIntegrations, isCredentialServiceType, normalizeIntegrationServiceType, } from "./integrations/rules.js";
@@ -0,0 +1,25 @@
1
+ export declare const INTEGRATIONS_SDK_INTEGRATION_ID = "__connections__";
2
+ export declare const INTEGRATIONS_CLASS_NAME = "Connections";
3
+ /**
4
+ * `accessToken` is the service's primary credential (for Twilio the account SID).
5
+ * Call `get` where the value is used: a token refreshed mid-run is what the next call returns.
6
+ */
7
+ export type Connected<Extra = {}> = {
8
+ status: "connected";
9
+ accessToken: string;
10
+ } & Extra;
11
+ /** No account behind the entry, or its token could not be refreshed. `connectUrl` is where a person fixes that. */
12
+ export type NeedsConnect = {
13
+ status: "missing" | "expired";
14
+ connectUrl: string;
15
+ };
16
+ export interface IntegrationsClient<ByName extends Record<string, unknown>> {
17
+ /** The credentials of the `zite.config.json` integration entry named `name`. */
18
+ get<Name extends keyof ByName & string>(name: Name): Promise<ByName[Name]>;
19
+ }
20
+ /**
21
+ * `ByName` maps each entry name to its answer, e.g.
22
+ * `{ crm: Connected<{ instanceUrl: string }> | NeedsConnect }`; `zitejs generate`
23
+ * writes it per app into `.zite/integrations/index.ts`.
24
+ */
25
+ export declare function createIntegrationsClient<ByName extends Record<string, unknown>>(): IntegrationsClient<ByName>;
@@ -0,0 +1,17 @@
1
+ import { getSdkCall } from "../internal/sdkCall.js";
2
+ // The worker and runner answer this call under its original name; renaming the
3
+ // wire protocol would break every deployed bundle for no user-visible gain.
4
+ export const INTEGRATIONS_SDK_INTEGRATION_ID = "__connections__";
5
+ export const INTEGRATIONS_CLASS_NAME = "Connections";
6
+ /**
7
+ * `ByName` maps each entry name to its answer, e.g.
8
+ * `{ crm: Connected<{ instanceUrl: string }> | NeedsConnect }`; `zitejs generate`
9
+ * writes it per app into `.zite/integrations/index.ts`.
10
+ */
11
+ export function createIntegrationsClient() {
12
+ return {
13
+ get(name) {
14
+ return getSdkCall()(INTEGRATIONS_SDK_INTEGRATION_ID, INTEGRATIONS_CLASS_NAME, "get", { name });
15
+ },
16
+ };
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { describe, it, expectTypeOf } from "vitest";
2
+ import { createIntegrationsClient, } from "./client.js";
3
+ import { integrations as untyped } from "./index.js";
4
+ import { CREDENTIAL_FIELDS, isCredentialServiceType } from "../index.js";
5
+ describe("createIntegrationsClient", () => {
6
+ const client = createIntegrationsClient();
7
+ it("types each name's answer", () => {
8
+ expectTypeOf(client.get("crm")).resolves.toEqualTypeOf();
9
+ });
10
+ it("rejects a name the app cannot see", () => {
11
+ // @ts-expect-error not a visible entry
12
+ void client.get("nope");
13
+ });
14
+ // What `.zite/integrations/index.ts` emits when nothing is visible.
15
+ it("accepts an empty map, naming nothing", () => {
16
+ const none = createIntegrationsClient();
17
+ expectTypeOf().toEqualTypeOf();
18
+ });
19
+ it("narrows to the connected fields on status", async () => {
20
+ const crm = await client.get("crm");
21
+ if (crm.status === "connected") {
22
+ expectTypeOf(crm.instanceUrl).toEqualTypeOf();
23
+ }
24
+ else {
25
+ expectTypeOf(crm.connectUrl).toEqualTypeOf();
26
+ }
27
+ });
28
+ });
29
+ describe("the untyped zitejs/integrations client", () => {
30
+ it("takes any name; only accessToken is certain", async () => {
31
+ const entry = await untyped.get("anything");
32
+ if (entry.status === "connected") {
33
+ expectTypeOf(entry.accessToken).toEqualTypeOf();
34
+ expectTypeOf(entry.instanceUrl).toEqualTypeOf();
35
+ }
36
+ });
37
+ });
38
+ describe("CREDENTIAL_FIELDS", () => {
39
+ it("is keyed by service type, and a string narrows to one", () => {
40
+ expectTypeOf(CREDENTIAL_FIELDS.salesforce).toEqualTypeOf();
41
+ const type = "salesforce";
42
+ // @ts-expect-error a plain string is not a service type
43
+ void CREDENTIAL_FIELDS[type];
44
+ if (isCredentialServiceType(type)) {
45
+ expectTypeOf(CREDENTIAL_FIELDS[type]).toEqualTypeOf();
46
+ }
47
+ });
48
+ });
@@ -0,0 +1,3 @@
1
+ import { type Connected, type NeedsConnect } from "./client.js";
2
+ export type { Connected, NeedsConnect } from "./client.js";
3
+ export declare const integrations: import("./client.js").IntegrationsClient<Record<string, NeedsConnect | Connected<Partial<Record<string, string>>>>>;
@@ -0,0 +1,3 @@
1
+ import { createIntegrationsClient, } from "./client.js";
2
+ // What code with no generated `.zite/integrations/index.ts` gets, e.g. a repo-root script.
3
+ export const integrations = createIntegrationsClient();
@@ -0,0 +1,67 @@
1
+ declare const FIELDS_BY_TYPE: {
2
+ googleCalendar: readonly string[];
3
+ googleDrive: readonly string[];
4
+ gmail: readonly string[];
5
+ googleDocs: readonly string[];
6
+ hubspot: readonly string[];
7
+ salesforce: readonly string[];
8
+ twilio: readonly string[];
9
+ slack: readonly string[];
10
+ linear: readonly string[];
11
+ openai: readonly string[];
12
+ googlesheets: readonly string[];
13
+ microsoftTeams: readonly string[];
14
+ onedrive: readonly string[];
15
+ outlook: readonly string[];
16
+ intercom: readonly string[];
17
+ mailchimp: readonly string[];
18
+ anthropic: readonly string[];
19
+ gemini: readonly string[];
20
+ stripe: readonly string[];
21
+ trello: readonly string[];
22
+ notion: readonly string[];
23
+ facebookPages: readonly string[];
24
+ monday: readonly string[];
25
+ resend: readonly string[];
26
+ clickhouse: readonly string[];
27
+ amplitude: readonly string[];
28
+ elevenlabs: readonly string[];
29
+ brevo: readonly string[];
30
+ calendly: readonly string[];
31
+ asana: readonly string[];
32
+ xero: readonly string[];
33
+ googleForms: readonly string[];
34
+ linkedin: readonly string[];
35
+ github: readonly string[];
36
+ quickbooks: readonly string[];
37
+ firecrawl: readonly string[];
38
+ jira: readonly string[];
39
+ typeform: readonly string[];
40
+ apollo: readonly string[];
41
+ attio: readonly string[];
42
+ };
43
+ /** A service type `integrations.get` serves: one used through its npm package with a token. */
44
+ export type CredentialServiceType = keyof typeof FIELDS_BY_TYPE;
45
+ /** Per service type: `accessToken`, then its extra labels sorted (Salesforce `instanceUrl`). */
46
+ export declare const CREDENTIAL_FIELDS: Readonly<Record<CredentialServiceType, readonly string[]>>;
47
+ /**
48
+ * Types the runtime never serves: those with a generated client (Airtable, email,
49
+ * the project database, Fillout) and those whose key is a frontend value (Maps, Analytics).
50
+ */
51
+ export declare const NON_CREDENTIAL_SERVICE_TYPES: readonly string[];
52
+ export declare const normalizeIntegrationServiceType: (type: string) => string;
53
+ export declare const isCredentialServiceType: (type: string) => type is CredentialServiceType;
54
+ /** One entry `integrations.get` can name from a source dir. */
55
+ export type GettableIntegration = {
56
+ name: string;
57
+ serviceType: CredentialServiceType;
58
+ };
59
+ export type ConfigIntegrations = Readonly<Record<string, {
60
+ type?: unknown;
61
+ } | undefined>>;
62
+ /**
63
+ * The entries a source dir can name, by name: its own, then the workspace's under
64
+ * keys it has not taken. An entry whose type the runtime would not serve is dropped.
65
+ */
66
+ export declare const gettableIntegrations: (rootIntegrations: ConfigIntegrations | undefined, appIntegrations?: ConfigIntegrations) => GettableIntegration[];
67
+ export {};