@hitch42/applet 0.1.0-beta.10
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.
- package/LICENSE +21 -0
- package/dist/_internal/client.d.ts +7 -0
- package/dist/_internal/client.js +169 -0
- package/dist/_internal/server.d.ts +92 -0
- package/dist/_internal/server.js +181 -0
- package/dist/applet.d.ts +1 -0
- package/dist/applet.js +184 -0
- package/dist/batch-Qa4B4-Yp.d.ts +30 -0
- package/dist/build-BlmLiKro.js +90 -0
- package/dist/build-CcT-wNN9.js +2 -0
- package/dist/ci-0Q2mUh53.js +68 -0
- package/dist/client.d.ts +48 -0
- package/dist/client.js +38 -0
- package/dist/contract-QUvEGiRI.d.ts +23 -0
- package/dist/deploy-BbL3GObV.js +2579 -0
- package/dist/email-DBTVhNB_.js +62 -0
- package/dist/generate-XB-72Fzj.js +537 -0
- package/dist/generate-wIN3IYqL.js +2 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +251 -0
- package/dist/init-DwsXRUk-.js +244 -0
- package/dist/lazy-client-x0DCI98S.js +64 -0
- package/dist/load-config-OKZU3UKn.js +27 -0
- package/dist/login-BBO74hPC.js +12 -0
- package/dist/logs-XvK6Zs5Y.js +183 -0
- package/dist/manifest-4ZAc6HUk.js +191 -0
- package/dist/manifest-I-9s8R36.d.ts +88 -0
- package/dist/me-BW7Paary.js +32 -0
- package/dist/project-B0qNXnFu.js +536 -0
- package/dist/prompt-ChS-grYO.js +29 -0
- package/dist/query-cache-CXmAyLv3.js +82 -0
- package/dist/react.d.ts +37 -0
- package/dist/react.js +515 -0
- package/dist/schedules-BkWZsbSs.js +42 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +104 -0
- package/dist/telephony-protocol-D-nf3Gca.d.ts +76 -0
- package/dist/telephony.d.ts +77 -0
- package/dist/telephony.js +607 -0
- package/dist/upload-DKZzz5kW.d.ts +112 -0
- package/dist/upload-DRbVhuDm.js +114 -0
- package/dist/variables-0tjnAeut.js +108 -0
- package/dist/vite.d.ts +63 -0
- package/dist/vite.js +816 -0
- package/dist/workflow-CTo2kZF1.js +51 -0
- package/dist/workflow-DurM6Fwx.d.ts +28 -0
- package/dist/workflow-engine-MogejNfF.js +117 -0
- package/dist/workspace-DruAZPmV.js +390 -0
- package/package.json +112 -0
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +163 -0
- package/scaffold/.agents/skills/hitch-applets/references/client.md +73 -0
- package/scaffold/.agents/skills/hitch-applets/references/email-tracking.md +49 -0
- package/scaffold/.agents/skills/hitch-applets/references/files.md +109 -0
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +198 -0
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +37 -0
- package/scaffold/.agents/skills/hitch-applets/references/performance.md +74 -0
- package/scaffold/.agents/skills/hitch-applets/references/records.md +176 -0
- package/scaffold/.agents/skills/hitch-applets/references/schedules.md +91 -0
- package/scaffold/.agents/skills/hitch-applets/references/server.md +95 -0
- package/scaffold/.agents/skills/hitch-applets/references/workflows.md +81 -0
- package/scaffold/.agents/skills/hitch-cli/SKILL.md +129 -0
- package/scaffold/.agents/skills/hitch-data/SKILL.md +52 -0
- package/scaffold/.agents/skills/hitch-data/references/attributes.md +127 -0
- package/scaffold/.agents/skills/hitch-data/references/limits.md +64 -0
- package/scaffold/.agents/skills/hitch-theory/SKILL.md +151 -0
- package/scaffold/.agents/skills/react-best-practices/SKILL.md +127 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-effect-event-deps.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-defer-await.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-dependencies.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-parallel.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-analyzable-paths.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-conditional.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +46 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-preload.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-event-listeners.md +78 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-function-results.md +58 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-storage.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-flatmap-filter.md +51 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-length-check-first.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-request-idle-callback.md +104 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-resource-hints.md +81 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-script-defer-async.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-usetransition-loading.md +43 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-no-inline-components.md +75 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/scaffold/.agents/skills/tanstack-query/SKILL.md +136 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/automatic-invalidation-after-mutations.md +124 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/broadcast-realtime-and-multi-tab-synchronization.md +150 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/compose-query-with-tanstack-router-and-start.md +248 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/enforce-query-best-practices-with-eslint.md +167 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/persist-offline-and-restore-caches.md +181 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/query-data-and-forms.md +156 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/build-query-abstractions.md +130 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/cancel-queries-and-consume-abort-signals.md +176 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/concurrent-optimistic-updates.md +135 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/coordinate-query-execution.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/design-query-keys-and-options.md +182 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/fetch-and-observe-queries.md +193 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/implement-optimistic-updates-and-cache-writes.md +168 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/paginate-and-build-infinite-queries.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/seed-placeholder-select-and-transform-data.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/selectors-and-derived-state.md +145 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/tune-defaults-freshness-retries-and-refetching.md +172 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/understand-query-internals-and-observers.md +104 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/write-mutations-and-invalidate-related-queries.md +183 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/debug-with-devtools.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/handle-status-and-errors.md +119 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/shape-data-and-render-efficiently.md +184 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-framework-adapter-reactivity.md +185 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-suspense-and-error-boundaries.md +208 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/migrate-major-versions-and-codemods.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/prefetch-and-remove-request-waterfalls.md +228 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/setup-query-client-and-providers.md +161 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/ssr-hydration-and-streaming.md +209 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/test-query-code.md +160 -0
- package/scaffold/.agents/skills/tanstack-router/SKILL.md +134 -0
- package/scaffold/.agents/skills/tanstack-router/references/compositions/router-query.md +412 -0
- package/scaffold/.agents/skills/tanstack-router/references/react-router.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/auth-and-guards.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/code-splitting.md +318 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/data-loading.md +490 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/index.md +158 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/navigation.md +433 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/not-found-and-errors.md +436 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/path-params.md +383 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/search-params.md +343 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/ssr.md +462 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/type-safety.md +493 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-plugin.md +250 -0
- package/scaffold/.agents/skills/tanstack-router/references/virtual-file-routes.md +215 -0
- package/scaffold/.agents/skills/web-design-guidelines/SKILL.md +184 -0
- package/scaffold/.github/workflows/hitch.yml +15 -0
- package/scaffold/AGENTS.md +38 -0
- package/scaffold/applet/components.json +25 -0
- package/scaffold/applet/src/client/components/ui/alert.tsx +73 -0
- package/scaffold/applet/src/client/components/ui/avatar.tsx +100 -0
- package/scaffold/applet/src/client/components/ui/badge.tsx +55 -0
- package/scaffold/applet/src/client/components/ui/button.tsx +90 -0
- package/scaffold/applet/src/client/components/ui/card.tsx +85 -0
- package/scaffold/applet/src/client/components/ui/checkbox.tsx +35 -0
- package/scaffold/applet/src/client/components/ui/dialog.tsx +164 -0
- package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +235 -0
- package/scaffold/applet/src/client/components/ui/input-group.tsx +144 -0
- package/scaffold/applet/src/client/components/ui/input.tsx +22 -0
- package/scaffold/applet/src/client/components/ui/label.tsx +29 -0
- package/scaffold/applet/src/client/components/ui/popover.tsx +74 -0
- package/scaffold/applet/src/client/components/ui/select.tsx +243 -0
- package/scaffold/applet/src/client/components/ui/separator.tsx +25 -0
- package/scaffold/applet/src/client/components/ui/sheet.tsx +177 -0
- package/scaffold/applet/src/client/components/ui/skeleton.tsx +13 -0
- package/scaffold/applet/src/client/components/ui/spinner.tsx +17 -0
- package/scaffold/applet/src/client/components/ui/switch.tsx +41 -0
- package/scaffold/applet/src/client/components/ui/table.tsx +114 -0
- package/scaffold/applet/src/client/components/ui/tabs.tsx +78 -0
- package/scaffold/applet/src/client/components/ui/textarea.tsx +23 -0
- package/scaffold/applet/src/client/lib/utils.ts +1 -0
- package/scaffold/applet/src/client/styles/globals.css +136 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/hitch/email.ts
|
|
4
|
+
const emailTagKeySchema = z.string().regex(/^[A-Za-z][A-Za-z0-9_]{0,63}$/);
|
|
5
|
+
const emailTagsSchema = z.record(emailTagKeySchema, z.string().min(1).max(200)).refine((tags) => Object.keys(tags).length <= 10);
|
|
6
|
+
const createEmailPixelInputSchema = z.object({ tags: emailTagsSchema.optional() }).strict();
|
|
7
|
+
const createEmailPixelOutputSchema = z.object({
|
|
8
|
+
id: z.uuid(),
|
|
9
|
+
url: z.url()
|
|
10
|
+
});
|
|
11
|
+
const getEmailTrackerInputSchema = z.object({ id: z.uuid() });
|
|
12
|
+
const markEmailTrackerSentInputSchema = z.object({
|
|
13
|
+
id: z.uuid(),
|
|
14
|
+
sentAt: z.iso.datetime({ offset: true }).optional()
|
|
15
|
+
}).strict();
|
|
16
|
+
const markEmailTrackerSentBodySchema = z.object({ sentAt: z.iso.datetime({ offset: true }).optional() }).strict();
|
|
17
|
+
const emailTrackerSchema = z.object({
|
|
18
|
+
id: z.uuid(),
|
|
19
|
+
url: z.url(),
|
|
20
|
+
tags: emailTagsSchema,
|
|
21
|
+
createdAt: z.iso.datetime(),
|
|
22
|
+
sentAt: z.iso.datetime().nullable(),
|
|
23
|
+
firstHitAt: z.iso.datetime().nullable(),
|
|
24
|
+
lastHitAt: z.iso.datetime().nullable()
|
|
25
|
+
});
|
|
26
|
+
const listEmailTrackersInputSchema = z.object({
|
|
27
|
+
filter: emailTagsSchema.optional(),
|
|
28
|
+
sentAt: z.object({
|
|
29
|
+
$gte: z.iso.datetime({ offset: true }).optional(),
|
|
30
|
+
$lt: z.iso.datetime({ offset: true }).optional()
|
|
31
|
+
}).strict().optional(),
|
|
32
|
+
cursor: z.uuid().optional(),
|
|
33
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
34
|
+
}).strict();
|
|
35
|
+
const listEmailTrackersOutputSchema = z.object({
|
|
36
|
+
trackers: z.array(emailTrackerSchema),
|
|
37
|
+
nextCursor: z.uuid().nullable()
|
|
38
|
+
});
|
|
39
|
+
function emailContract(appletCode) {
|
|
40
|
+
const path = `/v1/applets/${encodeURIComponent(appletCode)}/email/tracking`;
|
|
41
|
+
return { tracking: {
|
|
42
|
+
createPixel: oc.route({
|
|
43
|
+
method: "POST",
|
|
44
|
+
path: `${path}/pixels`,
|
|
45
|
+
successStatus: 201
|
|
46
|
+
}).input(createEmailPixelInputSchema.optional()).output(createEmailPixelOutputSchema),
|
|
47
|
+
markSent: oc.route({
|
|
48
|
+
method: "POST",
|
|
49
|
+
path: `${path}/{id}/sent`
|
|
50
|
+
}).input(markEmailTrackerSentInputSchema).output(emailTrackerSchema),
|
|
51
|
+
get: oc.route({
|
|
52
|
+
method: "GET",
|
|
53
|
+
path: `${path}/{id}`
|
|
54
|
+
}).input(getEmailTrackerInputSchema).output(emailTrackerSchema),
|
|
55
|
+
list: oc.route({
|
|
56
|
+
method: "POST",
|
|
57
|
+
path: `${path}/list`
|
|
58
|
+
}).input(listEmailTrackersInputSchema.optional()).output(listEmailTrackersOutputSchema)
|
|
59
|
+
} };
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { emailTagsSchema as a, listEmailTrackersInputSchema as c, markEmailTrackerSentInputSchema as d, emailTagKeySchema as i, listEmailTrackersOutputSchema as l, createEmailPixelOutputSchema as n, emailTrackerSchema as o, emailContract as r, getEmailTrackerInputSchema as s, createEmailPixelInputSchema as t, markEmailTrackerSentBodySchema as u };
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { f as resolveInstanceUrl, l as organizationInstanceUrl, n as fetchWithOrgAuth } from "./workspace-DruAZPmV.js";
|
|
2
|
+
import { i as loadGitHubActionsBuildInputs, n as githubActionsContext } from "./ci-0Q2mUh53.js";
|
|
3
|
+
import { r as prepareAppletProject } from "./project-B0qNXnFu.js";
|
|
4
|
+
import { t as loadAppletConfig } from "./load-config-OKZU3UKn.js";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { access, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
8
|
+
import { dirname, resolve } from "node:path";
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { createClient } from "@hey-api/openapi-ts";
|
|
12
|
+
//#region src/generated.ts
|
|
13
|
+
const GENERATED_HEADER = "// Generated by @hitch42/applet — do not edit";
|
|
14
|
+
async function writeGenerated(path, source) {
|
|
15
|
+
await mkdir(dirname(path), { recursive: true });
|
|
16
|
+
await writeFile(path, source);
|
|
17
|
+
}
|
|
18
|
+
async function writeManifestGen(outDir, manifest) {
|
|
19
|
+
await writeGenerated(resolve(outDir, "manifest.gen.ts"), `${GENERATED_HEADER}\nexport default ${JSON.stringify(manifest, null, 2)} as const;\n`);
|
|
20
|
+
}
|
|
21
|
+
async function writeAppletRoot(root) {
|
|
22
|
+
await writeGenerated(resolve(root, ".hitch/appletRoot.gen.tsx"), `${GENERATED_HEADER}
|
|
23
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
24
|
+
import { createRootRouteWithContext, Outlet } from "@tanstack/react-router";
|
|
25
|
+
|
|
26
|
+
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
|
27
|
+
component: () => <Outlet />,
|
|
28
|
+
});
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
async function writeClientEntry(root, options) {
|
|
32
|
+
const hitchGenImport = options.hasHitchGen ? "import \"./index.ts\";\n" : "";
|
|
33
|
+
const routerSetup = options.hasRouter ? `import { getRouter } from "../src/client/router.tsx";
|
|
34
|
+
|
|
35
|
+
const router = getRouter({ routeTree });` : `import { QueryClient } from "@tanstack/react-query";
|
|
36
|
+
import { createRouter } from "@tanstack/react-router";
|
|
37
|
+
|
|
38
|
+
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 30_000 } } });
|
|
39
|
+
const router = createRouter({ routeTree, context: { queryClient } });`;
|
|
40
|
+
await writeGenerated(resolve(root, ".hitch/entry.gen.tsx"), `${GENERATED_HEADER}
|
|
41
|
+
${hitchGenImport}import { mountApplet } from "@hitch42/applet/_internal/client";
|
|
42
|
+
import { routeTree } from "./routeTree.gen.ts";
|
|
43
|
+
${routerSetup}
|
|
44
|
+
|
|
45
|
+
mountApplet(router, ${JSON.stringify(options.appletCode)});
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
async function writeServerEntry(root, hasHitchGen, appletCode) {
|
|
49
|
+
await writeGenerated(resolve(root, ".hitch/entry.server.gen.ts"), `${GENERATED_HEADER}
|
|
50
|
+
${hasHitchGen ? "import \"./index.ts\";\n" : ""}import server from "../src/server/main.ts";
|
|
51
|
+
import { createWorkerEntry } from "@hitch42/applet/_internal/server";
|
|
52
|
+
|
|
53
|
+
export default createWorkerEntry(server, { code: ${JSON.stringify(appletCode)} });
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/typegen/operations.ts
|
|
58
|
+
const RECORD_ROUTE = /^\/v1\/([^/]+)(?:\/(\{id\}(?:\/history)?|query|update|aggregate))?$/;
|
|
59
|
+
const COMPONENT_REF_PREFIX$1 = "#/components/schemas/";
|
|
60
|
+
const BATCH_PATH = "/v1/batch";
|
|
61
|
+
const BATCH_ITEM_PATH = "paths./v1/batch.post.requestBody.content.application/json.schema.properties.requests.items";
|
|
62
|
+
const BATCH_ID_PATH = /^\^\/v1\/([^/]+)\/\[0-9a-fA-F-\]\{36\}\$$/;
|
|
63
|
+
const VERBS = {
|
|
64
|
+
"get:": "list",
|
|
65
|
+
"post:": "create",
|
|
66
|
+
"get:{id}": "get",
|
|
67
|
+
"get:{id}/history": "history",
|
|
68
|
+
"patch:{id}": "update",
|
|
69
|
+
"delete:{id}": "delete",
|
|
70
|
+
"post:query": "query",
|
|
71
|
+
"post:update": "updateWhere",
|
|
72
|
+
"post:aggregate": "aggregate"
|
|
73
|
+
};
|
|
74
|
+
const COMPONENT_RESPONSE_STATUS = {
|
|
75
|
+
list: "200",
|
|
76
|
+
create: "201",
|
|
77
|
+
get: "200",
|
|
78
|
+
history: "200",
|
|
79
|
+
update: "200",
|
|
80
|
+
query: "200"
|
|
81
|
+
};
|
|
82
|
+
function isObject$2(value) {
|
|
83
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
84
|
+
}
|
|
85
|
+
function recordOperation(path, method) {
|
|
86
|
+
const match = RECORD_ROUTE.exec(path);
|
|
87
|
+
const objectCode = match?.[1];
|
|
88
|
+
if (objectCode === void 0 || objectCode === "batch") return void 0;
|
|
89
|
+
const key = `${method.toLowerCase()}:${match?.at(2) ?? ""}`;
|
|
90
|
+
if (!Object.hasOwn(VERBS, key)) return void 0;
|
|
91
|
+
return {
|
|
92
|
+
objectCode,
|
|
93
|
+
verb: VERBS[key]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function recordObjectCode(path) {
|
|
97
|
+
const match = RECORD_ROUTE.exec(path);
|
|
98
|
+
return match?.[1] === "batch" ? void 0 : match?.[1];
|
|
99
|
+
}
|
|
100
|
+
function withRecordOperationId(path, method, operation) {
|
|
101
|
+
const record = recordOperation(path, method);
|
|
102
|
+
return record ? {
|
|
103
|
+
...operation,
|
|
104
|
+
operationId: `${record.objectCode}.${record.verb}`
|
|
105
|
+
} : void 0;
|
|
106
|
+
}
|
|
107
|
+
function batchEntry(entry, index) {
|
|
108
|
+
const properties = isObject$2(entry) && isObject$2(entry.properties) ? entry.properties : void 0;
|
|
109
|
+
const method = isObject$2(properties?.method) ? properties.method.enum : void 0;
|
|
110
|
+
const path = isObject$2(properties?.path) ? properties.path : void 0;
|
|
111
|
+
const methodValue = Array.isArray(method) && method.length === 1 ? method[0] : void 0;
|
|
112
|
+
const enumPath = Array.isArray(path?.enum) && path.enum.length === 1 ? path.enum[0] : void 0;
|
|
113
|
+
if (Array.isArray(method) && method.length !== 1 || Array.isArray(path?.enum) && path.enum.length !== 1) throw new Error(`${BATCH_ITEM_PATH}.oneOf[${index}] expected a create, update, delete, or updateWhere batch entry, found ${describeValue$1(entry)}`);
|
|
114
|
+
if (methodValue === "POST" && typeof enumPath === "string") {
|
|
115
|
+
const updateWhere = /^\/v1\/([^/]+)\/update$/.exec(enumPath);
|
|
116
|
+
if (updateWhere?.[1] !== void 0) return {
|
|
117
|
+
objectCode: updateWhere[1],
|
|
118
|
+
verb: "updateWhere"
|
|
119
|
+
};
|
|
120
|
+
const create = /^\/v1\/([^/]+)$/.exec(enumPath);
|
|
121
|
+
if (create?.[1] !== void 0 && create[1] !== "batch") return {
|
|
122
|
+
objectCode: create[1],
|
|
123
|
+
verb: "create"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const idPath = typeof path?.pattern === "string" ? BATCH_ID_PATH.exec(path.pattern) : void 0;
|
|
127
|
+
if (idPath?.[1] !== void 0 && (methodValue === "PATCH" || methodValue === "DELETE")) return {
|
|
128
|
+
objectCode: idPath[1],
|
|
129
|
+
verb: methodValue === "PATCH" ? "update" : "delete"
|
|
130
|
+
};
|
|
131
|
+
throw new Error(`${BATCH_ITEM_PATH}.oneOf[${index}] expected a create, update, delete, or updateWhere batch entry, found ${describeValue$1(entry)}`);
|
|
132
|
+
}
|
|
133
|
+
function prepareBatchOperation(operation, manifest) {
|
|
134
|
+
if (!isObject$2(operation)) throw new Error(`paths.${BATCH_PATH}.post expected an object, found ${describeValue$1(operation)}`);
|
|
135
|
+
const requestBody = isObject$2(operation.requestBody) ? operation.requestBody : void 0;
|
|
136
|
+
const content = isObject$2(requestBody?.content) ? requestBody.content : void 0;
|
|
137
|
+
const mediaType = isObject$2(content?.["application/json"]) ? content["application/json"] : void 0;
|
|
138
|
+
const schema = isObject$2(mediaType?.schema) ? mediaType.schema : void 0;
|
|
139
|
+
const properties = isObject$2(schema?.properties) ? schema.properties : void 0;
|
|
140
|
+
const requests = isObject$2(properties?.requests) ? properties.requests : void 0;
|
|
141
|
+
const items = isObject$2(requests?.items) ? requests.items : void 0;
|
|
142
|
+
if (items === void 0) throw new Error(`${BATCH_ITEM_PATH} expected an object, found ${describeValue$1(items)}`);
|
|
143
|
+
if (items.oneOf === void 0) {
|
|
144
|
+
if (items.type === "object" && Object.keys(items).length === 1) return void 0;
|
|
145
|
+
throw new Error(`${BATCH_ITEM_PATH}.oneOf expected a non-empty array, found ${describeValue$1(items.oneOf)}`);
|
|
146
|
+
}
|
|
147
|
+
if (!Array.isArray(items.oneOf)) throw new Error(`${BATCH_ITEM_PATH}.oneOf expected an array, found ${describeValue$1(items.oneOf)}`);
|
|
148
|
+
if (items.oneOf.length === 0) throw new Error(`${BATCH_ITEM_PATH}.oneOf expected a non-empty array, found an empty array`);
|
|
149
|
+
const entries = items.oneOf.filter((entry, index) => {
|
|
150
|
+
const { objectCode, verb } = batchEntry(entry, index);
|
|
151
|
+
if (manifest === void 0) return true;
|
|
152
|
+
const object = manifest.objects.find(({ code }) => code === objectCode);
|
|
153
|
+
if (object === void 0) return false;
|
|
154
|
+
switch (verb) {
|
|
155
|
+
case "create": return object.canCreate;
|
|
156
|
+
case "delete": return object.canDelete;
|
|
157
|
+
case "update":
|
|
158
|
+
case "updateWhere": return object.readWrite.length > 0;
|
|
159
|
+
default: return true;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (entries.length === 0) return void 0;
|
|
163
|
+
items.oneOf = entries;
|
|
164
|
+
return {
|
|
165
|
+
...operation,
|
|
166
|
+
operationId: "batch"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function prepareRecordSpec(spec, manifest) {
|
|
170
|
+
if (!isObject$2(spec) || !isObject$2(spec.paths)) throw new Error("spec: expected an OpenAPI document with paths");
|
|
171
|
+
const prepared = structuredClone(spec);
|
|
172
|
+
prepared.paths = Object.fromEntries(Object.entries(prepared.paths).flatMap(([path, pathItem]) => {
|
|
173
|
+
if (path === BATCH_PATH) {
|
|
174
|
+
if (!isObject$2(pathItem)) return [];
|
|
175
|
+
const operation = prepareBatchOperation(pathItem.post, manifest);
|
|
176
|
+
return operation === void 0 ? [] : [[path, { post: operation }]];
|
|
177
|
+
}
|
|
178
|
+
const objectCode = recordObjectCode(path);
|
|
179
|
+
if (!isObject$2(pathItem)) {
|
|
180
|
+
if (objectCode === void 0) return [];
|
|
181
|
+
throw new Error(`object "${objectCode}": paths.${path} expected an object, found ${describeValue$1(pathItem)}`);
|
|
182
|
+
}
|
|
183
|
+
const operations = Object.entries(pathItem).flatMap(([method, operation]) => {
|
|
184
|
+
const record = recordOperation(path, method);
|
|
185
|
+
if (record === void 0) return [];
|
|
186
|
+
if (!isObject$2(operation)) throw new Error(`object "${record.objectCode}": paths.${path}.${method} expected an object, found ${describeValue$1(operation)}`);
|
|
187
|
+
if (record.verb in COMPONENT_RESPONSE_STATUS) {
|
|
188
|
+
const status = COMPONENT_RESPONSE_STATUS[record.verb];
|
|
189
|
+
const responses = isObject$2(operation.responses) ? operation.responses : void 0;
|
|
190
|
+
const response = isObject$2(responses?.[status]) ? responses[status] : void 0;
|
|
191
|
+
const content = isObject$2(response?.content) ? response.content : void 0;
|
|
192
|
+
const mediaType = isObject$2(content?.["application/json"]) ? content["application/json"] : void 0;
|
|
193
|
+
const schema = isObject$2(mediaType) ? mediaType.schema : void 0;
|
|
194
|
+
const ref = isObject$2(schema) ? schema.$ref : void 0;
|
|
195
|
+
if (typeof ref !== "string" || !ref.startsWith(COMPONENT_REF_PREFIX$1) || ref.slice(21).length === 0 || ref.slice(21).includes("/")) throw new Error(`object "${record.objectCode}": paths.${path}.${method}.responses.${status}.content.application/json.schema expected a component schema $ref, found ${describeValue$1(schema, true)}`);
|
|
196
|
+
}
|
|
197
|
+
const prepared = withRecordOperationId(path, method, operation);
|
|
198
|
+
return prepared ? [[method, prepared]] : [];
|
|
199
|
+
});
|
|
200
|
+
return operations.length > 0 ? [[path, Object.fromEntries(operations)]] : [];
|
|
201
|
+
}));
|
|
202
|
+
manifest?.objects.forEach(({ code, optionalAttributes }) => {
|
|
203
|
+
if (optionalAttributes === void 0 || optionalAttributes.length === 0) return;
|
|
204
|
+
const responsePath = `/v1/${code}/{id}`;
|
|
205
|
+
const response = prepared.paths[responsePath]?.get?.responses;
|
|
206
|
+
const schema = isObject$2(response) && isObject$2(response["200"]) ? response["200"].content : void 0;
|
|
207
|
+
const mediaType = isObject$2(schema) && isObject$2(schema["application/json"]) ? schema["application/json"] : void 0;
|
|
208
|
+
const ref = isObject$2(mediaType) && isObject$2(mediaType.schema) ? mediaType.schema.$ref : void 0;
|
|
209
|
+
const recordPath = `paths.${responsePath}.get.responses.200.content.application/json.schema`;
|
|
210
|
+
if (typeof ref !== "string" || !ref.startsWith(COMPONENT_REF_PREFIX$1)) throw new Error(`object "${code}": ${recordPath} expected a component schema $ref, found ${describeValue$1(mediaType)}`);
|
|
211
|
+
const componentName = ref.slice(21);
|
|
212
|
+
const components = isObject$2(prepared.components) ? prepared.components : void 0;
|
|
213
|
+
const record = (isObject$2(components?.schemas) ? components.schemas : void 0)?.[componentName];
|
|
214
|
+
const componentPath = `components.schemas.${componentName}`;
|
|
215
|
+
if (!isObject$2(record) || !isObject$2(record.properties)) throw new Error(`object "${code}": ${componentPath}.properties expected an object, found ${describeValue$1(isObject$2(record) ? record.properties : record)}`);
|
|
216
|
+
const properties = record.properties;
|
|
217
|
+
optionalAttributes.forEach((attributeCode) => {
|
|
218
|
+
const property = properties[attributeCode];
|
|
219
|
+
const propertyPath = `${componentPath}.properties.${attributeCode}`;
|
|
220
|
+
if (!isObject$2(property)) throw new Error(`object "${code}": ${propertyPath} expected an object, found ${describeValue$1(property)}`);
|
|
221
|
+
const types = Array.isArray(property.type) ? property.type : [property.type];
|
|
222
|
+
properties[attributeCode] = property.type !== void 0 ? {
|
|
223
|
+
...property,
|
|
224
|
+
type: types.includes("null") ? types : [...types, "null"],
|
|
225
|
+
...Array.isArray(property.enum) && !property.enum.includes(null) ? { enum: [...property.enum, null] } : {}
|
|
226
|
+
} : Array.isArray(property.oneOf) ? {
|
|
227
|
+
...property,
|
|
228
|
+
oneOf: [...property.oneOf, { type: "null" }]
|
|
229
|
+
} : Array.isArray(property.anyOf) && property.anyOf.some((item) => isObject$2(item) && item.type === "null") ? property : { anyOf: [property, { type: "null" }] };
|
|
230
|
+
});
|
|
231
|
+
if (Array.isArray(record.required)) record.required = record.required.filter((name) => !optionalAttributes.includes(name));
|
|
232
|
+
});
|
|
233
|
+
return prepared;
|
|
234
|
+
}
|
|
235
|
+
function describeValue$1(value, expectedRef = false) {
|
|
236
|
+
if (value === void 0) return "undefined";
|
|
237
|
+
if (value === null) return "null";
|
|
238
|
+
if (Array.isArray(value)) return "an array";
|
|
239
|
+
if (typeof value === "object") return expectedRef ? "an object without a valid $ref" : "an object";
|
|
240
|
+
return `${typeof value} ${JSON.stringify(value)}`;
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/typegen/catalog.ts
|
|
244
|
+
const COMPONENT_REF_PREFIX = "#/components/schemas/";
|
|
245
|
+
const RESERVED_ATTRIBUTE_CODES = /* @__PURE__ */ new Set([
|
|
246
|
+
"id",
|
|
247
|
+
"name",
|
|
248
|
+
"expected_version"
|
|
249
|
+
]);
|
|
250
|
+
function isObject$1(value) {
|
|
251
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
252
|
+
}
|
|
253
|
+
function describeValue(value, objectDescription = "an object") {
|
|
254
|
+
if (value === void 0) return "undefined";
|
|
255
|
+
if (value === null) return "null";
|
|
256
|
+
if (Array.isArray(value)) return "an array";
|
|
257
|
+
if (typeof value === "object") return objectDescription;
|
|
258
|
+
return `${typeof value} ${JSON.stringify(value)}`;
|
|
259
|
+
}
|
|
260
|
+
function componentName(value, path, objectCode) {
|
|
261
|
+
const ref = isObject$1(value) ? value.$ref : void 0;
|
|
262
|
+
if (typeof ref !== "string" || !ref.startsWith(COMPONENT_REF_PREFIX)) throw new Error(`object "${objectCode}": ${path} expected a component schema $ref, found ${describeValue(value, "an object without a $ref")}`);
|
|
263
|
+
const name = ref.slice(21);
|
|
264
|
+
if (name.length === 0 || name.includes("/")) throw new Error(`object "${objectCode}": ${path} expected a direct component schema $ref, found ${JSON.stringify(ref)}`);
|
|
265
|
+
return name;
|
|
266
|
+
}
|
|
267
|
+
function catalogFromSpec(spec) {
|
|
268
|
+
if (!isObject$1(spec) || !isObject$1(spec.paths) || !isObject$1(spec.components)) throw new Error("spec: expected an OpenAPI document with paths and components");
|
|
269
|
+
const schemas = spec.components.schemas;
|
|
270
|
+
if (!isObject$1(schemas)) throw new Error("spec.components.schemas: expected an object");
|
|
271
|
+
return Object.fromEntries(Object.entries(spec.paths).flatMap(([path, pathItem]) => {
|
|
272
|
+
const operation = recordOperation(path, "post");
|
|
273
|
+
if (operation?.verb !== "create") return [];
|
|
274
|
+
if (!isObject$1(pathItem)) throw new Error(`object "${operation.objectCode}": paths.${path} expected an object, found ${describeValue(pathItem)}`);
|
|
275
|
+
if (pathItem.post === void 0) return [];
|
|
276
|
+
if (!isObject$1(pathItem.post)) throw new Error(`object "${operation.objectCode}": paths.${path}.post expected an object, found ${describeValue(pathItem.post)}`);
|
|
277
|
+
const requestSchema = isObject$1(pathItem.post.requestBody) ? pathItem.post.requestBody : void 0;
|
|
278
|
+
const content = isObject$1(requestSchema?.content) ? requestSchema.content : void 0;
|
|
279
|
+
const schemaName = componentName((isObject$1(content?.["application/json"]) ? content["application/json"] : void 0)?.schema, `paths.${path}.post.requestBody.content.application/json.schema`, operation.objectCode);
|
|
280
|
+
const response = isObject$1(pathItem.post.responses) ? pathItem.post.responses["201"] : void 0;
|
|
281
|
+
const responseContent = isObject$1(response) && isObject$1(response.content) ? response.content["application/json"] : void 0;
|
|
282
|
+
const responseName = componentName(isObject$1(responseContent) ? responseContent.schema : void 0, `paths.${path}.post.responses.201.content.application/json.schema`, operation.objectCode);
|
|
283
|
+
const schema = schemas[schemaName];
|
|
284
|
+
if (!isObject$1(schema) || !isObject$1(schema.properties)) throw new Error(`object "${operation.objectCode}": components.schemas.${schemaName} expected request properties, found ${describeValue(schema, "an object without properties")}`);
|
|
285
|
+
if (!isObject$1(schemas[responseName])) throw new Error(`object "${operation.objectCode}": components.schemas.${responseName} expected an object, found ${describeValue(schemas[responseName])}`);
|
|
286
|
+
return [[operation.objectCode, Object.keys(schema.properties).filter((code) => !RESERVED_ATTRIBUTE_CODES.has(code))]];
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/typegen/guard.ts
|
|
291
|
+
function isObject(value) {
|
|
292
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
293
|
+
}
|
|
294
|
+
function guardManifestForSpec(spec, manifest) {
|
|
295
|
+
if (!isObject(spec) || !isObject(spec.paths)) throw new Error("spec: expected an OpenAPI document with paths");
|
|
296
|
+
const stamp = isObject(spec.info) ? spec.info["x-hitch-org"] : void 0;
|
|
297
|
+
if (stamp !== void 0 && stamp !== manifest.org) throw new Error(`manifest targets org "${manifest.org}" but the fetched spec belongs to org "${typeof stamp === "string" ? stamp : JSON.stringify(stamp)}". Check HITCH_API_URL and your login.`);
|
|
298
|
+
const available = [...new Set(Object.keys(spec.paths).flatMap((path) => {
|
|
299
|
+
const objectCode = recordObjectCode(path);
|
|
300
|
+
return objectCode === void 0 ? [] : [objectCode];
|
|
301
|
+
}))].sort();
|
|
302
|
+
const missing = manifest.objects.find(({ code }) => !available.includes(code));
|
|
303
|
+
if (missing !== void 0) throw new Error(`object "${missing.code}" not found in org spec (available: ${available.join(", ")})`);
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/typegen/register.ts
|
|
307
|
+
function registerFile(catalog, options) {
|
|
308
|
+
const catalogType = catalog === void 0 ? "type Catalog = Record<string, string>;" : `type Catalog = {
|
|
309
|
+
${Object.entries(catalog).map(([objectCode, attributeCodes]) => ` ${JSON.stringify(objectCode)}: ${attributeCodes.map((code) => JSON.stringify(code)).join(" | ") || "never"};`).join("\n")}
|
|
310
|
+
};`;
|
|
311
|
+
return `// Generated by @hitch42/applet — do not edit.
|
|
312
|
+
import type manifest from "./manifest.gen.ts";
|
|
313
|
+
${options.hasServer ? "import type server from \"../src/server/main.ts\";\nimport type * as serverModule from \"../src/server/main.ts\";\n" : ""}${options.hasClient ? "import type { Route as indexRoute } from \"../src/client/routes/index.tsx\";\n" : ""}${options.hasServer ? "import type { AnyRouter } from \"@orpc/server\";\n" : ""}${options.hasClient ? "import type { AnyRoute } from \"@tanstack/react-router\";\n" : ""}${options.hasServer ? "import type { WorkflowDefinition } from \"@hitch42/applet/server\";\nimport type { TelephonyMessageReceivedEvent } from \"@hitch42/applet\";\n" : ""}
|
|
314
|
+
${catalogType}
|
|
315
|
+
|
|
316
|
+
type Expect<T extends true> = T;
|
|
317
|
+
${options.hasServer ? `type ServerWorkflows = typeof serverModule extends {
|
|
318
|
+
workflows: infer Workflows extends Record<string, WorkflowDefinition<any, any>>;
|
|
319
|
+
}
|
|
320
|
+
? Workflows
|
|
321
|
+
: {};
|
|
322
|
+
type _SrcServerMainMustDefaultExportRouterAndValidWorkflows = Expect<
|
|
323
|
+
typeof server extends {
|
|
324
|
+
router: AnyRouter;
|
|
325
|
+
workflows?: Record<string, WorkflowDefinition<any, any>>;
|
|
326
|
+
telephony?: { onMessageReceived?: (event: TelephonyMessageReceivedEvent) => void | Promise<void> };
|
|
327
|
+
}
|
|
328
|
+
? true
|
|
329
|
+
: false
|
|
330
|
+
>;
|
|
331
|
+
type _SrcServerWorkflowsMustBeNamedExport = Expect<
|
|
332
|
+
typeof server extends { workflows: infer Workflows }
|
|
333
|
+
? typeof serverModule extends { workflows: Workflows }
|
|
334
|
+
? true
|
|
335
|
+
: false
|
|
336
|
+
: true
|
|
337
|
+
>;
|
|
338
|
+
` : ""}${options.hasClient ? `type _SrcClientRoutesIndexMustExportARoute = Expect<
|
|
339
|
+
typeof indexRoute extends AnyRoute ? true : false
|
|
340
|
+
>;
|
|
341
|
+
` : ""}
|
|
342
|
+
declare module "@hitch42/applet" {
|
|
343
|
+
interface HitchRegister {
|
|
344
|
+
catalog: Catalog;
|
|
345
|
+
manifest: typeof manifest;
|
|
346
|
+
${options.hasServer ? ` router: (typeof server)["router"];
|
|
347
|
+
workflows: ServerWorkflows;
|
|
348
|
+
` : ""} }
|
|
349
|
+
}
|
|
350
|
+
`;
|
|
351
|
+
}
|
|
352
|
+
async function writeRegisterDeclaration(outDir, catalog) {
|
|
353
|
+
await mkdir(resolve(outDir), { recursive: true });
|
|
354
|
+
await writeFile(resolve(outDir, "register.ts"), registerFile(catalog, {
|
|
355
|
+
hasClient: existsSync(resolve(outDir, "../src/client/routes/index.tsx")),
|
|
356
|
+
hasServer: existsSync(resolve(outDir, "../src/server/main.ts"))
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/typegen/generate.ts
|
|
361
|
+
const execFileAsync = promisify(execFile);
|
|
362
|
+
const require = createRequire(import.meta.url);
|
|
363
|
+
async function generateRouteTree(root) {
|
|
364
|
+
await execFileAsync(process.execPath, [resolve(dirname(require.resolve("@tanstack/router-cli/package.json")), "bin/tsr.cjs"), "generate"], { cwd: root });
|
|
365
|
+
}
|
|
366
|
+
async function addTypeScriptImportExtensions(outDir) {
|
|
367
|
+
await Promise.all((await readdir(outDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".ts")).map(async ({ name }) => {
|
|
368
|
+
const path = resolve(outDir, name);
|
|
369
|
+
const source = await readFile(path, "utf8");
|
|
370
|
+
await writeFile(path, source.replace(/(["'])(\.\.?\/[^"']+)\1/g, (match, quote, specifier) => /(?:\.[cm]?[jt]sx?|json|node)$/i.test(specifier) ? match : `${quote}${specifier}.ts${quote}`));
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
function isErrno(error, code) {
|
|
374
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
375
|
+
}
|
|
376
|
+
async function fileExists(path) {
|
|
377
|
+
try {
|
|
378
|
+
await access(path);
|
|
379
|
+
return true;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
if (isErrno(error, "ENOENT")) return false;
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function responseJson(response) {
|
|
386
|
+
try {
|
|
387
|
+
return await response.json();
|
|
388
|
+
} catch (error) {
|
|
389
|
+
throw new Error("Failed to parse the organization's OpenAPI spec.", { cause: error });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function loadAuthenticatedSpec(manifest, options) {
|
|
393
|
+
const github = githubActionsContext(options.store?.env ?? process.env);
|
|
394
|
+
if (github !== void 0) return (await loadGitHubActionsBuildInputs(github, options, manifest.requiresChangeset)).openapi;
|
|
395
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
396
|
+
const response = await fetchWithOrgAuth(manifest.org, options, (instanceUrl, accessToken) => fetcher(`${instanceUrl}/api/v1/openapi.json${manifest.requiresChangeset === void 0 ? "" : `?changeset_id=${encodeURIComponent(manifest.requiresChangeset)}`}`, { headers: { Authorization: `Bearer ${accessToken}` } }));
|
|
397
|
+
if (!response.ok) throw new Error(`Failed to load the organization's OpenAPI spec (${response.status}).`);
|
|
398
|
+
return responseJson(response);
|
|
399
|
+
}
|
|
400
|
+
async function loadThemeStylesheet(orgCode, options) {
|
|
401
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
402
|
+
const instanceUrl = await resolveInstanceUrl(void 0, options.store);
|
|
403
|
+
const response = await fetcher(`${organizationInstanceUrl(instanceUrl, orgCode)}/theme.css`);
|
|
404
|
+
if (!response.ok) throw new Error(`Failed to load the organization's theme (${response.status}).`);
|
|
405
|
+
return response.text();
|
|
406
|
+
}
|
|
407
|
+
async function loadBuildInputs(manifest, options) {
|
|
408
|
+
const github = githubActionsContext(options.store?.env ?? process.env);
|
|
409
|
+
if (github !== void 0) {
|
|
410
|
+
const inputs = await loadGitHubActionsBuildInputs(github, options, manifest.requiresChangeset);
|
|
411
|
+
return {
|
|
412
|
+
spec: inputs.openapi,
|
|
413
|
+
theme: inputs.theme
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
spec: await loadAuthenticatedSpec(manifest, options),
|
|
418
|
+
theme: await loadThemeStylesheet(manifest.org, options)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
async function generateContract(spec, outDir) {
|
|
422
|
+
await mkdir(outDir, { recursive: true });
|
|
423
|
+
const paths = spec !== null && typeof spec === "object" && "paths" in spec ? spec.paths : void 0;
|
|
424
|
+
if (!(paths !== null && typeof paths === "object" && !Array.isArray(paths) && Object.keys(paths).length === 0)) {
|
|
425
|
+
const themePath = resolve(outDir, "theme.css");
|
|
426
|
+
const existingTheme = await readFile(themePath, "utf8").catch((error) => {
|
|
427
|
+
if (isErrno(error, "ENOENT")) return void 0;
|
|
428
|
+
throw error;
|
|
429
|
+
});
|
|
430
|
+
try {
|
|
431
|
+
await createClient({
|
|
432
|
+
input: spec,
|
|
433
|
+
logs: { level: "silent" },
|
|
434
|
+
output: outDir,
|
|
435
|
+
plugins: ["zod", {
|
|
436
|
+
name: "orpc",
|
|
437
|
+
contracts: {
|
|
438
|
+
strategy: "single",
|
|
439
|
+
nesting: "operationId",
|
|
440
|
+
segmentName: { casing: "preserve" }
|
|
441
|
+
}
|
|
442
|
+
}]
|
|
443
|
+
});
|
|
444
|
+
} finally {
|
|
445
|
+
if (existingTheme !== void 0) await writeFile(themePath, existingTheme);
|
|
446
|
+
}
|
|
447
|
+
await addTypeScriptImportExtensions(outDir);
|
|
448
|
+
}
|
|
449
|
+
const orpcPath = resolve(outDir, "orpc.gen.ts");
|
|
450
|
+
if (!await fileExists(orpcPath)) await Promise.all([writeFile(orpcPath, "// Generated by applet generate — no operations in spec.\nexport const contract = {} as const;\n"), fileExists(resolve(outDir, "zod.gen.ts")).then((exists) => exists ? void 0 : writeFile(resolve(outDir, "zod.gen.ts"), "// Generated by applet generate — no schemas in spec.\nexport {};\n"))]);
|
|
451
|
+
}
|
|
452
|
+
async function writeContractFiles(outDir, manifest, spec) {
|
|
453
|
+
guardManifestForSpec(spec, manifest);
|
|
454
|
+
const catalog = catalogFromSpec(spec);
|
|
455
|
+
const preparedSpec = prepareRecordSpec(spec, manifest);
|
|
456
|
+
await generateContract(preparedSpec, outDir);
|
|
457
|
+
await writeManifestGen(outDir, manifest);
|
|
458
|
+
await writeRegisterDeclaration(outDir, catalog);
|
|
459
|
+
await writeFile(resolve(outDir, "index.ts"), `// Generated by applet generate — do not edit.
|
|
460
|
+
import { emailContract, filesContract as files, meContract as me, pruneContract, registerHitchContract, teamsContract as teams, telephonyContract as telephony } from "@hitch42/applet";
|
|
461
|
+
import manifest from "./manifest.gen.ts";
|
|
462
|
+
|
|
463
|
+
${Object.hasOwn(preparedSpec.paths, "/v1/batch") ? `import { contract as generated } from "./orpc.gen.ts";
|
|
464
|
+
|
|
465
|
+
const { batch, ...records } = generated;
|
|
466
|
+
|
|
467
|
+
export const contract = { records, batch, email: emailContract(manifest.code), files, me, teams, telephony } as const;` : `import { contract as records } from "./orpc.gen.ts";
|
|
468
|
+
|
|
469
|
+
export const contract = { records, email: emailContract(manifest.code), files, me, teams, telephony } as const;`}
|
|
470
|
+
registerHitchContract(pruneContract(contract, manifest));
|
|
471
|
+
|
|
472
|
+
declare module "@hitch42/applet" {
|
|
473
|
+
interface HitchRegister {
|
|
474
|
+
contract: typeof contract;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export type * from "./zod.gen.ts";
|
|
479
|
+
`);
|
|
480
|
+
}
|
|
481
|
+
async function syncContract(outDir, manifest, options = {}) {
|
|
482
|
+
await writeContractFiles(outDir, manifest, await loadAuthenticatedSpec(manifest, options));
|
|
483
|
+
}
|
|
484
|
+
async function runGenerate(options) {
|
|
485
|
+
await prepareAppletProject();
|
|
486
|
+
const root = process.cwd();
|
|
487
|
+
const outDir = resolve("./.hitch");
|
|
488
|
+
const manifest = await loadAppletConfig(process.cwd());
|
|
489
|
+
const { spec, theme: themeStylesheet } = await loadBuildInputs(manifest, options);
|
|
490
|
+
const stagedDir = await mkdtemp(resolve("./.hitch.tmp-"));
|
|
491
|
+
const backupDir = `${stagedDir}.backup`;
|
|
492
|
+
let staged = true;
|
|
493
|
+
let backedUp = false;
|
|
494
|
+
try {
|
|
495
|
+
await writeContractFiles(stagedDir, manifest, spec);
|
|
496
|
+
await writeFile(resolve(stagedDir, "theme.css"), themeStylesheet);
|
|
497
|
+
if (await fileExists(outDir)) {
|
|
498
|
+
await rename(outDir, backupDir);
|
|
499
|
+
backedUp = true;
|
|
500
|
+
}
|
|
501
|
+
let installed = false;
|
|
502
|
+
try {
|
|
503
|
+
await rename(stagedDir, outDir);
|
|
504
|
+
staged = false;
|
|
505
|
+
installed = true;
|
|
506
|
+
if (await fileExists(resolve(root, "src/client/routes"))) {
|
|
507
|
+
if (!await fileExists(resolve(root, "src/client/routes/__root.tsx"))) await writeAppletRoot(root);
|
|
508
|
+
await generateRouteTree(root);
|
|
509
|
+
}
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (installed) await rm(outDir, {
|
|
512
|
+
force: true,
|
|
513
|
+
recursive: true
|
|
514
|
+
});
|
|
515
|
+
if (backedUp) {
|
|
516
|
+
await rename(backupDir, outDir);
|
|
517
|
+
backedUp = false;
|
|
518
|
+
}
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
if (backedUp) {
|
|
522
|
+
await rm(backupDir, {
|
|
523
|
+
force: true,
|
|
524
|
+
recursive: true
|
|
525
|
+
});
|
|
526
|
+
backedUp = false;
|
|
527
|
+
}
|
|
528
|
+
} finally {
|
|
529
|
+
if (staged) await rm(stagedDir, {
|
|
530
|
+
force: true,
|
|
531
|
+
recursive: true
|
|
532
|
+
});
|
|
533
|
+
if (backedUp && !await fileExists(outDir)) await rename(backupDir, outDir);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
//#endregion
|
|
537
|
+
export { prepareRecordSpec as a, writeClientEntry as c, writeRegisterDeclaration as i, writeManifestGen as l, runGenerate as n, recordOperation as o, syncContract as r, writeAppletRoot as s, generateContract as t, writeServerEntry as u };
|