@hitch42/applet 0.1.1-beta.6 → 0.1.1-beta.8
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/dist/_internal/server.d.ts +14 -1
- package/dist/_internal/server.js +23 -74
- package/dist/applet.js +50 -29
- package/dist/build-CFCKKMn7.js +2 -0
- package/dist/{build-DIsKQQT0.js → build-DANx9Hgg.js} +28 -13
- package/dist/{ci-D0nC_Ho7.js → ci-5qWl7lT3.js} +20 -4
- package/dist/client.d.ts +2 -2
- package/dist/{contract-DMrpbaLJ.d.ts → contract-rP_eV9PK.d.ts} +1 -1
- package/dist/{deploy-0dlUvCt3.js → deploy-MQWLG2EA.js} +130 -15
- package/dist/email-DBTVhNB_.js +62 -0
- package/dist/{generate-C6eAL5eo.js → generate-BxYHdoAQ.js} +18 -17
- package/dist/generate-CQ07Hdpo.js +2 -0
- package/dist/index.d.ts +115 -4
- package/dist/index.js +141 -26
- package/dist/{init-tEMeys9M.js → init-jDEgYIXg.js} +5 -5
- package/dist/load-config-BYSxDVAl.js +27 -0
- package/dist/login-C7RKmmTe.js +12 -0
- package/dist/{logs-m00pzr_O.js → logs-DgjgAxj2.js} +3 -3
- package/dist/{manifest-DyoJ1MIL.js → manifest-Bp87Uz0a.js} +6 -0
- package/dist/{manifest-BQCAxATS.d.ts → manifest-DD_SGkS6.d.ts} +5 -0
- package/dist/me-BW7Paary.js +32 -0
- package/dist/{project-mW0vsN8A.js → project-CbYPtpPq.js} +16 -14
- package/dist/prompt-ChS-grYO.js +29 -0
- package/dist/schedules-dhU1yyFr.js +42 -0
- package/dist/server.d.ts +2 -2
- package/dist/telephony-protocol-DRgiYjqQ.d.ts +74 -0
- package/dist/telephony.d.ts +77 -0
- package/dist/telephony.js +607 -0
- package/dist/{variables-CP7Ce3Np.js → variables-Bwnr7oxf.js} +3 -28
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +458 -27
- package/dist/workflow-engine-MogejNfF.js +117 -0
- package/dist/{auth-DaVK0k5R.js → workspace-XVtM12ZC.js} +44 -4
- package/package.json +6 -1
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +32 -16
- package/scaffold/.agents/skills/hitch-applets/references/email-tracking.md +49 -0
- package/scaffold/.agents/skills/hitch-applets/references/files.md +3 -3
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +21 -2
- package/scaffold/.agents/skills/hitch-applets/references/performance.md +1 -1
- package/scaffold/.agents/skills/hitch-applets/references/records.md +4 -3
- package/scaffold/.agents/skills/hitch-applets/references/schedules.md +7 -2
- package/scaffold/.agents/skills/hitch-applets/references/server.md +0 -3
- package/scaffold/.agents/skills/hitch-applets/references/workflows.md +11 -3
- package/scaffold/.agents/skills/hitch-cli/SKILL.md +24 -22
- package/scaffold/.agents/skills/hitch-data/SKILL.md +2 -2
- package/scaffold/.agents/skills/hitch-data/references/limits.md +10 -10
- package/scaffold/.agents/skills/hitch-theory/SKILL.md +150 -0
- package/scaffold/.github/workflows/hitch.yml +0 -6
- package/dist/build-B70gkDi5.js +0 -2
- package/dist/generate-D5rSXXa-.js +0 -2
- package/dist/load-config-BnxocLAQ.js +0 -56
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
1
3
|
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
5
|
import { spawn } from "node:child_process";
|
|
4
6
|
import { homedir } from "node:os";
|
|
5
7
|
//#region ../shared/auth/src/store.mts
|
|
@@ -326,6 +328,33 @@ async function loginWithDevice(instanceUrl, options = {}) {
|
|
|
326
328
|
};
|
|
327
329
|
}
|
|
328
330
|
//#endregion
|
|
331
|
+
//#region ../shared/auth/src/workspace.mts
|
|
332
|
+
const ORG_CODE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
333
|
+
const require = createRequire(import.meta.url);
|
|
334
|
+
function readWorkspaceConfig(dir) {
|
|
335
|
+
const path = resolve(dir, "hitch.config.ts");
|
|
336
|
+
if (!existsSync(path)) return void 0;
|
|
337
|
+
let module;
|
|
338
|
+
try {
|
|
339
|
+
module = require(path);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
throw new Error(`Failed to load ${path}.`, { cause: error });
|
|
342
|
+
}
|
|
343
|
+
const config = module !== null && typeof module === "object" && "default" in module ? module.default : module;
|
|
344
|
+
if (config === null || typeof config !== "object" || !("sdk" in config) || typeof config.sdk !== "string" || config.sdk.length === 0 || !("org" in config) || typeof config.org !== "string" || !ORG_CODE_PATTERN.test(config.org)) throw new Error(`Invalid Hitch config in ${path}: "sdk" must be a package spec and "org" an organization code.`);
|
|
345
|
+
const githubWorkflow = "githubWorkflow" in config ? config.githubWorkflow : void 0;
|
|
346
|
+
if (githubWorkflow !== void 0 && typeof githubWorkflow !== "boolean") throw new Error(`Invalid Hitch config in ${path}: "githubWorkflow" must be a boolean.`);
|
|
347
|
+
return {
|
|
348
|
+
sdk: config.sdk,
|
|
349
|
+
org: config.org,
|
|
350
|
+
githubWorkflow
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function findWorkspaceConfig(cwd) {
|
|
354
|
+
const appletsDir = dirname(cwd);
|
|
355
|
+
return readWorkspaceConfig(cwd) ?? (basename(appletsDir) === "applets" ? readWorkspaceConfig(dirname(appletsDir)) : void 0);
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
329
358
|
//#region src/auth.ts
|
|
330
359
|
async function fetchWithOrgAuth(orgCode, options, request) {
|
|
331
360
|
const fetcher = options.fetch ?? globalThis.fetch;
|
|
@@ -334,9 +363,10 @@ async function fetchWithOrgAuth(orgCode, options, request) {
|
|
|
334
363
|
const auth = await resolveAuthForOrg(instanceUrl, orgCode, options.store);
|
|
335
364
|
if (auth !== void 0) {
|
|
336
365
|
const response = await request(organizationUrl, bearerToken(auth));
|
|
337
|
-
|
|
366
|
+
rejectEnvironmentKey(response, auth);
|
|
367
|
+
if (response.status !== 401) return response;
|
|
338
368
|
}
|
|
339
|
-
if (!(options.stdinIsTTY ?? process.stdin.isTTY === true)) throw new
|
|
369
|
+
if (!(options.stdinIsTTY ?? process.stdin.isTTY === true)) throw new AuthenticationError("Not logged in. Run applet login, or set HITCH_API_KEY.");
|
|
340
370
|
await (options.login ?? loginWithDevice)(instanceUrl, {
|
|
341
371
|
expectedOrg: orgCode,
|
|
342
372
|
fetch: fetcher,
|
|
@@ -347,4 +377,14 @@ async function fetchWithOrgAuth(orgCode, options, request) {
|
|
|
347
377
|
return request(organizationUrl, bearerToken(credentials));
|
|
348
378
|
}
|
|
349
379
|
//#endregion
|
|
350
|
-
|
|
380
|
+
//#region src/workspace.ts
|
|
381
|
+
function resolveWorkspaceOrg(appletRoot) {
|
|
382
|
+
const appletsDir = dirname(appletRoot);
|
|
383
|
+
if (basename(appletsDir) === "applets") {
|
|
384
|
+
const config = readWorkspaceConfig(dirname(appletsDir));
|
|
385
|
+
if (config !== void 0) return config.org;
|
|
386
|
+
}
|
|
387
|
+
throw new Error(`No Hitch workspace found for ${appletRoot}. An applet must live at <workspace>/applets/<code> with hitch.config.ts in the workspace root.`);
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
export { loginWithDevice as a, organizationInstanceUrl as c, resolveInstanceUrl as d, readWorkspaceConfig as i, rejectEnvironmentKey as l, fetchWithOrgAuth as n, AuthenticationError as o, findWorkspaceConfig as r, bearerToken as s, resolveWorkspaceOrg as t, resolveAuthForOrg as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hitch42/applet",
|
|
3
|
-
"version": "0.1.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"applet": "./dist/applet.js"
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"source": "./src/react/index.ts",
|
|
28
28
|
"default": "./dist/react.js"
|
|
29
29
|
},
|
|
30
|
+
"./telephony": {
|
|
31
|
+
"source": "./src/telephony/index.ts",
|
|
32
|
+
"default": "./dist/telephony.js"
|
|
33
|
+
},
|
|
30
34
|
"./vite": {
|
|
31
35
|
"source": "./src/vite.ts",
|
|
32
36
|
"default": "./dist/vite.js"
|
|
@@ -53,6 +57,7 @@
|
|
|
53
57
|
"@tanstack/react-query-persist-client": "5.101.4",
|
|
54
58
|
"@tanstack/router-cli": "^1.167.24",
|
|
55
59
|
"@tanstack/router-plugin": "^1.168.23",
|
|
60
|
+
"@telnyx/webrtc": "^2.27.10",
|
|
56
61
|
"@tiptap/core": "^3.30.3",
|
|
57
62
|
"@tiptap/extension-image": "^3.30.3",
|
|
58
63
|
"@tiptap/extension-list": "^3.30.3",
|
|
@@ -13,24 +13,27 @@ Treat each applet as an isolated microfrontend. Keep it separate from the data s
|
|
|
13
13
|
|
|
14
14
|
This file holds the common rules. Read each matching reference before you edit an applet. Paths are relative to this file.
|
|
15
15
|
|
|
16
|
-
| Read this
|
|
17
|
-
|
|
|
18
|
-
| `references/client.md`
|
|
19
|
-
| `references/records.md`
|
|
20
|
-
| `references/files.md`
|
|
21
|
-
| `references/
|
|
22
|
-
| `references/
|
|
23
|
-
| `references/
|
|
24
|
-
| `references/
|
|
25
|
-
| `references/
|
|
26
|
-
| `references/
|
|
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/email-tracking.md` | Creating pixels and reading raw email hits |
|
|
22
|
+
| `references/markdown.md` | Rendering or editing markdown, image uploads, Hitch file links |
|
|
23
|
+
| `references/server.md` | The applet server: secrets, env variables, external services, the `api` client |
|
|
24
|
+
| `references/schedules.md` | Cron schedules and background runs |
|
|
25
|
+
| `references/workflows.md` | Multi-step server work: `createWorkflow`, `step.do`, `step.sleep`, starting, reading, and listing runs |
|
|
26
|
+
| `references/manifest.md` | The applet definition in `applet.manifest.ts`: objects, access, variables, and packages |
|
|
27
|
+
| `references/performance.md` | Data access design: client versus server, N+1 reads, includes, aggregates, pagination, batch writes |
|
|
27
28
|
|
|
28
29
|
For attribute types, filters, and API limits, read `../hitch-data/SKILL.md`. To inspect or change Hitch data and settings from the shell, read `../hitch-cli/SKILL.md`.
|
|
29
30
|
|
|
30
31
|
## Work with the workspace
|
|
31
32
|
|
|
32
33
|
- Run package commands with `pnpm`.
|
|
33
|
-
-
|
|
34
|
+
- Run `pnpm exec applet login` from the workspace root or an applet directory to sign in. It uses the organization in `hitch.config.ts` and shares credentials with `hitch login`. Add `--no-browser` to print the sign-in URL without opening a browser.
|
|
35
|
+
- A server permission error is not a login request.
|
|
36
|
+
- After a workspace is linked to a repository, deploy in two ways. Run `pnpm exec applet deploy` to release a change immediately. Push to the production branch to deploy through CI and keep releases in sync with the repository.
|
|
34
37
|
- Run `pnpm exec applet new <code>` from the workspace root to create a full applet.
|
|
35
38
|
- Add `--client-only` or `--server-only` when the applet needs only one part.
|
|
36
39
|
- Use `--client-only` for a user interface that uses the current user's Hitch access and needs no server.
|
|
@@ -44,16 +47,21 @@ For attribute types, filters, and API limits, read `../hitch-data/SKILL.md`. To
|
|
|
44
47
|
- Run `pnpm --dir applets/<code> run generate` after a Hitch object, operation, or theme changes.
|
|
45
48
|
- Run generation at the start of a session when the task depends on current Hitch types.
|
|
46
49
|
- Run `pnpm --dir applets/<code> run dev` and `pnpm --dir applets/<code> run build` from the workspace root.
|
|
50
|
+
- The dev server runs as you in the production org. Reads and writes use real data.
|
|
51
|
+
- The dev server prints a preview link. Open the link to see the local applet inside Hitch.
|
|
52
|
+
- Chrome asks once to allow local network access for the Hitch site.
|
|
53
|
+
- The preview lasts until you select Stop preview or close the tab.
|
|
54
|
+
- A pending `requiresChangeset` schema is not available in dev. Deploy to test it.
|
|
47
55
|
- Run `pnpm --dir applets/<code> run deploy` only when the user requests a deployment.
|
|
48
|
-
- Edit only `hitch.config.ts`, `applet.manifest.ts`,
|
|
49
|
-
- Treat all other workspace files as generated output. The applet CLI replaces them.
|
|
56
|
+
- Edit only `hitch.config.ts`, `applet.manifest.ts`, files in `src`, and `.gitignore` rules below the generated block.
|
|
57
|
+
- Treat all other workspace files as generated output. The applet CLI replaces them. It creates `.github/workflows/hitch.yml` only when the file is absent. To stop it, set `githubWorkflow: false` in `hitch.config.ts` and delete the file.
|
|
50
58
|
- Remember that generation refreshes the local theme copy. Deployed applets load the public org theme from its Hitch `.com` URL.
|
|
51
59
|
|
|
52
60
|
## Use Vite and Vitest
|
|
53
61
|
|
|
54
62
|
- Import configuration APIs from `vite`.
|
|
55
63
|
- Import test APIs from `vitest`.
|
|
56
|
-
-
|
|
64
|
+
- Do not edit `vite.config.ts`. The applet CLI rewrites it. Do not add another Vite config.
|
|
57
65
|
- Add applet packages to `dependencies` or `devDependencies` in `applet.manifest.ts`.
|
|
58
66
|
|
|
59
67
|
## Access Hitch data
|
|
@@ -121,7 +129,7 @@ Read `references/files.md` before download links, attachment attributes, or serv
|
|
|
121
129
|
- Add useful `console.<level>` calls around important server work and failures. Deployed logs often provide the clearest evidence for applet issues.
|
|
122
130
|
- Choose `console.log`, `console.info`, `console.warn`, or `console.error` deliberately. The console method sets the entry level used by log filters.
|
|
123
131
|
- Run `pnpm --dir applets/<code> exec applet logs --filter '{"type":"applet"}'` from the workspace root to inspect applet code logs.
|
|
124
|
-
- Include the applet type filter during diagnosis. Without it, Cloudflare invocation entries can hide the useful applet entries.
|
|
132
|
+
- Include the applet type filter during diagnosis. Without it, Cloudflare invocation entries can hide the useful applet entries. For a workflow run, use `--run <id>` or `--filter '{"type":"workflow"}'` instead.
|
|
125
133
|
- Do not log secrets, tokens, or personal data.
|
|
126
134
|
|
|
127
135
|
## Verify changes
|
|
@@ -138,3 +146,11 @@ pnpm --dir applets/<code> run build
|
|
|
138
146
|
If missing packages block a check, stop package checks. Do not retry the check through another binary.
|
|
139
147
|
|
|
140
148
|
If a command fails, report the exact blocker. Do not deploy as a verification step.
|
|
149
|
+
|
|
150
|
+
## Deploy from GitHub
|
|
151
|
+
|
|
152
|
+
- A push to the production branch runs `applet deploy` in the workspace root through GitHub Actions.
|
|
153
|
+
- An applet deploys when its own folder changed since its current CI release. Every applet deploys when a file outside `applets/` changed. An applet without a CI release always deploys. An applet also deploys when CI cannot load the commit of its current release.
|
|
154
|
+
- The deploy skips other applets and logs the reason. A skip is expected, not a failure.
|
|
155
|
+
- A schema change in Hitch alone does not trigger CI.
|
|
156
|
+
- Do not import from a sibling applet folder. The change check does not follow such imports.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Email tracking
|
|
2
|
+
|
|
3
|
+
Create one pixel for each email. Put its URL in the HTML before send.
|
|
4
|
+
Add the pixel next to CID images. A CID image cannot make a tracking request.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
const { id, url } = await hitch.email.tracking.createPixel({
|
|
8
|
+
tags: { campaignId: "fall", buyerId: "acme", version: "v2" },
|
|
9
|
+
});
|
|
10
|
+
const html = `<p>Hello</p><img src="${url}" width="1" height="1" alt="">`;
|
|
11
|
+
// Send the email with the applet's email provider.
|
|
12
|
+
await hitch.email.tracking.markSent({ id });
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`markSent({ id, sentAt })` accepts an ISO time. A later call replaces the sent time.
|
|
16
|
+
`get({ id })` returns one tracker. `list()` returns up to 100 trackers by default.
|
|
17
|
+
Use `filter`, `sentAt`, `cursor`, and `limit` to select a page.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const tracker = await hitch.email.tracking.get({ id });
|
|
21
|
+
const query = {
|
|
22
|
+
filter: { buyerId: "acme" },
|
|
23
|
+
sentAt: { $gte: "2026-10-01T00:00:00Z" },
|
|
24
|
+
limit: 100,
|
|
25
|
+
};
|
|
26
|
+
const page = await hitch.email.tracking.list(query);
|
|
27
|
+
const next = page.nextCursor
|
|
28
|
+
? await hitch.email.tracking.list({ ...query, cursor: page.nextCursor })
|
|
29
|
+
: null;
|
|
30
|
+
const opened =
|
|
31
|
+
tracker.lastHitAt !== null &&
|
|
32
|
+
new Date(tracker.lastHitAt).getTime() >
|
|
33
|
+
new Date(tracker.sentAt ?? tracker.createdAt).getTime() + 60_000;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The example rule counts a tracker as opened when `lastHitAt` is more than 60 seconds after send or creation.
|
|
37
|
+
Hitch records `firstHitAt` and `lastHitAt`. It does not classify opens.
|
|
38
|
+
Each page must repeat the same filter, `sentAt`, and limit.
|
|
39
|
+
|
|
40
|
+
Tags stay fixed after creation. Use at most 10 tags. A key starts with an ASCII letter.
|
|
41
|
+
The rest contains ASCII letters, digits, or underscores, up to 64 characters total.
|
|
42
|
+
Each value has 1 to 200 characters. A filter matches every tag by exact equality.
|
|
43
|
+
Everyone with applet access sees all its trackers.
|
|
44
|
+
|
|
45
|
+
Gmail can fetch through `GoogleImageProxy` a few seconds after send.
|
|
46
|
+
A real Gmail open has the same user agent. A Gmail draft preview can fetch before send.
|
|
47
|
+
Apple Mail Privacy Protection fetches on delivery with `Mozilla/5.0`.
|
|
48
|
+
That fetch looks like a real open. Proton fetches on delivery.
|
|
49
|
+
Clients with images off never fetch the pixel.
|
|
@@ -67,7 +67,7 @@ Use `download` when a component needs one fresh link without query state:
|
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
69
|
const file = await hitch.files.download(id);
|
|
70
|
-
file.downloadUrl; // a download link, valid for
|
|
70
|
+
file.downloadUrl; // a download link, valid for at least a day
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Use the `get` query utilities when React needs file metadata as query state.
|
|
@@ -86,11 +86,11 @@ if (fileQuery.data) {
|
|
|
86
86
|
row.filename; // "report.csv"
|
|
87
87
|
row.contentType; // "text/csv"
|
|
88
88
|
row.sizeBytes; // 1024
|
|
89
|
-
downloadUrl; // a download link, valid for
|
|
89
|
+
downloadUrl; // a download link, valid for at least a day
|
|
90
90
|
}
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
A `downloadUrl` expires after
|
|
93
|
+
A `downloadUrl` expires after 24 to 48 hours. Call `get` again for a fresh link instead of storing one.
|
|
94
94
|
|
|
95
95
|
## Link a file to a record
|
|
96
96
|
|
|
@@ -11,6 +11,7 @@ The applet tool reads `org` from `hitch.config.ts` in the workspace root.
|
|
|
11
11
|
- `name` gives the applet a display name.
|
|
12
12
|
- `code` gives the applet its stable code and URL prefix.
|
|
13
13
|
- `objects` declares record objects and attribute access.
|
|
14
|
+
- `requiresChangeset` optionally names the schema changeset that the code needs.
|
|
14
15
|
- `variables` optionally declares runtime variables.
|
|
15
16
|
- `schedules` optionally declares scheduled server calls.
|
|
16
17
|
- `dependencies` optionally declares applet runtime packages.
|
|
@@ -42,6 +43,22 @@ The manifest and applet definition reject unknown fields.
|
|
|
42
43
|
- Expect the Vite base path to be `/<code>/`.
|
|
43
44
|
- Include the code in each client route path.
|
|
44
45
|
|
|
46
|
+
## Set `requiresChangeset`
|
|
47
|
+
|
|
48
|
+
Set `requiresChangeset` when the code needs a schema change that is not live yet.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
requiresChangeset: "0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Use the ID of a changeset in the same organization.
|
|
55
|
+
- Generate, build, and deploy all use this field. The CLI has no changeset flag.
|
|
56
|
+
- While the changeset is pending, types and the build use the schema that the changeset will produce.
|
|
57
|
+
- While the changeset is pending, a deploy binds the release to the changeset. The release goes live when a person applies the changeset in Hitch.
|
|
58
|
+
- After the changeset applies, the field uses the live schema. A deploy then promotes as usual. Remove the field when it is convenient.
|
|
59
|
+
- A deploy fails when the changeset was abandoned. Remove the field or name a pending changeset.
|
|
60
|
+
- A git push uses the same rules. The push never applies the changeset.
|
|
61
|
+
|
|
45
62
|
## Declare objects
|
|
46
63
|
|
|
47
64
|
Add one entry for each object that the applet needs.
|
|
@@ -75,14 +92,16 @@ Each object entry has five required fields.
|
|
|
75
92
|
|
|
76
93
|
The object declaration caps record access for the applet identity.
|
|
77
94
|
|
|
78
|
-
- Declare only the objects that
|
|
79
|
-
- Declare only the attributes that
|
|
95
|
+
- Declare only the objects that the applet uses.
|
|
96
|
+
- Declare only the attributes that the applet uses.
|
|
97
|
+
- The declaration sets both the generated contract and the applet identity's cap.
|
|
80
98
|
- Use `readOnly` for attributes that the applet identity must not change.
|
|
81
99
|
- Use `readWrite` for attributes that the applet identity can change.
|
|
82
100
|
- Set `canCreate` and `canDelete` to the required values.
|
|
83
101
|
- The generated contract omits undeclared objects.
|
|
84
102
|
- The generated contract removes `create` when `canCreate` is false.
|
|
85
103
|
- The generated contract removes `delete` when `canDelete` is false.
|
|
104
|
+
- The generated contract removes `update` and `updateWhere` when `readWrite` is empty.
|
|
86
105
|
- Regenerate after an object grant changes.
|
|
87
106
|
- Do not restore a removed object or method with a cast.
|
|
88
107
|
|
|
@@ -33,7 +33,7 @@ Never call `get` once per row of a list. Use one of these instead:
|
|
|
33
33
|
## Page every list
|
|
34
34
|
|
|
35
35
|
- Every list request returns at most 200 records. A larger `limit` is clamped to 200 with no error.
|
|
36
|
-
- Show a page size or a bounded scroll area in the product, with a next-page control that passes the returned `cursor`.
|
|
36
|
+
- Show a page size or a bounded scroll area in the product, with a next-page control that passes the returned `next_cursor` as `cursor`.
|
|
37
37
|
- Loop the cursor only when the code must visit the whole dataset. Name the bound in a comment, and stop at it.
|
|
38
38
|
- Do not write a whole-dataset loop for a number that `aggregate` can return.
|
|
39
39
|
|
|
@@ -97,7 +97,7 @@ const page = await hitch.records.deals.query({
|
|
|
97
97
|
- Use a direct field value as shorthand for `$eq`.
|
|
98
98
|
- Use `$eq`, `$in`, or `$not_empty` for identity fields.
|
|
99
99
|
- Use `$eq` or `$not_empty` for checkbox fields.
|
|
100
|
-
- Use
|
|
100
|
+
- Use `$eq`, `$in`, or `$not_empty` for attachment fields. Pass file IDs.
|
|
101
101
|
- Use `$eq`, `$in`, `$not_empty`, `$contains`, `$starts_with`, or `$ends_with` for text fields.
|
|
102
102
|
- Use `$eq`, `$in`, `$not_empty`, `$lt`, `$lte`, `$gt`, or `$gte` for ordered fields.
|
|
103
103
|
- Use `$contains`, `$starts_with`, `$ends_with`, `$lt`, `$lte`, `$gt`, `$gte`, and `sort` only on an attribute with the `indexed` flag. Read `../../hitch-data/references/limits.md`.
|
|
@@ -106,11 +106,11 @@ const page = await hitch.records.deals.query({
|
|
|
106
106
|
- Pass a string to `$contains`, `$starts_with`, and `$ends_with`.
|
|
107
107
|
- Sort with `{ attribute, direction }`.
|
|
108
108
|
- Use `asc` or `desc` as the sort direction.
|
|
109
|
-
- Do not sort by multi-value, reference, user, or
|
|
109
|
+
- Do not sort by multi-value, reference, user, attachment, or rich text attributes.
|
|
110
110
|
- Omit `sort` to sort by `createdAt` in descending order.
|
|
111
111
|
- Use a positive integer for `limit`.
|
|
112
112
|
- The service caps `limit` at 200 and uses 50 by default.
|
|
113
|
-
- Pass the returned
|
|
113
|
+
- Pass the returned `next_cursor` back as `cursor`. A null `next_cursor` means the last page.
|
|
114
114
|
- Pass `include` as one string. Use the include name, which is the code without its `_id` or `_ids` suffix. Separate several names with commas.
|
|
115
115
|
|
|
116
116
|
## Aggregate records
|
|
@@ -148,6 +148,7 @@ The generated contract contains only objects declared in the manifest.
|
|
|
148
148
|
|
|
149
149
|
- The contract removes `create` when `canCreate` is false.
|
|
150
150
|
- The contract removes `delete` when `canDelete` is false.
|
|
151
|
+
- The contract removes `update` and `updateWhere` when `readWrite` is empty.
|
|
151
152
|
- Treat a missing method as a missing manifest grant, and edit the manifest when required.
|
|
152
153
|
- Run generation after a manifest or Hitch schema change.
|
|
153
154
|
- Do not bypass the contract with a cast.
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Use a schedule to call an applet server procedure from a cron expression.
|
|
4
4
|
|
|
5
|
+
## Local dev
|
|
6
|
+
|
|
7
|
+
The dev server does not run cron.
|
|
8
|
+
Run `pnpm --dir applets/<code> exec applet schedules run <schedule>` from the workspace root while `pnpm --dir applets/<code> run dev` runs.
|
|
9
|
+
The run acts as you, not as the applet. The run writes real data.
|
|
10
|
+
|
|
5
11
|
## Define the target procedure
|
|
6
12
|
|
|
7
13
|
- Add the target to the applet's server router.
|
|
@@ -44,7 +50,7 @@ schedules: [
|
|
|
44
50
|
- The five fields are minute, hour, day of month, month, and day of week.
|
|
45
51
|
- The registered router type checks the path after type generation.
|
|
46
52
|
- A nested procedure uses its nested router path.
|
|
47
|
-
- The scheduler invokes the named router procedure with no input.
|
|
53
|
+
- The scheduler invokes the named router procedure with the input `{}`. Accept an empty object or declare no input.
|
|
48
54
|
|
|
49
55
|
## Understand the run identity
|
|
50
56
|
|
|
@@ -82,5 +88,4 @@ const uploaded = await hitch.files.upload(file);
|
|
|
82
88
|
- Read the stored status, path, HTTP status, and error details.
|
|
83
89
|
- Check the applet logs for procedure details.
|
|
84
90
|
- Follow the log commands in the main Hitch applet skill.
|
|
85
|
-
- Include `--level log` during diagnosis.
|
|
86
91
|
- Do not log secrets, tokens, or personal data.
|
|
@@ -29,9 +29,6 @@ export default { router };
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
- Nest procedure objects when the applet needs nested paths.
|
|
32
|
-
- Do not name a top-level procedure `hitch`.
|
|
33
|
-
- The client reserves `hitch` for the Hitch data client.
|
|
34
|
-
- The generated API client type rejects a router with that reserved key.
|
|
35
32
|
|
|
36
33
|
## Call the router from the client
|
|
37
34
|
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Use a workflow for server work that runs in steps, retries a step, or waits between steps.
|
|
4
4
|
|
|
5
|
+
## Local dev
|
|
6
|
+
|
|
7
|
+
In `pnpm --dir applets/<code> run dev`, workflows run in the dev server as you. The server keeps runs in memory.
|
|
8
|
+
Runs disappear when the dev server stops.
|
|
9
|
+
In local dev, a step fails when it runs longer than 5 minutes. A deployed step can run for 15 minutes.
|
|
10
|
+
Set `HITCH_DEV_SKIP_SLEEP=1` in the shell that runs `pnpm --dir applets/<code> run dev` to skip sleeps and step retry delays.
|
|
11
|
+
A `.env` file does not set this value for the dev server.
|
|
12
|
+
With this value, a polling loop that sleeps between checks calls Hitch without pause.
|
|
13
|
+
|
|
5
14
|
## Define a workflow
|
|
6
15
|
|
|
7
16
|
- Import `createWorkflow` from `@hitch42/applet/server`.
|
|
@@ -63,11 +72,10 @@ export default { router, workflows };
|
|
|
63
72
|
- Each step has the same limits as a scheduled run: 30,000 milliseconds of CPU and 1,000 subrequests.
|
|
64
73
|
- A step that still fails after its retries throws inside the workflow function. Catch it to run a compensating step. An error you do not catch fails the run.
|
|
65
74
|
- A run keeps the release it started on. A new release does not stop or change a run that is in progress or asleep.
|
|
66
|
-
-
|
|
75
|
+
- The owner or a member with the `applets:manage` capability can terminate an open run from the applet settings page. A terminated run has the status `terminated` and does not resume.
|
|
67
76
|
|
|
68
77
|
## Diagnose a failed run
|
|
69
78
|
|
|
70
79
|
- Call `get(id)` and read `error`.
|
|
71
80
|
- Workflow step output is in the applet logs with the type `workflow`, the run id, and the workflow code.
|
|
72
|
-
- Run `
|
|
73
|
-
- Include `--level log` during diagnosis.
|
|
81
|
+
- Run `pnpm --dir applets/<code> exec applet logs --run <id>` from the workspace root to see one run.
|
|
@@ -12,25 +12,26 @@ The `hitch` command talks to one organization on one Hitch instance. Its data an
|
|
|
12
12
|
## Sign in
|
|
13
13
|
|
|
14
14
|
- Run `hitch login` to sign in with the device flow. The command prints a URL and a code. Open the URL and confirm the code. Add `--no-browser` when the shell cannot open a browser.
|
|
15
|
+
- `applet login` uses this same device flow and credential store. Both commands preselect the organization from `hitch.config.ts` at the workspace root or in an applet directory. Use `hitch login --org <code>` to select it explicitly.
|
|
15
16
|
- Run `hitch login --api-key` to store an API key instead. The command prompts for the key. Pass `--api-key <key>` to skip the prompt.
|
|
16
|
-
- Set `HITCH_API_KEY` to use a key for one shell session. It overrides every stored login. While it is set, `hitch login` and `hitch logout` refuse to run.
|
|
17
|
+
- Set `HITCH_API_KEY` to use a key for one shell session. It overrides every stored login. While it is set, `hitch login`, `applet login`, and `hitch logout` refuse to run.
|
|
17
18
|
- Run `hitch whoami` to print the current person, email, organization name, and organization id as JSON.
|
|
18
|
-
- Run `hitch logout` to remove
|
|
19
|
-
- Set `HITCH_API_URL`
|
|
19
|
+
- Run `hitch logout` to remove the stored logins and cached command sets for the current instance.
|
|
20
|
+
- Set `HITCH_API_URL` for an instance other than the default. `hitch login --instance <url>` affects only that login. Set `HITCH_API_URL` too, so later commands use the same instance.
|
|
20
21
|
|
|
21
22
|
## Select the organization
|
|
22
23
|
|
|
23
24
|
The CLI resolves the organization in this order. The first match wins.
|
|
24
25
|
|
|
25
26
|
1. `--org <code>` on any command.
|
|
26
|
-
2. The workspace organization: the `
|
|
27
|
+
2. The workspace organization: the `org` field in `hitch.config.ts` in the current directory, or in the workspace root when the current directory is an applet under `applets/`.
|
|
27
28
|
3. `HITCH_DEFAULT_ORG`.
|
|
28
29
|
|
|
29
30
|
There is no remembered active organization. When `--org` and the workspace disagree, the command fails with `--org "x" does not match workspace organization "y"`. When several organizations are stored and none is selected, the command fails and names the three ways to choose.
|
|
30
31
|
|
|
31
32
|
## Refresh the commands
|
|
32
33
|
|
|
33
|
-
- Run `hitch sync` after a
|
|
34
|
+
- `hitch login` syncs the commands. Run `hitch sync` after a schema change, and whenever `hitch --help` lacks an object. The command downloads the organization's API and caches the generated commands. It prints `Synced 3 objects.`
|
|
34
35
|
- If the CLI prints `the cached commands ... no longer load — run hitch sync`, run `hitch sync`. Until then only the built-in commands work.
|
|
35
36
|
- The `admin` commands appear only when the signed-in identity holds an admin capability. A missing `admin` command means no capability, not a broken CLI.
|
|
36
37
|
|
|
@@ -40,11 +41,11 @@ Every generated command follows the API route.
|
|
|
40
41
|
|
|
41
42
|
- Data commands: `hitch <object> <verb>`. The object is its code, for example `deals`. The verbs are `list`, `get`, `create`, `update`, `delete`, `query`, `update-where`, and `aggregate`. `hitch batch` sends a batch.
|
|
42
43
|
- Admin commands: `hitch admin <route segments> <verb>`. The verb comes from the HTTP method: `list` or `get` for GET, `create` for POST, `update` for PATCH, `set` for PUT, `delete` for DELETE. For example `PUT /admin/v1/teams/{id}/members/{userId}` is `hitch admin teams members set`.
|
|
43
|
-
- Every path, query, and body field becomes a flag in kebab case: `--id`, `--team-id`, `--can-read`. An object
|
|
44
|
+
- Every path, query, and body field becomes a flag in kebab case: `--id`, `--team-id`, `--can-read`. An object field takes JSON: `--filter '{"stage":"won"}'`. An array field takes one JSON value per item: `--aggregates '{"op":"sum","attribute":"amount","as":"total"}' '{"op":"count","as":"n"}'`.
|
|
44
45
|
- A required field is a required flag. The CLI does not prompt for a missing value. It prints the usage error.
|
|
45
46
|
- Run `hitch <object> <verb> --help` to see the flags for that organization's schema.
|
|
46
47
|
- Every `delete` command asks for confirmation. Pass `--yes` to skip it. A script without a terminal must pass `--yes`.
|
|
47
|
-
- A command prints its result as JSON. A list prints one
|
|
48
|
+
- A command prints its result as JSON. A list prints one JSON object with `data` and `next_cursor`. A command with no result prints nothing.
|
|
48
49
|
- An error prints `hitch: <message>` and exits with code 1.
|
|
49
50
|
|
|
50
51
|
## Work with records
|
|
@@ -59,8 +60,8 @@ hitch deals update --id 0192f1a2-3b4c-7d5e-8f60-123456789abc --stage won
|
|
|
59
60
|
hitch deals delete --id 0192f1a2-3b4c-7d5e-8f60-123456789abc --yes
|
|
60
61
|
hitch deals query --filter '{"stage":{"$in":["open","won"]}}' --sort '{"attribute":"createdAt","direction":"desc"}' --limit 50
|
|
61
62
|
hitch deals update-where --filter '{"stage":"open"}' --set '{"stage":"lost"}' --expect 3
|
|
62
|
-
hitch deals aggregate --aggregates '
|
|
63
|
-
hitch batch --requests '
|
|
63
|
+
hitch deals aggregate --aggregates '{"op":"sum","attribute":"amount","as":"total"}' --group-by '[{"attribute":"stage"}]'
|
|
64
|
+
hitch batch --requests '{"method":"POST","path":"/v1/deals","body":{"name":"A","stage":"open"}}'
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
- The filter, sort, include, aggregate, and batch bodies use the same shapes as the SDK. Read the `hitch-data` skill for the rules and limits.
|
|
@@ -106,22 +107,23 @@ hitch admin changesets update --id <changesetId> --adopt <otherChangesetId>
|
|
|
106
107
|
|
|
107
108
|
- A schema command without `--changeset-id` applies at once when it has no blockers. With a blocker, Hitch stages it in a new pending changeset and names it in the error.
|
|
108
109
|
- Add `--dry-run` to evaluate a schema command without a change.
|
|
109
|
-
- Add `--indexed` to an attribute that a query must sort or filter with ordered or text operators. Add `--is-multi` for a multi-value attribute. Pass `--type-config`
|
|
110
|
+
- Add `--indexed` to an attribute that a query must sort or filter with ordered or text operators. Add `--is-multi` for a multi-value attribute. Pass `--type-config` as JSON. Pass `--options` as one JSON value per option.
|
|
110
111
|
- Run `blockers list` before an apply. Any blocker stops the apply with HTTP 409.
|
|
111
112
|
- Run `hitch sync` after an apply, so the new objects and attributes appear as commands.
|
|
112
113
|
- Run `pnpm --dir applets/<code> run generate` after an apply, so applet types match.
|
|
114
|
+
- To build applet code before the apply, set `requiresChangeset` in `applet.manifest.ts` to the changeset ID. Generate, build, and deploy then use the changeset schema, and the release goes live when the changeset applies.
|
|
113
115
|
|
|
114
116
|
## Fix common errors
|
|
115
117
|
|
|
116
|
-
| Message | Cause and fix
|
|
117
|
-
| ---------------------------------------------------- |
|
|
118
|
-
| `Not logged in. Run hitch login.` | No stored login and no `HITCH_API_KEY`. Sign in.
|
|
119
|
-
| `HITCH_API_KEY was rejected.` | The key in the environment is invalid or revoked. Unset it or replace it.
|
|
120
|
-
| `API key is scoped to organization "x", not "y".` | The key belongs to another organization. Select `x` or use another key.
|
|
121
|
-
| `--org "x" does not match workspace organization` | Run the command outside the workspace, or drop `--org`.
|
|
122
|
-
| `Multiple organizations are stored ...` | Pass `--org`,
|
|
123
|
-
| `the cached commands ... no longer load` | Run `hitch sync`.
|
|
124
|
-
| `Refusing to delete without --yes` | Add `--yes` in a script.
|
|
125
|
-
| `hitch <object>` is an unknown command | Run `hitch sync`. The object is new or the cache is stale.
|
|
126
|
-
| `User's teams lack the `read` grant on this object.` | The identity has no grant on that object. Ask for a team grant, or use another identity.
|
|
127
|
-
| `hitch admin` is missing | The identity holds no admin capability.
|
|
118
|
+
| Message | Cause and fix |
|
|
119
|
+
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `Not logged in. Run hitch login.` | No stored login and no `HITCH_API_KEY`. Sign in. |
|
|
121
|
+
| `HITCH_API_KEY was rejected.` | The key in the environment is invalid or revoked. Unset it or replace it. |
|
|
122
|
+
| `API key is scoped to organization "x", not "y".` | The key belongs to another organization. Select `x` or use another key. |
|
|
123
|
+
| `--org "x" does not match workspace organization` | Run the command outside the workspace, or drop `--org`. |
|
|
124
|
+
| `Multiple organizations are stored ...` | Pass `--org`, use the workspace `hitch.config.ts`, or set `HITCH_DEFAULT_ORG`. |
|
|
125
|
+
| `the cached commands ... no longer load` | Run `hitch sync`. |
|
|
126
|
+
| `Refusing to delete without --yes` | Add `--yes` in a script. |
|
|
127
|
+
| `hitch <object>` is an unknown command | Run `hitch whoami`. If you are signed in, run `hitch sync`. The object is new or the cache is stale. |
|
|
128
|
+
| `User's teams lack the `read` grant on this object.` | The identity has no grant on that object. Ask for a team grant, or use another identity. |
|
|
129
|
+
| `hitch admin` is missing | The identity holds no admin capability. |
|
|
@@ -42,10 +42,10 @@ An organization's data lives in Hitch objects. Each object has attributes. Each
|
|
|
42
42
|
- Every attribute accepts `$eq` and `$not_empty`. Every attribute except checkbox accepts `$in`.
|
|
43
43
|
- Ordered operators, text-match operators, and `sort` need the `indexed` flag on the attribute. A new attribute starts with the flag off. Ask for the flag on the attributes a query needs, not on every attribute.
|
|
44
44
|
- A filter can nest through a reference, through an inverse face, and can express an anti-join with `$not`.
|
|
45
|
-
-
|
|
45
|
+
- Aggregating or grouping by an attribute does not need the flag. An aggregate filter follows the same flag rules as a query filter. Sum, average, minimum, and maximum need a single-value numeric attribute. A group dimension is a checkbox, select, reference, user, or a bucketed date or datetime.
|
|
46
46
|
|
|
47
47
|
## Schema changes
|
|
48
48
|
|
|
49
49
|
- Every object or attribute change goes through a changeset. Hitch evaluates the change against existing records and dependent applets.
|
|
50
50
|
- A change that would break existing data, such as a unique constraint with duplicates, is blocked until the data is fixed.
|
|
51
|
-
- An applet release can bind to a pending changeset. The changeset apply switches the schema and promotes the release together.
|
|
51
|
+
- An applet release can bind to a pending changeset. Set `requiresChangeset` in `applet.manifest.ts` to bind it. The changeset apply switches the schema and promotes the release together.
|
|
@@ -8,15 +8,15 @@ Some limits clamp with no error. Some reject the request. Design for both.
|
|
|
8
8
|
|
|
9
9
|
A page is the unit of a read. A screen shows one page and asks for the next one. A page holds at most 200 root records. An included reference has no cap on linked records, so include only small links.
|
|
10
10
|
|
|
11
|
-
| Limit | Value | Past the limit
|
|
12
|
-
| ---------------------------------- | ----------- |
|
|
13
|
-
| `limit` on `list` and `query` | 50 default | Clamped to 200, no error
|
|
14
|
-
| `limit` on `list` and `query`, max | 200 |
|
|
15
|
-
| Filter nesting depth | 20 | HTTP 400
|
|
16
|
-
| `include` depth | 1 | HTTP 400 for a dotted path
|
|
17
|
-
| `include` on an inverse reference | Not allowed | HTTP 400
|
|
18
|
-
| Records per included reference | No cap |
|
|
19
|
-
| IDs in one `$in` array | No cap | Only 200 records return per page
|
|
11
|
+
| Limit | Value | Past the limit |
|
|
12
|
+
| ---------------------------------- | ----------- | ----------------------------------------------------- |
|
|
13
|
+
| `limit` on `list` and `query` | 50 default | Clamped to 200, no error |
|
|
14
|
+
| `limit` on `list` and `query`, max | 200 | `list` clamps to 200; the `query` schema rejects more |
|
|
15
|
+
| Filter nesting depth | 20 | HTTP 400 |
|
|
16
|
+
| `include` depth | 1 | HTTP 400 for a dotted path |
|
|
17
|
+
| `include` on an inverse reference | Not allowed | HTTP 400 |
|
|
18
|
+
| Records per included reference | No cap | Every linked record the caller can read returns |
|
|
19
|
+
| IDs in one `$in` array | No cap | Only 200 records return per page |
|
|
20
20
|
|
|
21
21
|
## Aggregates
|
|
22
22
|
|
|
@@ -56,7 +56,7 @@ Advanced filters and sorting are opt-in per attribute. This keeps each record ch
|
|
|
56
56
|
- Ordered operators, text-match operators, and `sort` need the `indexed` flag on the attribute. Without it the request fails with HTTP 400 and a message that starts with `Enable advanced filters and sort for`.
|
|
57
57
|
- New attributes start with `indexed` off. Ask the organization admin to turn it on for the attributes a query needs. Do not ask for it on every attribute.
|
|
58
58
|
- No attribute allows `sort` when it is multi-value, rich text, reference, user, or attachment.
|
|
59
|
-
-
|
|
59
|
+
- Aggregating or grouping by an attribute does not need `indexed`. An aggregate filter follows the same `indexed` rules as a query filter. Sum, average, minimum, and maximum need a single-value numeric attribute.
|
|
60
60
|
|
|
61
61
|
## Grant conditions
|
|
62
62
|
|