zitejs 0.9.122 → 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 (70) 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 +5 -1
  4. package/dist/cjs/bundle/index.js +46 -26
  5. package/dist/cjs/check/index.js +2 -2
  6. package/dist/cjs/cli.js +1 -1
  7. package/dist/cjs/dev/index.d.ts +5 -0
  8. package/dist/cjs/dev/index.js +47 -1
  9. package/dist/cjs/index.d.ts +2 -0
  10. package/dist/cjs/index.js +7 -1
  11. package/dist/cjs/integrations/client.d.ts +25 -0
  12. package/dist/cjs/integrations/client.js +21 -0
  13. package/dist/cjs/integrations/client.test-d.d.ts +1 -0
  14. package/dist/cjs/integrations/client.test-d.js +50 -0
  15. package/dist/cjs/integrations/index.d.ts +3 -0
  16. package/dist/cjs/integrations/index.js +6 -0
  17. package/dist/cjs/integrations/rules.d.ts +67 -0
  18. package/dist/cjs/integrations/rules.js +104 -0
  19. package/dist/cjs/integrations/rules.test.d.ts +1 -0
  20. package/dist/cjs/integrations/rules.test.js +86 -0
  21. package/dist/cjs/internal/sdkCall.d.ts +1 -0
  22. package/dist/cjs/internal/sdkCall.js +3 -1
  23. package/dist/cjs/runtime/index.d.ts +1 -0
  24. package/dist/cjs/runtime/index.js +3 -1
  25. package/dist/cjs/sourceRoots.d.ts +13 -0
  26. package/dist/cjs/sourceRoots.js +38 -1
  27. package/dist/cjs/sourceRoots.test.d.ts +1 -0
  28. package/dist/cjs/sourceRoots.test.js +30 -0
  29. package/dist/cjs/sync/lib.d.ts +9 -0
  30. package/dist/cjs/sync/lib.js +53 -0
  31. package/dist/cjs/sync/lib.test.js +119 -0
  32. package/dist/esm/auth/index.d.ts +2 -2
  33. package/dist/esm/bundle/index.d.ts +5 -1
  34. package/dist/esm/bundle/index.js +47 -28
  35. package/dist/esm/check/index.js +3 -3
  36. package/dist/esm/cli.js +1 -1
  37. package/dist/esm/dev/index.d.ts +5 -0
  38. package/dist/esm/dev/index.js +48 -3
  39. package/dist/esm/index.d.ts +2 -0
  40. package/dist/esm/index.js +1 -0
  41. package/dist/esm/integrations/client.d.ts +25 -0
  42. package/dist/esm/integrations/client.js +17 -0
  43. package/dist/esm/integrations/client.test-d.d.ts +1 -0
  44. package/dist/esm/integrations/client.test-d.js +48 -0
  45. package/dist/esm/integrations/index.d.ts +3 -0
  46. package/dist/esm/integrations/index.js +3 -0
  47. package/dist/esm/integrations/rules.d.ts +67 -0
  48. package/dist/esm/integrations/rules.js +98 -0
  49. package/dist/esm/integrations/rules.test.d.ts +1 -0
  50. package/dist/esm/integrations/rules.test.js +84 -0
  51. package/dist/esm/internal/sdkCall.d.ts +1 -0
  52. package/dist/esm/internal/sdkCall.js +2 -1
  53. package/dist/esm/runtime/index.d.ts +1 -0
  54. package/dist/esm/runtime/index.js +1 -0
  55. package/dist/esm/sourceRoots.d.ts +13 -0
  56. package/dist/esm/sourceRoots.js +35 -1
  57. package/dist/esm/sourceRoots.test.d.ts +1 -0
  58. package/dist/esm/sourceRoots.test.js +28 -0
  59. package/dist/esm/sync/lib.d.ts +9 -0
  60. package/dist/esm/sync/lib.js +52 -0
  61. package/dist/esm/sync/lib.test.js +120 -1
  62. package/package.json +10 -1
  63. package/dist/cjs/meta/index.d.ts +0 -14
  64. package/dist/cjs/meta/index.js +0 -12
  65. package/dist/cjs/notifications/index.d.ts +0 -25
  66. package/dist/cjs/notifications/index.js +0 -12
  67. package/dist/esm/meta/index.d.ts +0 -14
  68. package/dist/esm/meta/index.js +0 -8
  69. package/dist/esm/notifications/index.d.ts +0 -25
  70. package/dist/esm/notifications/index.js +0 -8
@@ -2,7 +2,7 @@
2
2
  * `npx zitejs bundle` — Bundle endpoint files for cloudflare-lambda deployment.
3
3
  *
4
4
  * Replaces the legacy `scripts/bundle-endpoints.js` from zitejs-starter.
5
- * Reads endpoints from src/api/*.ts, resolves SDK imports via .zite/backend.ts
5
+ * Reads an app's src/api/*.ts endpoints (an automation's single src/workflow.ts), resolves SDK imports via .zite/backend.ts
6
6
  * (monorepo) or src/__zite__/integrations.ts (legacy), and outputs bundled ESM
7
7
  * code suitable for the cloudflare-lambda worker runtime.
8
8
  *
@@ -24,7 +24,9 @@ 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 { findSourceDir } from '../sourceRoots.js';
27
+ import { INTEGRATIONS_CLASS_NAME, INTEGRATIONS_SDK_INTEGRATION_ID, } from '../integrations/client.js';
28
+ import { SDK_NOT_INITIALIZED_MESSAGE } from '../internal/sdkCall.js';
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,
30
32
  // esbuild fails the one endpoint with a resolvable error instead. Pinned by
@@ -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()
@@ -297,7 +315,7 @@ function createAliasPlugin(opts) {
297
315
  },
298
316
  };
299
317
  }
300
- function generateEndpointWrapper(endpointName, usedImports, sdkExportKinds, sdkSource) {
318
+ function generateEndpointWrapper(entryImport, usedImports, sdkExportKinds, sdkSource) {
301
319
  // Always import the prebundled runtime to ensure globalThis.__wrapSdkCall
302
320
  // is available for createTableClient/createSqlClient from zitejs/runtime.
303
321
  const runtimeInit = `import { __wrapSdkCall, initRuntime } from '@zite/endpoints-runtime-sdk';
@@ -308,14 +326,14 @@ initRuntime();`;
308
326
  ${runtimeInit}
309
327
  import * as sdk from '${sdkSource}';
310
328
  Object.assign(globalThis, sdk);
311
- import endpoint from './api/${endpointName}';
329
+ import endpoint from '${entryImport}';
312
330
  globalThis.__endpoint = endpoint;
313
331
  `;
314
332
  }
315
333
  if (usedImports.length === 0) {
316
334
  return `
317
335
  ${runtimeInit}
318
- import endpoint from './api/${endpointName}';
336
+ import endpoint from '${entryImport}';
319
337
  globalThis.__endpoint = endpoint;
320
338
  `;
321
339
  }
@@ -343,11 +361,11 @@ globalThis.__endpoint = endpoint;
343
361
  ${runtimeInit}
344
362
  ${importStatements.join('\n')}
345
363
  ${globalAssign}
346
- import endpoint from './api/${endpointName}';
364
+ import endpoint from '${entryImport}';
347
365
  globalThis.__endpoint = endpoint;
348
366
  `;
349
367
  }
350
- async function bundleEndpointsImpl(baseDir, endpointNames) {
368
+ async function bundleEndpointsImpl(baseDir, root, endpointNames) {
351
369
  const bundledEndpoints = {};
352
370
  const endpointErrors = {};
353
371
  const aliasPlugin = createAliasPlugin({ baseDir });
@@ -362,14 +380,20 @@ async function bundleEndpointsImpl(baseDir, endpointNames) {
362
380
  }
363
381
  catch { }
364
382
  for (const name of endpointNames) {
383
+ if (root === 'automations' && name !== AUTOMATION_ENDPOINT_NAME) {
384
+ endpointErrors[name] =
385
+ `An automation is one workflow, src/workflow.ts, bundled as "${AUTOMATION_ENDPOINT_NAME}"; there is no endpoint "${name}"`;
386
+ continue;
387
+ }
388
+ const entryFile = endpointEntryFile(root, name);
365
389
  let usedImports = null;
366
390
  try {
367
- const endpointPath = path.join(baseDir, 'src', 'api', `${name}.ts`);
368
- const endpointCode = fs.readFileSync(endpointPath, 'utf-8');
391
+ const endpointCode = fs.readFileSync(path.join(baseDir, entryFile), 'utf-8');
369
392
  usedImports = getUsedSdkImports(endpointCode);
370
393
  }
371
394
  catch { }
372
- const wrapperCode = generateEndpointWrapper(name, usedImports, sdkExportKinds, sdkSource);
395
+ // The wrapper resolves from `src/`, so the entry is imported relative to it.
396
+ const wrapperCode = generateEndpointWrapper('./' + path.relative('src', entryFile).replace(/\.ts$/, ''), usedImports, sdkExportKinds, sdkSource);
373
397
  try {
374
398
  const result = await esbuild.build({
375
399
  ...BASE_BUILD_OPTIONS,
@@ -592,18 +616,13 @@ export async function runBundle() {
592
616
  // as missing.
593
617
  const explicitNames = args.filter((a, i) => !a.startsWith('--') && !(appFlag !== -1 && i === appFlag + 1));
594
618
  let endpointNames;
619
+ const root = sourceRootOf(baseDir) ?? 'apps';
595
620
  if (explicitNames.length > 0) {
596
621
  endpointNames = explicitNames;
597
622
  }
598
623
  else {
599
- const apiDir = path.join(baseDir, 'src', 'api');
600
- endpointNames = fs.existsSync(apiDir)
601
- ? fs
602
- .readdirSync(apiDir)
603
- .filter(f => f.endsWith('.ts'))
604
- .map(f => f.replace('.ts', ''))
605
- : [];
624
+ endpointNames = listEndpointNames(root, baseDir);
606
625
  }
607
626
  // Even with no endpoints, zite.auth.ts may still need bundling.
608
- return bundleEndpointsImpl(baseDir, endpointNames);
627
+ return bundleEndpointsImpl(baseDir, root, endpointNames);
609
628
  }
@@ -1,7 +1,7 @@
1
1
  import { execFileSync } from 'child_process';
2
2
  import { existsSync } from 'fs';
3
3
  import { join } from 'path';
4
- import { listSourceDirs } from '../sourceRoots.js';
4
+ import { listEndpointNames, listSourceDirs } from '../sourceRoots.js';
5
5
  /**
6
6
  * An argument array, not a command string, so no shell is involved.
7
7
  *
@@ -62,7 +62,7 @@ export async function runCheck() {
62
62
  process.exit(1);
63
63
  }
64
64
  let allPassed = true;
65
- for (const { dir: app, path: appPath } of appDirs) {
65
+ for (const { root, dir: app, path: appPath } of appDirs) {
66
66
  console.log(`\n── ${appPath} ──`);
67
67
  // Only tsconfig.app.json compiles anything. The app's tsconfig.json is a
68
68
  // solution-style config — `"files": []` plus a reference — so
@@ -89,7 +89,7 @@ export async function runCheck() {
89
89
  // Endpoints never reach vite — they are bundled separately for the lambda,
90
90
  // against a different resolver. tsc and vite both pass on an endpoint whose
91
91
  // `zitejs/*` alias has no generated file behind it; only the bundler knows.
92
- if (existsSync(join(appPath, 'src', 'api'))) {
92
+ if (listEndpointNames(root, appPath).length > 0) {
93
93
  process.stdout.write(' bundle endpoints ... ');
94
94
  const bundle = run('npx', ['zitejs', 'bundle', '--app', app], '.');
95
95
  const failures = bundle.ok ? bundleFailures(bundle.output) : [bundle.output];
package/dist/esm/cli.js CHANGED
@@ -40,7 +40,7 @@ async function main() {
40
40
  console.error(' dev Run sync then watch for changes (like npx convex dev)');
41
41
  console.error(' generate Regenerate every app and automation .zite/ (monorepo)');
42
42
  console.error(' check Run tsc --noEmit (and vite build where there is one) for every app and automation');
43
- console.error(' bundle Bundle src/api/*.ts endpoints for cloudflare-lambda');
43
+ console.error(' bundle Bundle endpoints (src/api/*.ts, or an automation\'s src/workflow.ts) for cloudflare-lambda');
44
44
  process.exit(1);
45
45
  }
46
46
  }
@@ -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;
@@ -190,7 +235,7 @@ export async function runDev() {
190
235
  });
191
236
  }
192
237
  if (!watchingAny) {
193
- console.log("No apps or automations with src/api/ found.");
238
+ console.log("No apps with src/api/ found.");
194
239
  }
195
240
  // 4. Watch zite.schema.json for schema drift
196
241
  if (existsSync("zite.schema.json")) {
@@ -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 {};
@@ -0,0 +1,98 @@
1
+ // Which `zite.config.json` entries `integrations.get` can name, and the fields
2
+ // each answers with. No imports, so the platform applies these same rules.
3
+ const extras = (...labels) => [
4
+ "accessToken",
5
+ ...[...labels].sort(),
6
+ ];
7
+ const FIELDS_BY_TYPE = {
8
+ googleCalendar: extras(),
9
+ googleDrive: extras(),
10
+ gmail: extras(),
11
+ googleDocs: extras(),
12
+ hubspot: extras(),
13
+ salesforce: extras("instanceUrl"),
14
+ twilio: extras("authToken"),
15
+ slack: extras(),
16
+ linear: extras(),
17
+ openai: extras(),
18
+ googlesheets: extras(),
19
+ microsoftTeams: extras(),
20
+ onedrive: extras(),
21
+ outlook: extras(),
22
+ intercom: extras(),
23
+ mailchimp: extras("server"),
24
+ anthropic: extras(),
25
+ gemini: extras(),
26
+ stripe: extras(),
27
+ trello: extras("clientApiKey"),
28
+ notion: extras(),
29
+ facebookPages: extras(),
30
+ monday: extras(),
31
+ resend: extras(),
32
+ clickhouse: extras("url", "username"),
33
+ amplitude: extras("apiKey"),
34
+ elevenlabs: extras(),
35
+ brevo: extras(),
36
+ calendly: extras(),
37
+ asana: extras(),
38
+ xero: extras("tenantId"),
39
+ googleForms: extras(),
40
+ linkedin: extras("personUrn"),
41
+ github: extras(),
42
+ quickbooks: extras("realmId", "apiBaseUrl"),
43
+ firecrawl: extras(),
44
+ jira: extras("cloudId", "siteUrl"),
45
+ typeform: extras(),
46
+ apollo: extras(),
47
+ attio: extras(),
48
+ };
49
+ /** Per service type: `accessToken`, then its extra labels sorted (Salesforce `instanceUrl`). */
50
+ export const CREDENTIAL_FIELDS = FIELDS_BY_TYPE;
51
+ /**
52
+ * Types the runtime never serves: those with a generated client (Airtable, email,
53
+ * the project database, Fillout) and those whose key is a frontend value (Maps, Analytics).
54
+ */
55
+ export const NON_CREDENTIAL_SERVICE_TYPES = [
56
+ "airtable",
57
+ "databases",
58
+ "email",
59
+ "fillout",
60
+ "googleAnalytics",
61
+ "googlemaps",
62
+ ];
63
+ /** Spellings older configs, or an agent writing the entry's slug, put in `type`. */
64
+ const LEGACY_SERVICE_TYPES = {
65
+ googlecalendar: "googleCalendar",
66
+ "google-drive": "googleDrive",
67
+ "google-docs": "googleDocs",
68
+ "google-calendar": "googleCalendar",
69
+ "google-sheets": "googlesheets",
70
+ "google-analytics": "googleAnalytics",
71
+ "google-maps": "googlemaps",
72
+ "microsoft-teams": "microsoftTeams",
73
+ "one-drive": "onedrive",
74
+ };
75
+ // Own keys only: a name like `constructor` must not match Object.prototype.
76
+ const hasOwn = (record, key) => Object.prototype.hasOwnProperty.call(record, key);
77
+ export const normalizeIntegrationServiceType = (type) => hasOwn(LEGACY_SERVICE_TYPES, type) ? LEGACY_SERVICE_TYPES[type] : type;
78
+ export const isCredentialServiceType = (type) => hasOwn(FIELDS_BY_TYPE, type);
79
+ /**
80
+ * The entries a source dir can name, by name: its own, then the workspace's under
81
+ * keys it has not taken. An entry whose type the runtime would not serve is dropped.
82
+ */
83
+ export const gettableIntegrations = (rootIntegrations, appIntegrations) => {
84
+ const own = appIntegrations ?? {};
85
+ const workspace = rootIntegrations ?? {};
86
+ const visible = [];
87
+ for (const [name, entry] of [
88
+ ...Object.entries(own),
89
+ ...Object.entries(workspace).filter(([name]) => !hasOwn(own, name)),
90
+ ]) {
91
+ if (typeof entry?.type !== "string")
92
+ continue;
93
+ const serviceType = normalizeIntegrationServiceType(entry.type);
94
+ if (isCredentialServiceType(serviceType))
95
+ visible.push({ name, serviceType });
96
+ }
97
+ return visible.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
98
+ };
@@ -0,0 +1 @@
1
+ export {};