zitejs 0.9.91 → 0.9.93

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.
@@ -42,9 +42,17 @@ function loginWithRedirect(opts) {
42
42
  // any non-ASCII in the path fails its regex and the destination is dropped —
43
43
  // but never inspects the path of an absolute one. Normalising here means a
44
44
  // caller can pass whichever form reads best without having to know that.
45
- const redirectUrl = new URL(opts?.redirectUrl ?? window.location.href, window.location.href).toString();
46
- window.location.href =
47
- '/auth/login?' + new URLSearchParams({ redirectUrl }).toString();
45
+ const target = new URL(opts?.redirectUrl ?? window.location.href, window.location.href);
46
+ // Say nothing when there's nothing to say. The sign-in page already returns
47
+ // people to the app root when it isn't told otherwise, so naming the root
48
+ // spells out the default — and puts an encoded copy of the app's own URL in
49
+ // the address bar of every logged-out visitor to the front page, which is the
50
+ // common case. Only pass a destination when it IS one.
51
+ const isRoot = target.pathname === '/' && target.search === '' && target.hash === '';
52
+ window.location.href = isRoot
53
+ ? '/auth/login'
54
+ : '/auth/login?' +
55
+ new URLSearchParams({ redirectUrl: target.toString() }).toString();
48
56
  }
49
57
  function logout(opts) {
50
58
  (0, exports.signOut)().then(() => {
@@ -83,6 +83,24 @@ function redirectParamOf(href) {
83
83
  });
84
84
  });
85
85
  (0, vitest_1.describe)('loginWithRedirect', () => {
86
+ (0, vitest_1.it)('says nothing when the destination is the app root', () => {
87
+ // The sign-in page already returns people to the root, so naming it would
88
+ // just put an encoded copy of the app's own URL in the address bar — and a
89
+ // logged-out visitor to the front page is the common case.
90
+ const location = stubLocation('https://app.zite.so/');
91
+ authExports.loginWithRedirect();
92
+ (0, vitest_1.expect)(location.href).toBe('/auth/login');
93
+ });
94
+ (0, vitest_1.it)('says nothing when an explicit redirectUrl is the app root', () => {
95
+ const location = stubLocation('https://app.zite.so/pricing');
96
+ authExports.loginWithRedirect({ redirectUrl: '/' });
97
+ (0, vitest_1.expect)(location.href).toBe('/auth/login');
98
+ });
99
+ (0, vitest_1.it)('still names a root path that carries a query or hash', () => {
100
+ const location = stubLocation('https://app.zite.so/?invite=abc');
101
+ authExports.loginWithRedirect();
102
+ (0, vitest_1.expect)(redirectParamOf(location.href)).toBe('https://app.zite.so/?invite=abc');
103
+ });
86
104
  (0, vitest_1.it)('captures the current URL when called with no arguments', () => {
87
105
  const location = stubLocation('https://app.zite.so/orders/123?tab=open');
88
106
  authExports.loginWithRedirect();
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Server-side auth hooks — the `ziteAuth({ hooks })` config an app
3
+ * default-exports from `zite.auth.ts` (app root). The platform bundles that
4
+ * file at build time and invokes the hooks at auth lifecycle moments
5
+ * (sign-up, sign-in, session checks). Hooks run like backend app code, so
6
+ * they can use the app's integrations and `zitejs/db`.
7
+ *
8
+ * All hooks fail open on error/timeout — a broken hook never locks users
9
+ * out. Gate hooks return allow / deny / defer; `verifySession` returns an
10
+ * active verdict and may enrich the app-visible user.
11
+ */
12
+ export interface ZiteAuthHookUser {
13
+ id: string;
14
+ email: string;
15
+ name: string;
16
+ }
17
+ /**
18
+ * allow admits past disabled/domain-restricted signup; deny blocks with an
19
+ * optional reason; anything else (empty object / no return) defers to the
20
+ * platform rules.
21
+ */
22
+ export type ZiteAuthGateDecision = {
23
+ allow: true;
24
+ } | {
25
+ deny: true;
26
+ reason?: string;
27
+ } | {
28
+ allow?: undefined;
29
+ deny?: undefined;
30
+ } | void;
31
+ /**
32
+ * active: false revokes the session; active: true keeps it, and `user`
33
+ * merges extra fields into the app-visible user.
34
+ */
35
+ export type ZiteAuthSessionVerdict = {
36
+ active: false;
37
+ reason?: string;
38
+ } | {
39
+ active: true;
40
+ user?: Record<string, unknown>;
41
+ } | {
42
+ active?: undefined;
43
+ } | void;
44
+ export interface ZiteAuthHooks {
45
+ beforeSignUp?(input: {
46
+ email: string;
47
+ firstName?: string;
48
+ lastName?: string;
49
+ }): Promise<ZiteAuthGateDecision> | ZiteAuthGateDecision;
50
+ beforeSignIn?(input: {
51
+ email: string;
52
+ }): Promise<ZiteAuthGateDecision> | ZiteAuthGateDecision;
53
+ afterSignUp?(input: {
54
+ user: ZiteAuthHookUser;
55
+ }): Promise<unknown> | unknown;
56
+ afterSignIn?(input: {
57
+ user: ZiteAuthHookUser;
58
+ }): Promise<unknown> | unknown;
59
+ verifySession?(input: {
60
+ user: ZiteAuthHookUser;
61
+ }): Promise<ZiteAuthSessionVerdict> | ZiteAuthSessionVerdict;
62
+ }
63
+ export interface ZiteAuthConfig {
64
+ hooks?: ZiteAuthHooks;
65
+ }
66
+ export declare const ziteAuth: (config: ZiteAuthConfig) => ZiteAuthConfig;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * Server-side auth hooks — the `ziteAuth({ hooks })` config an app
4
+ * default-exports from `zite.auth.ts` (app root). The platform bundles that
5
+ * file at build time and invokes the hooks at auth lifecycle moments
6
+ * (sign-up, sign-in, session checks). Hooks run like backend app code, so
7
+ * they can use the app's integrations and `zitejs/db`.
8
+ *
9
+ * All hooks fail open on error/timeout — a broken hook never locks users
10
+ * out. Gate hooks return allow / deny / defer; `verifySession` returns an
11
+ * active verdict and may enrich the app-visible user.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.ziteAuth = void 0;
15
+ const ziteAuth = (config) => config;
16
+ exports.ziteAuth = ziteAuth;
@@ -268,6 +268,16 @@ function createAliasPlugin(opts) {
268
268
  }
269
269
  return { path: 'zitejs/email', external: true };
270
270
  });
271
+ // zitejs/auth/server is types + an identity wrapper — inline a shim so
272
+ // zite.auth.ts bundles without resolving the installed package.
273
+ build.onResolve({ filter: /^zitejs\/auth\/server$/ }, () => ({
274
+ path: 'zitejs-auth-server-shim',
275
+ namespace: 'zite-virtual',
276
+ }));
277
+ build.onLoad({ filter: /.*/, namespace: 'zite-virtual' }, () => ({
278
+ contents: 'export const ziteAuth = (config) => config;',
279
+ loader: 'js',
280
+ }));
271
281
  // zitejs/runtime is NOT in PREBUNDLED_LIBS — it's a thin fetch()
272
282
  // wrapper that gets bundled inline by esbuild (no special handling).
273
283
  for (const [pkgName, modulePath] of Object.entries(PREBUNDLED_LIBS)) {
@@ -392,9 +402,78 @@ async function bundleEndpointsImpl(baseDir, endpointNames) {
392
402
  }
393
403
  }
394
404
  }
405
+ let bundledAuthHooks;
406
+ let authHooksError;
407
+ if (fs.existsSync(path.join(baseDir, 'zite.auth.ts'))) {
408
+ const runtimeInit = `import { __wrapSdkCall, initRuntime } from '@zite/endpoints-runtime-sdk';
409
+ globalThis.__wrapSdkCall = __wrapSdkCall;
410
+ initRuntime();`;
411
+ const dispatcherCode = `
412
+ ${runtimeInit}
413
+ import { z } from 'zod';
414
+ import { createEndpoint } from '${sdkSource}';
415
+ import auth from '../zite.auth';
416
+
417
+ const endpoint = createEndpoint({
418
+ description: 'Runs the auth lifecycle hooks defined in zite.auth.ts',
419
+ inputSchema: z.object({
420
+ hook: z.enum([
421
+ 'beforeSignUp',
422
+ 'afterSignUp',
423
+ 'beforeSignIn',
424
+ 'afterSignIn',
425
+ 'verifySession',
426
+ ]),
427
+ payload: z.record(z.unknown()).default({}),
428
+ }),
429
+ execute: async ({ input }) => {
430
+ const hook = auth.hooks?.[input.hook];
431
+ if (!hook) return {};
432
+ return (await hook(input.payload as never)) ?? {};
433
+ },
434
+ });
435
+ globalThis.__endpoint = endpoint;
436
+ `;
437
+ try {
438
+ const result = await esbuild.build({
439
+ ...BASE_BUILD_OPTIONS,
440
+ stdin: {
441
+ contents: dispatcherCode,
442
+ resolveDir: `${baseDir}/src`,
443
+ loader: 'ts',
444
+ },
445
+ logLevel: 'warning',
446
+ plugins: [aliasPlugin],
447
+ });
448
+ if (result.outputFiles && result.outputFiles.length > 0) {
449
+ bundledAuthHooks = result.outputFiles[0].text;
450
+ }
451
+ else {
452
+ authHooksError = 'No output generated';
453
+ }
454
+ }
455
+ catch (err) {
456
+ const e = err;
457
+ if (e.errors && Array.isArray(e.errors)) {
458
+ authHooksError = e.errors
459
+ .map(er => {
460
+ const loc = er.location
461
+ ? `${er.location.file || 'unknown'}:${er.location.line || '?'}:${er.location.column || '?'}`
462
+ : 'unknown';
463
+ return `${loc} - ${er.text}`;
464
+ })
465
+ .join('\n');
466
+ }
467
+ else {
468
+ authHooksError = e.message || String(err);
469
+ }
470
+ }
471
+ }
395
472
  const output = JSON.stringify({
396
473
  bundledEndpoints,
397
474
  endpointErrors: Object.keys(endpointErrors).length > 0 ? endpointErrors : undefined,
475
+ bundledAuthHooks,
476
+ authHooksError,
398
477
  });
399
478
  fs.writeFileSync('/tmp/zitejs-bundle-result.json', output);
400
479
  console.log(output);
@@ -504,18 +583,13 @@ async function runBundle() {
504
583
  }
505
584
  else {
506
585
  const apiDir = path.join(baseDir, 'src', 'api');
507
- if (!fs.existsSync(apiDir)) {
508
- console.log(JSON.stringify({ bundledEndpoints: {} }));
509
- return;
510
- }
511
- endpointNames = fs
512
- .readdirSync(apiDir)
513
- .filter(f => f.endsWith('.ts'))
514
- .map(f => f.replace('.ts', ''));
515
- }
516
- if (endpointNames.length === 0) {
517
- console.log(JSON.stringify({ bundledEndpoints: {} }));
518
- return;
586
+ endpointNames = fs.existsSync(apiDir)
587
+ ? fs
588
+ .readdirSync(apiDir)
589
+ .filter(f => f.endsWith('.ts'))
590
+ .map(f => f.replace('.ts', ''))
591
+ : [];
519
592
  }
593
+ // Even with no endpoints, zite.auth.ts may still need bundling.
520
594
  return bundleEndpointsImpl(baseDir, endpointNames);
521
595
  }
@@ -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,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Meta = exports.ZiteMeta = void 0;
4
+ const sdkCall_js_1 = require("../internal/sdkCall.js");
5
+ const META_SDK_INTEGRATION_ID = '__meta__';
6
+ class ZiteMeta {
7
+ static listUsers() {
8
+ return (0, sdkCall_js_1.getSdkCall)()(META_SDK_INTEGRATION_ID, 'ZiteMeta', 'listUsers', {});
9
+ }
10
+ }
11
+ exports.ZiteMeta = ZiteMeta;
12
+ exports.Meta = ZiteMeta;
@@ -146,6 +146,13 @@ export interface SendEmailParams {
146
146
  replyTo?: string;
147
147
  /** 'formatted' (default) for styled HTML, 'plain' for text-only. */
148
148
  layout?: "formatted" | "plain";
149
+ /**
150
+ * Text direction of the email. Use 'rtl' for right-to-left languages
151
+ * (Hebrew, Arabic) — sets dir="rtl" on the email wrapper and right-aligns
152
+ * text blocks. HTML like `<div dir="rtl">` inside text blocks is stripped
153
+ * by the sanitizer and will NOT work. Defaults to 'ltr'.
154
+ */
155
+ direction?: "ltr" | "rtl";
149
156
  /** Custom logo shown above the email content (header area), for branding. */
150
157
  logo?: {
151
158
  url: string;
@@ -738,6 +738,9 @@ function generateEmailSdk(integrationId) {
738
738
  "//",
739
739
  "// await Email.send({ to, subject, body: [{ type: 'text', content: '...' }] })",
740
740
  "// => { success: boolean; messageId: string }",
741
+ "//",
742
+ "// RTL languages (Hebrew, Arabic): pass direction: 'rtl' — dir attributes",
743
+ "// inside body text are stripped by the sanitizer and will not work.",
741
744
  "",
742
745
  "import { createEmailClient } from 'zitejs/runtime';",
743
746
  "",
@@ -35,9 +35,17 @@ export function loginWithRedirect(opts) {
35
35
  // any non-ASCII in the path fails its regex and the destination is dropped —
36
36
  // but never inspects the path of an absolute one. Normalising here means a
37
37
  // caller can pass whichever form reads best without having to know that.
38
- const redirectUrl = new URL(opts?.redirectUrl ?? window.location.href, window.location.href).toString();
39
- window.location.href =
40
- '/auth/login?' + new URLSearchParams({ redirectUrl }).toString();
38
+ const target = new URL(opts?.redirectUrl ?? window.location.href, window.location.href);
39
+ // Say nothing when there's nothing to say. The sign-in page already returns
40
+ // people to the app root when it isn't told otherwise, so naming the root
41
+ // spells out the default — and puts an encoded copy of the app's own URL in
42
+ // the address bar of every logged-out visitor to the front page, which is the
43
+ // common case. Only pass a destination when it IS one.
44
+ const isRoot = target.pathname === '/' && target.search === '' && target.hash === '';
45
+ window.location.href = isRoot
46
+ ? '/auth/login'
47
+ : '/auth/login?' +
48
+ new URLSearchParams({ redirectUrl: target.toString() }).toString();
41
49
  }
42
50
  export function logout(opts) {
43
51
  signOut().then(() => {
@@ -48,6 +48,24 @@ describe('zitejs/auth exports', () => {
48
48
  });
49
49
  });
50
50
  describe('loginWithRedirect', () => {
51
+ it('says nothing when the destination is the app root', () => {
52
+ // The sign-in page already returns people to the root, so naming it would
53
+ // just put an encoded copy of the app's own URL in the address bar — and a
54
+ // logged-out visitor to the front page is the common case.
55
+ const location = stubLocation('https://app.zite.so/');
56
+ authExports.loginWithRedirect();
57
+ expect(location.href).toBe('/auth/login');
58
+ });
59
+ it('says nothing when an explicit redirectUrl is the app root', () => {
60
+ const location = stubLocation('https://app.zite.so/pricing');
61
+ authExports.loginWithRedirect({ redirectUrl: '/' });
62
+ expect(location.href).toBe('/auth/login');
63
+ });
64
+ it('still names a root path that carries a query or hash', () => {
65
+ const location = stubLocation('https://app.zite.so/?invite=abc');
66
+ authExports.loginWithRedirect();
67
+ expect(redirectParamOf(location.href)).toBe('https://app.zite.so/?invite=abc');
68
+ });
51
69
  it('captures the current URL when called with no arguments', () => {
52
70
  const location = stubLocation('https://app.zite.so/orders/123?tab=open');
53
71
  authExports.loginWithRedirect();
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Server-side auth hooks — the `ziteAuth({ hooks })` config an app
3
+ * default-exports from `zite.auth.ts` (app root). The platform bundles that
4
+ * file at build time and invokes the hooks at auth lifecycle moments
5
+ * (sign-up, sign-in, session checks). Hooks run like backend app code, so
6
+ * they can use the app's integrations and `zitejs/db`.
7
+ *
8
+ * All hooks fail open on error/timeout — a broken hook never locks users
9
+ * out. Gate hooks return allow / deny / defer; `verifySession` returns an
10
+ * active verdict and may enrich the app-visible user.
11
+ */
12
+ export interface ZiteAuthHookUser {
13
+ id: string;
14
+ email: string;
15
+ name: string;
16
+ }
17
+ /**
18
+ * allow admits past disabled/domain-restricted signup; deny blocks with an
19
+ * optional reason; anything else (empty object / no return) defers to the
20
+ * platform rules.
21
+ */
22
+ export type ZiteAuthGateDecision = {
23
+ allow: true;
24
+ } | {
25
+ deny: true;
26
+ reason?: string;
27
+ } | {
28
+ allow?: undefined;
29
+ deny?: undefined;
30
+ } | void;
31
+ /**
32
+ * active: false revokes the session; active: true keeps it, and `user`
33
+ * merges extra fields into the app-visible user.
34
+ */
35
+ export type ZiteAuthSessionVerdict = {
36
+ active: false;
37
+ reason?: string;
38
+ } | {
39
+ active: true;
40
+ user?: Record<string, unknown>;
41
+ } | {
42
+ active?: undefined;
43
+ } | void;
44
+ export interface ZiteAuthHooks {
45
+ beforeSignUp?(input: {
46
+ email: string;
47
+ firstName?: string;
48
+ lastName?: string;
49
+ }): Promise<ZiteAuthGateDecision> | ZiteAuthGateDecision;
50
+ beforeSignIn?(input: {
51
+ email: string;
52
+ }): Promise<ZiteAuthGateDecision> | ZiteAuthGateDecision;
53
+ afterSignUp?(input: {
54
+ user: ZiteAuthHookUser;
55
+ }): Promise<unknown> | unknown;
56
+ afterSignIn?(input: {
57
+ user: ZiteAuthHookUser;
58
+ }): Promise<unknown> | unknown;
59
+ verifySession?(input: {
60
+ user: ZiteAuthHookUser;
61
+ }): Promise<ZiteAuthSessionVerdict> | ZiteAuthSessionVerdict;
62
+ }
63
+ export interface ZiteAuthConfig {
64
+ hooks?: ZiteAuthHooks;
65
+ }
66
+ export declare const ziteAuth: (config: ZiteAuthConfig) => ZiteAuthConfig;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Server-side auth hooks — the `ziteAuth({ hooks })` config an app
3
+ * default-exports from `zite.auth.ts` (app root). The platform bundles that
4
+ * file at build time and invokes the hooks at auth lifecycle moments
5
+ * (sign-up, sign-in, session checks). Hooks run like backend app code, so
6
+ * they can use the app's integrations and `zitejs/db`.
7
+ *
8
+ * All hooks fail open on error/timeout — a broken hook never locks users
9
+ * out. Gate hooks return allow / deny / defer; `verifySession` returns an
10
+ * active verdict and may enrich the app-visible user.
11
+ */
12
+ export const ziteAuth = (config) => config;
@@ -232,6 +232,16 @@ function createAliasPlugin(opts) {
232
232
  }
233
233
  return { path: 'zitejs/email', external: true };
234
234
  });
235
+ // zitejs/auth/server is types + an identity wrapper — inline a shim so
236
+ // zite.auth.ts bundles without resolving the installed package.
237
+ build.onResolve({ filter: /^zitejs\/auth\/server$/ }, () => ({
238
+ path: 'zitejs-auth-server-shim',
239
+ namespace: 'zite-virtual',
240
+ }));
241
+ build.onLoad({ filter: /.*/, namespace: 'zite-virtual' }, () => ({
242
+ contents: 'export const ziteAuth = (config) => config;',
243
+ loader: 'js',
244
+ }));
235
245
  // zitejs/runtime is NOT in PREBUNDLED_LIBS — it's a thin fetch()
236
246
  // wrapper that gets bundled inline by esbuild (no special handling).
237
247
  for (const [pkgName, modulePath] of Object.entries(PREBUNDLED_LIBS)) {
@@ -356,9 +366,78 @@ async function bundleEndpointsImpl(baseDir, endpointNames) {
356
366
  }
357
367
  }
358
368
  }
369
+ let bundledAuthHooks;
370
+ let authHooksError;
371
+ if (fs.existsSync(path.join(baseDir, 'zite.auth.ts'))) {
372
+ const runtimeInit = `import { __wrapSdkCall, initRuntime } from '@zite/endpoints-runtime-sdk';
373
+ globalThis.__wrapSdkCall = __wrapSdkCall;
374
+ initRuntime();`;
375
+ const dispatcherCode = `
376
+ ${runtimeInit}
377
+ import { z } from 'zod';
378
+ import { createEndpoint } from '${sdkSource}';
379
+ import auth from '../zite.auth';
380
+
381
+ const endpoint = createEndpoint({
382
+ description: 'Runs the auth lifecycle hooks defined in zite.auth.ts',
383
+ inputSchema: z.object({
384
+ hook: z.enum([
385
+ 'beforeSignUp',
386
+ 'afterSignUp',
387
+ 'beforeSignIn',
388
+ 'afterSignIn',
389
+ 'verifySession',
390
+ ]),
391
+ payload: z.record(z.unknown()).default({}),
392
+ }),
393
+ execute: async ({ input }) => {
394
+ const hook = auth.hooks?.[input.hook];
395
+ if (!hook) return {};
396
+ return (await hook(input.payload as never)) ?? {};
397
+ },
398
+ });
399
+ globalThis.__endpoint = endpoint;
400
+ `;
401
+ try {
402
+ const result = await esbuild.build({
403
+ ...BASE_BUILD_OPTIONS,
404
+ stdin: {
405
+ contents: dispatcherCode,
406
+ resolveDir: `${baseDir}/src`,
407
+ loader: 'ts',
408
+ },
409
+ logLevel: 'warning',
410
+ plugins: [aliasPlugin],
411
+ });
412
+ if (result.outputFiles && result.outputFiles.length > 0) {
413
+ bundledAuthHooks = result.outputFiles[0].text;
414
+ }
415
+ else {
416
+ authHooksError = 'No output generated';
417
+ }
418
+ }
419
+ catch (err) {
420
+ const e = err;
421
+ if (e.errors && Array.isArray(e.errors)) {
422
+ authHooksError = e.errors
423
+ .map(er => {
424
+ const loc = er.location
425
+ ? `${er.location.file || 'unknown'}:${er.location.line || '?'}:${er.location.column || '?'}`
426
+ : 'unknown';
427
+ return `${loc} - ${er.text}`;
428
+ })
429
+ .join('\n');
430
+ }
431
+ else {
432
+ authHooksError = e.message || String(err);
433
+ }
434
+ }
435
+ }
359
436
  const output = JSON.stringify({
360
437
  bundledEndpoints,
361
438
  endpointErrors: Object.keys(endpointErrors).length > 0 ? endpointErrors : undefined,
439
+ bundledAuthHooks,
440
+ authHooksError,
362
441
  });
363
442
  fs.writeFileSync('/tmp/zitejs-bundle-result.json', output);
364
443
  console.log(output);
@@ -468,18 +547,13 @@ export async function runBundle() {
468
547
  }
469
548
  else {
470
549
  const apiDir = path.join(baseDir, 'src', 'api');
471
- if (!fs.existsSync(apiDir)) {
472
- console.log(JSON.stringify({ bundledEndpoints: {} }));
473
- return;
474
- }
475
- endpointNames = fs
476
- .readdirSync(apiDir)
477
- .filter(f => f.endsWith('.ts'))
478
- .map(f => f.replace('.ts', ''));
479
- }
480
- if (endpointNames.length === 0) {
481
- console.log(JSON.stringify({ bundledEndpoints: {} }));
482
- return;
550
+ endpointNames = fs.existsSync(apiDir)
551
+ ? fs
552
+ .readdirSync(apiDir)
553
+ .filter(f => f.endsWith('.ts'))
554
+ .map(f => f.replace('.ts', ''))
555
+ : [];
483
556
  }
557
+ // Even with no endpoints, zite.auth.ts may still need bundling.
484
558
  return bundleEndpointsImpl(baseDir, endpointNames);
485
559
  }
@@ -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,8 @@
1
+ import { getSdkCall } from '../internal/sdkCall.js';
2
+ const META_SDK_INTEGRATION_ID = '__meta__';
3
+ export class ZiteMeta {
4
+ static listUsers() {
5
+ return getSdkCall()(META_SDK_INTEGRATION_ID, 'ZiteMeta', 'listUsers', {});
6
+ }
7
+ }
8
+ export const Meta = ZiteMeta;
@@ -146,6 +146,13 @@ export interface SendEmailParams {
146
146
  replyTo?: string;
147
147
  /** 'formatted' (default) for styled HTML, 'plain' for text-only. */
148
148
  layout?: "formatted" | "plain";
149
+ /**
150
+ * Text direction of the email. Use 'rtl' for right-to-left languages
151
+ * (Hebrew, Arabic) — sets dir="rtl" on the email wrapper and right-aligns
152
+ * text blocks. HTML like `<div dir="rtl">` inside text blocks is stripped
153
+ * by the sanitizer and will NOT work. Defaults to 'ltr'.
154
+ */
155
+ direction?: "ltr" | "rtl";
149
156
  /** Custom logo shown above the email content (header area), for branding. */
150
157
  logo?: {
151
158
  url: string;
@@ -728,6 +728,9 @@ export function generateEmailSdk(integrationId) {
728
728
  "//",
729
729
  "// await Email.send({ to, subject, body: [{ type: 'text', content: '...' }] })",
730
730
  "// => { success: boolean; messageId: string }",
731
+ "//",
732
+ "// RTL languages (Hebrew, Arabic): pass direction: 'rtl' — dir attributes",
733
+ "// inside body text are stripped by the sanitizer and will not work.",
731
734
  "",
732
735
  "import { createEmailClient } from 'zitejs/runtime';",
733
736
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zitejs",
3
- "version": "0.9.91",
3
+ "version": "0.9.93",
4
4
  "description": "The Zite framework — build apps on Zite Database",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -47,6 +47,12 @@
47
47
  "import": "./dist/esm/auth/index.js",
48
48
  "require": "./dist/cjs/auth/index.js"
49
49
  },
50
+ "./auth/server": {
51
+ "types": "./dist/esm/auth/server.d.ts",
52
+ "import": "./dist/esm/auth/server.js",
53
+ "require": "./dist/cjs/auth/server.js",
54
+ "default": "./dist/esm/auth/server.js"
55
+ },
50
56
  "./auth-server": {
51
57
  "types": "./dist/esm/auth-server/index.d.ts",
52
58
  "import": "./dist/esm/auth-server/index.js",