@hitch42/applet 0.1.0-beta.2

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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_internal/client.d.ts +7 -0
  3. package/dist/_internal/client.js +60 -0
  4. package/dist/_internal/server.d.ts +79 -0
  5. package/dist/_internal/server.js +232 -0
  6. package/dist/applet.d.ts +1 -0
  7. package/dist/applet.js +160 -0
  8. package/dist/auth-DaVK0k5R.js +350 -0
  9. package/dist/batch-Qa4B4-Yp.d.ts +30 -0
  10. package/dist/build-BRkmrUcj.js +74 -0
  11. package/dist/build-CMluoV6q.js +2 -0
  12. package/dist/client.d.ts +48 -0
  13. package/dist/client.js +38 -0
  14. package/dist/contract-DMrpbaLJ.d.ts +23 -0
  15. package/dist/deploy-DKB-Tef7.js +2416 -0
  16. package/dist/generate-BFmIzHaR.js +484 -0
  17. package/dist/generate-CefnB-bp.js +2 -0
  18. package/dist/index.d.ts +74 -0
  19. package/dist/index.js +136 -0
  20. package/dist/init-CQRsnd3i.js +240 -0
  21. package/dist/lazy-client-x0DCI98S.js +64 -0
  22. package/dist/load-config-BnxocLAQ.js +56 -0
  23. package/dist/logs-m00pzr_O.js +183 -0
  24. package/dist/manifest-BQCAxATS.d.ts +81 -0
  25. package/dist/manifest-DyoJ1MIL.js +174 -0
  26. package/dist/project-CEmDnlKa.js +346 -0
  27. package/dist/react.d.ts +37 -0
  28. package/dist/react.js +517 -0
  29. package/dist/server.d.ts +46 -0
  30. package/dist/server.js +104 -0
  31. package/dist/upload-DRbVhuDm.js +114 -0
  32. package/dist/upload-YbRKEqR6.d.ts +112 -0
  33. package/dist/variables-CP7Ce3Np.js +133 -0
  34. package/dist/vite.d.ts +63 -0
  35. package/dist/vite.js +358 -0
  36. package/dist/workflow-CTo2kZF1.js +51 -0
  37. package/dist/workflow-DurM6Fwx.d.ts +28 -0
  38. package/package.json +104 -0
  39. package/scaffold/.agents/skills/hitch-applets/SKILL.md +137 -0
  40. package/scaffold/.agents/skills/hitch-applets/references/client.md +73 -0
  41. package/scaffold/.agents/skills/hitch-applets/references/files.md +109 -0
  42. package/scaffold/.agents/skills/hitch-applets/references/manifest.md +171 -0
  43. package/scaffold/.agents/skills/hitch-applets/references/markdown.md +37 -0
  44. package/scaffold/.agents/skills/hitch-applets/references/records.md +152 -0
  45. package/scaffold/.agents/skills/hitch-applets/references/schedules.md +86 -0
  46. package/scaffold/.agents/skills/hitch-applets/references/server.md +98 -0
  47. package/scaffold/.agents/skills/hitch-applets/references/workflows.md +73 -0
  48. package/scaffold/AGENTS.md +38 -0
  49. package/scaffold/applet/components.json +25 -0
  50. package/scaffold/applet/src/client/components/ui/alert.tsx +73 -0
  51. package/scaffold/applet/src/client/components/ui/avatar.tsx +100 -0
  52. package/scaffold/applet/src/client/components/ui/badge.tsx +55 -0
  53. package/scaffold/applet/src/client/components/ui/button.tsx +90 -0
  54. package/scaffold/applet/src/client/components/ui/card.tsx +85 -0
  55. package/scaffold/applet/src/client/components/ui/checkbox.tsx +35 -0
  56. package/scaffold/applet/src/client/components/ui/dialog.tsx +164 -0
  57. package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +235 -0
  58. package/scaffold/applet/src/client/components/ui/input-group.tsx +144 -0
  59. package/scaffold/applet/src/client/components/ui/input.tsx +22 -0
  60. package/scaffold/applet/src/client/components/ui/label.tsx +29 -0
  61. package/scaffold/applet/src/client/components/ui/popover.tsx +74 -0
  62. package/scaffold/applet/src/client/components/ui/select.tsx +243 -0
  63. package/scaffold/applet/src/client/components/ui/separator.tsx +25 -0
  64. package/scaffold/applet/src/client/components/ui/sheet.tsx +177 -0
  65. package/scaffold/applet/src/client/components/ui/skeleton.tsx +13 -0
  66. package/scaffold/applet/src/client/components/ui/spinner.tsx +16 -0
  67. package/scaffold/applet/src/client/components/ui/switch.tsx +41 -0
  68. package/scaffold/applet/src/client/components/ui/table.tsx +114 -0
  69. package/scaffold/applet/src/client/components/ui/tabs.tsx +78 -0
  70. package/scaffold/applet/src/client/components/ui/textarea.tsx +23 -0
  71. package/scaffold/applet/src/client/lib/utils.ts +6 -0
  72. package/scaffold/applet/src/client/styles/globals.css +136 -0
package/dist/vite.js ADDED
@@ -0,0 +1,358 @@
1
+ import { i as APPLET_CODE_PATTERN, n as appletManifestSchema, r as appletSourceDefinitionSchema } from "./manifest-DyoJ1MIL.js";
2
+ import { i as resolveInstanceUrl, n as organizationInstanceUrl } from "./auth-DaVK0k5R.js";
3
+ import { c as writeClientEntry, i as writeRegisterDeclaration, l as writeManifestGen, r as syncContract, s as writeAppletRoot, u as writeServerEntry } from "./generate-BFmIzHaR.js";
4
+ import { i as resolveWorkspaceOrg } from "./load-config-BnxocLAQ.js";
5
+ import { existsSync } from "node:fs";
6
+ import { appendFile, readFile, readdir, rm, writeFile } from "node:fs/promises";
7
+ import { basename, relative, resolve } from "node:path";
8
+ import { cloudflare } from "@cloudflare/vite-plugin";
9
+ import { tanstackRouter } from "@tanstack/router-plugin/vite";
10
+ import react from "@vitejs/plugin-react";
11
+ //#region src/vite.ts
12
+ const APPLET_COMPATIBILITY_DATE = "2026-07-14";
13
+ const APPLET_COMPATIBILITY_FLAGS = ["nodejs_compat"];
14
+ function clientBootstrapScript(parentOrigin, appletCode) {
15
+ return `(function () {
16
+ var parentOrigin = ${parentOrigin === void 0 ? "null" : JSON.stringify(parentOrigin)};
17
+ var appletCode = ${JSON.stringify(appletCode)};
18
+ var appletCodePattern = ${APPLET_CODE_PATTERN.toString()};
19
+
20
+ Object.defineProperty(window, "__hitchCacheContext", {
21
+ configurable: false,
22
+ writable: false,
23
+ value: window.parent !== window && parentOrigin !== null
24
+ ? fetch("/__applet/context/" + encodeURIComponent(appletCode), {
25
+ credentials: "same-origin",
26
+ headers: { accept: "application/json" }
27
+ }).then(function (response) {
28
+ if (!response.ok) throw new Error("Applet cache context returned " + response.status + ".");
29
+ return response.json();
30
+ }).catch(function (error) {
31
+ console.error("Could not load the applet cache context.", error);
32
+ return null;
33
+ })
34
+ : null
35
+ });
36
+
37
+ function applyTheme(theme) {
38
+ document.documentElement.classList.toggle("dark", theme === "dark");
39
+ }
40
+
41
+ function reportLocation() {
42
+ if (window.parent === window || parentOrigin === null) return;
43
+ window.parent.postMessage(
44
+ {
45
+ type: "hitch:location",
46
+ href: window.location.pathname + window.location.search + window.location.hash
47
+ },
48
+ parentOrigin
49
+ );
50
+ }
51
+
52
+ var params = new URLSearchParams(window.location.search);
53
+ var theme = params.get("_hitch_theme");
54
+ var dark = theme === "dark" ||
55
+ (theme !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches);
56
+ applyTheme(dark ? "dark" : "light");
57
+ if (params.has("_hitch_theme")) {
58
+ var cleanUrl = new URL(window.location.href);
59
+ cleanUrl.searchParams.delete("_hitch_theme");
60
+ window.history.replaceState(window.history.state, "", cleanUrl);
61
+ }
62
+
63
+ var pushState = window.history.pushState;
64
+ window.history.pushState = function () {
65
+ var result = pushState.apply(window.history, arguments);
66
+ reportLocation();
67
+ return result;
68
+ };
69
+ var replaceState = window.history.replaceState;
70
+ window.history.replaceState = function () {
71
+ var result = replaceState.apply(window.history, arguments);
72
+ reportLocation();
73
+ return result;
74
+ };
75
+ window.addEventListener("popstate", reportLocation);
76
+ window.addEventListener("hashchange", reportLocation);
77
+
78
+ window.addEventListener("message", function (event) {
79
+ if (
80
+ event.source !== window.parent ||
81
+ (parentOrigin !== null && event.origin !== parentOrigin) ||
82
+ !event.data
83
+ ) return;
84
+
85
+ if (
86
+ parentOrigin !== null &&
87
+ event.data.type === "hitch:location-request"
88
+ ) {
89
+ reportLocation();
90
+ return;
91
+ }
92
+
93
+ if (
94
+ event.data.type === "hitch:theme" &&
95
+ (event.data.theme === "light" || event.data.theme === "dark")
96
+ ) {
97
+ applyTheme(event.data.theme);
98
+ return;
99
+ }
100
+
101
+ if (
102
+ parentOrigin !== null &&
103
+ event.data.type === "hitch:navigation-probe" &&
104
+ typeof event.data.requestId === "string"
105
+ ) {
106
+ window.parent.postMessage(
107
+ { type: "hitch:navigation-ready", requestId: event.data.requestId },
108
+ parentOrigin
109
+ );
110
+ return;
111
+ }
112
+
113
+ if (
114
+ parentOrigin === null ||
115
+ event.data.type !== "hitch:navigate" ||
116
+ typeof event.data.appletCode !== "string" ||
117
+ !appletCodePattern.test(event.data.appletCode) ||
118
+ (event.data.theme !== "light" && event.data.theme !== "dark")
119
+ ) return;
120
+
121
+ var nextHref = typeof event.data.href === "string"
122
+ ? event.data.href
123
+ : "/" + event.data.appletCode;
124
+ var nextUrl = new URL(nextHref, window.location.origin);
125
+ var nextAppletPath = "/" + event.data.appletCode;
126
+ if (
127
+ nextUrl.origin !== window.location.origin ||
128
+ (nextUrl.pathname !== nextAppletPath && !nextUrl.pathname.startsWith(nextAppletPath + "/"))
129
+ ) return;
130
+
131
+ applyTheme(event.data.theme);
132
+ if (
133
+ nextUrl.pathname + nextUrl.search + nextUrl.hash ===
134
+ window.location.pathname + window.location.search + window.location.hash
135
+ ) return;
136
+ nextUrl.searchParams.set("_hitch_theme", event.data.theme);
137
+ window.location.replace(nextUrl);
138
+ });
139
+ })();`;
140
+ }
141
+ function applet(sourceDefinition, options = {}) {
142
+ const parsedSourceDefinition = appletSourceDefinitionSchema.safeParse(sourceDefinition);
143
+ if (!parsedSourceDefinition.success) {
144
+ const raw = sourceDefinition;
145
+ const orgMessage = typeof raw === "object" && raw !== null && "org" in raw ? " org now lives in hitch.config.ts, not in applet.manifest.ts." : "";
146
+ throw new Error(`applet(): ${parsedSourceDefinition.error.message}${orgMessage}`);
147
+ }
148
+ const { dependencies: _dependencies, devDependencies: _devDependencies, ...definition } = parsedSourceDefinition.data;
149
+ const parsedDefinition = { data: definition };
150
+ let appletConfig;
151
+ let buildRootOutDir;
152
+ let clearedOutDir = false;
153
+ let contractSync;
154
+ let themeStylesheetUrl;
155
+ const virtualRouteConfig = {
156
+ type: "root",
157
+ file: "__root.tsx",
158
+ children: [{
159
+ type: "physical",
160
+ directory: ".",
161
+ pathPrefix: parsedDefinition.data.code
162
+ }]
163
+ };
164
+ const resolveAppletConfig = (root) => {
165
+ const resolvedRoot = resolve(root);
166
+ if (appletConfig?.root === resolvedRoot) return appletConfig;
167
+ const directoryCode = basename(resolvedRoot);
168
+ if (directoryCode !== parsedDefinition.data.code) throw new Error(`Applet directory "${directoryCode}" does not match code "${parsedDefinition.data.code}". Rename the directory or change the code.`);
169
+ const org = resolveWorkspaceOrg(resolvedRoot);
170
+ const routesDirectory = resolve(resolvedRoot, "src/client/routes");
171
+ const hasClient = existsSync(routesDirectory);
172
+ const hasServer = existsSync(resolve(resolvedRoot, "src/server/main.ts"));
173
+ if (!hasServer && (parsedDefinition.data.schedules?.length ?? 0) > 0) throw new Error("An applet with schedules must have src/server/main.ts.");
174
+ const legacyMain = resolve(resolvedRoot, "src/client/main.tsx");
175
+ const indexHtml = resolve(resolvedRoot, "index.html");
176
+ if (hasClient && existsSync(legacyMain)) throw new Error(`Applet client entrypoint must use file-based routes. Move your component into src/client/routes/index.tsx: ${legacyMain}`);
177
+ if (hasClient && !existsSync(indexHtml)) throw new Error(`Missing index.html at ${indexHtml}. Create it with <div id="root"></div> and <script type="module" src="/.hitch/entry.gen.tsx"><\/script>.`);
178
+ appletConfig = {
179
+ root: resolvedRoot,
180
+ manifest: appletManifestSchema.parse({
181
+ org,
182
+ ...parsedDefinition.data
183
+ }),
184
+ routesDirectory,
185
+ hasClient,
186
+ hasAuthorRoot: hasClient && existsSync(resolve(routesDirectory, "__root.tsx")),
187
+ hasServer,
188
+ hasRouter: existsSync(resolve(resolvedRoot, "src/client/router.tsx"))
189
+ };
190
+ if (hasClient) virtualRouteConfig.file = appletConfig.hasAuthorRoot ? "__root.tsx" : relative(routesDirectory, resolve(resolvedRoot, ".hitch/appletRoot.gen.tsx"));
191
+ return appletConfig;
192
+ };
193
+ const requireAppletConfig = () => {
194
+ if (appletConfig === void 0) throw new Error("Vite did not provide an applet project root.");
195
+ return appletConfig;
196
+ };
197
+ let apiManifest;
198
+ const callerRoot = resolve(".");
199
+ if (basename(callerRoot) === parsedDefinition.data.code && basename(resolve(callerRoot, "..")) === "applets") apiManifest = appletManifestSchema.parse({
200
+ org: resolveWorkspaceOrg(callerRoot),
201
+ ...parsedDefinition.data
202
+ });
203
+ const orchestrator = {
204
+ name: "hitch-applet",
205
+ enforce: "pre",
206
+ sharedDuringBuild: true,
207
+ api: { get manifest() {
208
+ if (apiManifest === void 0) throw new Error("Applet manifest is not available before Vite resolves the project.");
209
+ return apiManifest;
210
+ } },
211
+ async config(config, environment) {
212
+ const { root, manifest, hasClient, hasAuthorRoot, hasServer, hasRouter } = resolveAppletConfig(config.root ?? resolve("."));
213
+ apiManifest = manifest;
214
+ buildRootOutDir = resolve(root, config.build?.outDir ?? "dist");
215
+ if (environment.command === "build" && !clearedOutDir) {
216
+ await rm(buildRootOutDir, {
217
+ force: true,
218
+ recursive: true
219
+ });
220
+ clearedOutDir = true;
221
+ }
222
+ if (environment.command === "build") themeStylesheetUrl = `${organizationInstanceUrl(await resolveInstanceUrl(void 0, options.store), manifest.org)}/theme.css`;
223
+ if (environment.command === "build" && manifest.objects.length > 0) {
224
+ contractSync ??= syncContract(resolve(root, ".hitch"), manifest, {
225
+ ...options,
226
+ changesetId: (options.store?.env ?? process.env).HITCH_CHANGESET_ID
227
+ });
228
+ await contractSync;
229
+ }
230
+ const hasHitchGen = existsSync(resolve(root, ".hitch/index.ts"));
231
+ await Promise.all([
232
+ writeManifestGen(resolve(root, ".hitch"), manifest),
233
+ hasClient ? writeClientEntry(root, {
234
+ appletCode: manifest.code,
235
+ hasHitchGen,
236
+ hasRouter
237
+ }) : void 0,
238
+ hasClient && !hasAuthorRoot ? writeAppletRoot(root) : void 0,
239
+ hasServer ? writeServerEntry(root, hasHitchGen, manifest.code) : void 0,
240
+ hasHitchGen ? void 0 : writeRegisterDeclaration(resolve(root, ".hitch"))
241
+ ]);
242
+ return { base: `/${manifest.code}/` };
243
+ },
244
+ configResolved(config) {
245
+ apiManifest = resolveAppletConfig(config.root).manifest;
246
+ },
247
+ configureServer(server) {
248
+ const { manifest, root } = requireAppletConfig();
249
+ const mount = `/${manifest.code}`;
250
+ const apiPrefix = `${mount}/api`;
251
+ server.middlewares.use((request, response, next) => {
252
+ const url = request.url ?? "";
253
+ if (url === "/_hitch/client.js" || url.startsWith("/_hitch/client.js?")) {
254
+ response.statusCode = 200;
255
+ response.setHeader("Cache-Control", "no-store");
256
+ response.setHeader("Content-Type", "application/javascript; charset=utf-8");
257
+ response.end("// PostHog is disabled in standalone applet development.\n");
258
+ return;
259
+ }
260
+ if (url === "/theme.css" || url.startsWith("/theme.css?")) {
261
+ readFile(resolve(root, ".hitch/theme.css")).then((theme) => {
262
+ response.statusCode = 200;
263
+ response.setHeader("Cache-Control", "no-cache");
264
+ response.setHeader("Content-Type", "text/css; charset=utf-8");
265
+ response.end(theme);
266
+ }, (error) => {
267
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
268
+ next();
269
+ return;
270
+ }
271
+ next(error);
272
+ });
273
+ return;
274
+ }
275
+ if (url === apiPrefix || url.startsWith(`${apiPrefix}/`) || url.startsWith(`${apiPrefix}?`)) request.url = url.slice(mount.length);
276
+ next();
277
+ });
278
+ },
279
+ transformIndexHtml: {
280
+ order: "post",
281
+ handler(html) {
282
+ const parentOrigin = themeStylesheetUrl === void 0 ? void 0 : new URL(themeStylesheetUrl).origin;
283
+ return {
284
+ html: themeStylesheetUrl === void 0 ? html : html.replace("href=\"/theme.css\"", `href="${themeStylesheetUrl}"`),
285
+ tags: [{
286
+ tag: "script",
287
+ attrs: {
288
+ async: true,
289
+ "vite-ignore": true,
290
+ src: "/_hitch/client.js"
291
+ },
292
+ injectTo: "head-prepend"
293
+ }, {
294
+ tag: "script",
295
+ children: clientBootstrapScript(parentOrigin, requireAppletConfig().manifest.code),
296
+ injectTo: "head-prepend"
297
+ }]
298
+ };
299
+ }
300
+ },
301
+ writeBundle: {
302
+ order: "post",
303
+ async handler(outputOptions, bundle) {
304
+ if (buildRootOutDir === void 0) throw new Error("Applet build output is not configured.");
305
+ if (this.environment.name === "client") {
306
+ await writeFile(resolve(buildRootOutDir, "manifest.json"), `${JSON.stringify(requireAppletConfig().manifest, null, 2)}\n`);
307
+ if (!requireAppletConfig().hasServer) await appendFile(resolve(buildRootOutDir, ".assetsignore"), "manifest.json\n");
308
+ console.log(`client → ${relative(requireAppletConfig().root, outputOptions.dir ?? buildRootOutDir)}/`);
309
+ return;
310
+ }
311
+ if (!requireAppletConfig().hasServer || this.environment.name !== "worker") return;
312
+ if (!requireAppletConfig().hasClient) await writeFile(resolve(buildRootOutDir, "manifest.json"), `${JSON.stringify(requireAppletConfig().manifest, null, 2)}\n`);
313
+ const workerDir = outputOptions.dir ?? resolve(buildRootOutDir, "worker");
314
+ const mainModule = Object.values(bundle).find((output) => output.type === "chunk" && output.isEntry)?.fileName;
315
+ const strayModules = (await readdir(workerDir, { recursive: true })).filter((path) => /\.(js|mjs)$/.test(path) && path !== mainModule);
316
+ if (strayModules.length > 0) throw new Error(`The applet server must bundle to a single worker/index.js module; found: ${strayModules.join(", ")}. Avoid dynamic import() in server code.`);
317
+ console.log(`server → ${relative(requireAppletConfig().root, resolve(workerDir, mainModule ?? ""))}`);
318
+ }
319
+ }
320
+ };
321
+ const cloudflarePlugins = cloudflare({
322
+ get assetsOnly() {
323
+ return !requireAppletConfig().hasServer;
324
+ },
325
+ config: (workerConfig) => {
326
+ const { root, manifest, hasClient, hasServer } = requireAppletConfig();
327
+ return {
328
+ ...workerConfig,
329
+ name: manifest.code.replaceAll("_", "-"),
330
+ compatibility_date: APPLET_COMPATIBILITY_DATE,
331
+ compatibility_flags: APPLET_COMPATIBILITY_FLAGS,
332
+ ...hasClient ? { assets: {
333
+ not_found_handling: "single-page-application",
334
+ run_worker_first: ["/api/*"]
335
+ } } : {},
336
+ ...hasServer ? { main: resolve(root, ".hitch/entry.server.gen.ts") } : {}
337
+ };
338
+ },
339
+ get viteEnvironment() {
340
+ return requireAppletConfig().hasServer ? { name: "worker" } : void 0;
341
+ }
342
+ }).map((plugin) => ({
343
+ ...plugin,
344
+ apply: ((_config, { mode }) => mode !== "test")
345
+ }));
346
+ return [
347
+ orchestrator,
348
+ ...existsSync(resolve("src/client/routes")) ? [tanstackRouter({
349
+ generatedRouteTree: ".hitch/routeTree.gen.ts",
350
+ routesDirectory: "src/client/routes",
351
+ routeFileIgnorePattern: "__root",
352
+ virtualRouteConfig
353
+ }), react()] : [],
354
+ cloudflarePlugins
355
+ ];
356
+ }
357
+ //#endregion
358
+ export { applet };
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ //#region src/server/identity.ts
4
+ const requestContext = new AsyncLocalStorage();
5
+ function runWithRequestContext(context, fn) {
6
+ return requestContext.run(context, fn);
7
+ }
8
+ function tryGetRequestContext() {
9
+ return requestContext.getStore();
10
+ }
11
+ function getRequestContext() {
12
+ const context = requestContext.getStore();
13
+ if (context === void 0) throw new Error("Hitch request context is unavailable outside a request.");
14
+ return context;
15
+ }
16
+ function getIdentityBearer(identity) {
17
+ return identity.kind === "user" ? identity.sessionToken : identity.token;
18
+ }
19
+ //#endregion
20
+ //#region src/server/workflow.ts
21
+ const DURATION_MULTIPLIERS = {
22
+ milliseconds: 1,
23
+ seconds: 1e3,
24
+ minutes: 6e4,
25
+ hours: 36e5,
26
+ days: 864e5,
27
+ weeks: 6048e5
28
+ };
29
+ const MAX_DURATION_MS = 365 * DURATION_MULTIPLIERS.days;
30
+ const unit = z.number().nonnegative().optional();
31
+ const workflowDurationSchema = z.strictObject({
32
+ milliseconds: unit,
33
+ seconds: unit,
34
+ minutes: unit,
35
+ hours: unit,
36
+ days: unit,
37
+ weeks: unit
38
+ }).refine((duration) => sumMilliseconds(duration) <= MAX_DURATION_MS, "must not exceed 365 days");
39
+ function sumMilliseconds(duration) {
40
+ return Object.entries(DURATION_MULTIPLIERS).reduce((total, [key, multiplier]) => total + (duration[key] ?? 0) * multiplier, 0);
41
+ }
42
+ function toMilliseconds(duration) {
43
+ const parsed = workflowDurationSchema.safeParse(duration);
44
+ if (!parsed.success) throw new Error(`Invalid workflow duration: ${z.prettifyError(parsed.error)}`);
45
+ return sumMilliseconds(parsed.data);
46
+ }
47
+ function createWorkflow(run) {
48
+ return { run };
49
+ }
50
+ //#endregion
51
+ export { getRequestContext as a, getIdentityBearer as i, createWorkflow as n, runWithRequestContext as o, toMilliseconds as r, tryGetRequestContext as s, MAX_DURATION_MS as t };
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ //#region src/server/workflow.d.ts
3
+ type WorkflowDuration = {
4
+ milliseconds?: number;
5
+ seconds?: number;
6
+ minutes?: number;
7
+ hours?: number;
8
+ days?: number;
9
+ weeks?: number;
10
+ };
11
+ type WorkflowStep = {
12
+ do<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
13
+ do<T>(name: string, options: {
14
+ retries?: {
15
+ limit: number;
16
+ delay: WorkflowDuration;
17
+ backoff?: "constant" | "linear" | "exponential";
18
+ };
19
+ }, fn: () => T | Promise<T>): Promise<T>;
20
+ sleep(name: string, duration: WorkflowDuration): Promise<void>;
21
+ };
22
+ type WorkflowDefinition<Payload, Result> = {
23
+ readonly run: (step: WorkflowStep, payload: Payload) => Promise<Result>;
24
+ };
25
+ type AnyWorkflowDefinition = WorkflowDefinition<any, any>;
26
+ declare function createWorkflow<Payload, Result>(run: (step: WorkflowStep, payload: Payload) => Promise<Result>): WorkflowDefinition<Payload, Result>;
27
+ //#endregion
28
+ export { createWorkflow as a, WorkflowStep as i, WorkflowDefinition as n, WorkflowDuration as r, AnyWorkflowDefinition as t };
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@hitch42/applet",
3
+ "version": "0.1.0-beta.2",
4
+ "license": "MIT",
5
+ "bin": {
6
+ "applet": "./dist/applet.js"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "scaffold"
11
+ ],
12
+ "type": "module",
13
+ "exports": {
14
+ ".": {
15
+ "source": "./src/index.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./server": {
19
+ "source": "./src/author/server.ts",
20
+ "default": "./dist/server.js"
21
+ },
22
+ "./client": {
23
+ "source": "./src/author/client.ts",
24
+ "default": "./dist/client.js"
25
+ },
26
+ "./react": {
27
+ "source": "./src/react/index.ts",
28
+ "default": "./dist/react.js"
29
+ },
30
+ "./vite": {
31
+ "source": "./src/vite.ts",
32
+ "default": "./dist/vite.js"
33
+ },
34
+ "./_internal/client": {
35
+ "source": "./src/internal/client.ts",
36
+ "default": "./dist/_internal/client.js"
37
+ },
38
+ "./_internal/server": {
39
+ "source": "./src/internal/server.ts",
40
+ "default": "./dist/_internal/server.js"
41
+ }
42
+ },
43
+ "dependencies": {
44
+ "@cloudflare/vite-plugin": "^1.51.1",
45
+ "@fontsource-variable/inter": "^5.3.0",
46
+ "@hey-api/openapi-ts": "0.0.0-next-20260824173136",
47
+ "@orpc/client": "^1.15.0",
48
+ "@orpc/contract": "^1.15.0",
49
+ "@orpc/openapi-client": "^1.15.0",
50
+ "@orpc/tanstack-query": "^1.15.0",
51
+ "@tailwindcss/vite": "^4.3.0",
52
+ "@tanstack/query-async-storage-persister": "5.101.4",
53
+ "@tanstack/react-query-persist-client": "5.101.4",
54
+ "@tanstack/router-cli": "^1.167.24",
55
+ "@tanstack/router-plugin": "^1.168.23",
56
+ "@tiptap/core": "^3.30.3",
57
+ "@tiptap/extension-image": "^3.30.3",
58
+ "@tiptap/extension-list": "^3.30.3",
59
+ "@tiptap/extension-table": "^3.30.3",
60
+ "@tiptap/extensions": "^3.30.3",
61
+ "@tiptap/markdown": "^3.30.3",
62
+ "@tiptap/react": "^3.30.3",
63
+ "@tiptap/starter-kit": "^3.30.3",
64
+ "@vitejs/plugin-react": "^6.0.3",
65
+ "class-variance-authority": "^0.7.1",
66
+ "clsx": "^2.1.1",
67
+ "croner": "^10.0.1",
68
+ "lucide-react": "^1.33.0",
69
+ "react-aria-components": "^1.20.0",
70
+ "shadcn": "^4.16.1",
71
+ "streamdown": "^2.6.0",
72
+ "tailwind-merge": "^3.6.0",
73
+ "tailwindcss": "^4.3.0",
74
+ "tw-animate-css": "^1.4.0",
75
+ "vite": "8.3.0",
76
+ "vitest": "4.1.11",
77
+ "wrangler": "^4.120.0",
78
+ "zod": "^4.4.3"
79
+ },
80
+ "peerDependencies": {
81
+ "@orpc/server": "^1.15.0",
82
+ "@tanstack/react-query": "5.101.4",
83
+ "@tanstack/react-router": "^1.170.18",
84
+ "@types/node": "^24",
85
+ "@types/react": "^19.2.0",
86
+ "@types/react-dom": "^19.2.0",
87
+ "react": "^19.2.8",
88
+ "react-dom": "^19.2.8",
89
+ "typescript": "^7.0.2"
90
+ },
91
+ "engines": {
92
+ "node": ">=24"
93
+ },
94
+ "repository": {
95
+ "type": "git",
96
+ "url": "git+https://github.com/hitch-42/hitch.git",
97
+ "directory": "packages/applet"
98
+ },
99
+ "publishConfig": {
100
+ "access": "public",
101
+ "registry": "https://registry.npmjs.org",
102
+ "tag": "beta"
103
+ }
104
+ }
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: hitch-applets
3
+ description: Build, change, test, and debug Hitch applets. Use for applet routes, Hitch data access, applet servers, contract and theme generation, local development, deployment, and applet UI work.
4
+ ---
5
+
6
+ # Hitch Applets
7
+
8
+ Build small applets against an organization's Hitch data. Keep each applet focused on one related workflow.
9
+
10
+ Treat each applet as an isolated microfrontend. Keep it separate from the data storage layer, and access Hitch data through the SDK.
11
+
12
+ ## Go deeper
13
+
14
+ This file holds the common rules. Read each matching reference before you edit an applet. Paths are relative to this file.
15
+
16
+ | Read this | When the task involves |
17
+ | ------------------------- | -------------------------------------------------------------------------------------------------------- |
18
+ | `references/client.md` | Client routes, navigation, page layout, forms, controls, or other user interface work |
19
+ | `references/records.md` | Listing, querying, creating, or updating records; filters, includes, aggregates; a missing record method |
20
+ | `references/files.md` | Uploading or reading files, attachment attributes, download links |
21
+ | `references/markdown.md` | Rendering or editing markdown, image uploads, Hitch file links |
22
+ | `references/server.md` | The applet server: secrets, env variables, external services, the `api` client |
23
+ | `references/schedules.md` | Cron schedules and background runs |
24
+ | `references/workflows.md` | Multi-step server work: `createWorkflow`, `step.do`, `step.sleep`, starting, reading, and listing runs |
25
+ | `references/manifest.md` | The applet definition in `applet.manifest.ts`: objects, access, variables, and packages |
26
+
27
+ ## Work with the workspace
28
+
29
+ - Run package commands with `pnpm`.
30
+ - If `package.json` is absent, read `sdk` from `hitch.config.ts`. Run `pnpm dlx <sdk> bootstrap` from the workspace root.
31
+ - Run `pnpm exec applet new <code>` from the workspace root to create a full applet.
32
+ - Add `--client-only` or `--server-only` when the applet needs only one part.
33
+ - Use `--client-only` for a user interface that uses the current user's Hitch access and needs no server.
34
+ - Use `--server-only` for an API or schedule that needs no user interface.
35
+ - The `new` command creates the applet before it installs packages and generates Hitch types.
36
+ - If installation or generation fails, inspect the created applet. Do not run `new` again.
37
+ - Run `pnpm install`, then rerun `pnpm exec applet generate` from the applet directory.
38
+ - If the user selects `--no-install`, do not run package scripts until applet dependencies are available.
39
+ - If generation cannot select a saved login, ask the user to fix the login or provide `HITCH_API_KEY`.
40
+ - Do not install or run a different Hitch CLI to repair applet generation.
41
+ - Run `pnpm --dir applets/<code> run generate` after a Hitch object, operation, or theme changes.
42
+ - Run generation at the start of a session when the task depends on current Hitch types.
43
+ - Run `pnpm --dir applets/<code> run dev` and `pnpm --dir applets/<code> run build` from the workspace root.
44
+ - Run `pnpm --dir applets/<code> run deploy` only when the user requests a deployment.
45
+ - Edit only `hitch.config.ts`, `applet.manifest.ts`, and files in `src`.
46
+ - Treat all other workspace files as generated output. The applet CLI replaces them.
47
+ - Remember that generation refreshes the local theme copy. Deployed applets load the public org theme from its Hitch `.com` URL.
48
+
49
+ ## Use Vite and Vitest
50
+
51
+ - Import configuration APIs from `vite`.
52
+ - Import test APIs from `vitest`.
53
+ - Keep Vite configuration in the generated `vite.config.ts` file.
54
+ - Add applet packages to `dependencies` or `devDependencies` in `applet.manifest.ts`.
55
+
56
+ ## Access Hitch data
57
+
58
+ Use the client SDK unless the task needs a secret or an external service:
59
+
60
+ ```ts
61
+ import { useQuery } from "@tanstack/react-query";
62
+ import { hitch } from "@hitch42/applet/client";
63
+
64
+ const deals = useQuery(
65
+ hitch.records.deals.list.queryOptions({
66
+ input: { query: {} },
67
+ }),
68
+ );
69
+ ```
70
+
71
+ The client SDK uses the current user's identity and access. Do not add a server proxy for filtering or access control.
72
+
73
+ Use the server SDK for secrets, external services, or imperative Hitch data access:
74
+
75
+ ```ts
76
+ import { env, hitch } from "@hitch42/applet/server";
77
+ ```
78
+
79
+ Define an applet server with an oRPC router:
80
+
81
+ ```ts
82
+ import { os } from "@orpc/server";
83
+
84
+ const router = {
85
+ hello: os.handler(() => ({ hello: "World!" })),
86
+ };
87
+
88
+ export default { router };
89
+ ```
90
+
91
+ Call applet server procedures through the generated client API:
92
+
93
+ ```ts
94
+ import { useQuery } from "@tanstack/react-query";
95
+ import { api } from "@hitch42/applet/client";
96
+
97
+ const greeting = useQuery(api.hello.queryOptions());
98
+ ```
99
+
100
+ Use generated types as the source of truth. Do not add casts or local copies that bypass them.
101
+
102
+ Do not invent fields or silence type errors with casts. Ask the user when generated types exclude a requested field or operation.
103
+
104
+ ## Work with files
105
+
106
+ Upload a file with one call. The SDK creates the file record and sends the bytes directly to storage:
107
+
108
+ ```ts
109
+ import { hitch } from "@hitch42/applet/client";
110
+
111
+ const uploaded = await hitch.files.upload(file); // file is a web File
112
+ ```
113
+
114
+ Read `references/files.md` before download links, attachment attributes, or server-side uploads.
115
+
116
+ ## Diagnose with logs
117
+
118
+ - Add useful `console.<level>` calls around important server work and failures. Deployed logs often provide the clearest evidence for applet issues.
119
+ - Choose `console.log`, `console.info`, `console.warn`, or `console.error` deliberately. The console method sets the entry level used by log filters.
120
+ - Run `pnpm --dir applets/<code> exec applet logs --filter '{"type":"applet"}'` from the workspace root to inspect applet code logs.
121
+ - Include the applet type filter during diagnosis. Without it, Cloudflare invocation entries can hide the useful applet entries.
122
+ - Do not log secrets, tokens, or personal data.
123
+
124
+ ## Verify changes
125
+
126
+ Run these commands from the workspace root before deployment:
127
+
128
+ ```sh
129
+ pnpm --dir applets/<code> run generate
130
+ pnpm --dir applets/<code> run typecheck
131
+ pnpm --dir applets/<code> run test # Only when the applet contains tests.
132
+ pnpm --dir applets/<code> run build
133
+ ```
134
+
135
+ If missing packages block a check, stop package checks. Do not retry the check through another binary.
136
+
137
+ If a command fails, report the exact blocker. Do not deploy as a verification step.