zitejs 0.9.94 → 0.9.95

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.
@@ -30,9 +30,17 @@ export declare const useSession: () => {
30
30
  query?: import("better-auth").SessionQueryParams;
31
31
  } | undefined) => Promise<void>;
32
32
  };
33
+ /**
34
+ * `loginWithRedirect` and `logout` are returned as well as exported. The
35
+ * pre-monorepo `useAuth()` returned them, so app code that destructures them
36
+ * from the hook is common — and returning them here means the legacy compat
37
+ * shim is the only thing that has to know that, not every migrated app.
38
+ */
33
39
  export declare function useAuth(): {
34
40
  user: User | null;
35
41
  isLoading: boolean;
42
+ loginWithRedirect: typeof loginWithRedirect;
43
+ logout: typeof logout;
36
44
  };
37
45
  export declare const signIn: {
38
46
  magicLink: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
@@ -239,17 +247,28 @@ export declare function updateProfile(data: {
239
247
  statusText: string;
240
248
  };
241
249
  }>;
242
- export type User = {
250
+ /**
251
+ * An `interface`, not a type alias, so a migrated app can widen it by
252
+ * declaration merging (`.zite/user-extensions.d.ts`). A legacy user-sync app's
253
+ * code reads arbitrary columns off the synced row; those can't be enumerated
254
+ * here, and a type alias can't be reopened to admit them.
255
+ *
256
+ * `firstName`/`lastName` are optional rather than `string | null`: the
257
+ * pre-monorepo User declared them optional, so `{ firstName?: string }` is the
258
+ * shape existing app code is written against. The producers normalize null to
259
+ * undefined — see `authTablesService` and the workflow-runner's sync branch.
260
+ */
261
+ export interface User {
243
262
  id: string;
244
263
  name: string;
245
- firstName: string | null;
246
- lastName: string | null;
264
+ firstName?: string;
265
+ lastName?: string;
247
266
  email: string;
248
267
  emailVerified: boolean;
249
268
  image: string | null;
250
269
  createdAt: Date;
251
270
  updatedAt: Date;
252
- };
271
+ }
253
272
  export interface ZiteAuthMethods {
254
273
  emailPassword?: boolean;
255
274
  magicLink?: boolean;
@@ -13,11 +13,19 @@ const authClient = createAuthClient({
13
13
  ],
14
14
  });
15
15
  export const useSession = authClient.useSession;
16
+ /**
17
+ * `loginWithRedirect` and `logout` are returned as well as exported. The
18
+ * pre-monorepo `useAuth()` returned them, so app code that destructures them
19
+ * from the hook is common — and returning them here means the legacy compat
20
+ * shim is the only thing that has to know that, not every migrated app.
21
+ */
16
22
  export function useAuth() {
17
23
  const { data, isPending } = useSession();
18
24
  return {
19
25
  user: data?.user ?? null,
20
26
  isLoading: isPending,
27
+ loginWithRedirect,
28
+ logout,
21
29
  };
22
30
  }
23
31
  export const signIn = authClient.signIn;
@@ -1,9 +1,10 @@
1
+ /** Must agree with `User` in `zitejs/auth` and `AuthUser` in `zitejs/runtime`. */
1
2
  export interface ZiteAuthUser {
2
3
  id: string;
3
4
  email: string;
4
5
  name: string;
5
- firstName: string;
6
- lastName: string;
6
+ firstName?: string;
7
+ lastName?: string;
7
8
  }
8
9
  import type { ZiteAuthMethods } from '../auth/index.js';
9
10
  export type { ZiteAuthMethods };
@@ -11,38 +11,62 @@ export interface ZiteRequestContext {
11
11
  };
12
12
  userId?: string;
13
13
  organizationId?: string;
14
- [key: string]: unknown;
15
- }
16
- export interface ZiteScheduledContext extends ZiteRequestContext {
17
- scheduledAt: string;
18
14
  }
15
+ /**
16
+ * Context on a scheduled (cron-fired) run. There is no authenticated user —
17
+ * the workflow-runner sends `{ user: null }` literally, because a cron trigger
18
+ * is an internal call with no session. Only endpoints that declare a `schedule`
19
+ * see this in their context union, so plain endpoints keep a non-null user.
20
+ */
21
+ export type ZiteScheduledContext = {
22
+ user: null;
23
+ };
24
+ export type ZiteErrorCode = "BAD_REQUEST" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMITED" | "INTERNAL_ERROR";
25
+ /**
26
+ * Throw to fail an endpoint with a specific HTTP status. The worker maps `code`
27
+ * through a fixed table (BAD_REQUEST -> 400, and so on); it reads `code` and
28
+ * nothing else, so an error carrying only a numeric `statusCode` fell through
29
+ * to a generic 500.
30
+ */
19
31
  export declare class ZiteError extends Error {
20
- statusCode: number;
21
- constructor(message: string, options?: {
22
- statusCode?: number;
32
+ code: ZiteErrorCode;
33
+ constructor(options: {
34
+ code: ZiteErrorCode;
35
+ message: string;
23
36
  });
24
37
  }
25
- type SchemaLike<T> = {
26
- _output: T;
27
- parse: (data: unknown) => T;
38
+ /**
39
+ * Structurally matches a zod schema. `TIn` is the schema's *input* type, which
40
+ * differs from its output wherever a field has a default or a transform —
41
+ * `z.number().default(10)` accepts `undefined` but produces `number`.
42
+ */
43
+ type SchemaLike<TOut, TIn = TOut> = {
44
+ _output: TOut;
45
+ _input: TIn;
46
+ parse: (data: unknown) => TOut;
28
47
  };
29
48
  export type ZiteStreamInterface = {
30
49
  write: (data: unknown) => Promise<void>;
31
50
  forward: (asyncIterable: AsyncIterable<string>) => Promise<string>;
32
51
  };
33
- export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream extends boolean = false> {
52
+ export interface EndpointConfig<TInput = unknown, TOutput = unknown, TStream extends boolean = false, TSchedule extends ZiteSchedule | undefined = undefined, TRawInput = TInput> {
34
53
  description?: string;
35
- inputSchema?: SchemaLike<TInput>;
54
+ inputSchema?: SchemaLike<TInput, TRawInput>;
36
55
  outputSchema?: SchemaLike<TOutput>;
37
56
  stream?: TStream;
38
57
  authenticated?: boolean;
39
- schedule?: ZiteSchedule;
58
+ /**
59
+ * When set, the endpoint also fires on this cron schedule. It stays
60
+ * request-callable — a schedule is an additional trigger, not a replacement.
61
+ * Declaring one widens `context` so `context.user` must be null-checked.
62
+ */
63
+ schedule?: TSchedule;
40
64
  webhook?: ZiteWebhook;
41
65
  execute: (params: {
42
66
  input: TInput;
43
- context: ZiteRequestContext | ZiteScheduledContext;
67
+ context: TSchedule extends ZiteSchedule ? ZiteRequestContext | ZiteScheduledContext : ZiteRequestContext;
44
68
  } & (TStream extends true ? {
45
69
  stream: ZiteStreamInterface;
46
70
  } : {})) => Promise<TOutput> | TOutput;
47
71
  }
48
- export declare function createEndpoint<TInput = unknown, TOutput = unknown, TStream extends boolean = false>(config: EndpointConfig<TInput, TOutput, TStream>): EndpointConfig<TInput, TOutput, TStream>;
72
+ export declare function createEndpoint<TInput = unknown, TOutput = unknown, TStream extends boolean = false, TSchedule extends ZiteSchedule | undefined = undefined, TRawInput = TInput>(config: EndpointConfig<TInput, TOutput, TStream, TSchedule, TRawInput>): EndpointConfig<TInput, TOutput, TStream, TSchedule, TRawInput>;
@@ -1,9 +1,15 @@
1
+ /**
2
+ * Throw to fail an endpoint with a specific HTTP status. The worker maps `code`
3
+ * through a fixed table (BAD_REQUEST -> 400, and so on); it reads `code` and
4
+ * nothing else, so an error carrying only a numeric `statusCode` fell through
5
+ * to a generic 500.
6
+ */
1
7
  export class ZiteError extends Error {
2
- statusCode;
3
- constructor(message, options) {
4
- super(message);
8
+ code;
9
+ constructor(options) {
10
+ super(options.message);
5
11
  this.name = "ZiteError";
6
- this.statusCode = options?.statusCode ?? 500;
12
+ this.code = options.code;
7
13
  }
8
14
  }
9
15
  export function createEndpoint(config) {
@@ -174,6 +174,19 @@ function getSdkImportSource(baseDir) {
174
174
  }
175
175
  return 'zite-integrations-backend-sdk';
176
176
  }
177
+ /**
178
+ * `zitejs/db`, `zitejs/api`, `zitejs/integrations` and `zitejs/email` are
179
+ * tsconfig aliases onto generated files, not real package subpaths — they are
180
+ * absent from the `exports` map, and `zitejs` isn't in PREBUNDLED_LIBS either.
181
+ * So marking one external doesn't defer resolution, it guarantees a module
182
+ * that fails to instantiate the first time the deployed endpoint is invoked,
183
+ * long after the build reported success. Fail here instead, where the message
184
+ * can name the file that's missing.
185
+ */
186
+ const unresolvedAlias = (specifier, expectedPath) => {
187
+ throw new Error(`Cannot resolve '${specifier}': expected generated file at ${expectedPath}. ` +
188
+ `Run \`npx zitejs generate\` before bundling.`);
189
+ };
177
190
  function createAliasPlugin(opts) {
178
191
  return {
179
192
  name: 'zite-alias',
@@ -198,7 +211,7 @@ function createAliasPlugin(opts) {
198
211
  if (sdkPath)
199
212
  return { path: sdkPath };
200
213
  }
201
- return { path: 'zitejs/backend', external: true };
214
+ return unresolvedAlias(args.path, `${opts.baseDir}/.zite/backend.ts`);
202
215
  });
203
216
  }
204
217
  // Resolve zitejs/db to .zite/db.ts — check app dir first, then
@@ -212,7 +225,18 @@ function createAliasPlugin(opts) {
212
225
  if (fs.existsSync(rootDbPath))
213
226
  return { path: rootDbPath };
214
227
  }
215
- return { path: 'zitejs/db', external: true };
228
+ return unresolvedAlias('zitejs/db', `${opts.baseDir}/.zite/db.ts or the project root's`);
229
+ });
230
+ // Resolve zitejs/api to the app's generated endpoint callers. Endpoints
231
+ // calling other endpoints is a supported shape — `createCaller` has a
232
+ // server-side branch that dials the runner directly.
233
+ build.onResolve({ filter: /^zitejs\/api$/ }, () => {
234
+ if (opts.baseDir) {
235
+ const apiPath = path.resolve(opts.baseDir, '.zite/api.ts');
236
+ if (fs.existsSync(apiPath))
237
+ return { path: apiPath };
238
+ }
239
+ return unresolvedAlias('zitejs/api', `${opts.baseDir}/.zite/api.ts`);
216
240
  });
217
241
  // Resolve zitejs/integrations to .zite/integrations/airtable.ts
218
242
  build.onResolve({ filter: /^zitejs\/integrations$/ }, () => {
@@ -221,7 +245,7 @@ function createAliasPlugin(opts) {
221
245
  if (fs.existsSync(intPath))
222
246
  return { path: intPath };
223
247
  }
224
- return { path: 'zitejs/integrations', external: true };
248
+ return unresolvedAlias('zitejs/integrations', `${opts.baseDir}/.zite/integrations/airtable.ts`);
225
249
  });
226
250
  // Resolve zitejs/email to .zite/integrations/email.ts
227
251
  build.onResolve({ filter: /^zitejs\/email$/ }, () => {
@@ -230,7 +254,7 @@ function createAliasPlugin(opts) {
230
254
  if (fs.existsSync(intPath))
231
255
  return { path: intPath };
232
256
  }
233
- return { path: 'zitejs/email', external: true };
257
+ return unresolvedAlias('zitejs/email', `${opts.baseDir}/.zite/integrations/email.ts`);
234
258
  });
235
259
  // zitejs/auth/server is types + an identity wrapper — inline a shim so
236
260
  // zite.auth.ts bundles without resolving the installed package.
@@ -23,6 +23,36 @@ function run(cmd, cwd) {
23
23
  return { ok: false, output: (e.stderr ?? '') + (e.stdout ?? '') };
24
24
  }
25
25
  }
26
+ /**
27
+ * `zitejs bundle` reports per-endpoint failures inside its JSON payload and
28
+ * still exits 0, so a non-empty `endpointErrors` is the only signal there is.
29
+ */
30
+ function bundleFailures(output) {
31
+ const line = output
32
+ .trim()
33
+ .split('\n')
34
+ .reverse()
35
+ .find(l => l.startsWith('{'));
36
+ if (!line)
37
+ return ['bundle produced no result'];
38
+ let result;
39
+ try {
40
+ result = JSON.parse(line);
41
+ }
42
+ catch {
43
+ return [`could not parse bundle output: ${line.slice(0, 200)}`];
44
+ }
45
+ const failures = [];
46
+ if (result.error)
47
+ failures.push(result.error);
48
+ for (const [name, err] of Object.entries(result.endpointErrors ?? {})) {
49
+ failures.push(`${name}: ${err}`);
50
+ }
51
+ if (result.authHooksError) {
52
+ failures.push(`zite.auth.ts: ${result.authHooksError}`);
53
+ }
54
+ return failures;
55
+ }
26
56
  export async function runCheck() {
27
57
  const appDirs = findAppDirs();
28
58
  if (appDirs.length === 0) {
@@ -33,11 +63,19 @@ export async function runCheck() {
33
63
  for (const app of appDirs) {
34
64
  const appPath = join('apps', app);
35
65
  console.log(`\n── ${app} ──`);
66
+ // Only tsconfig.app.json compiles anything. The app's tsconfig.json is a
67
+ // solution-style config — `"files": []` plus a reference — so
68
+ // `tsc --noEmit -p tsconfig.json` type-checks zero files and exits 0. It
69
+ // used to be the fallback, which meant an app missing tsconfig.app.json
70
+ // reported a passing typecheck it never ran.
36
71
  const tsconfigAppPath = join(appPath, 'tsconfig.app.json');
37
- const tsconfigPath = existsSync(tsconfigAppPath) ? tsconfigAppPath : join(appPath, 'tsconfig.json');
38
- if (existsSync(tsconfigPath)) {
72
+ if (!existsSync(tsconfigAppPath)) {
73
+ console.log(` tsc --noEmit ... ✗ (no ${tsconfigAppPath})`);
74
+ allPassed = false;
75
+ }
76
+ else {
39
77
  process.stdout.write(' tsc --noEmit ... ');
40
- const tsc = run(`npx tsc --noEmit -p ${tsconfigPath}`, '.');
78
+ const tsc = run(`npx tsc --noEmit -p ${tsconfigAppPath}`, '.');
41
79
  if (tsc.ok) {
42
80
  console.log('✓');
43
81
  }
@@ -47,6 +85,22 @@ export async function runCheck() {
47
85
  allPassed = false;
48
86
  }
49
87
  }
88
+ // Endpoints never reach vite — they are bundled separately for the lambda,
89
+ // against a different resolver. tsc and vite both pass on an endpoint whose
90
+ // `zitejs/*` alias has no generated file behind it; only the bundler knows.
91
+ if (existsSync(join(appPath, 'src', 'api'))) {
92
+ process.stdout.write(' bundle endpoints ... ');
93
+ const bundle = run(`npx zitejs bundle --app ${app}`, '.');
94
+ const failures = bundle.ok ? bundleFailures(bundle.output) : [bundle.output];
95
+ if (failures.length === 0) {
96
+ console.log('✓');
97
+ }
98
+ else {
99
+ console.log('✗');
100
+ console.log(failures.map(l => ` ${l}`).join('\n'));
101
+ allPassed = false;
102
+ }
103
+ }
50
104
  const viteConfig = join(appPath, 'vite.config.ts');
51
105
  if (existsSync(viteConfig)) {
52
106
  process.stdout.write(' vite build ... ');
@@ -1,11 +1,37 @@
1
1
  import type { NotificationsCreateParams, NotificationsCreateResult } from "../notifications/index.js";
2
- export interface TableFindAllOptions {
2
+ /**
3
+ * A comparison against one field. Mirrors base-runner's operator set
4
+ * (`LegacyWhereConditionOperators`); anything else in the object is ignored.
5
+ */
6
+ export type FilterCondition<V> = {
7
+ /** Substring match (text fields) or "has any of these" (linked records). */
8
+ contains?: V extends Array<infer E> ? E | E[] : V;
9
+ /** Not equal, or — against `null` — "is set". */
10
+ not?: V | null;
11
+ /** Empty array means "no filter", not "match nothing". */
12
+ in?: V extends Array<infer E> ? E[] : V[];
13
+ /** Empty array means "no filter", not "match everything". */
14
+ notIn?: V extends Array<infer E> ? E[] : V[];
15
+ lt?: V;
16
+ lte?: V;
17
+ gt?: V;
18
+ gte?: V;
19
+ };
20
+ /**
21
+ * Filters keyed by the record's own field names. Typed rather than `unknown`
22
+ * because an unrecognized key is not an error at runtime — it passes through
23
+ * the SDK-name-to-field-id transform verbatim, matches no column, and the query
24
+ * comes back **unfiltered**. A typo silently returns every row.
25
+ */
26
+ export type RecordFilters<T> = {
27
+ [K in keyof T]?: T[K] | FilterCondition<T[K]>;
28
+ };
29
+ export interface TableFindAllOptions<T = Record<string, unknown>> {
3
30
  limit?: number;
4
31
  offset?: number;
5
32
  sort?: unknown[];
6
- filter?: unknown;
7
- filters?: unknown;
8
- fields?: string[];
33
+ filters?: RecordFilters<T>;
34
+ fields?: Array<Extract<keyof T, string>>;
9
35
  }
10
36
  export interface BulkCreateResult<T> {
11
37
  success: boolean;
@@ -16,34 +42,40 @@ export interface UpdateResult<T> {
16
42
  fields: Partial<T>;
17
43
  }
18
44
  export interface DeleteResult {
45
+ success: true;
19
46
  id: string;
20
47
  }
21
- export interface TableClient<T> {
22
- findAll(params?: TableFindAllOptions): Promise<{
48
+ /**
49
+ * `TInput` is the record's *write* shape, which is not `T`: computed fields
50
+ * can't be written, and several field types accept looser input than they
51
+ * store. Defaults to `T` so a hand-written `TableClient<Foo>` still compiles.
52
+ */
53
+ export interface TableClient<T, TInput = T> {
54
+ findAll(params?: TableFindAllOptions<T>): Promise<{
23
55
  records: T[];
24
56
  hasMore: boolean;
25
57
  }>;
26
58
  findOne(params: {
27
59
  id?: string;
28
- filters?: unknown;
29
- fields?: string[];
60
+ filters?: RecordFilters<T>;
61
+ fields?: Array<Extract<keyof T, string>>;
30
62
  }): Promise<T | undefined>;
31
63
  create(params: {
32
- record: Partial<T>;
64
+ record: Partial<TInput>;
33
65
  }): Promise<T>;
34
66
  update(params: {
35
67
  id: string;
36
- record: Partial<T>;
68
+ record: Partial<TInput>;
37
69
  }): Promise<UpdateResult<T>>;
38
70
  delete(params: {
39
71
  id: string;
40
72
  }): Promise<DeleteResult>;
41
73
  bulkCreate(params: {
42
- records: Partial<T>[];
74
+ records: Partial<TInput>[];
43
75
  matchOn?: string[];
44
76
  }): Promise<BulkCreateResult<T>>;
45
77
  }
46
- export declare function createTableClient<T>(className: string): TableClient<T>;
78
+ export declare function createTableClient<T, TInput = T>(className: string): TableClient<T, TInput>;
47
79
  export interface SqlResult {
48
80
  rows: Record<string, unknown>[];
49
81
  columns: Array<{
@@ -57,15 +89,16 @@ export declare function createSqlClient(): (params: {
57
89
  query: string;
58
90
  params?: unknown[];
59
91
  }) => Promise<SqlResult>;
92
+ /** Must agree with `User` in `zitejs/auth` and `ZiteAuthUser` in `zitejs/auth-server` — the three used to disagree on whether the name fields were nullable or optional. */
60
93
  export interface AuthUser {
61
94
  id: string;
62
95
  name: string;
63
96
  email: string;
64
- firstName: string | null;
65
- lastName: string | null;
97
+ firstName?: string;
98
+ lastName?: string;
66
99
  image: string | null;
67
100
  }
68
- export type FindAllAuthUsersOptions = Omit<TableFindAllOptions, "fields"> & {
101
+ export type FindAllAuthUsersOptions = Omit<TableFindAllOptions<AuthUser>, "fields"> & {
69
102
  /** Restrict results to users belonging to any of these apps. */
70
103
  appIds?: string[];
71
104
  };
@@ -80,11 +113,13 @@ export interface AuthClient<T extends AuthUser = AuthUser> {
80
113
  updateUserProfile(userId: string, profile: UpdateAuthUserProfile): Promise<T>;
81
114
  }
82
115
  export declare function createAuthClient<T extends AuthUser = AuthUser>(): AuthClient<T>;
83
- export interface AirtableTableClient<T> {
116
+ /** `TInput` is the record's write shape — see `TableClient` for why it isn't `T`. */
117
+ export interface AirtableTableClient<T, TInput = T> {
84
118
  findAll(params?: {
119
+ /** An opaque cursor from a previous call — not a row count. */
85
120
  offset?: string;
86
121
  limit?: number;
87
- filters?: unknown;
122
+ filters?: RecordFilters<T>;
88
123
  }): Promise<{
89
124
  records: T[];
90
125
  offset: string | undefined;
@@ -92,17 +127,17 @@ export interface AirtableTableClient<T> {
92
127
  }>;
93
128
  findOne(params: {
94
129
  id?: string;
95
- filters?: unknown;
130
+ filters?: RecordFilters<T>;
96
131
  }): Promise<T | undefined>;
97
132
  create(params: {
98
- record: Partial<T>;
133
+ record: Partial<TInput>;
99
134
  }): Promise<T>;
100
135
  bulkCreate(params: {
101
- records: Partial<T>[];
136
+ records: Partial<TInput>[];
102
137
  }): Promise<T[]>;
103
138
  update(params: {
104
139
  id: string;
105
- record: Partial<T>;
140
+ record: Partial<TInput>;
106
141
  }): Promise<{
107
142
  id: string;
108
143
  fields: Partial<T>;
@@ -111,7 +146,7 @@ export interface AirtableTableClient<T> {
111
146
  id: string;
112
147
  }): Promise<DeleteResult>;
113
148
  }
114
- export declare function createAirtableClient<T>(integrationId: string, className: string, implicitParams: Record<string, unknown>): AirtableTableClient<T>;
149
+ export declare function createAirtableClient<T, TInput = T>(integrationId: string, className: string, implicitParams: Record<string, unknown>): AirtableTableClient<T, TInput>;
115
150
  /** A block of content in an email body. */
116
151
  export type EmailBlock = {
117
152
  type: "text";