zitejs 0.9.131 → 0.9.134

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.
@@ -67,7 +67,7 @@ export declare const signIn: {
67
67
  }, FetchOptions["throw"] extends true ? true : false>>;
68
68
  } & {
69
69
  social: <FetchOptions extends import("@better-auth/core").ClientFetchOption<Partial<{
70
- provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
70
+ provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | "dropbox" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
71
71
  callbackURL?: string | undefined;
72
72
  newUserCallbackURL?: string | undefined;
73
73
  errorCallbackURL?: string | undefined;
@@ -91,7 +91,7 @@ export declare const signIn: {
91
91
  loginHint?: string | undefined;
92
92
  additionalData?: Record<string, any> | undefined;
93
93
  }> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, any> | undefined>>(data_0: import("better-auth/react").Prettify<{
94
- provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
94
+ provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | "dropbox" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
95
95
  callbackURL?: string | undefined;
96
96
  newUserCallbackURL?: string | undefined;
97
97
  errorCallbackURL?: string | undefined;
@@ -129,6 +129,10 @@ export interface ZiteRequestContext {
129
129
  user: {
130
130
  id: string;
131
131
  email: string;
132
+ roles: Array<{
133
+ id: string;
134
+ name: string;
135
+ }>;
132
136
  [key: string]: unknown;
133
137
  };
134
138
  userId?: string;
@@ -4,7 +4,7 @@ const vitest_1 = require("vitest");
4
4
  const zod_1 = require("zod");
5
5
  const index_js_1 = require("./index.js");
6
6
  const index_js_2 = require("../runtime/index.js");
7
- const requestContext = { user: { id: "u1", email: "a@b.c" } };
7
+ const requestContext = { user: { id: "u1", email: "a@b.c", roles: [] } };
8
8
  (0, vitest_1.afterEach)(() => {
9
9
  vitest_1.vi.unstubAllGlobals();
10
10
  });
@@ -100,6 +100,7 @@ const PREBUNDLED_LIBS = {
100
100
  '@elevenlabs/elevenlabs-js': '__elevenlabs__.js',
101
101
  'resend': '__resend__.js',
102
102
  '@clickhouse/client-web': '__clickhouse__.js',
103
+ 'dropbox': '__dropbox__.js',
103
104
  // Three packages, one module: the presigners take an S3Client instance, so a
104
105
  // separate bundle each would hand them a structurally identical but distinct
105
106
  // class. The worker bundles all three together for that reason.
@@ -40,6 +40,8 @@ declare const FIELDS_BY_TYPE: {
40
40
  apollo: readonly string[];
41
41
  attio: readonly string[];
42
42
  granola: readonly string[];
43
+ dropbox: readonly string[];
44
+ amazonS3: readonly string[];
43
45
  };
44
46
  /** A service type `integrations.get` serves: one used through its npm package with a token. */
45
47
  export type CredentialServiceType = keyof typeof FIELDS_BY_TYPE;
@@ -49,6 +49,10 @@ const FIELDS_BY_TYPE = {
49
49
  apollo: extras(),
50
50
  attio: extras(),
51
51
  granola: extras(),
52
+ dropbox: extras(),
53
+ // The IAM key alone reaches nothing: every call needs the region and the
54
+ // bucket, and S3-compatible providers need the endpoint too.
55
+ amazonS3: extras("keyId", "region", "bucket", "endpoint", "pathPrefix"),
52
56
  };
53
57
  /** Per service type: `accessToken`, then its extra labels sorted (Salesforce `instanceUrl`). */
54
58
  exports.CREDENTIAL_FIELDS = FIELDS_BY_TYPE;
@@ -58,6 +62,8 @@ exports.CREDENTIAL_FIELDS = FIELDS_BY_TYPE;
58
62
  */
59
63
  exports.OPTIONAL_CREDENTIAL_FIELDS = {
60
64
  jira: ["siteUrl"],
65
+ // Only S3-compatible providers set an endpoint, and a prefix is opt-in.
66
+ amazonS3: ["endpoint", "pathPrefix"],
61
67
  };
62
68
  /**
63
69
  * Types the runtime never serves: those with a generated client (Airtable, email,
@@ -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;
@@ -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,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Notifications = exports.ZiteNotifications = void 0;
4
+ const sdkCall_js_1 = require("../internal/sdkCall.js");
5
+ const NOTIFICATIONS_SDK_INTEGRATION_ID = '__notifications__';
6
+ class ZiteNotifications {
7
+ static create(params) {
8
+ return (0, sdkCall_js_1.getSdkCall)()(NOTIFICATIONS_SDK_INTEGRATION_ID, 'ZiteNotifications', 'create', params);
9
+ }
10
+ }
11
+ exports.ZiteNotifications = ZiteNotifications;
12
+ exports.Notifications = ZiteNotifications;
@@ -1302,7 +1302,8 @@ function generateBackendWrapperTs(envVarNames = []) {
1302
1302
  " User,",
1303
1303
  " 'name' | 'emailVerified' | 'image' | 'createdAt' | 'updatedAt'",
1304
1304
  "> &",
1305
- " Pick<User, 'id' | 'email'>;",
1305
+ " Pick<User, 'id' | 'email'> &",
1306
+ " Pick<_ZiteRequestContext['user'], 'roles'>;",
1306
1307
  "",
1307
1308
  'export interface ZiteRequestContext extends Omit<_ZiteRequestContext, "user"> {',
1308
1309
  " user: ZiteEndpointUser;",
@@ -1343,6 +1344,14 @@ function generateBackendWrapperTs(envVarNames = []) {
1343
1344
  " return config;",
1344
1345
  "}",
1345
1346
  "",
1347
+ "/**",
1348
+ " * Checks if a user has a specific role. Returns true if the user has the role, false",
1349
+ " * otherwise. If the user is not logged in, returns false.",
1350
+ " */",
1351
+ "export const hasRole = (context: ZiteRequestContext, roleId: string) => {",
1352
+ " return context.user?.roles?.some((r) => r.id === roleId);",
1353
+ "};",
1354
+ "",
1346
1355
  // Declared in the wrapper (not a standalone .d.ts) so the merge is
1347
1356
  // guaranteed to be in the program — every endpoint imports this file,
1348
1357
  // while tsconfig `include` does not reliably pick up dot-directories.
@@ -11,6 +11,41 @@ const estree_walker_1 = require("estree-walker");
11
11
  const domTagNames_js_1 = require("./domTagNames.js");
12
12
  const validExtensions = new Set([".jsx", ".tsx"]);
13
13
  const fragmentNames = new Set(["Fragment", "React.Fragment"]);
14
+ // react-three-fiber reads a dashed prop as a path into the three.js object, so
15
+ // any data-zite-* prop that reaches one throws. Components from the three
16
+ // ecosystem (drei, react-spring's `animated`) spread their props onto three
17
+ // objects, and anything rendered in a scene may be a three object too. So a
18
+ // component is only tagged where it is known to render DOM: under a DOM
19
+ // element or drei's <Html>. In files that import the three ecosystem, a JSX
20
+ // tree's root is not assumed to be DOM, since scene components live there.
21
+ const threeIntrinsicDomNames = new Set(["audio", "line", "path", "source"]);
22
+ function isThreeModule(source) {
23
+ return (source.startsWith("@react-three/") || source === "@react-spring/three");
24
+ }
25
+ /** Local binding name -> imported name ("*" for a namespace import). */
26
+ function getThreeImports(ast) {
27
+ const imports = new Map();
28
+ for (const statement of ast.program.body) {
29
+ if (statement.type === "ImportDeclaration" &&
30
+ isThreeModule(statement.source.value)) {
31
+ for (const specifier of statement.specifiers) {
32
+ imports.set(specifier.local.name, specifier.type === "ImportSpecifier"
33
+ ? specifier.imported.name ?? specifier.imported.value
34
+ : "*");
35
+ }
36
+ }
37
+ }
38
+ return imports;
39
+ }
40
+ /** The three-ecosystem name an element refers to, if it comes from one. */
41
+ function getThreeName(componentName, threeImports) {
42
+ const segments = componentName.split(".");
43
+ const imported = threeImports.get(segments[0]);
44
+ if (imported === undefined) {
45
+ return null;
46
+ }
47
+ return imported === "*" ? segments[segments.length - 1] : imported;
48
+ }
14
49
  function getComponentName(elementName) {
15
50
  if (elementName.type === "JSXIdentifier") {
16
51
  return elementName.name;
@@ -51,18 +86,52 @@ function ziteId() {
51
86
  plugins: ["jsx", "typescript"],
52
87
  });
53
88
  const magicString = new magic_string_1.default(code);
89
+ const threeImports = getThreeImports(ast);
90
+ const rootContext = threeImports.size > 0 ? "unknown" : "dom";
91
+ // The render context each open JSXElement gives its children.
92
+ const contextStack = [];
54
93
  (0, estree_walker_1.walk)(ast, {
94
+ leave(_node) {
95
+ if (_node.type === "JSXElement") {
96
+ contextStack.pop();
97
+ }
98
+ },
55
99
  enter(_node) {
56
100
  const node = _node;
57
101
  if (node.type === "JSXElement") {
58
102
  const openingElement = node.openingElement;
59
103
  const children = node.children;
104
+ const context = contextStack[contextStack.length - 1] ?? rootContext;
60
105
  const componentName = getComponentName(openingElement.name);
61
- if (!componentName) {
106
+ const kind = componentName ? classifyElement(componentName) : null;
107
+ const threeName = componentName
108
+ ? getThreeName(componentName, threeImports)
109
+ : null;
110
+ let childContext = context;
111
+ if (threeName === "Html") {
112
+ childContext = "dom";
113
+ }
114
+ else if (threeName !== null) {
115
+ childContext = "scene";
116
+ }
117
+ else if (kind === "dom") {
118
+ childContext = "dom";
119
+ }
120
+ else if (componentName &&
121
+ kind === null &&
122
+ !fragmentNames.has(componentName)) {
123
+ childContext = "scene";
124
+ }
125
+ contextStack.push(childContext);
126
+ if (!componentName || !kind) {
62
127
  return;
63
128
  }
64
- const kind = classifyElement(componentName);
65
- if (!kind) {
129
+ const isTagged = kind === "component"
130
+ ? threeName === "Canvas" ||
131
+ (threeName === null && context === "dom")
132
+ : !threeIntrinsicDomNames.has(componentName) ||
133
+ context === "dom";
134
+ if (!isTagged) {
66
135
  return;
67
136
  }
68
137
  const line = openingElement.loc?.start?.line ?? 0;
@@ -42,9 +42,65 @@ async function transform(code) {
42
42
  const source = 'const a = <group><mesh position-x={1} /><ambientLight intensity={0.5} /></group>;';
43
43
  (0, vitest_1.expect)(await transform(source)).toBe(source);
44
44
  });
45
- (0, vitest_1.it)('tags DOM elements nested inside non-DOM intrinsics', async () => {
45
+ (0, vitest_1.it)('tags DOM but not components nested inside non-DOM intrinsics', async () => {
46
46
  const code = await transform('const a = <group><Html><div /></Html></group>;');
47
- (0, vitest_1.expect)(code).toBe('const a = <group><Html data-zite-id="src/App.tsx|1|17" data-zite-component="Html"><div data-zite-id="src/App.tsx|1|23" /></Html></group>;');
47
+ (0, vitest_1.expect)(code).toBe('const a = <group><Html><div data-zite-id="src/App.tsx|1|23" /></Html></group>;');
48
+ });
49
+ (0, vitest_1.describe)('react-three scenes', () => {
50
+ const scene = (jsx) => transform("import { Canvas } from '@react-three/fiber';\n" +
51
+ "import { Html, Text } from '@react-three/drei';\n" +
52
+ "import { Button } from './Button';\n" +
53
+ `const a = ${jsx};`);
54
+ (0, vitest_1.it)('skips three components, nested components and three.js intrinsics', async () => {
55
+ const code = await scene('<Canvas><Text>Hi</Text><Button /><line /></Canvas>');
56
+ (0, vitest_1.expect)(code).toContain('<Text>Hi</Text>');
57
+ (0, vitest_1.expect)(code).toContain('<Button />');
58
+ (0, vitest_1.expect)(code).toContain('<line />');
59
+ });
60
+ (0, vitest_1.it)('tags the Canvas container and UI next to it', async () => {
61
+ const code = await scene('<div><Canvas /><Button>Start</Button></div>');
62
+ (0, vitest_1.expect)(code).toMatch(/<Canvas data-zite-id="[^"]+" data-zite-component="Canvas" \/>/);
63
+ (0, vitest_1.expect)(code).toMatch(/<Button data-zite-id="[^"]+" data-zite-component="Button" data-zite-editable="true">/);
64
+ });
65
+ (0, vitest_1.it)('tags DOM and components inside an <Html> overlay again', async () => {
66
+ const code = await scene('<Canvas><Html><Button /><div /></Html></Canvas>');
67
+ (0, vitest_1.expect)(code).toContain('<Html>');
68
+ (0, vitest_1.expect)(code).toMatch(/<Button data-zite-id="[^"]+" data-zite-component="Button" \/>/);
69
+ (0, vitest_1.expect)(code).toMatch(/<div data-zite-id="[^"]+" \/>/);
70
+ });
71
+ (0, vitest_1.it)('skips the root of a scene component in the same file', async () => {
72
+ const code = await scene('<><Button position={[0, 0, 0]} /></>');
73
+ (0, vitest_1.expect)(code).toContain('<Button position={[0, 0, 0]} />');
74
+ });
75
+ (0, vitest_1.it)('skips children of three wrapper components', async () => {
76
+ const code = await transform("import { Float } from '@react-three/drei';\nimport { Box } from './Box';\nconst a = <Float><Box /></Float>;");
77
+ (0, vitest_1.expect)(code).toContain('<Float><Box /></Float>');
78
+ });
79
+ (0, vitest_1.it)('recognises aliased and namespaced three imports', async () => {
80
+ const code = await transform("import { Canvas as C } from '@react-three/fiber';\nimport * as Drei from '@react-three/drei';\n" +
81
+ "import { Box, Card } from './ui';\nconst a = <C><Box /><Drei.Html><Card /></Drei.Html></C>;");
82
+ (0, vitest_1.expect)(code).toMatch(/<C data-zite-id="[^"]+" data-zite-component="C">/);
83
+ (0, vitest_1.expect)(code).toContain('<Box />');
84
+ (0, vitest_1.expect)(code).toContain('<Drei.Html>');
85
+ (0, vitest_1.expect)(code).toMatch(/<Card data-zite-id="[^"]+" data-zite-component="Card" \/>/);
86
+ });
87
+ (0, vitest_1.it)('tags SVG lines and paths in DOM next to a scene', async () => {
88
+ const code = await scene('<div><svg><line /><path /></svg></div>');
89
+ (0, vitest_1.expect)(code).toMatch(/<line data-zite-id="[^"]+" \/>/);
90
+ (0, vitest_1.expect)(code).toMatch(/<path data-zite-id="[^"]+" \/>/);
91
+ });
92
+ (0, vitest_1.it)("skips react-spring's animated three elements", async () => {
93
+ const code = await transform("import { animated } from '@react-spring/three';\nconst a = <animated.mesh />;");
94
+ (0, vitest_1.expect)(code).toContain('<animated.mesh />');
95
+ });
96
+ (0, vitest_1.it)('skips components nested in a scene element in a file without three imports', async () => {
97
+ const code = await transform("import { Tree } from './Tree';\nconst a = <group><Tree /></group>;");
98
+ (0, vitest_1.expect)(code).toContain('<Tree />');
99
+ });
100
+ });
101
+ (0, vitest_1.it)('keeps tagging components in files that do not import react-three', async () => {
102
+ const code = await transform("import { Card } from './Card';\nconst a = <Card />;");
103
+ (0, vitest_1.expect)(code).toContain('<Card data-zite-id="src/App.tsx|2|10" data-zite-component="Card" />');
48
104
  });
49
105
  (0, vitest_1.it)('leaves fragments untouched', async () => {
50
106
  const source = 'const a = <Fragment><React.Fragment key="a" /></Fragment>;';
@@ -67,7 +67,7 @@ export declare const signIn: {
67
67
  }, FetchOptions["throw"] extends true ? true : false>>;
68
68
  } & {
69
69
  social: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
70
- provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
70
+ provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | "dropbox" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
71
71
  callbackURL?: string | undefined;
72
72
  newUserCallbackURL?: string | undefined;
73
73
  errorCallbackURL?: string | undefined;
@@ -91,7 +91,7 @@ export declare const signIn: {
91
91
  loginHint?: string | undefined;
92
92
  additionalData?: Record<string, any> | undefined;
93
93
  }> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, any> | undefined>>(data_0: import("better-auth/react").Prettify<{
94
- provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
94
+ provider: "salesforce" | "slack" | "linear" | "notion" | "linkedin" | "github" | "dropbox" | (string & {}) | "huggingface" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "spotify" | "twitch" | "twitter" | "kick" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "zoom" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
95
95
  callbackURL?: string | undefined;
96
96
  newUserCallbackURL?: string | undefined;
97
97
  errorCallbackURL?: string | undefined;
@@ -129,6 +129,10 @@ export interface ZiteRequestContext {
129
129
  user: {
130
130
  id: string;
131
131
  email: string;
132
+ roles: Array<{
133
+ id: string;
134
+ name: string;
135
+ }>;
132
136
  [key: string]: unknown;
133
137
  };
134
138
  userId?: string;
@@ -2,7 +2,7 @@ import { describe, it, expect, afterEach, vi } from "vitest";
2
2
  import { z } from "zod";
3
3
  import { createEndpoint, ZiteError, } from "./index.js";
4
4
  import { createTableClient } from "../runtime/index.js";
5
- const requestContext = { user: { id: "u1", email: "a@b.c" } };
5
+ const requestContext = { user: { id: "u1", email: "a@b.c", roles: [] } };
6
6
  afterEach(() => {
7
7
  vi.unstubAllGlobals();
8
8
  });
@@ -62,6 +62,7 @@ const PREBUNDLED_LIBS = {
62
62
  '@elevenlabs/elevenlabs-js': '__elevenlabs__.js',
63
63
  'resend': '__resend__.js',
64
64
  '@clickhouse/client-web': '__clickhouse__.js',
65
+ 'dropbox': '__dropbox__.js',
65
66
  // Three packages, one module: the presigners take an S3Client instance, so a
66
67
  // separate bundle each would hand them a structurally identical but distinct
67
68
  // class. The worker bundles all three together for that reason.
@@ -40,6 +40,8 @@ declare const FIELDS_BY_TYPE: {
40
40
  apollo: readonly string[];
41
41
  attio: readonly string[];
42
42
  granola: readonly string[];
43
+ dropbox: readonly string[];
44
+ amazonS3: readonly string[];
43
45
  };
44
46
  /** A service type `integrations.get` serves: one used through its npm package with a token. */
45
47
  export type CredentialServiceType = keyof typeof FIELDS_BY_TYPE;
@@ -46,6 +46,10 @@ const FIELDS_BY_TYPE = {
46
46
  apollo: extras(),
47
47
  attio: extras(),
48
48
  granola: extras(),
49
+ dropbox: extras(),
50
+ // The IAM key alone reaches nothing: every call needs the region and the
51
+ // bucket, and S3-compatible providers need the endpoint too.
52
+ amazonS3: extras("keyId", "region", "bucket", "endpoint", "pathPrefix"),
49
53
  };
50
54
  /** Per service type: `accessToken`, then its extra labels sorted (Salesforce `instanceUrl`). */
51
55
  export const CREDENTIAL_FIELDS = FIELDS_BY_TYPE;
@@ -55,6 +59,8 @@ export const CREDENTIAL_FIELDS = FIELDS_BY_TYPE;
55
59
  */
56
60
  export const OPTIONAL_CREDENTIAL_FIELDS = {
57
61
  jira: ["siteUrl"],
62
+ // Only S3-compatible providers set an endpoint, and a prefix is opt-in.
63
+ amazonS3: ["endpoint", "pathPrefix"],
58
64
  };
59
65
  /**
60
66
  * Types the runtime never serves: those with a generated client (Airtable, email,
@@ -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;
@@ -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;
@@ -1287,7 +1287,8 @@ export function generateBackendWrapperTs(envVarNames = []) {
1287
1287
  " User,",
1288
1288
  " 'name' | 'emailVerified' | 'image' | 'createdAt' | 'updatedAt'",
1289
1289
  "> &",
1290
- " Pick<User, 'id' | 'email'>;",
1290
+ " Pick<User, 'id' | 'email'> &",
1291
+ " Pick<_ZiteRequestContext['user'], 'roles'>;",
1291
1292
  "",
1292
1293
  'export interface ZiteRequestContext extends Omit<_ZiteRequestContext, "user"> {',
1293
1294
  " user: ZiteEndpointUser;",
@@ -1328,6 +1329,14 @@ export function generateBackendWrapperTs(envVarNames = []) {
1328
1329
  " return config;",
1329
1330
  "}",
1330
1331
  "",
1332
+ "/**",
1333
+ " * Checks if a user has a specific role. Returns true if the user has the role, false",
1334
+ " * otherwise. If the user is not logged in, returns false.",
1335
+ " */",
1336
+ "export const hasRole = (context: ZiteRequestContext, roleId: string) => {",
1337
+ " return context.user?.roles?.some((r) => r.id === roleId);",
1338
+ "};",
1339
+ "",
1331
1340
  // Declared in the wrapper (not a standalone .d.ts) so the merge is
1332
1341
  // guaranteed to be in the program — every endpoint imports this file,
1333
1342
  // while tsconfig `include` does not reliably pick up dot-directories.
@@ -5,6 +5,41 @@ import { walk } from "estree-walker";
5
5
  import { domTagNames } from "./domTagNames.js";
6
6
  const validExtensions = new Set([".jsx", ".tsx"]);
7
7
  const fragmentNames = new Set(["Fragment", "React.Fragment"]);
8
+ // react-three-fiber reads a dashed prop as a path into the three.js object, so
9
+ // any data-zite-* prop that reaches one throws. Components from the three
10
+ // ecosystem (drei, react-spring's `animated`) spread their props onto three
11
+ // objects, and anything rendered in a scene may be a three object too. So a
12
+ // component is only tagged where it is known to render DOM: under a DOM
13
+ // element or drei's <Html>. In files that import the three ecosystem, a JSX
14
+ // tree's root is not assumed to be DOM, since scene components live there.
15
+ const threeIntrinsicDomNames = new Set(["audio", "line", "path", "source"]);
16
+ function isThreeModule(source) {
17
+ return (source.startsWith("@react-three/") || source === "@react-spring/three");
18
+ }
19
+ /** Local binding name -> imported name ("*" for a namespace import). */
20
+ function getThreeImports(ast) {
21
+ const imports = new Map();
22
+ for (const statement of ast.program.body) {
23
+ if (statement.type === "ImportDeclaration" &&
24
+ isThreeModule(statement.source.value)) {
25
+ for (const specifier of statement.specifiers) {
26
+ imports.set(specifier.local.name, specifier.type === "ImportSpecifier"
27
+ ? specifier.imported.name ?? specifier.imported.value
28
+ : "*");
29
+ }
30
+ }
31
+ }
32
+ return imports;
33
+ }
34
+ /** The three-ecosystem name an element refers to, if it comes from one. */
35
+ function getThreeName(componentName, threeImports) {
36
+ const segments = componentName.split(".");
37
+ const imported = threeImports.get(segments[0]);
38
+ if (imported === undefined) {
39
+ return null;
40
+ }
41
+ return imported === "*" ? segments[segments.length - 1] : imported;
42
+ }
8
43
  function getComponentName(elementName) {
9
44
  if (elementName.type === "JSXIdentifier") {
10
45
  return elementName.name;
@@ -45,18 +80,52 @@ export function ziteId() {
45
80
  plugins: ["jsx", "typescript"],
46
81
  });
47
82
  const magicString = new MagicString(code);
83
+ const threeImports = getThreeImports(ast);
84
+ const rootContext = threeImports.size > 0 ? "unknown" : "dom";
85
+ // The render context each open JSXElement gives its children.
86
+ const contextStack = [];
48
87
  walk(ast, {
88
+ leave(_node) {
89
+ if (_node.type === "JSXElement") {
90
+ contextStack.pop();
91
+ }
92
+ },
49
93
  enter(_node) {
50
94
  const node = _node;
51
95
  if (node.type === "JSXElement") {
52
96
  const openingElement = node.openingElement;
53
97
  const children = node.children;
98
+ const context = contextStack[contextStack.length - 1] ?? rootContext;
54
99
  const componentName = getComponentName(openingElement.name);
55
- if (!componentName) {
100
+ const kind = componentName ? classifyElement(componentName) : null;
101
+ const threeName = componentName
102
+ ? getThreeName(componentName, threeImports)
103
+ : null;
104
+ let childContext = context;
105
+ if (threeName === "Html") {
106
+ childContext = "dom";
107
+ }
108
+ else if (threeName !== null) {
109
+ childContext = "scene";
110
+ }
111
+ else if (kind === "dom") {
112
+ childContext = "dom";
113
+ }
114
+ else if (componentName &&
115
+ kind === null &&
116
+ !fragmentNames.has(componentName)) {
117
+ childContext = "scene";
118
+ }
119
+ contextStack.push(childContext);
120
+ if (!componentName || !kind) {
56
121
  return;
57
122
  }
58
- const kind = classifyElement(componentName);
59
- if (!kind) {
123
+ const isTagged = kind === "component"
124
+ ? threeName === "Canvas" ||
125
+ (threeName === null && context === "dom")
126
+ : !threeIntrinsicDomNames.has(componentName) ||
127
+ context === "dom";
128
+ if (!isTagged) {
60
129
  return;
61
130
  }
62
131
  const line = openingElement.loc?.start?.line ?? 0;
@@ -37,9 +37,65 @@ describe('ziteId', () => {
37
37
  const source = 'const a = <group><mesh position-x={1} /><ambientLight intensity={0.5} /></group>;';
38
38
  expect(await transform(source)).toBe(source);
39
39
  });
40
- it('tags DOM elements nested inside non-DOM intrinsics', async () => {
40
+ it('tags DOM but not components nested inside non-DOM intrinsics', async () => {
41
41
  const code = await transform('const a = <group><Html><div /></Html></group>;');
42
- expect(code).toBe('const a = <group><Html data-zite-id="src/App.tsx|1|17" data-zite-component="Html"><div data-zite-id="src/App.tsx|1|23" /></Html></group>;');
42
+ expect(code).toBe('const a = <group><Html><div data-zite-id="src/App.tsx|1|23" /></Html></group>;');
43
+ });
44
+ describe('react-three scenes', () => {
45
+ const scene = (jsx) => transform("import { Canvas } from '@react-three/fiber';\n" +
46
+ "import { Html, Text } from '@react-three/drei';\n" +
47
+ "import { Button } from './Button';\n" +
48
+ `const a = ${jsx};`);
49
+ it('skips three components, nested components and three.js intrinsics', async () => {
50
+ const code = await scene('<Canvas><Text>Hi</Text><Button /><line /></Canvas>');
51
+ expect(code).toContain('<Text>Hi</Text>');
52
+ expect(code).toContain('<Button />');
53
+ expect(code).toContain('<line />');
54
+ });
55
+ it('tags the Canvas container and UI next to it', async () => {
56
+ const code = await scene('<div><Canvas /><Button>Start</Button></div>');
57
+ expect(code).toMatch(/<Canvas data-zite-id="[^"]+" data-zite-component="Canvas" \/>/);
58
+ expect(code).toMatch(/<Button data-zite-id="[^"]+" data-zite-component="Button" data-zite-editable="true">/);
59
+ });
60
+ it('tags DOM and components inside an <Html> overlay again', async () => {
61
+ const code = await scene('<Canvas><Html><Button /><div /></Html></Canvas>');
62
+ expect(code).toContain('<Html>');
63
+ expect(code).toMatch(/<Button data-zite-id="[^"]+" data-zite-component="Button" \/>/);
64
+ expect(code).toMatch(/<div data-zite-id="[^"]+" \/>/);
65
+ });
66
+ it('skips the root of a scene component in the same file', async () => {
67
+ const code = await scene('<><Button position={[0, 0, 0]} /></>');
68
+ expect(code).toContain('<Button position={[0, 0, 0]} />');
69
+ });
70
+ it('skips children of three wrapper components', async () => {
71
+ const code = await transform("import { Float } from '@react-three/drei';\nimport { Box } from './Box';\nconst a = <Float><Box /></Float>;");
72
+ expect(code).toContain('<Float><Box /></Float>');
73
+ });
74
+ it('recognises aliased and namespaced three imports', async () => {
75
+ const code = await transform("import { Canvas as C } from '@react-three/fiber';\nimport * as Drei from '@react-three/drei';\n" +
76
+ "import { Box, Card } from './ui';\nconst a = <C><Box /><Drei.Html><Card /></Drei.Html></C>;");
77
+ expect(code).toMatch(/<C data-zite-id="[^"]+" data-zite-component="C">/);
78
+ expect(code).toContain('<Box />');
79
+ expect(code).toContain('<Drei.Html>');
80
+ expect(code).toMatch(/<Card data-zite-id="[^"]+" data-zite-component="Card" \/>/);
81
+ });
82
+ it('tags SVG lines and paths in DOM next to a scene', async () => {
83
+ const code = await scene('<div><svg><line /><path /></svg></div>');
84
+ expect(code).toMatch(/<line data-zite-id="[^"]+" \/>/);
85
+ expect(code).toMatch(/<path data-zite-id="[^"]+" \/>/);
86
+ });
87
+ it("skips react-spring's animated three elements", async () => {
88
+ const code = await transform("import { animated } from '@react-spring/three';\nconst a = <animated.mesh />;");
89
+ expect(code).toContain('<animated.mesh />');
90
+ });
91
+ it('skips components nested in a scene element in a file without three imports', async () => {
92
+ const code = await transform("import { Tree } from './Tree';\nconst a = <group><Tree /></group>;");
93
+ expect(code).toContain('<Tree />');
94
+ });
95
+ });
96
+ it('keeps tagging components in files that do not import react-three', async () => {
97
+ const code = await transform("import { Card } from './Card';\nconst a = <Card />;");
98
+ expect(code).toContain('<Card data-zite-id="src/App.tsx|2|10" data-zite-component="Card" />');
43
99
  });
44
100
  it('leaves fragments untouched', async () => {
45
101
  const source = 'const a = <Fragment><React.Fragment key="a" /></Fragment>;';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zitejs",
3
- "version": "0.9.131",
3
+ "version": "0.9.134",
4
4
  "description": "The Zite framework — build apps on Zite Database",
5
5
  "type": "module",
6
6
  "sideEffects": false,