zitejs 0.9.119 → 0.9.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/auth/config.d.ts +3 -0
- package/dist/cjs/auth/config.js +7 -0
- package/dist/cjs/auth/index.js +30 -4
- package/dist/cjs/auth/useAuth.test.d.ts +1 -0
- package/dist/cjs/auth/useAuth.test.js +82 -0
- package/dist/cjs/backend/index.d.ts +30 -3
- package/dist/cjs/backend/index.js +2 -2
- package/dist/cjs/bundle/index.d.ts +1 -1
- package/dist/cjs/bundle/index.js +6 -2
- package/dist/cjs/check/index.js +5 -13
- package/dist/cjs/cli.js +2 -2
- package/dist/cjs/dev/index.js +31 -44
- package/dist/cjs/meta/index.d.ts +14 -0
- package/dist/cjs/meta/index.js +12 -0
- package/dist/cjs/notifications/index.d.ts +25 -0
- package/dist/cjs/notifications/index.js +12 -0
- package/dist/cjs/sourceRoots.d.ts +19 -0
- package/dist/cjs/sourceRoots.js +33 -0
- package/dist/cjs/sync/lib.d.ts +6 -0
- package/dist/cjs/sync/lib.js +13 -0
- package/dist/cjs/sync/lib.test.js +28 -0
- package/dist/cjs/upload/index.js +19 -0
- package/dist/cjs/upload/index.test.js +30 -3
- package/dist/cjs/vite/domTagNames.d.ts +1 -0
- package/dist/cjs/vite/domTagNames.js +257 -0
- package/dist/cjs/vite/index.js +20 -11
- package/dist/cjs/vite/index.test.d.ts +1 -0
- package/dist/cjs/vite/index.test.js +53 -0
- package/dist/esm/auth/config.d.ts +3 -0
- package/dist/esm/auth/config.js +6 -0
- package/dist/esm/auth/index.js +28 -2
- package/dist/esm/auth/useAuth.test.d.ts +1 -0
- package/dist/esm/auth/useAuth.test.js +80 -0
- package/dist/esm/backend/index.d.ts +30 -3
- package/dist/esm/backend/index.js +2 -2
- package/dist/esm/bundle/index.d.ts +1 -1
- package/dist/esm/bundle/index.js +6 -2
- package/dist/esm/check/index.js +6 -14
- package/dist/esm/cli.js +2 -2
- package/dist/esm/dev/index.js +32 -45
- package/dist/esm/meta/index.d.ts +14 -0
- package/dist/esm/meta/index.js +8 -0
- package/dist/esm/notifications/index.d.ts +25 -0
- package/dist/esm/notifications/index.js +8 -0
- package/dist/esm/sourceRoots.d.ts +19 -0
- package/dist/esm/sourceRoots.js +28 -0
- package/dist/esm/sync/lib.d.ts +6 -0
- package/dist/esm/sync/lib.js +12 -0
- package/dist/esm/sync/lib.test.js +29 -1
- package/dist/esm/upload/index.js +19 -0
- package/dist/esm/upload/index.test.js +30 -3
- package/dist/esm/vite/domTagNames.d.ts +1 -0
- package/dist/esm/vite/domTagNames.js +254 -0
- package/dist/esm/vite/index.js +20 -11
- package/dist/esm/vite/index.test.d.ts +1 -0
- package/dist/esm/vite/index.test.js +48 -0
- package/package.json +1 -1
|
@@ -3,6 +3,27 @@ export type { ZiteSchedule };
|
|
|
3
3
|
export type ZiteWebhook = {
|
|
4
4
|
paused?: boolean;
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* A provider trigger: what should fire the endpoint, declared as a literal.
|
|
8
|
+
* The platform registers it with the provider at publish, verifies each
|
|
9
|
+
* delivery and queues it; the endpoint only sees the payload. A `poll`
|
|
10
|
+
* trigger runs the endpoint on a cadence with `input.__poll.cursor` from the
|
|
11
|
+
* previous run, and the endpoint returns `{ cursor }` to advance it.
|
|
12
|
+
*/
|
|
13
|
+
export type ZiteTrigger = {
|
|
14
|
+
/**
|
|
15
|
+
* The connection's name in `zite.config.json`. Registered with the
|
|
16
|
+
* provider by `src/triggers/<endpointId>/subscribe.ts` and taken down
|
|
17
|
+
* by `unsubscribe.ts`, which the platform runs at publish and teardown.
|
|
18
|
+
*/
|
|
19
|
+
integration: string;
|
|
20
|
+
paused?: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
provider: "poll";
|
|
23
|
+
/** `<n>m` (1–59) or `<n>h` (1–23) */
|
|
24
|
+
every: string;
|
|
25
|
+
paused?: boolean;
|
|
26
|
+
};
|
|
6
27
|
export interface ZiteRequestContext {
|
|
7
28
|
user: {
|
|
8
29
|
id: string;
|
|
@@ -58,7 +79,7 @@ export type ZiteStreamInterface = {
|
|
|
58
79
|
write: (data: unknown) => Promise<void>;
|
|
59
80
|
forward: (asyncIterable: AsyncIterable<string>) => Promise<string>;
|
|
60
81
|
};
|
|
61
|
-
export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream extends boolean = false, TSchedule extends ZiteSchedule | undefined = undefined, TWebhook extends ZiteWebhook | undefined = undefined, TRawInput = TInput> {
|
|
82
|
+
export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream extends boolean = false, TSchedule extends ZiteSchedule | undefined = undefined, TWebhook extends ZiteWebhook | undefined = undefined, TRawInput = TInput, TTrigger extends ZiteTrigger | undefined = undefined> {
|
|
62
83
|
description?: string;
|
|
63
84
|
inputSchema?: SchemaLike<TInput, TRawInput>;
|
|
64
85
|
/**
|
|
@@ -82,6 +103,12 @@ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream ext
|
|
|
82
103
|
* `{ user: null }` for a webhook fire exactly as it does for a cron one.
|
|
83
104
|
*/
|
|
84
105
|
webhook?: TWebhook;
|
|
106
|
+
/**
|
|
107
|
+
* When set, the platform fires this endpoint from the named provider. Like
|
|
108
|
+
* `schedule` and `webhook`, this widens `context`: a provider delivery has
|
|
109
|
+
* no signed-in user.
|
|
110
|
+
*/
|
|
111
|
+
trigger?: TTrigger;
|
|
85
112
|
/**
|
|
86
113
|
* Set by {@link createEndpoint}, not by hand. The worker validates
|
|
87
114
|
* `inputSchema` for bundles built before this did, and skips ones that
|
|
@@ -90,7 +117,7 @@ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream ext
|
|
|
90
117
|
validatesInput?: boolean;
|
|
91
118
|
execute: (params: {
|
|
92
119
|
input: TInput;
|
|
93
|
-
context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : TWebhook extends ZiteWebhook ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;
|
|
120
|
+
context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : TWebhook extends ZiteWebhook ? ZiteRequestContext | ZiteScheduledContext : TTrigger extends ZiteTrigger ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;
|
|
94
121
|
} & (TStream extends true ? {
|
|
95
122
|
stream: ZiteStreamInterface;
|
|
96
123
|
} : {})) => Promise<TOutput> | TOutput;
|
|
@@ -102,4 +129,4 @@ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream ext
|
|
|
102
129
|
* field could be missing and the endpoint ran anyway. Parsing here is also what
|
|
103
130
|
* makes `TRawInput` -> `TInput` (defaults, transforms) true at runtime.
|
|
104
131
|
*/
|
|
105
|
-
export declare function createEndpoint<TInput = unknown, TOutput = unknown, TStream extends boolean = false, TSchedule extends ZiteSchedule | undefined = undefined, TWebhook extends ZiteWebhook | undefined = undefined, TRawInput = TInput>(config: EndpointConfig<TInput, TOutput, TStream, TSchedule, TWebhook, TRawInput>): EndpointConfig<TInput, TOutput, TStream, TSchedule, TWebhook, TRawInput>;
|
|
132
|
+
export declare function createEndpoint<TInput = unknown, TOutput = unknown, TStream extends boolean = false, TSchedule extends ZiteSchedule | undefined = undefined, TWebhook extends ZiteWebhook | undefined = undefined, TRawInput = TInput, TTrigger extends ZiteTrigger | undefined = undefined>(config: EndpointConfig<TInput, TOutput, TStream, TSchedule, TWebhook, TRawInput, TTrigger>): EndpointConfig<TInput, TOutput, TStream, TSchedule, TWebhook, TRawInput, TTrigger>;
|
|
@@ -27,7 +27,7 @@ export class ZiteError extends Error {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
/** Injected by the runner on a platform-fired run — see `isPlatformTriggeredInput`. */
|
|
30
|
-
const PLATFORM_TRIGGER_KEYS = ["__cron", "__webhook"];
|
|
30
|
+
const PLATFORM_TRIGGER_KEYS = ["__cron", "__webhook", "__poll"];
|
|
31
31
|
function isPlatformTriggeredInput(input) {
|
|
32
32
|
if (typeof input !== "object" || input === null)
|
|
33
33
|
return false;
|
|
@@ -55,7 +55,7 @@ function describeIssues(error) {
|
|
|
55
55
|
*/
|
|
56
56
|
export function createEndpoint(config) {
|
|
57
57
|
const { inputSchema, execute } = config;
|
|
58
|
-
const firesWithoutARequest = Boolean(config.schedule || config.webhook);
|
|
58
|
+
const firesWithoutARequest = Boolean(config.schedule || config.webhook || config.trigger);
|
|
59
59
|
// An `inputSchema` that isn't a validator stays inert, as it was.
|
|
60
60
|
if (!inputSchema || typeof inputSchema.parse !== "function")
|
|
61
61
|
return config;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
10
|
* npx zitejs bundle # bundle all endpoints
|
|
11
|
-
* npx zitejs bundle --app admin-panel # bundle endpoints for
|
|
11
|
+
* npx zitejs bundle --app admin-panel # bundle endpoints for one app or automation, by dir name
|
|
12
12
|
* npx zitejs bundle --script <path> # bundle a one-off script
|
|
13
13
|
*
|
|
14
14
|
* Output: JSON to stdout
|
package/dist/esm/bundle/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
10
|
* npx zitejs bundle # bundle all endpoints
|
|
11
|
-
* npx zitejs bundle --app admin-panel # bundle endpoints for
|
|
11
|
+
* npx zitejs bundle --app admin-panel # bundle endpoints for one app or automation, by dir name
|
|
12
12
|
* npx zitejs bundle --script <path> # bundle a one-off script
|
|
13
13
|
*
|
|
14
14
|
* Output: JSON to stdout
|
|
@@ -24,6 +24,7 @@ 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
28
|
// workerd's `nodejs_compat` does not provide these. Left external they bundle
|
|
28
29
|
// fine and then kill the worker at load time with `No such module`; excluded,
|
|
29
30
|
// esbuild fails the one endpoint with a resolvable error instead. Pinned by
|
|
@@ -578,7 +579,10 @@ export async function runBundle() {
|
|
|
578
579
|
const appFlag = args.indexOf('--app');
|
|
579
580
|
let baseDir = process.cwd();
|
|
580
581
|
if (appFlag !== -1 && args[appFlag + 1]) {
|
|
581
|
-
|
|
582
|
+
const name = args[appFlag + 1];
|
|
583
|
+
// A dir that exists under no root still resolves under `apps/`, so the
|
|
584
|
+
// error names the path a caller expects rather than "not found".
|
|
585
|
+
baseDir = path.resolve(baseDir, findSourceDir(name, baseDir)?.path ?? path.join('apps', name));
|
|
582
586
|
}
|
|
583
587
|
// If explicit endpoint names passed, use those; otherwise find all.
|
|
584
588
|
// Skipping `--app`'s VALUE as well as the flag: it is a positional arg, so
|
package/dist/esm/check/index.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
|
|
5
|
-
const appsDir = 'apps';
|
|
6
|
-
if (!existsSync(appsDir))
|
|
7
|
-
return [];
|
|
8
|
-
return readdirSync(appsDir, { withFileTypes: true })
|
|
9
|
-
.filter(d => d.isDirectory())
|
|
10
|
-
.map(d => d.name);
|
|
11
|
-
}
|
|
4
|
+
import { listSourceDirs } from '../sourceRoots.js';
|
|
12
5
|
/**
|
|
13
6
|
* An argument array, not a command string, so no shell is involved.
|
|
14
7
|
*
|
|
@@ -63,15 +56,14 @@ function bundleFailures(output) {
|
|
|
63
56
|
return failures;
|
|
64
57
|
}
|
|
65
58
|
export async function runCheck() {
|
|
66
|
-
const appDirs =
|
|
59
|
+
const appDirs = listSourceDirs();
|
|
67
60
|
if (appDirs.length === 0) {
|
|
68
|
-
console.error('No apps found
|
|
61
|
+
console.error('No apps or automations found under apps/ or automations/.');
|
|
69
62
|
process.exit(1);
|
|
70
63
|
}
|
|
71
64
|
let allPassed = true;
|
|
72
|
-
for (const app of appDirs) {
|
|
73
|
-
|
|
74
|
-
console.log(`\n── ${app} ──`);
|
|
65
|
+
for (const { dir: app, path: appPath } of appDirs) {
|
|
66
|
+
console.log(`\n── ${appPath} ──`);
|
|
75
67
|
// Only tsconfig.app.json compiles anything. The app's tsconfig.json is a
|
|
76
68
|
// solution-style config — `"files": []` plus a reference — so
|
|
77
69
|
// `tsc --noEmit -p tsconfig.json` type-checks zero files and exits 0. It
|
package/dist/esm/cli.js
CHANGED
|
@@ -38,8 +38,8 @@ async function main() {
|
|
|
38
38
|
console.error('Commands:');
|
|
39
39
|
console.error(' sync Generate .zite/db.ts from database schema (single-app)');
|
|
40
40
|
console.error(' dev Run sync then watch for changes (like npx convex dev)');
|
|
41
|
-
console.error(' generate
|
|
42
|
-
console.error(' check Run tsc --noEmit and vite build for
|
|
41
|
+
console.error(' generate Regenerate every app and automation .zite/ (monorepo)');
|
|
42
|
+
console.error(' check Run tsc --noEmit (and vite build where there is one) for every app and automation');
|
|
43
43
|
console.error(' bundle Bundle src/api/*.ts endpoints for cloudflare-lambda');
|
|
44
44
|
process.exit(1);
|
|
45
45
|
}
|
package/dist/esm/dev/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { watch } from "fs";
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
+
import { listSourceDirs } from "../sourceRoots.js";
|
|
4
5
|
import { runSync } from "../sync/index.js";
|
|
5
|
-
import { generateDbTs, generateApiTs, generateBackendWrapperTs, generateAirtableTs, generateEmailSdk, } from "../sync/lib.js";
|
|
6
|
+
import { generateDbTs, generateApiTs, generateBackendWrapperTs, generateAirtableTs, findEmailIntegrationId, generateEmailSdk, } from "../sync/lib.js";
|
|
6
7
|
const debounceTimers = new Map();
|
|
7
8
|
function debounce(key, fn, ms) {
|
|
8
9
|
const existing = debounceTimers.get(key);
|
|
@@ -10,17 +11,9 @@ function debounce(key, fn, ms) {
|
|
|
10
11
|
clearTimeout(existing);
|
|
11
12
|
debounceTimers.set(key, setTimeout(fn, ms));
|
|
12
13
|
}
|
|
13
|
-
function findAppDirs() {
|
|
14
|
-
const appsDir = "apps";
|
|
15
|
-
if (!existsSync(appsDir))
|
|
16
|
-
return [];
|
|
17
|
-
return readdirSync(appsDir, { withFileTypes: true })
|
|
18
|
-
.filter((d) => d.isDirectory())
|
|
19
|
-
.map((d) => d.name);
|
|
20
|
-
}
|
|
21
14
|
function getFlowId(appDir) {
|
|
22
15
|
try {
|
|
23
|
-
const configPath = join(
|
|
16
|
+
const configPath = join(appDir.path, "zite.config.json");
|
|
24
17
|
if (existsSync(configPath)) {
|
|
25
18
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
26
19
|
return config.id;
|
|
@@ -29,28 +22,22 @@ function getFlowId(appDir) {
|
|
|
29
22
|
catch { }
|
|
30
23
|
return undefined;
|
|
31
24
|
}
|
|
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) {
|
|
25
|
+
const readJsonFile = (path) => {
|
|
37
26
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
}
|
|
27
|
+
return existsSync(path)
|
|
28
|
+
? JSON.parse(readFileSync(path, "utf-8"))
|
|
29
|
+
: undefined;
|
|
47
30
|
}
|
|
48
|
-
catch {
|
|
49
|
-
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function getEmailIntegrationId(appDir) {
|
|
36
|
+
return findEmailIntegrationId(readJsonFile(join(appDir.path, "zite.config.json")), readJsonFile("zite.config.json"));
|
|
50
37
|
}
|
|
51
38
|
function getDeclaredEnvVarNames(appDir) {
|
|
52
39
|
try {
|
|
53
|
-
const configPath = join(
|
|
40
|
+
const configPath = join(appDir.path, "zite.config.json");
|
|
54
41
|
if (!existsSync(configPath))
|
|
55
42
|
return [];
|
|
56
43
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
@@ -61,7 +48,7 @@ function getDeclaredEnvVarNames(appDir) {
|
|
|
61
48
|
}
|
|
62
49
|
}
|
|
63
50
|
function regenerateAppTypedWrappers(appDir) {
|
|
64
|
-
const outDir = join(
|
|
51
|
+
const outDir = join(appDir.path, ".zite");
|
|
65
52
|
mkdirSync(outDir, { recursive: true });
|
|
66
53
|
// user.ts and auth.ts no longer generated — User type is fixed in zitejs/auth
|
|
67
54
|
// Email integration: generate the Email client at .zite/integrations/email.ts.
|
|
@@ -76,7 +63,7 @@ function regenerateAppTypedWrappers(appDir) {
|
|
|
76
63
|
writeFileSync(join(outDir, "backend.ts"), generateBackendWrapperTs(getDeclaredEnvVarNames(appDir)));
|
|
77
64
|
}
|
|
78
65
|
function regenerateAppApiTs(appDir) {
|
|
79
|
-
const apiDir = join(
|
|
66
|
+
const apiDir = join(appDir.path, "src", "api");
|
|
80
67
|
if (!existsSync(apiDir))
|
|
81
68
|
return;
|
|
82
69
|
// Sorted because `readdir` order is unspecified — it is whatever the
|
|
@@ -96,14 +83,14 @@ function regenerateAppApiTs(appDir) {
|
|
|
96
83
|
}));
|
|
97
84
|
const content = generateApiTs(endpointFiles);
|
|
98
85
|
if (content) {
|
|
99
|
-
const outDir = join(
|
|
86
|
+
const outDir = join(appDir.path, ".zite");
|
|
100
87
|
mkdirSync(outDir, { recursive: true });
|
|
101
88
|
writeFileSync(join(outDir, "api.ts"), content);
|
|
102
|
-
console.log(`Updated
|
|
89
|
+
console.log(`Updated ${appDir.path}/.zite/api.ts`);
|
|
103
90
|
}
|
|
104
91
|
}
|
|
105
92
|
function regenerateAppAirtableSdk(appDir) {
|
|
106
|
-
const lockPath = join(
|
|
93
|
+
const lockPath = join(appDir.path, "zite.lock");
|
|
107
94
|
console.log(`[airtable-sdk] Checking ${lockPath} exists: ${existsSync(lockPath)}`);
|
|
108
95
|
if (!existsSync(lockPath))
|
|
109
96
|
return;
|
|
@@ -124,15 +111,15 @@ function regenerateAppAirtableSdk(appDir) {
|
|
|
124
111
|
};
|
|
125
112
|
const content = generateAirtableTs(airtableLock);
|
|
126
113
|
if (content) {
|
|
127
|
-
const outDir = join(
|
|
114
|
+
const outDir = join(appDir.path, ".zite", "integrations");
|
|
128
115
|
mkdirSync(outDir, { recursive: true });
|
|
129
116
|
writeFileSync(join(outDir, "airtable.ts"), content);
|
|
130
|
-
console.log(`Updated
|
|
117
|
+
console.log(`Updated ${appDir.path}/.zite/integrations/airtable.ts`);
|
|
131
118
|
}
|
|
132
119
|
}
|
|
133
120
|
}
|
|
134
121
|
catch (err) {
|
|
135
|
-
console.error(`[airtable-sdk] Error processing lock for ${appDir}:`, err);
|
|
122
|
+
console.error(`[airtable-sdk] Error processing lock for ${appDir.path}:`, err);
|
|
136
123
|
}
|
|
137
124
|
}
|
|
138
125
|
export async function runGenerate() {
|
|
@@ -156,8 +143,8 @@ export async function runGenerate() {
|
|
|
156
143
|
catch (err) {
|
|
157
144
|
console.warn("DB SDK generation failed:", err instanceof Error ? err.message : err);
|
|
158
145
|
}
|
|
159
|
-
// 2. Find
|
|
160
|
-
const appDirs =
|
|
146
|
+
// 2. Find every app and automation and regenerate their .zite/ files
|
|
147
|
+
const appDirs = listSourceDirs();
|
|
161
148
|
for (const app of appDirs) {
|
|
162
149
|
regenerateAppApiTs(app);
|
|
163
150
|
regenerateAppTypedWrappers(app);
|
|
@@ -177,33 +164,33 @@ export async function runDev() {
|
|
|
177
164
|
console.warn("Initial sync failed (continuing with watcher):", err instanceof Error ? err.message : err);
|
|
178
165
|
}
|
|
179
166
|
console.log("");
|
|
180
|
-
// 2. Find
|
|
181
|
-
const appDirs =
|
|
167
|
+
// 2. Find every app and automation and regenerate their .zite/ files
|
|
168
|
+
const appDirs = listSourceDirs();
|
|
182
169
|
for (const app of appDirs) {
|
|
183
170
|
regenerateAppApiTs(app);
|
|
184
171
|
regenerateAppTypedWrappers(app);
|
|
185
172
|
}
|
|
186
|
-
// 3. Watch each
|
|
173
|
+
// 3. Watch each dir's src/api/ for endpoint changes
|
|
187
174
|
let watchingAny = false;
|
|
188
175
|
for (const app of appDirs) {
|
|
189
|
-
const apiDir = join(
|
|
176
|
+
const apiDir = join(app.path, "src", "api");
|
|
190
177
|
if (!existsSync(apiDir))
|
|
191
178
|
continue;
|
|
192
179
|
watchingAny = true;
|
|
193
|
-
console.log(`Watching
|
|
180
|
+
console.log(`Watching ${app.path}/src/api/ for endpoint changes...`);
|
|
194
181
|
watch(apiDir, { recursive: true }, (_event, filename) => {
|
|
195
182
|
if (!filename)
|
|
196
183
|
return;
|
|
197
184
|
if (!filename.endsWith(".ts") && !filename.endsWith(".js"))
|
|
198
185
|
return;
|
|
199
|
-
debounce(app, () => {
|
|
200
|
-
console.log(`Endpoint changed in ${app}: ${filename}`);
|
|
186
|
+
debounce(app.path, () => {
|
|
187
|
+
console.log(`Endpoint changed in ${app.path}: ${filename}`);
|
|
201
188
|
regenerateAppApiTs(app);
|
|
202
189
|
}, 200);
|
|
203
190
|
});
|
|
204
191
|
}
|
|
205
192
|
if (!watchingAny) {
|
|
206
|
-
console.log("No apps with src/api/ found.");
|
|
193
|
+
console.log("No apps or automations with src/api/ found.");
|
|
207
194
|
}
|
|
208
195
|
// 4. Watch zite.schema.json for schema drift
|
|
209
196
|
if (existsSync("zite.schema.json")) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type ZiteProjectUser = {
|
|
2
|
+
uuid: string;
|
|
3
|
+
firstName: string | null;
|
|
4
|
+
lastName: string | null;
|
|
5
|
+
email: string;
|
|
6
|
+
profilePictureUrl: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type MetaListUsersResult = {
|
|
9
|
+
users: ZiteProjectUser[];
|
|
10
|
+
};
|
|
11
|
+
export declare class ZiteMeta {
|
|
12
|
+
static listUsers(): Promise<MetaListUsersResult>;
|
|
13
|
+
}
|
|
14
|
+
export declare const Meta: typeof ZiteMeta;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface NotificationLink {
|
|
2
|
+
path?: string;
|
|
3
|
+
params?: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
export type NotificationsCreateParams = {
|
|
6
|
+
recipients: string[];
|
|
7
|
+
title: string;
|
|
8
|
+
body?: string;
|
|
9
|
+
link?: NotificationLink;
|
|
10
|
+
path?: string;
|
|
11
|
+
params?: Record<string, string>;
|
|
12
|
+
payload?: Record<string, unknown>;
|
|
13
|
+
idempotencyKey?: string;
|
|
14
|
+
};
|
|
15
|
+
export type NotificationsCreateResult = {
|
|
16
|
+
created: number;
|
|
17
|
+
} | {
|
|
18
|
+
created: 0;
|
|
19
|
+
preview: true;
|
|
20
|
+
wouldCreate: number;
|
|
21
|
+
};
|
|
22
|
+
export declare class ZiteNotifications {
|
|
23
|
+
static create(params: NotificationsCreateParams): Promise<NotificationsCreateResult>;
|
|
24
|
+
}
|
|
25
|
+
export declare const Notifications: typeof ZiteNotifications;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getSdkCall } from '../internal/sdkCall.js';
|
|
2
|
+
const NOTIFICATIONS_SDK_INTEGRATION_ID = '__notifications__';
|
|
3
|
+
export class ZiteNotifications {
|
|
4
|
+
static create(params) {
|
|
5
|
+
return getSdkCall()(NOTIFICATIONS_SDK_INTEGRATION_ID, 'ZiteNotifications', 'create', params);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export const Notifications = ZiteNotifications;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The repo-root directories a project's own source lives under: `apps/` for
|
|
3
|
+
* apps and `automations/` for automations (endpoints with no frontend). The
|
|
4
|
+
* backend keeps the same list; a dir name is unique across both roots, so a
|
|
5
|
+
* bare name still identifies one dir.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SOURCE_ROOTS: readonly ["apps", "automations"];
|
|
8
|
+
export type SourceRoot = (typeof SOURCE_ROOTS)[number];
|
|
9
|
+
export interface SourceDir {
|
|
10
|
+
root: SourceRoot;
|
|
11
|
+
/** The bare directory name. */
|
|
12
|
+
dir: string;
|
|
13
|
+
/** `<root>/<dir>`, relative to the repo root. */
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
/** Every source dir under every root, in root order then name order. */
|
|
17
|
+
export declare function listSourceDirs(cwd?: string): SourceDir[];
|
|
18
|
+
/** The source dir with this bare name, under whichever root holds it. */
|
|
19
|
+
export declare function findSourceDir(name: string, cwd?: string): SourceDir | undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* The repo-root directories a project's own source lives under: `apps/` for
|
|
5
|
+
* apps and `automations/` for automations (endpoints with no frontend). The
|
|
6
|
+
* backend keeps the same list; a dir name is unique across both roots, so a
|
|
7
|
+
* bare name still identifies one dir.
|
|
8
|
+
*/
|
|
9
|
+
export const SOURCE_ROOTS = ["apps", "automations"];
|
|
10
|
+
/** Every source dir under every root, in root order then name order. */
|
|
11
|
+
export function listSourceDirs(cwd = ".") {
|
|
12
|
+
const dirs = [];
|
|
13
|
+
for (const root of SOURCE_ROOTS) {
|
|
14
|
+
const rootPath = join(cwd, root);
|
|
15
|
+
if (!existsSync(rootPath))
|
|
16
|
+
continue;
|
|
17
|
+
for (const entry of readdirSync(rootPath, { withFileTypes: true })) {
|
|
18
|
+
if (!entry.isDirectory())
|
|
19
|
+
continue;
|
|
20
|
+
dirs.push({ root, dir: entry.name, path: join(root, entry.name) });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return dirs;
|
|
24
|
+
}
|
|
25
|
+
/** The source dir with this bare name, under whichever root holds it. */
|
|
26
|
+
export function findSourceDir(name, cwd = ".") {
|
|
27
|
+
return listSourceDirs(cwd).find((d) => d.dir === name);
|
|
28
|
+
}
|
package/dist/esm/sync/lib.d.ts
CHANGED
|
@@ -82,6 +82,12 @@ export declare function normalizeAirtableLockNames(lock: AirtableLock): {
|
|
|
82
82
|
};
|
|
83
83
|
export declare function generateAirtableTs(inputLock: AirtableLock): string | null;
|
|
84
84
|
export declare function generateBackendWrapperTs(envVarNames?: string[]): string;
|
|
85
|
+
/**
|
|
86
|
+
* The key of the email integration an app sends through, which is the
|
|
87
|
+
* integrationId the runtime SDK bridge dispatches on. The app's own entry wins;
|
|
88
|
+
* otherwise it is the workspace email the app has `integrationSettings` for.
|
|
89
|
+
*/
|
|
90
|
+
export declare function findEmailIntegrationId(appConfig: unknown, workspaceConfig: unknown): string | undefined;
|
|
85
91
|
/**
|
|
86
92
|
* Generate `.zite/integrations/email.ts` — the `Email` client for an app with
|
|
87
93
|
* an email integration connected. Mirrors the airtable SDK generation: a thin
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -1333,6 +1333,18 @@ export function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1333
1333
|
"",
|
|
1334
1334
|
].join("\n");
|
|
1335
1335
|
}
|
|
1336
|
+
/**
|
|
1337
|
+
* The key of the email integration an app sends through, which is the
|
|
1338
|
+
* integrationId the runtime SDK bridge dispatches on. The app's own entry wins;
|
|
1339
|
+
* otherwise it is the workspace email the app has `integrationSettings` for.
|
|
1340
|
+
*/
|
|
1341
|
+
export function findEmailIntegrationId(appConfig, workspaceConfig) {
|
|
1342
|
+
const app = (appConfig ?? {});
|
|
1343
|
+
const workspace = (workspaceConfig ?? {});
|
|
1344
|
+
const isEmail = (config, id) => config.integrations?.[id]?.type === "email";
|
|
1345
|
+
return (Object.keys(app.integrations ?? {}).find((id) => isEmail(app, id)) ??
|
|
1346
|
+
Object.keys(app.integrationSettings ?? {}).find((id) => isEmail(workspace, id)));
|
|
1347
|
+
}
|
|
1336
1348
|
/**
|
|
1337
1349
|
* Generate `.zite/integrations/email.ts` — the `Email` client for an app with
|
|
1338
1350
|
* an email integration connected. Mirrors the airtable SDK generation: a thin
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import ts from 'typescript';
|
|
3
|
-
import { generateAirtableTs, generateApiTs, generateBackendWrapperTs, generateDbTs, generateEmailSdk, } from './lib.js';
|
|
3
|
+
import { findEmailIntegrationId, generateAirtableTs, generateApiTs, generateBackendWrapperTs, generateDbTs, generateEmailSdk, } from './lib.js';
|
|
4
4
|
/**
|
|
5
5
|
* Syntax errors in the emitted source. `.zite/db.ts` sits at the repo root and
|
|
6
6
|
* every app imports it, so anything unparseable here fails typecheck for the
|
|
@@ -380,3 +380,31 @@ describe('generateBackendWrapperTs', () => {
|
|
|
380
380
|
expect(output).toContain('ZiteError');
|
|
381
381
|
});
|
|
382
382
|
});
|
|
383
|
+
describe('findEmailIntegrationId', () => {
|
|
384
|
+
const workspace = {
|
|
385
|
+
integrations: {
|
|
386
|
+
slack: { type: 'slack' },
|
|
387
|
+
'team-email': { type: 'email' },
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
it('finds the app\'s own email entry', () => {
|
|
391
|
+
expect(findEmailIntegrationId({ integrations: { mail: { type: 'email' } } }, workspace)).toBe('mail');
|
|
392
|
+
});
|
|
393
|
+
it('finds the workspace email the app has settings for', () => {
|
|
394
|
+
expect(findEmailIntegrationId({ integrationSettings: { slack: {}, 'team-email': { fromName: 'Ops' } } }, workspace)).toBe('team-email');
|
|
395
|
+
});
|
|
396
|
+
// An app that never opted in generates no client, so `zitejs/email` stays
|
|
397
|
+
// unresolved there instead of sending as a sender it never chose.
|
|
398
|
+
it('ignores a workspace email the app has no settings for', () => {
|
|
399
|
+
expect(findEmailIntegrationId({}, workspace)).toBeUndefined();
|
|
400
|
+
});
|
|
401
|
+
it('prefers the app\'s own entry over a workspace one', () => {
|
|
402
|
+
expect(findEmailIntegrationId({
|
|
403
|
+
integrations: { mail: { type: 'email' } },
|
|
404
|
+
integrationSettings: { 'team-email': {} },
|
|
405
|
+
}, workspace)).toBe('mail');
|
|
406
|
+
});
|
|
407
|
+
it('tolerates missing configs', () => {
|
|
408
|
+
expect(findEmailIntegrationId(undefined, undefined)).toBeUndefined();
|
|
409
|
+
});
|
|
410
|
+
});
|
package/dist/esm/upload/index.js
CHANGED
|
@@ -106,6 +106,25 @@ async function performUpload(data, filename) {
|
|
|
106
106
|
if (!putRes.ok) {
|
|
107
107
|
throw new FileUploadError('Upload failed');
|
|
108
108
|
}
|
|
109
|
+
// Gateways that predate the storage ledger omit the token. The file is
|
|
110
|
+
// already at fileUrl, so those uploads still succeed.
|
|
111
|
+
if (session.completionToken) {
|
|
112
|
+
const completeRes = await fetch(getApiUrl() +
|
|
113
|
+
'/v1/zite/public/' +
|
|
114
|
+
flowId +
|
|
115
|
+
'/complete-upload?mode=' +
|
|
116
|
+
mode, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: {
|
|
119
|
+
'Content-Type': 'application/json',
|
|
120
|
+
...authHeaders(),
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify({ completionToken: session.completionToken }),
|
|
123
|
+
});
|
|
124
|
+
if (!completeRes.ok) {
|
|
125
|
+
throw new FileUploadError(await readErrorMessage(completeRes, 'Upload failed'));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
109
128
|
return session.fileUrl;
|
|
110
129
|
}
|
|
111
130
|
export async function uploadFile({ data, filename, }) {
|
|
@@ -23,7 +23,7 @@ describe('toUploadBlob', () => {
|
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
describe('uploadFile', () => {
|
|
26
|
-
it('requests a session
|
|
26
|
+
it('requests a session, PUTs the bytes, then completes the upload', async () => {
|
|
27
27
|
const file = new File(['hello-world'], 'hello.txt', { type: 'text/plain' });
|
|
28
28
|
fetchMock
|
|
29
29
|
.mockResolvedValueOnce({
|
|
@@ -33,12 +33,14 @@ describe('uploadFile', () => {
|
|
|
33
33
|
presignedUrl: 'https://s3.example.com/put',
|
|
34
34
|
fileUrl: 'https://uploads.zite.com/orgid-1/zite-uploads/app_123/hello.txt',
|
|
35
35
|
contentType: 'text/plain',
|
|
36
|
+
completionToken: 'signed-token',
|
|
36
37
|
}),
|
|
37
38
|
})
|
|
38
|
-
.mockResolvedValueOnce({ ok: true })
|
|
39
|
+
.mockResolvedValueOnce({ ok: true })
|
|
40
|
+
.mockResolvedValueOnce({ ok: true, json: async () => ({ success: true }) });
|
|
39
41
|
const result = await uploadFile({ data: file, filename: 'hello.txt' });
|
|
40
42
|
expect(result.fileUrl).toContain('hello.txt');
|
|
41
|
-
expect(fetchMock).toHaveBeenCalledTimes(
|
|
43
|
+
expect(fetchMock).toHaveBeenCalledTimes(3);
|
|
42
44
|
expect(fetchMock.mock.calls[0][0]).toBe('https://api.example.com/v1/zite/public/app_123/upload-session?mode=live');
|
|
43
45
|
const sessionInit = fetchMock.mock.calls[0][1];
|
|
44
46
|
expect(JSON.parse(sessionInit.body)).toEqual({
|
|
@@ -48,6 +50,31 @@ describe('uploadFile', () => {
|
|
|
48
50
|
});
|
|
49
51
|
expect(fetchMock.mock.calls[1][0]).toBe('https://s3.example.com/put');
|
|
50
52
|
expect(fetchMock.mock.calls[1][1].method).toBe('PUT');
|
|
53
|
+
expect(fetchMock.mock.calls[2][0]).toBe('https://api.example.com/v1/zite/public/app_123/complete-upload?mode=live');
|
|
54
|
+
const completeInit = fetchMock.mock.calls[2][1];
|
|
55
|
+
expect(completeInit.method).toBe('POST');
|
|
56
|
+
expect(JSON.parse(completeInit.body)).toEqual({
|
|
57
|
+
completionToken: 'signed-token',
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it('skips completion when the gateway does not issue a token', async () => {
|
|
61
|
+
fetchMock
|
|
62
|
+
.mockResolvedValueOnce({
|
|
63
|
+
ok: true,
|
|
64
|
+
json: async () => ({
|
|
65
|
+
success: true,
|
|
66
|
+
presignedUrl: 'https://s3.example.com/put',
|
|
67
|
+
fileUrl: 'https://uploads.zite.com/orgid-1/zite-uploads/app_123/hello.txt',
|
|
68
|
+
contentType: 'text/plain',
|
|
69
|
+
}),
|
|
70
|
+
})
|
|
71
|
+
.mockResolvedValueOnce({ ok: true });
|
|
72
|
+
const result = await uploadFile({
|
|
73
|
+
data: new File(['hello-world'], 'hello.txt', { type: 'text/plain' }),
|
|
74
|
+
filename: 'hello.txt',
|
|
75
|
+
});
|
|
76
|
+
expect(result.fileUrl).toContain('hello.txt');
|
|
77
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
51
78
|
});
|
|
52
79
|
it('surfaces the plan-limit message from the session endpoint', async () => {
|
|
53
80
|
fetchMock.mockResolvedValueOnce({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const domTagNames: ReadonlySet<string>;
|