attio 0.0.1-experimental.20240920 → 0.0.1-experimental.20240926

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.
@@ -0,0 +1,32 @@
1
+ import { graphqlServer } from "@hono/graphql-server";
2
+ import { serve } from "@hono/node-server";
3
+ import fs from "fs";
4
+ import { buildSchema } from "graphql";
5
+ import { Hono } from "hono";
6
+ import path, { dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { findAvailablePort } from "../util/find-available-port.js";
9
+ export function startGraphqlServer(sendBack) {
10
+ let server = null;
11
+ const startServer = async () => {
12
+ const currentFilePath = fileURLToPath(import.meta.url);
13
+ const currentDirPath = dirname(currentFilePath);
14
+ const schemaPath = path.resolve(currentDirPath, "..", "schema.graphql");
15
+ const schemaString = fs.readFileSync(schemaPath, "utf8");
16
+ const port = await findAvailablePort(8700);
17
+ const schema = buildSchema(schemaString);
18
+ const app = new Hono();
19
+ const rootResolver = () => {
20
+ return {};
21
+ };
22
+ app.use("/graphql", graphqlServer({ schema, rootResolver, graphiql: true }));
23
+ server = serve({ fetch: app.fetch, port });
24
+ sendBack({ type: "GraphQL Server Started", port });
25
+ };
26
+ startServer();
27
+ return () => {
28
+ if (!server)
29
+ return;
30
+ server.close();
31
+ };
32
+ }
@@ -1,8 +1,8 @@
1
1
  import { glob } from "glob";
2
2
  import path from "path";
3
3
  import { getModuleHash } from "../get-module-hash.js";
4
- export async function generateServerEntry({ appDirAbsolute, webhooksDirAbsolute, log, }) {
5
- const [serverFunctionConcretePaths, webhookConcretePaths] = await Promise.all([
4
+ export async function generateServerEntry({ appDirAbsolute, webhooksDirAbsolute, eventDirAbsolute, log, }) {
5
+ const [serverFunctionConcretePaths, webhookConcretePaths, eventConcretePaths] = await Promise.all([
6
6
  Promise.all([
7
7
  glob("**/*.server.ts", { nodir: true, cwd: appDirAbsolute }),
8
8
  glob("**/*.server.js", { nodir: true, cwd: appDirAbsolute }),
@@ -11,6 +11,10 @@ export async function generateServerEntry({ appDirAbsolute, webhooksDirAbsolute,
11
11
  glob("**/*.webhook.ts", { nodir: true, cwd: webhooksDirAbsolute }),
12
12
  glob("**/*.webhook.js", { nodir: true, cwd: webhooksDirAbsolute }),
13
13
  ]).then((paths) => paths.flat()),
14
+ Promise.all([
15
+ glob("*.event.ts", { nodir: true, cwd: eventDirAbsolute }),
16
+ glob("*.event.js", { nodir: true, cwd: eventDirAbsolute }),
17
+ ]).then((paths) => paths.flat()),
14
18
  ]);
15
19
  const serverFunctionModules = serverFunctionConcretePaths.map((path) => ({
16
20
  path,
@@ -20,6 +24,10 @@ export async function generateServerEntry({ appDirAbsolute, webhooksDirAbsolute,
20
24
  path,
21
25
  id: path.replace(/\.webhook\.(js|ts)$/, ""),
22
26
  }));
27
+ const eventModules = eventConcretePaths.map((path) => ({
28
+ path,
29
+ id: path.replace(/\.event\.(js|ts)$/, ""),
30
+ }));
23
31
  for (const module of serverFunctionModules) {
24
32
  log?.(`🔎 Found server module "${module.path}"`);
25
33
  }
@@ -27,6 +35,7 @@ export async function generateServerEntry({ appDirAbsolute, webhooksDirAbsolute,
27
35
 
28
36
  const modules = new Map()
29
37
  const webhookModules = new Map()
38
+ const eventModules = new Map()
30
39
 
31
40
 
32
41
  ${serverFunctionModules
@@ -37,8 +46,13 @@ export async function generateServerEntry({ appDirAbsolute, webhooksDirAbsolute,
37
46
  .map((module) => `webhookModules.set("${module.id}", () => import(${JSON.stringify(path.join(webhooksDirAbsolute, module.path))}))`)
38
47
  .join("\n")}
39
48
 
49
+ ${eventModules
50
+ .map((module) => `eventModules.set("${module.id}", () => import(${JSON.stringify(path.join(eventDirAbsolute, module.path))}))`)
51
+ .join("\n")}
52
+
40
53
  var stdin_default;
41
54
  var stdin_webhooks_default;
55
+ var stdin_events_default;
42
56
  function main() {
43
57
  stdin_default = async function(moduleHash, args) {
44
58
  const module = modules.get(moduleHash)
@@ -79,6 +93,26 @@ export async function generateServerEntry({ appDirAbsolute, webhooksDirAbsolute,
79
93
 
80
94
  return func(...args)
81
95
  }
96
+
97
+ stdin_events_default = async function(eventModuleId, args) {
98
+ const module = eventModules.get(eventModuleId)
99
+
100
+ if (!module) {
101
+ throw new Error("Event handler not found")
102
+ }
103
+
104
+ const func = (await module()).default
105
+
106
+ if (!func) {
107
+ throw new Error(\`Default export not found in module \${eventModuleId}\`)
108
+ }
109
+
110
+ if (typeof func !== "function") {
111
+ throw new Error(\`\${eventModuleId} does not export a function\`)
112
+ }
113
+
114
+ return func(...args)
115
+ }
82
116
  }
83
117
 
84
118
 
@@ -1,5 +1,5 @@
1
1
  export { useDialog } from "./use-dialog.js";
2
2
  export { useGraphQL } from "./use-graphql.js";
3
- export { useLoader } from "./use-loader.js";
3
+ export { useAsyncCache, AsyncCacheConfig, AsyncFunction } from "./use-async-cache.js";
4
4
  export { useQuery } from "./use-query.js";
5
5
  export { useRecord } from "./use-record.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * An async function that returns data.
3
+ */
4
+ export type AsyncFunction<Input extends Array<any>, Output> = (...args: Input) => Promise<Output>;
5
+ /**
6
+ * A type-safe configuration object for multiple loaders.
7
+ *
8
+ * The key is used to cache the result of the loader.
9
+ * The value is a loader that takes an array of arguments and returns a promise.
10
+ */
11
+ export interface AsyncCacheConfig {
12
+ [K: string]: AsyncFunction<Array<any>, any> | [AsyncFunction<Array<any>, any>, ...Array<any>];
13
+ }
14
+ /**
15
+ * A hook that returns, and caches, the results of calling multiple async functions,
16
+ * typically to load data.
17
+ */
18
+ export declare function useAsyncCache<Config extends AsyncCacheConfig>(config: Config): {
19
+ values: {
20
+ [K in keyof Config]: Config[K] extends AsyncFunction<any, infer Output> ? Output : Config[K] extends [AsyncFunction<infer Input, infer Output>, ...infer Args] ? Args extends Input ? Output : never : never;
21
+ };
22
+ invalidate: (name: Extract<keyof Config, string>) => void;
23
+ };
@@ -88,7 +88,9 @@ export default function Dev({ options: { debug } }) {
88
88
  React.createElement(Text, { color: "redBright" }, "\u274C Upload Error"),
89
89
  React.createElement(Text, null,
90
90
  "\u2013 ",
91
- snapshot.context.uploadError?.message))))),
91
+ snapshot.context.uploadError?.message)))),
92
+ snapshot.matches({ Watching: { Graphql: "Started" } }) && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
93
+ React.createElement(Text, null, "Press \"o\" to open GraphQL Explorer")))),
92
94
  hasErrors && (React.createElement(ScrollBox, { borderStyle: "round", padding: 1 },
93
95
  jsError && renderBuildErrors(jsError),
94
96
  tsErrors && renderTypeScriptErrors(tsErrors)))));
@@ -1,7 +1,10 @@
1
1
  import chokidar from "chokidar";
2
+ import open from "open";
3
+ import readline from "readline";
2
4
  import { assign, fromCallback, setup, enqueueActions } from "xstate";
3
5
  import { completeBundleUpload } from "../api/complete-bundle-upload.js";
4
6
  import { createDevVersion } from "../api/create-dev-version.js";
7
+ import { startGraphqlServer } from "../api/start-graphql-server.js";
5
8
  import { startUpload } from "../api/start-upload.js";
6
9
  import { loadAppConfigFile } from "../util/app-config.js";
7
10
  import { loadDeveloperConfig, loadInitialDeveloperConfig, } from "../util/load-developer-config.js";
@@ -30,6 +33,24 @@ export const devMachine = setup({
30
33
  }
31
34
  sendBack({ type: "Initialized", config });
32
35
  }),
36
+ listenForGraphqlOpen: fromCallback(({ input }) => {
37
+ readline.emitKeypressEvents(process.stdin);
38
+ if (process.stdin.isTTY) {
39
+ process.stdin.setRawMode(true);
40
+ }
41
+ const handleKeyPress = (str, key) => {
42
+ if (key.name?.toLowerCase() === "o") {
43
+ open(`http://localhost:${input.graphqlPort}/graphql`);
44
+ }
45
+ };
46
+ process.stdin.on("keypress", handleKeyPress);
47
+ return () => {
48
+ process.stdin.removeListener("keypress", handleKeyPress);
49
+ if (process.stdin.isTTY) {
50
+ process.stdin.setRawMode(false);
51
+ }
52
+ };
53
+ }),
33
54
  prepareUpload: fromCallback(({ sendBack }) => {
34
55
  const prepareUpload = async () => {
35
56
  const config = await loadDeveloperConfig();
@@ -94,7 +115,13 @@ export const devMachine = setup({
94
115
  upload().catch((error) => sendBack({ type: "Upload Error", error }));
95
116
  }),
96
117
  watch: fromCallback(({ sendBack }) => {
97
- const watcher = chokidar.watch(["src/app", "src/assets", "src/webhooks", ".env"]);
118
+ const watcher = chokidar.watch([
119
+ "src/app",
120
+ "src/assets",
121
+ "src/webhooks",
122
+ "src/events",
123
+ ".env",
124
+ ]);
98
125
  watcher.on("ready", () => watcher.on("all", () => {
99
126
  sendBack({ type: "Change" });
100
127
  }));
@@ -102,6 +129,7 @@ export const devMachine = setup({
102
129
  watcher.close();
103
130
  };
104
131
  }),
132
+ graphql: fromCallback(({ sendBack }) => startGraphqlServer(sendBack)),
105
133
  },
106
134
  actions: {
107
135
  clearUploadError: assign({ uploadError: undefined }),
@@ -119,6 +147,9 @@ export const devMachine = setup({
119
147
  setDevVersion: assign({
120
148
  devVersion: (_, params) => params.devVersion,
121
149
  }),
150
+ setGraphqlPort: assign({
151
+ graphqlPort: (_, params) => params.port,
152
+ }),
122
153
  setSuccess: assign({
123
154
  jsContents: (_, params) => params.contents,
124
155
  lastSuccessfulJavaScriptBuild: (_, params) => params.time,
@@ -246,6 +277,31 @@ export const devMachine = setup({
246
277
  },
247
278
  initial: "Validating",
248
279
  },
280
+ Graphql: {
281
+ invoke: {
282
+ src: "graphql",
283
+ },
284
+ states: {
285
+ "Not Started": {
286
+ on: {
287
+ "GraphQL Server Started": {
288
+ target: "Started",
289
+ actions: {
290
+ type: "setGraphqlPort",
291
+ params: ({ event }) => event,
292
+ },
293
+ },
294
+ },
295
+ },
296
+ "Started": {
297
+ invoke: {
298
+ src: "listenForGraphqlOpen",
299
+ input: ({ context }) => ({ graphqlPort: context.graphqlPort }),
300
+ },
301
+ },
302
+ },
303
+ initial: "Not Started",
304
+ },
249
305
  },
250
306
  type: "parallel",
251
307
  },
@@ -24,6 +24,7 @@ export const jsMachine = setup({
24
24
  const appDir = "src/app";
25
25
  const assetsDir = "src/assets";
26
26
  const webhooksDir = "src/webhooks";
27
+ const eventsDir = "src/events";
27
28
  const log = (message) => {
28
29
  sendBack({ type: "Log", message });
29
30
  };
@@ -72,6 +73,7 @@ export const jsMachine = setup({
72
73
  const js = await generateServerEntry({
73
74
  appDirAbsolute: path.resolve(appDir),
74
75
  webhooksDirAbsolute: path.resolve(webhooksDir),
76
+ eventDirAbsolute: path.resolve(eventsDir),
75
77
  log,
76
78
  });
77
79
  if (js === lastJS) {