@hitch42/applet 0.1.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_internal/client.d.ts +7 -0
  3. package/dist/_internal/client.js +60 -0
  4. package/dist/_internal/server.d.ts +79 -0
  5. package/dist/_internal/server.js +232 -0
  6. package/dist/applet.d.ts +1 -0
  7. package/dist/applet.js +160 -0
  8. package/dist/auth-DaVK0k5R.js +350 -0
  9. package/dist/batch-Qa4B4-Yp.d.ts +30 -0
  10. package/dist/build-CEku1lyb.js +2 -0
  11. package/dist/build-Rk3xDZV7.js +74 -0
  12. package/dist/client.d.ts +48 -0
  13. package/dist/client.js +38 -0
  14. package/dist/contract-DMrpbaLJ.d.ts +23 -0
  15. package/dist/deploy-D3BGDqgC.js +2416 -0
  16. package/dist/generate-6Nbt7uii.js +2 -0
  17. package/dist/generate-CH0VQmUi.js +484 -0
  18. package/dist/index.d.ts +74 -0
  19. package/dist/index.js +136 -0
  20. package/dist/init-BS1Y-eNL.js +240 -0
  21. package/dist/lazy-client-x0DCI98S.js +64 -0
  22. package/dist/load-config-BnxocLAQ.js +56 -0
  23. package/dist/logs-m00pzr_O.js +183 -0
  24. package/dist/manifest-BQCAxATS.d.ts +81 -0
  25. package/dist/manifest-DyoJ1MIL.js +174 -0
  26. package/dist/project-CxN-Dtc7.js +346 -0
  27. package/dist/react.d.ts +37 -0
  28. package/dist/react.js +517 -0
  29. package/dist/server.d.ts +46 -0
  30. package/dist/server.js +104 -0
  31. package/dist/upload-DRbVhuDm.js +114 -0
  32. package/dist/upload-YbRKEqR6.d.ts +112 -0
  33. package/dist/variables-CP7Ce3Np.js +133 -0
  34. package/dist/vite.d.ts +63 -0
  35. package/dist/vite.js +358 -0
  36. package/dist/workflow-CTo2kZF1.js +51 -0
  37. package/dist/workflow-DurM6Fwx.d.ts +28 -0
  38. package/package.json +104 -0
  39. package/scaffold/.agents/skills/hitch-applets/SKILL.md +137 -0
  40. package/scaffold/.agents/skills/hitch-applets/references/client.md +73 -0
  41. package/scaffold/.agents/skills/hitch-applets/references/files.md +109 -0
  42. package/scaffold/.agents/skills/hitch-applets/references/manifest.md +171 -0
  43. package/scaffold/.agents/skills/hitch-applets/references/markdown.md +37 -0
  44. package/scaffold/.agents/skills/hitch-applets/references/records.md +152 -0
  45. package/scaffold/.agents/skills/hitch-applets/references/schedules.md +86 -0
  46. package/scaffold/.agents/skills/hitch-applets/references/server.md +98 -0
  47. package/scaffold/.agents/skills/hitch-applets/references/workflows.md +73 -0
  48. package/scaffold/AGENTS.md +38 -0
  49. package/scaffold/applet/components.json +25 -0
  50. package/scaffold/applet/src/client/components/ui/alert.tsx +73 -0
  51. package/scaffold/applet/src/client/components/ui/avatar.tsx +100 -0
  52. package/scaffold/applet/src/client/components/ui/badge.tsx +55 -0
  53. package/scaffold/applet/src/client/components/ui/button.tsx +90 -0
  54. package/scaffold/applet/src/client/components/ui/card.tsx +85 -0
  55. package/scaffold/applet/src/client/components/ui/checkbox.tsx +35 -0
  56. package/scaffold/applet/src/client/components/ui/dialog.tsx +164 -0
  57. package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +235 -0
  58. package/scaffold/applet/src/client/components/ui/input-group.tsx +144 -0
  59. package/scaffold/applet/src/client/components/ui/input.tsx +22 -0
  60. package/scaffold/applet/src/client/components/ui/label.tsx +29 -0
  61. package/scaffold/applet/src/client/components/ui/popover.tsx +74 -0
  62. package/scaffold/applet/src/client/components/ui/select.tsx +243 -0
  63. package/scaffold/applet/src/client/components/ui/separator.tsx +25 -0
  64. package/scaffold/applet/src/client/components/ui/sheet.tsx +177 -0
  65. package/scaffold/applet/src/client/components/ui/skeleton.tsx +13 -0
  66. package/scaffold/applet/src/client/components/ui/spinner.tsx +16 -0
  67. package/scaffold/applet/src/client/components/ui/switch.tsx +41 -0
  68. package/scaffold/applet/src/client/components/ui/table.tsx +114 -0
  69. package/scaffold/applet/src/client/components/ui/tabs.tsx +78 -0
  70. package/scaffold/applet/src/client/components/ui/textarea.tsx +23 -0
  71. package/scaffold/applet/src/client/lib/utils.ts +6 -0
  72. package/scaffold/applet/src/client/styles/globals.css +136 -0
@@ -0,0 +1,73 @@
1
+ # Client routes and interface
2
+
3
+ Read this file before you change client routes, navigation, page layout, forms, or controls.
4
+
5
+ ## Choose the client half
6
+
7
+ - Use a client-only applet for a user interface that uses the current user's Hitch access.
8
+ - Add a server only for secrets, external services, schedules, or imperative server work.
9
+ - Do not add a server proxy for normal Hitch data access.
10
+
11
+ ## Define routes
12
+
13
+ Hitch mounts each applet below `/<code>`. Include the applet code in every route path.
14
+
15
+ Use these file and path pairs:
16
+
17
+ | File | Route path |
18
+ | -------------------------------------- | ------------------- |
19
+ | `src/client/routes/index.tsx` | `/<code>/` |
20
+ | `src/client/routes/about.tsx` | `/<code>/about` |
21
+ | `src/client/routes/settings/index.tsx` | `/<code>/settings/` |
22
+
23
+ Define a page with `createFileRoute`:
24
+
25
+ ```tsx
26
+ import { createFileRoute } from "@tanstack/react-router";
27
+
28
+ export const Route = createFileRoute("/<code>/about")({
29
+ component: AboutPage,
30
+ });
31
+
32
+ function AboutPage() {
33
+ return <main>About</main>;
34
+ }
35
+ ```
36
+
37
+ - Keep the generated route path type as the source of truth.
38
+ - If TypeScript rejects a route, check its file name and `/<code>` prefix.
39
+ - Do not use a cast to bypass a route error.
40
+
41
+ ## Add navigation
42
+
43
+ Use TanStack Router's `Link` for a normal internal link:
44
+
45
+ ```tsx
46
+ import { Link } from "@tanstack/react-router";
47
+
48
+ <Link to="/<code>/about">About</Link>;
49
+ ```
50
+
51
+ Use the bundled `LinkButton` for a button-style link:
52
+
53
+ ```tsx
54
+ import { LinkButton } from "@/client/components/ui/button";
55
+
56
+ <LinkButton href="/<code>/about">About</LinkButton>;
57
+ ```
58
+
59
+ - Use a full path that starts with the applet code.
60
+ - Use `LinkButton` for button-style navigation. Use `Button` for an action.
61
+ - Do not add extra navigation that the user did not request.
62
+
63
+ ## Use the interface components
64
+
65
+ - Use components in `src/client/components/ui` before raw HTML controls.
66
+ - Inspect a component export before you use it.
67
+ - Browse the [shadcn component catalog](https://ui.shadcn.com/docs/components) before you create a control.
68
+ - Prefer a shadcn React Aria component when the catalog has a suitable component.
69
+ - Run `pnpm exec shadcn add <component>` from the applet directory to add a missing component.
70
+ - Keep installed components unchanged when possible.
71
+ - Copy a component outside the `ui` directory before a product-specific change.
72
+ - Use `cn` from `@/client/lib/utils` for class names.
73
+ - Keep global styles in `src/client/styles/globals.css`.
@@ -0,0 +1,109 @@
1
+ # Files
2
+
3
+ Use the file API to upload bytes, read file metadata, and link files to records.
4
+
5
+ ## Upload a file
6
+
7
+ - Import `hitch` from the client or server SDK.
8
+ - Call `hitch.files.upload(file)` with a web `File`.
9
+ - Use the same upload call in the client and server halves.
10
+ - Receive the complete file row plus a `downloadUrl` when the call resolves: storage has the bytes.
11
+ - A failed upload throws an `Error` and leaves no usable id. Call `upload(file)` again to retry — the abandoned row is harmless.
12
+
13
+ ```ts
14
+ import { hitch } from "@hitch42/applet/client";
15
+
16
+ const uploaded = await hitch.files.upload(file);
17
+ ```
18
+
19
+ Show progress or allow cancellation with the options argument:
20
+
21
+ ```ts
22
+ const controller = new AbortController();
23
+
24
+ const uploaded = await hitch.files.upload(file, {
25
+ signal: controller.signal,
26
+ onProgress: ({ loaded, total }) => setPercent(Math.round((100 * loaded) / total)),
27
+ });
28
+ ```
29
+
30
+ - `onProgress` reports uploaded bytes in the browser. The server half uploads without progress.
31
+ - Abort the signal to cancel; the call rejects with an abort error.
32
+
33
+ Use a TanStack mutation in a React component:
34
+
35
+ ```ts
36
+ import { useMutation } from "@tanstack/react-query";
37
+ import { hitch } from "@hitch42/applet/client";
38
+
39
+ const upload = useMutation({
40
+ mutationFn: (file: File) => hitch.files.upload(file),
41
+ });
42
+
43
+ upload.mutate(file);
44
+ ```
45
+
46
+ Use the server SDK for an imperative upload:
47
+
48
+ ```ts
49
+ import { hitch } from "@hitch42/applet/server";
50
+
51
+ const uploaded = await hitch.files.upload(file);
52
+ ```
53
+
54
+ ## Set the content type
55
+
56
+ The browser sets `file.type` for picked files. On the server, set the type when you construct the `File`:
57
+
58
+ ```ts
59
+ const file = new File([csvBytes], "report.csv", { type: "text/csv" });
60
+ ```
61
+
62
+ An empty `file.type` uploads as `application/octet-stream`.
63
+
64
+ ## Read a file
65
+
66
+ Use `download` when a component needs one fresh link without query state:
67
+
68
+ ```ts
69
+ const file = await hitch.files.download(id);
70
+ file.downloadUrl; // a download link, valid for one hour
71
+ ```
72
+
73
+ Use the `get` query utilities when React needs file metadata as query state.
74
+
75
+ `get` returns `{ data, downloadUrl }`. Every file you can fetch is complete: `upload` resolves only after storage confirms the bytes. A file whose upload never finished answers 404 with code `file_not_arrived` — treat that id as dead and upload again.
76
+
77
+ ```ts
78
+ const fileQuery = useQuery(
79
+ hitch.files.get.queryOptions({
80
+ input: { params: { id } },
81
+ }),
82
+ );
83
+
84
+ if (fileQuery.data) {
85
+ const { data: row, downloadUrl } = fileQuery.data;
86
+ row.filename; // "report.csv"
87
+ row.contentType; // "text/csv"
88
+ row.sizeBytes; // 1024
89
+ downloadUrl; // a download link, valid for one hour
90
+ }
91
+ ```
92
+
93
+ A `downloadUrl` expires after one hour. Call `get` again for a fresh link instead of storing one.
94
+
95
+ ## Link a file to a record
96
+
97
+ Store `uploaded.id` as the value of an attachment attribute. A multi-value attachment attribute takes an array of ids.
98
+
99
+ ```ts
100
+ const uploaded = await hitch.files.upload(file);
101
+
102
+ await hitch.records.claims.update.call({
103
+ params: { id: claimId },
104
+ body: { receipt_id: uploaded.id }, // multi-value: [uploaded.id]
105
+ });
106
+ ```
107
+
108
+ - Upload the file before the record write: the write rejects a missing id ("file not found") and a pending id ("file has not arrived").
109
+ - Store the file id, never a URL — URLs expire.
@@ -0,0 +1,171 @@
1
+ # Applet manifest
2
+
3
+ Define the applet manifest in `applet.manifest.ts`.
4
+
5
+ The authored definition omits `org`.
6
+ The applet tool reads `org` from `hitch.config.ts` in the workspace root.
7
+
8
+ ## Use top-level fields
9
+
10
+ - `org` identifies the Hitch organization.
11
+ - `name` gives the applet a display name.
12
+ - `code` gives the applet its stable code and URL prefix.
13
+ - `objects` declares record objects and attribute access.
14
+ - `variables` optionally declares runtime variables.
15
+ - `schedules` optionally declares scheduled server calls.
16
+ - `dependencies` optionally declares applet runtime packages.
17
+ - `devDependencies` optionally declares applet build packages.
18
+
19
+ The manifest and applet definition reject unknown fields.
20
+
21
+ ## Set `org`
22
+
23
+ - Use a non-empty organization code.
24
+ - Start the code with a lowercase letter.
25
+ - End the code with a lowercase letter or number.
26
+ - Use only lowercase letters, numbers, and non-consecutive hyphens.
27
+ - Do not put `org` in `applet.manifest.ts`.
28
+ - Set it in the root `hitch.config.ts` file.
29
+
30
+ ## Set `name`
31
+
32
+ - Use a string with 1 through 120 characters after trimming.
33
+ - Use the name as the applet display name.
34
+
35
+ ## Set `code`
36
+
37
+ - Start the code with a lowercase letter.
38
+ - End the code with a lowercase letter or number.
39
+ - Use only lowercase letters, numbers, and non-consecutive underscores.
40
+ - Make the applet directory name equal to the code.
41
+ - Rename the directory or change the code when they differ.
42
+ - Expect the Vite base path to be `/<code>/`.
43
+ - Include the code in each client route path.
44
+
45
+ ## Declare objects
46
+
47
+ Add one entry for each object that the applet needs.
48
+
49
+ ```ts
50
+ objects: [
51
+ {
52
+ code: "deals",
53
+ canCreate: true,
54
+ canDelete: false,
55
+ readOnly: ["created_by"],
56
+ readWrite: ["stage", "amount"],
57
+ },
58
+ ],
59
+ ```
60
+
61
+ Each object entry has five required fields.
62
+
63
+ - `code` identifies the Hitch object.
64
+ - `canCreate` is a boolean that controls the generated `create` method.
65
+ - `canDelete` is a boolean that controls the generated `delete` method.
66
+ - `readOnly` lists attribute codes that the applet can read but cannot write.
67
+ - `readWrite` lists attribute codes that the applet can read and write.
68
+
69
+ - Use the applet code format for each object code.
70
+ - Do not declare the same object code more than once.
71
+ - Use the applet code format for each attribute code.
72
+ - Do not list `id`, `name`, or `expected_version` as an attribute code.
73
+ - Do not repeat an attribute code in one list.
74
+ - Do not put one attribute code in both lists.
75
+
76
+ The object declaration caps record access for the applet identity.
77
+
78
+ - Declare only the objects that scheduled applet work needs.
79
+ - Declare only the attributes that scheduled applet work needs.
80
+ - Use `readOnly` for attributes that the applet identity must not change.
81
+ - Use `readWrite` for attributes that the applet identity can change.
82
+ - Set `canCreate` and `canDelete` to the required values.
83
+ - The generated contract omits undeclared objects.
84
+ - The generated contract removes `create` when `canCreate` is false.
85
+ - The generated contract removes `delete` when `canDelete` is false.
86
+ - Regenerate after an object grant changes.
87
+ - Do not restore a removed object or method with a cast.
88
+
89
+ Client requests use the current user's identity and access.
90
+ Scheduled requests use the applet identity and its manifest cap.
91
+
92
+ ## Declare variables
93
+
94
+ Declare variable names and policies without values.
95
+
96
+ ```ts
97
+ variables: {
98
+ API_URL: {},
99
+ API_TOKEN: { secret: true },
100
+ REPORT_CHANNEL: { optional: true },
101
+ },
102
+ ```
103
+
104
+ - Start each key with a letter or underscore.
105
+ - Use only letters, numbers, and underscores after the first character.
106
+ - Use at most 128 characters.
107
+ - Do not use a key that starts with `HITCH_`, without regard to letter case.
108
+ - Set `secret` to a boolean to mark how the platform stores the value.
109
+ - Set `optional` to a boolean to control whether the value is required.
110
+ - Omit `optional` or set it to false for a required variable.
111
+ - Set variable values on the platform.
112
+ - Do not put variable values in the manifest or source code.
113
+ - Read declared variables from `env` in server code.
114
+ - Expect an optional variable to have type `string | undefined`.
115
+ - Expect a required variable to have type `string`.
116
+
117
+ ## Declare schedules
118
+
119
+ Each schedule calls one registered server procedure.
120
+
121
+ ```ts
122
+ schedules: [
123
+ {
124
+ code: "refresh_cache",
125
+ name: "Refresh cache",
126
+ cron: "*/5 * * * *",
127
+ path: "/refreshCache",
128
+ },
129
+ ],
130
+ ```
131
+
132
+ - `code` gives the schedule a stable code.
133
+ - `name` gives the schedule a display name.
134
+ - `cron` sets its run times.
135
+ - `path` selects a server router procedure.
136
+
137
+ Apply these validation rules.
138
+
139
+ - Use the applet code format for each schedule code.
140
+ - Do not declare the same schedule code more than once.
141
+ - Use a name with 1 through 120 characters after trimming.
142
+ - Use a valid five-field cron expression.
143
+ - Put fields in minute, hour, day-of-month, month, and day-of-week order.
144
+ - Start `path` with one `/`.
145
+ - Do not start `path` with `//`.
146
+ - Do not put whitespace in `path`.
147
+ - Keep `src/server/main.ts` when the manifest has a schedule.
148
+
149
+ Generation registers the server router type.
150
+ After generation, `path` accepts only paths from that registered router.
151
+ Before router registration, the path type falls back to `string`.
152
+
153
+ ## Declare packages
154
+
155
+ Add direct package requirements to the manifest.
156
+
157
+ ```ts
158
+ dependencies: {
159
+ "date-fns": "^4.1.0",
160
+ },
161
+ devDependencies: {
162
+ "@types/example": "^1.0.0",
163
+ },
164
+ ```
165
+
166
+ - Put runtime imports in `dependencies`.
167
+ - Put build and test tools in `devDependencies`.
168
+ - Do not add a package that the applet CLI already supplies.
169
+ - Do not put one package in both dependency groups.
170
+ - Use package ranges that pnpm accepts.
171
+ - Run `applet generate`, `applet build`, or `applet deploy` to update generated package files.
@@ -0,0 +1,37 @@
1
+ # Markdown
2
+
3
+ Use the SDK components to edit and render markdown with Hitch file images.
4
+
5
+ ## Edit markdown
6
+
7
+ `MarkdownEditor` is a controlled Tiptap WYSIWYG editor. Store its markdown value in your form state. Pass `hitch.files` for image paste and drop.
8
+
9
+ The editor supports GFM tables and task lists. A plain table serializes as GFM. A table with block content in a cell (for example a list) serializes as inline HTML, which the `Markdown` renderer also renders.
10
+
11
+ ```tsx
12
+ import { hitch } from "@hitch42/applet/client";
13
+ import { MarkdownEditor } from "@hitch42/applet/react";
14
+
15
+ <MarkdownEditor value={value} onChange={setValue} files={hitch.files} />;
16
+ ```
17
+
18
+ The editor uploads pasted or dropped images. It stores each image as `![name](hitch-file://<file-id>)`.
19
+
20
+ The `files` value must supply both `upload(file)` and `download(id)`. The editor uses `download(id)` to show stored Hitch images.
21
+
22
+ While an upload runs, the editor shows the image from a temporary local URL and blocks native form submission, so a form cannot save a half-done upload. A failed upload removes the image and calls `onUploadError` if you pass one; without it, the editor logs the error and continues with the next file.
23
+
24
+ ## Render markdown
25
+
26
+ Pass the same files surface to `Markdown`:
27
+
28
+ ```tsx
29
+ import { hitch } from "@hitch42/applet/client";
30
+ import { Markdown } from "@hitch42/applet/react";
31
+
32
+ <Markdown files={hitch.files}>{value}</Markdown>;
33
+ ```
34
+
35
+ `Markdown` calls `hitch.files.download(id)` to replace each Hitch file id with a fresh download link.
36
+
37
+ Store markdown with `hitch-file://` file ids. Never store a presigned download URL because it expires.
@@ -0,0 +1,152 @@
1
+ # Records
2
+
3
+ Use the generated Hitch contract as the typed data plane.
4
+
5
+ - Import `hitch` from `@hitch42/applet/client` in client code.
6
+ - Import `hitch` from `@hitch42/applet/server` in server code.
7
+ - Read object codes, attribute codes, inputs, and outputs from the generated types.
8
+ - Use only the target applet's `.hitch` types as schema evidence.
9
+ - Never copy or infer types from a sibling applet.
10
+ - If the target applet has no generated types, resolve generation before you write record code.
11
+ - Do not add local copies of generated types.
12
+ - Do not use a cast to restore a missing object, field, or operation.
13
+
14
+ ## Use record verbs
15
+
16
+ - `list` sends `GET /v1/<objectCode>` and lists records with query parameters.
17
+ - `create` sends `POST /v1/<objectCode>` and creates one record.
18
+ - `get` sends `GET /v1/<objectCode>/{id}` and gets one record.
19
+ - `update` sends `PATCH /v1/<objectCode>/{id}` and updates one record.
20
+ - `delete` sends `DELETE /v1/<objectCode>/{id}` and deletes one record.
21
+ - `query` sends `POST /v1/<objectCode>/query` and lists records with a structured body.
22
+ - `updateWhere` sends `POST /v1/<objectCode>/update` and updates matching records.
23
+ - `aggregate` sends `POST /v1/<objectCode>/aggregate` and calculates aggregate values.
24
+
25
+ - Pass an object ID in `params.id` for `get`, `update`, and `delete`.
26
+ - Pass record fields in `body` for `create` and `update`.
27
+ - Pass structured query data in `body` for `query`.
28
+
29
+ ## Call records from the client
30
+
31
+ Use TanStack Query options from each generated procedure.
32
+
33
+ ```ts
34
+ import { useQuery } from "@tanstack/react-query";
35
+ import { hitch } from "@hitch42/applet/client";
36
+
37
+ const deals = useQuery(
38
+ hitch.records.deals.query.queryOptions({
39
+ input: {
40
+ body: { filter: { stage: { $eq: "open" } }, limit: 25 },
41
+ },
42
+ }),
43
+ );
44
+ ```
45
+
46
+ ```ts
47
+ import { useMutation } from "@tanstack/react-query";
48
+ import { hitch } from "@hitch42/applet/client";
49
+
50
+ const createDeal = useMutation(hitch.records.deals.create.mutationOptions());
51
+
52
+ createDeal.mutate({ body: { name: "New deal" } });
53
+ ```
54
+
55
+ - Use `.queryOptions(...)` for reads and `.mutationOptions(...)` for writes.
56
+ - The client uses the current user's identity and access.
57
+ - Do not add a server proxy for normal access control or filtering.
58
+
59
+ ## Call records from the server
60
+
61
+ ```ts
62
+ import { hitch } from "@hitch42/applet/server";
63
+
64
+ const deals = await hitch.records.deals.query({
65
+ body: { filter: { stage: { $eq: "open" } }, limit: 25 },
66
+ });
67
+
68
+ const deal = await hitch.records.deals.get({
69
+ params: { id: dealId },
70
+ });
71
+ ```
72
+
73
+ - Do not use `.queryOptions(...)` or `.mutationOptions(...)` on the server.
74
+ - Use the server SDK for imperative data access, secrets, or external services.
75
+
76
+ ## Build a query
77
+
78
+ The `query` body accepts `filter`, `sort`, `limit`, `cursor`, and `include`.
79
+
80
+ ```ts
81
+ const page = await hitch.records.deals.query({
82
+ body: {
83
+ filter: {
84
+ $and: [{ stage: { $in: ["open", "won"] } }, { amount: { $gte: 1000 } }],
85
+ },
86
+ sort: { attribute: "amount", direction: "desc" },
87
+ limit: 50,
88
+ cursor,
89
+ include: ["company"],
90
+ },
91
+ });
92
+ ```
93
+
94
+ - Use `$and` or `$or` with a non-empty array of filter nodes.
95
+ - Use `$not` with one filter node.
96
+ - Put more than one field in a node to combine those fields with AND.
97
+ - Use a direct field value as shorthand for `$eq`.
98
+ - Use `$eq`, `$in`, or `$not_empty` for identity fields.
99
+ - Use `$eq` or `$not_empty` for checkbox fields.
100
+ - Use only `$not_empty` for attachment fields.
101
+ - Use `$eq`, `$in`, `$not_empty`, `$contains`, `$starts_with`, or `$ends_with` for text fields.
102
+ - Use `$eq`, `$in`, `$not_empty`, `$lt`, `$lte`, `$gt`, or `$gte` for ordered fields.
103
+ - Pass `true` to `$not_empty`.
104
+ - Pass an array to `$in`.
105
+ - Pass a string to `$contains`, `$starts_with`, and `$ends_with`.
106
+ - Sort with `{ attribute, direction }`.
107
+ - Use `asc` or `desc` as the sort direction.
108
+ - Do not sort by multi-value, reference, user, or attachment attributes.
109
+ - Omit `sort` to sort by `createdAt` in descending order.
110
+ - Use a positive integer for `limit`.
111
+ - The service caps `limit` at 200 and uses 50 by default.
112
+ - Pass the returned pagination cursor back as `cursor`.
113
+ - Pass one attribute code or an array of attribute codes as `include`.
114
+
115
+ ## Aggregate records
116
+
117
+ Pass `filter`, `aggregates`, and `groupBy` in the aggregate body.
118
+ An aggregate or group attribute can name one reference step and a target attribute, for example `company_id.industry`.
119
+ Each aggregate can have its own `filter` with the same syntax as the top-level `filter`, including nested reference filters.
120
+ `groupBy` accepts an array of up to two dimensions.
121
+ The array form returns `keys` and `labels` arrays per group.
122
+ The object form orders category groups by an aggregate value (top-N by value).
123
+ The array form orders each dimension by key; `limit` selects the first N distinct keys in that order. It does not support top-N by value.
124
+
125
+ ```ts
126
+ const totals = await hitch.records.deals.aggregate({
127
+ body: { filter, aggregates, groupBy },
128
+ });
129
+ ```
130
+
131
+ ## Update matching records
132
+
133
+ Pass `filter`, `set`, optional `expect`, and optional `limit` to `updateWhere`.
134
+
135
+ ```ts
136
+ const result = await hitch.records.deals.updateWhere({
137
+ body: { filter, set: { stage: "closed" }, expect: 3 },
138
+ });
139
+ ```
140
+
141
+ - Use a nonnegative integer for `expect`.
142
+ - Use generated types for query, aggregate, group, set, and limit values.
143
+
144
+ ## Fix a missing method
145
+
146
+ The generated contract contains only objects declared in the manifest.
147
+
148
+ - The contract removes `create` when `canCreate` is false.
149
+ - The contract removes `delete` when `canDelete` is false.
150
+ - Treat a missing method as a missing manifest grant, and edit the manifest when required.
151
+ - Run generation after a manifest or Hitch schema change.
152
+ - Do not bypass the contract with a cast.
@@ -0,0 +1,86 @@
1
+ # Scheduled runs
2
+
3
+ Use a schedule to call an applet server procedure from a cron expression.
4
+
5
+ ## Define the target procedure
6
+
7
+ - Add the target to the applet's server router.
8
+ - Use `os.handler` to define the procedure.
9
+ - Default-export `{ router }`.
10
+
11
+ ```ts
12
+ import { os } from "@orpc/server";
13
+
14
+ const router = {
15
+ refresh: os.handler(async () => {
16
+ // Run the scheduled work.
17
+ }),
18
+ };
19
+
20
+ export default { router };
21
+ ```
22
+
23
+ ## Declare the schedule
24
+
25
+ - Add a `schedules` entry to the applet manifest.
26
+ - Give each schedule a unique `code`.
27
+ - Give each schedule a non-empty `name`.
28
+ - Use a valid five-field cron expression.
29
+ - Set `path` to the server procedure path.
30
+ - Start the path with `/`.
31
+ - Do not add whitespace to the path.
32
+
33
+ ```ts
34
+ schedules: [
35
+ {
36
+ code: "refresh",
37
+ name: "Refresh data",
38
+ cron: "*/5 * * * *",
39
+ path: "/refresh",
40
+ },
41
+ ],
42
+ ```
43
+
44
+ - The five fields are minute, hour, day of month, month, and day of week.
45
+ - The registered router type checks the path after type generation.
46
+ - A nested procedure uses its nested router path.
47
+ - The scheduler invokes the named router procedure with no input.
48
+
49
+ ## Understand the run identity
50
+
51
+ - A scheduled run acts as the applet.
52
+ - It has no current user.
53
+ - Hitch data calls use the applet's manifest grants.
54
+ - The manifest grants set the maximum data access.
55
+ - User-only endpoints return HTTP 403.
56
+ - Admin-only endpoints return HTTP 403.
57
+
58
+ ## Keep work within the run limits
59
+
60
+ - Each run has a 30,000 millisecond CPU limit.
61
+ - Each run has a 1,000 subrequest limit.
62
+ - Keep the procedure within both limits.
63
+ - The scheduler prevents overlap for 16 minutes after an active run starts.
64
+ - A later due run can start after that overlap window.
65
+
66
+ ## Upload files from a run
67
+
68
+ - Import `hitch` from `@hitch42/applet/server`.
69
+ - Call `hitch.files.upload(file)` with a web `File`.
70
+ - The upload uses the scheduled run's applet identity.
71
+ - The file bytes go directly to storage through a presigned URL.
72
+
73
+ ```ts
74
+ import { hitch } from "@hitch42/applet/server";
75
+
76
+ const uploaded = await hitch.files.upload(file);
77
+ ```
78
+
79
+ ## Diagnose a failed run
80
+
81
+ - Check the applet invocation history first.
82
+ - Read the stored status, path, HTTP status, and error details.
83
+ - Check the applet logs for procedure details.
84
+ - Follow the log commands in the main Hitch applet skill.
85
+ - Include `--level log` during diagnosis.
86
+ - Do not log secrets, tokens, or personal data.