@hitch42/applet 0.1.0-beta.3 → 0.1.0-beta.9

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 (55) hide show
  1. package/dist/_internal/server.d.ts +14 -1
  2. package/dist/_internal/server.js +23 -74
  3. package/dist/applet.js +53 -29
  4. package/dist/build-BfbTbt_r.js +2 -0
  5. package/dist/{build-Ch3pALLA.js → build-CAb26kMY.js} +30 -14
  6. package/dist/ci-5qWl7lT3.js +68 -0
  7. package/dist/client.d.ts +3 -3
  8. package/dist/{contract-DMrpbaLJ.d.ts → contract-rP_eV9PK.d.ts} +1 -1
  9. package/dist/{deploy-DP2Bg6nt.js → deploy-D_OFJ6-Q.js} +193 -30
  10. package/dist/email-DBTVhNB_.js +62 -0
  11. package/dist/generate-BgepN4-b.js +2 -0
  12. package/dist/{generate-BuszZ9-U.js → generate-CBPnHoqX.js} +40 -30
  13. package/dist/index.d.ts +122 -11
  14. package/dist/index.js +141 -26
  15. package/dist/{init-BjWWDqqX.js → init-tfzwuYnk.js} +12 -8
  16. package/dist/load-config-BYSxDVAl.js +27 -0
  17. package/dist/login-C7RKmmTe.js +12 -0
  18. package/dist/{logs-m00pzr_O.js → logs-DgjgAxj2.js} +3 -3
  19. package/dist/{manifest-DyoJ1MIL.js → manifest-Bp87Uz0a.js} +6 -0
  20. package/dist/{manifest-BQCAxATS.d.ts → manifest-DD_SGkS6.d.ts} +5 -0
  21. package/dist/me-BW7Paary.js +32 -0
  22. package/dist/{project-MN3mFxeE.js → project-D6XHllmC.js} +26 -20
  23. package/dist/prompt-ChS-grYO.js +29 -0
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +40 -42
  26. package/dist/schedules-dhU1yyFr.js +42 -0
  27. package/dist/server.d.ts +3 -3
  28. package/dist/telephony-protocol-D-nf3Gca.d.ts +76 -0
  29. package/dist/telephony.d.ts +77 -0
  30. package/dist/telephony.js +607 -0
  31. package/dist/{upload-YbRKEqR6.d.ts → upload-DKZzz5kW.d.ts} +6 -6
  32. package/dist/{variables-CP7Ce3Np.js → variables-Bwnr7oxf.js} +3 -28
  33. package/dist/vite.d.ts +1 -1
  34. package/dist/vite.js +458 -27
  35. package/dist/workflow-engine-MogejNfF.js +117 -0
  36. package/dist/{auth-DaVK0k5R.js → workspace-XVtM12ZC.js} +44 -4
  37. package/package.json +6 -1
  38. package/scaffold/.agents/skills/hitch-applets/SKILL.md +34 -15
  39. package/scaffold/.agents/skills/hitch-applets/references/email-tracking.md +49 -0
  40. package/scaffold/.agents/skills/hitch-applets/references/files.md +3 -3
  41. package/scaffold/.agents/skills/hitch-applets/references/manifest.md +21 -2
  42. package/scaffold/.agents/skills/hitch-applets/references/performance.md +74 -0
  43. package/scaffold/.agents/skills/hitch-applets/references/records.md +30 -8
  44. package/scaffold/.agents/skills/hitch-applets/references/schedules.md +7 -2
  45. package/scaffold/.agents/skills/hitch-applets/references/server.md +0 -3
  46. package/scaffold/.agents/skills/hitch-applets/references/workflows.md +11 -3
  47. package/scaffold/.agents/skills/hitch-cli/SKILL.md +129 -0
  48. package/scaffold/.agents/skills/hitch-data/SKILL.md +51 -0
  49. package/scaffold/.agents/skills/hitch-data/references/attributes.md +121 -0
  50. package/scaffold/.agents/skills/hitch-data/references/limits.md +64 -0
  51. package/scaffold/.agents/skills/hitch-theory/SKILL.md +150 -0
  52. package/scaffold/.github/workflows/hitch.yml +1 -7
  53. package/dist/build-BqoQ-uNz.js +0 -2
  54. package/dist/generate-CRVA0Ll5.js +0 -2
  55. package/dist/load-config-BnxocLAQ.js +0 -56
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: hitch-cli
3
+ description: Use the `hitch` command line to sign in, select an organization, and read or write Hitch data and administration from the shell. Use when a task needs to inspect records, try a filter or aggregate, create test data, manage teams, grants, or capabilities, apply a changeset, or check applet access without writing applet code.
4
+ ---
5
+
6
+ # Hitch CLI
7
+
8
+ The `hitch` command talks to one organization on one Hitch instance. Its data and admin commands are generated from that organization's API, so the command set matches the organization's objects.
9
+
10
+ `hitch` is separate from `applet`. Use `applet` to create, generate, build, and deploy applets. Use `hitch` to work with the organization's data and settings. Read the `hitch-applets` skill for `applet`.
11
+
12
+ ## Sign in
13
+
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.
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.
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.
18
+ - Run `hitch whoami` to print the current person, email, organization name, and organization id as JSON.
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.
21
+
22
+ ## Select the organization
23
+
24
+ The CLI resolves the organization in this order. The first match wins.
25
+
26
+ 1. `--org <code>` on any command.
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/`.
28
+ 3. `HITCH_DEFAULT_ORG`.
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.
31
+
32
+ ## Refresh the commands
33
+
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.`
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.
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.
37
+
38
+ ## Read the command names
39
+
40
+ Every generated command follows the API route.
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.
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`.
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"}'`.
45
+ - A required field is a required flag. The CLI does not prompt for a missing value. It prints the usage error.
46
+ - Run `hitch <object> <verb> --help` to see the flags for that organization's schema.
47
+ - Every `delete` command asks for confirmation. Pass `--yes` to skip it. A script without a terminal must pass `--yes`.
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.
49
+ - An error prints `hitch: <message>` and exits with code 1.
50
+
51
+ ## Work with records
52
+
53
+ Examples use an object called `deals` with attributes `amount`, `stage`, and `owner_id`. Ids are UUIDs.
54
+
55
+ ```sh
56
+ hitch deals list --limit 20 --include owner
57
+ hitch deals get --id 0192f1a2-3b4c-7d5e-8f60-123456789abc --include owner
58
+ hitch deals create --name "Acme renewal" --amount 5000 --stage open
59
+ hitch deals update --id 0192f1a2-3b4c-7d5e-8f60-123456789abc --stage won
60
+ hitch deals delete --id 0192f1a2-3b4c-7d5e-8f60-123456789abc --yes
61
+ hitch deals query --filter '{"stage":{"$in":["open","won"]}}' --sort '{"attribute":"createdAt","direction":"desc"}' --limit 50
62
+ hitch deals update-where --filter '{"stage":"open"}' --set '{"stage":"lost"}' --expect 3
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"}}'
65
+ ```
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.
68
+ - A list returns one page of at most 200 records and a cursor. Pass `--cursor <value>` for the next page.
69
+ - Data commands act as the signed-in person with that person's grants. An API key acts as the member who made it.
70
+
71
+ ## Administer the organization
72
+
73
+ Admin commands need the matching capability. Examples use fake ids.
74
+
75
+ ```sh
76
+ hitch admin teams list
77
+ hitch admin teams create --name Sales
78
+ hitch admin teams members set --id <teamId> --user-id <userId>
79
+ hitch admin capabilities list
80
+ hitch admin capabilities grants set --capability objects:manage --team-id <teamId>
81
+ hitch admin objects list
82
+ hitch admin objects access list --object-id <objectId>
83
+ hitch admin teams object-grants set --team-id <teamId> --object-id <objectId> --can-create --can-read --can-update --no-can-delete
84
+ hitch admin teams object-grants conditions create --team-id <teamId> --object-id <objectId> --operator equals --operand current_user --path <attributeId>
85
+ hitch admin objects default-grant set --object-id <objectId> --can-read --no-can-create --no-can-update --no-can-delete
86
+ hitch admin applets access list --code sales_portal
87
+ hitch admin applets grants set --code sales_portal --team-id <teamId>
88
+ ```
89
+
90
+ - Object grants for a team live under `admin teams object-grants`. The default grant that every member holds lives under `admin objects default-grant`. Each has `conditions` and `attributes` subcommands.
91
+ - Add `--delegate-attribute-id <attributeId>` to an object grant to delegate through a reference attribute.
92
+ - `admin objects access list` shows which grantees cannot read an object. `admin applets access list` shows which grantees cannot use what an applet reads.
93
+
94
+ ## Change the schema
95
+
96
+ Schema changes go through a changeset. The CLI names the lifecycle as state, not as actions.
97
+
98
+ ```sh
99
+ hitch admin changesets create
100
+ hitch admin objects create --name Deal --changeset-id <changesetId>
101
+ hitch admin objects attributes create --code deals --name Amount --type currency --changeset-id <changesetId>
102
+ hitch admin changesets blockers list --id <changesetId>
103
+ hitch admin changesets update --id <changesetId> --status applied
104
+ hitch admin changesets update --id <changesetId> --status abandoned
105
+ hitch admin changesets update --id <changesetId> --adopt <otherChangesetId>
106
+ ```
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.
109
+ - Add `--dry-run` to evaluate a schema command without a change.
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.
111
+ - Run `blockers list` before an apply. Any blocker stops the apply with HTTP 409.
112
+ - Run `hitch sync` after an apply, so the new objects and attributes appear as commands.
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.
115
+
116
+ ## Fix common errors
117
+
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. |
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: hitch-data
3
+ description: The Hitch data model and data API rules. Use when a task defines or changes objects and attributes, models a relationship, writes or validates record values, builds a filter or sort, or hits an API limit. Covers attribute types, references, required and null rules, the indexed flag, and the enforced read, write, and aggregate limits.
4
+ ---
5
+
6
+ # Hitch data
7
+
8
+ An organization's data lives in Hitch objects. Each object has attributes. Each record holds one value per attribute. Hitch validates, normalizes, and stores every value. Applets read and write records through the SDK and never keep a private copy.
9
+
10
+ ## Go deeper
11
+
12
+ | Read this | When the task involves |
13
+ | -------------------------- | ------------------------------------------------------------------------------------------------ |
14
+ | `references/attributes.md` | The behavior of one attribute type: accepted values, null and empty rules, normalization, config |
15
+ | `references/limits.md` | A page size, a batch size, an aggregate limit, a filter that fails, or a sort that fails |
16
+
17
+ ## Objects and records
18
+
19
+ - An object has a code, a name, and attributes. A record belongs to one object.
20
+ - Every record has an `id`, a required `name`, `createdAt`, `updatedAt`, and `deletedAt`.
21
+ - A record is JSON. Every owner attribute key is present on read. An empty attribute reads as `null`. An inverse reference face has no key.
22
+ - A record can be created with a client-supplied UUID `id`. Use this to link records inside one batch.
23
+
24
+ ## Attributes
25
+
26
+ - An attribute is schema, not a free JSON field. Choose a type. Hitch validates the value against the type and its configuration.
27
+ - Types: text, rich_text, integer, decimal, currency, checkbox, date, datetime, time, domain, url, email, phone, select, reference, user, attachment.
28
+ - A value is a non-empty value or `null`. No type accepts an empty string.
29
+ - Cardinality is configuration. Text, select, reference, user, and attachment can be multi-value.
30
+ - A required attribute rejects `null` and rejects an empty array.
31
+ - Store a fixed set of values as a select, not as text. Store a link to another record as a reference, not as text or as an id in a text field. Text cannot be a group dimension, and text matching needs the `indexed` flag.
32
+
33
+ ## References
34
+
35
+ - A reference attribute names a target object. Write the target record id.
36
+ - A reference can expose an inverse face on the target object. The owner face stores the link. The inverse face is read-only and is absent from the record JSON.
37
+ - One-to-many is a single reference on one side and a multi inverse on the other. Many-to-many is multi on both sides. `targetSingle` limits each target record to one link through the attribute.
38
+ - Do not model a plain relationship as a separate join object. A reference gives typed links, link cleanup when a target is deleted, filters through either face, and access through grants. Use a join object only when the relationship needs its own fields or lifecycle.
39
+
40
+ ## Filters and sorting
41
+
42
+ - Every attribute accepts `$eq` and `$not_empty`. Every attribute except checkbox accepts `$in`.
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
+ - A filter can nest through a reference, through an inverse face, and can express an anti-join with `$not`.
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
+
47
+ ## Schema changes
48
+
49
+ - Every object or attribute change goes through a changeset. Hitch evaluates the change against existing records and dependent applets.
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. Set `requiresChangeset` in `applet.manifest.ts` to bind it. The changeset apply switches the schema and promotes the release together.
@@ -0,0 +1,121 @@
1
+ # Attribute types
2
+
3
+ Hitch validates and normalizes every attribute value on write. Read this before you build a form, an import, or a write path.
4
+
5
+ ## Rules for every type
6
+
7
+ - A value is a non-empty value or `null`. Every type rejects an empty string.
8
+ - Send `null` to clear a value. A required attribute rejects `null` on create and on update.
9
+ - A single-value attribute rejects an array. A multi-value attribute rejects a scalar.
10
+ - A multi-value attribute holds an ordered array. An empty array is accepted when the attribute is not required, and reads back as `null`.
11
+ - Only text, select, reference, user, and attachment can be multi-value. Text allows duplicate entries. The other four reject duplicates.
12
+ - A `default` in the attribute configuration applies on create only, when the body omits the attribute.
13
+ - An `immutable` attribute rejects a change once it holds a non-null value. A null immutable attribute can still be set once.
14
+ - A `unique` attribute rejects a duplicate value with HTTP 409. Unique is never allowed on a multi-value attribute.
15
+ - Every owner attribute key is present on read. An empty attribute reads as `null`. An inverse reference face has no key.
16
+ - Every attribute accepts the `$eq` and `$not_empty` filters. Every attribute except checkbox accepts `$in`. Other operators and `sort` need the `indexed` flag. Read `limits.md`.
17
+
18
+ ## Text and rich text
19
+
20
+ | Type | Write | Normalization | Config | Unique | Multi |
21
+ | --------- | ------------------------------------------- | ------------- | --------------------------------------------------------- | ------ | ----- |
22
+ | text | Non-empty string, at most 256 characters | None | default, immutable, unique, minLength, maxLength, pattern | Yes | Yes |
23
+ | rich_text | Non-empty string, no cap unless `maxLength` | None | default, immutable, minLength, maxLength | No | No |
24
+
25
+ - `pattern` is a JavaScript regular expression string. It is not anchored.
26
+ - Rich text is stored and returned as written. Hitch does not sanitize or parse it. Rich text supports identity filters only and never sorts.
27
+
28
+ ## Numbers
29
+
30
+ | Type | Write | Read shape | Config | Unique |
31
+ | -------- | ------------------------------------------------------- | ---------- | ------------------------------------------- | ------ |
32
+ | integer | A JavaScript number that is a safe integer | number | default, immutable, unique, min, max | Yes |
33
+ | decimal | A decimal string or a number, at most 12 integer digits | string | default, immutable, unique, min, max, scale | Yes |
34
+ | currency | Same as decimal, scale defaults to 2 | string | decimal config plus `code` | Yes |
35
+
36
+ - Integer rejects a numeric string on write.
37
+ - Decimal returns a canonical string. Leading zeros are removed. With `scale`, the value is padded to exactly that many places. Without `scale`, trailing zeros are removed. Excess precision is rejected, never rounded.
38
+ - Decimal `min` and `max` are decimal strings. `scale` is 0 to 8 for decimal and 0 to 4 for currency.
39
+ - Currency `code` is three uppercase letters. It is metadata on the attribute, not part of the value. Hitch does no conversion.
40
+
41
+ ## Checkbox
42
+
43
+ - Write a boolean. Hitch does not coerce strings on write.
44
+ - Config: default, immutable. No unique, no multi.
45
+ - Filters: `$eq` and `$not_empty` only.
46
+
47
+ ## Date and time
48
+
49
+ | Type | Write | Read shape | Timezone | Config |
50
+ | -------- | ---------------------------------- | ----------------------- | --------------------- | ------------------------------------ |
51
+ | date | `YYYY-MM-DD`, a real calendar date | `YYYY-MM-DD` | None | default, immutable, unique, min, max |
52
+ | datetime | Any string `Date.parse` accepts | ISO 8601 in UTC, `...Z` | Converted to UTC | default, immutable, unique, min, max |
53
+ | time | `HH:MM` or `HH:MM:SS`, 24-hour | `HH:MM:SS` | None, wall clock only | default, immutable, unique |
54
+
55
+ - A datetime with an offset reads back in UTC. Store the organization timezone elsewhere when the display needs it.
56
+ - Date `min` and `max` are `YYYY-MM-DD` strings. Datetime `min` and `max` are datetime strings. Time has no `min` or `max`.
57
+
58
+ ## Domain, URL, email, phone
59
+
60
+ | Type | Write | Normalization | Config | Unique |
61
+ | ------ | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------- | ------ |
62
+ | domain | Host name with at least two labels, no scheme, no path | Trimmed, lowercased, one trailing dot removed | default, immutable, unique | Yes |
63
+ | url | `http` or `https` URL, at most 2048 bytes | Canonical WHATWG form: lowercased host, trailing slash on a bare origin | default, immutable, unique | Yes |
64
+ | email | One `@`, a dot in the domain part, at most 256 characters | Trimmed, lowercased | default, immutable, unique | Yes |
65
+ | phone | 7 to 14 digits, optional leading `+`, with space, `(`, `)`, `.`, `-` separators | Separators removed, leading `+` kept | default, immutable | No |
66
+
67
+ - Email has no deliverability check.
68
+ - Phone has no region parsing. Hitch adds no country code. Send the full international form when the organization needs it.
69
+
70
+ ## Select
71
+
72
+ - Write the option label, not the option id. Only an active option label matches.
73
+ - Hitch stores the option id and returns the label.
74
+ - A deleted option reads as `null` for a single select. It is dropped from the array for a multi select.
75
+ - Config: default, immutable. No unique. Multi-value allowed, duplicates rejected.
76
+ - Filters: identity operators only. Sort: allowed on a single select with `indexed`.
77
+
78
+ ## Reference
79
+
80
+ - Write the target record UUID, or an array of UUIDs for a multi reference. The target must be a live record of the declared target object.
81
+ - A reference has two faces. The owner face stores the link. The inverse face on the target object is read-only and is absent from the record JSON. A write to it fails with `read_only_field`.
82
+ - `targetSingle` means each target record can be linked at most once through this attribute. A second link fails with HTTP 409 `reference_conflict`.
83
+ - Config: none. A reference accepts no default, immutable, or unique.
84
+ - Read: the UUID or array of UUIDs under the attribute code. With `include`, the expanded record appears under the include name, which is the code without its `_id` or `_ids` suffix. Include is one level deep.
85
+ - Filters: identity operators on the id, plus a nested filter on the target object's attributes. Sort: never.
86
+
87
+ ## User
88
+
89
+ - Write the user UUID. The user must be a member of the organization.
90
+ - Config: default, immutable, unique. `default` is a UUID. Multi-value allowed, duplicates rejected.
91
+ - Read: the UUID. With `include`, the expansion is `{ id, firstName, lastName, email }`.
92
+ - Filters: identity operators only. Sort: never.
93
+
94
+ ## Attachment
95
+
96
+ - Write the file id from a completed upload. An unknown id fails with `file not found`. A file with no uploaded bytes fails with `file has not arrived`. Read `../../hitch-applets/references/files.md`.
97
+ - Config: immutable only. No default, no unique. Multi-value allowed, duplicates rejected.
98
+ - Read: the file id. `include` does not expand attachments. Call the files API for a download link.
99
+ - Filters: identity operators only. Sort: never.
100
+
101
+ ## Errors
102
+
103
+ | Situation | Status and code |
104
+ | -------------------------------------- | -------------------------------------------------------- |
105
+ | Value fails validation | 400 `invalid_field` |
106
+ | Required attribute missing on create | 400 `invalid_field` |
107
+ | Unknown attribute code in the body | 400 `unknown_field` |
108
+ | Write to an inverse reference face | 400 `read_only_field` |
109
+ | Change to a set immutable value | 400 `immutable_field` |
110
+ | Missing or blank record name | 400 `invalid_name` |
111
+ | Client-supplied `id` already exists | 409 `id_conflict` |
112
+ | Reference target missing | 400 `invalid_field` |
113
+ | User not a member | 400 `invalid_field` |
114
+ | Unique value already used | 409 `unique_conflict` |
115
+ | Single-target reference already linked | 409 `reference_conflict` |
116
+ | Operator not allowed for the attribute | 400 `invalid_filter` |
117
+ | Attribute cannot sort | 400 `invalid_sort` |
118
+ | Bad `limit`, `cursor`, or `include` | 400 `invalid_limit`, `invalid_cursor`, `invalid_include` |
119
+ | Bad aggregate or group | 400 `invalid_aggregate`, `invalid_group_by` |
120
+
121
+ Every record also has a `name`. It is a required non-empty string on create.
@@ -0,0 +1,64 @@
1
+ # Hitch limits
2
+
3
+ Hitch limits the data API on purpose. The limits force good software design. No screen accidentally loads 500,000 records. No report runs one request per row. No import writes half of its rows and then fails. The limits also protect the organization: one applet or one user cannot ruin the experience for everyone else, and the cost of running the data stays sensible. When a limit stops you, the design is wrong, not the limit. Change the design: page the list, aggregate in Hitch, batch the writes, or narrow the filter.
4
+
5
+ Some limits clamp with no error. Some reject the request. Design for both.
6
+
7
+ ## Reads
8
+
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
+
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
+
21
+ ## Aggregates
22
+
23
+ An aggregate answers a question about many records with a small result. The caps keep the result small enough to render. A question that needs more cells is two questions.
24
+
25
+ | Limit | Value | Past the limit |
26
+ | --------------------------------------- | ------------------------------------------------------------ | ------------------------ |
27
+ | Aggregates per call | 20 | HTTP 400 |
28
+ | Group dimensions | 2 | HTTP 400 |
29
+ | Multi-value group dimensions | 1 | HTTP 400 |
30
+ | Distinct keys per dimension, default | 50 | Clamped, no error |
31
+ | Distinct keys per dimension, max | 200 | Clamped to 200, no error |
32
+ | Cells, the product of dimension limits | 2500 | HTTP 400 |
33
+ | Reference steps in an aggregate path | 1 | HTTP 400 |
34
+ | Sum, average, min, max on a multi-value | Not allowed | HTTP 400 |
35
+ | Group dimension type | checkbox, select, reference, user, bucketed date or datetime | HTTP 400 |
36
+ | Aggregate alias `as` | 64 characters | HTTP 400 |
37
+ | Sum, average, min, max on a non-numeric | Not allowed | HTTP 400 |
38
+
39
+ ## Writes
40
+
41
+ A write is bounded and atomic. A batch either commits every entry or none. An `updateWhere` refuses a filter that matches more than it can hold in one page, so a loose filter cannot rewrite a whole object.
42
+
43
+ | Limit | Value | Past the limit |
44
+ | ------------------------------------- | ----------- | --------------------------------------- |
45
+ | Entries per batch | 100 | HTTP 400 before any write |
46
+ | Records per `updateWhere` | 200 | HTTP 400 `update_too_broad`, no write |
47
+ | `expect` on `updateWhere` | Exact count | HTTP 409 `expectation_failed`, no write |
48
+ | Idempotency key with a different body | Not allowed | HTTP 422 `idempotency_key_reuse` |
49
+ | Idempotency key retention | 24 hours | The key is forgotten |
50
+
51
+ ## Filters and sorting
52
+
53
+ Advanced filters and sorting are opt-in per attribute. This keeps each record cheap to write, and makes the attributes a query depends on an explicit decision.
54
+
55
+ - Every attribute accepts `$eq` and `$not_empty`. Every attribute except checkbox accepts `$in`.
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
+ - 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
+ - No attribute allows `sort` when it is multi-value, rich text, reference, user, or attachment.
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
+
61
+ ## Grant conditions
62
+
63
+ - A condition path has at most two hops.
64
+ - A record whose condition target is empty is invisible to that grant. A `get` on it returns HTTP 404.
@@ -0,0 +1,150 @@
1
+ ---
2
+ name: hitch-theory
3
+ description: The mental model of Hitch. Read before you design, review, or explain anything that touches Hitch data, applets, access, or query cost. Use when a decision depends on what Hitch is for, what Hitch handles versus what an applet handles, how attributes and references model data, how changesets and releases move together, or who a call acts as. Pairs with hitch-applets, which holds the build procedure.
4
+ ---
5
+
6
+ # Theory of Hitch
7
+
8
+ This skill explains how Hitch works and why. For the build procedure, read the `hitch-applets` skill.
9
+
10
+ ## 1. What Hitch is
11
+
12
+ Hitch is a platform for internal applications. It is not a CRM, a rostering product, a reporting product, or any other vertical. An organization builds its own rostering, job, or invoice applets on Hitch.
13
+
14
+ Hitch is two layers:
15
+
16
+ 1. A durable data system that the organization owns: objects, attributes, records, files, identity, and grants.
17
+ 2. A governed runtime for replaceable software: applets that Hitch builds, releases, hosts, and runs.
18
+
19
+ ## 2. What an applet is
20
+
21
+ An applet is not an app. An app is the whole machine from the old days of software: its own authentication, its own database, its own permissions, a frontend, a server, caches, a deployment pipeline, and monitoring. Every app rebuilds that machine from scratch. Every app then spends most of its life on the machine, not on the work. The organization never wanted the machine. It wanted the business logic.
22
+
23
+ AI has changed the economics of software. Code is now cheap to write and cheap to change, so software can be personal to one organization, one team, and one workflow. Hitch is built for that world. Hitch flips the model. Instead of one monolithic app designed to serve many unrelated users, an organization gets a set of many applets, completely custom and personalised to its needs. An applet is the closest thing to pure business logic: one workflow, the screens it needs, and the rules that make it correct. Hitch handles everything underneath: the data and its schema, identity, permissions, files, immutable releases, hosting, schedules, and durable workflows. The author never touches the machine. The author builds only what matters to the user.
24
+
25
+ Hitch takes the machine away for correctness and trust as much as for speed. Authentication, access control, schema validation, and release safety are the parts an agent gets wrong, and the parts an organization cannot afford to have wrong. Hitch handles them, so your organisation has the confidence it needs to fully adopt vibe coded software and move off of legacy SaaS systems.
26
+
27
+ In practice an applet is a client-only, server-only, or full-stack application that Hitch builds, releases, hosts, and runs inside the platform trust boundary. It is not a third-party plugin.
28
+
29
+ Every organization has its own tenant on Hitch. Its data, its users, its grants, and its applets all live there. When Hitch runs an applet, it runs it inside that tenant, the way AWS runs a workload inside a customer's account: the provider operates the infrastructure, but the account, the identities, and the data are the customer's. An applet sees its organization's tenant and nothing else.
30
+
31
+ Applets can be used standalone via their `.app` URL or within the Hitch shell. Standalone and embedded operation use the same identity, release, routes, and theme.
32
+
33
+ ## 3. What Hitch handles and what an applet handles
34
+
35
+ | Hitch handles | The applet handles |
36
+ | --------------------------------------------------------------- | --------------------------------------------- |
37
+ | Canonical organization records and files | One focused workflow and its presentation |
38
+ | Object and attribute schema, and references | Domain-specific UI composition |
39
+ | Validation, normalization, constraints, versions, audit | External API adapters and translation |
40
+ | Organization identity, authentication, grants | User interactions and app-specific validation |
41
+ | Immutable releases, schedules, the workflow engine, the runtime | Derived views and deliberate derived records |
42
+ | Theme delivery and platform navigation | Ephemeral client state |
43
+
44
+ ## 4. Why code and data are separate
45
+
46
+ The organization's Hitch data is the single source of truth. An applet reads and writes that data. It does not keep its own copy.
47
+
48
+ The data layer stands on its own so that every way of using it is a client of the same thing. An applet, the `hitch` command line, an MCP server for an AI agent, an API key in a script, and an import all speak to the same objects, the same schema, and the same permissions. None of them is a bridge bolted on later. Each one is generated from the organization's own schema, so it knows the organization's objects by name. Each one acts as the person or applet behind it, so it sees exactly what that caller may see. The result is that a person in a browser, an agent in a chat window, and a script in a terminal all work on the same records under the same rules, and each gets the interface that suits them. Software stops being one product with one screen and becomes your organizational data with many doors.
49
+
50
+ The boundary gives you:
51
+
52
+ - One source of truth that many applets share.
53
+ - One set of schema and integrity rules for every writer: applets, API keys, imports, and future code.
54
+ - Central permissions that applet UI code cannot weaken.
55
+ - Independent applet release and rollback with no data fork.
56
+ - Direct user-scoped reads from the browser, with no applet server proxy.
57
+ - One place for integrations: sync an external system into Hitch once, then let other applets use the same data.
58
+
59
+ Consequences:
60
+
61
+ - Do not add an applet-owned database, a parallel cache, or a copied object set. If an applet seems to need its own storage, Hitch has a gap. Raise the gap instead.
62
+ - Code rollback is not data recovery. A release switch never moves or restores records.
63
+ - When you install, upgrade, or remove an applet, the canonical data stays where it is.
64
+ - A domain rule can live in applet code when it controls one workflow. If the rule must hold for every writer, express it in the schema, so Hitch enforces it for every applet, API key, and import.
65
+ - Data normalization and UI hierarchy are separate decisions. Model distinct concepts as objects plus references when their lifecycle or permissions differ. Group them on one screen when that matches the user. Do not invent child objects to solve navigation.
66
+
67
+ ## 5. Attributes are schema
68
+
69
+ An attribute is schema, not a free JSON field. Hitch validates each value against its type and configuration, normalizes it, stores it in the record, and indexes it when asked. A value that should be a select or a reference, but is stored as free text, cannot be grouped or filtered well later.
70
+
71
+ - Types: text, rich text, integer, decimal, currency, checkbox, date, datetime, time, domain, url, email, phone, select, reference, user, attachment.
72
+ - Cardinality is configuration, not a type. Text, select, reference, user, and attachment can be multi-value. A multi-value is an ordered array.
73
+ - An empty attribute reads as null. A required multi-value attribute rejects an empty array.
74
+ - Uniqueness is a real constraint, per attribute, per organization.
75
+ - Advanced filters and sorting are opt-in per attribute with the `indexed` flag. New attributes default to off. Every attribute still supports equality and not-empty filters. Aggregating or grouping by an attribute needs no flag. Type rules still apply: a reference, user, attachment, rich text, or multi-value attribute never sorts. Do not turn the flag on for every attribute. Turn it on for the attributes a query needs.
76
+
77
+ References model relationships:
78
+
79
+ - A reference attribute names a target object. Cardinality on each side comes from the single or multi setting of each face.
80
+ - A reference can expose an inverse face on the target object. One face owns and stores the edges. The other face is derived. Write the owner face only.
81
+ - Hitch keeps the links typed, clears inbound links when a target is deleted, applies grants through the inverse, and lets you filter through either face. Do not invent a join object for a plain relationship. Use a join object only when the relationship needs its own fields or lifecycle.
82
+
83
+ Special cases:
84
+
85
+ - A user attribute points at a membership in the same organization. Removal of the member sweeps those values.
86
+ - An attachment attribute holds file IDs. Files live outside records, in their own storage. Create the file, upload it, then store the ID on the record.
87
+
88
+ ## 6. Changesets and releases move together
89
+
90
+ All schema mutations go through a changeset: an ordered list of object and attribute operations with a stored evaluation.
91
+
92
+ - A hard blocker means invalid schema or data. Examples: a unique constraint that finds duplicates, a required attribute with empty records, a tightened range with violators.
93
+ - A soft blocker means a dependency. Examples: applets, grant conditions, or delegations that depend on the object or attribute.
94
+ - Any blocker stops the apply. A submit with a blocker stages a pending changeset. The apply succeeds after the blocker is resolved.
95
+
96
+ An applet release can bind to a pending changeset. Hitch checks the manifest against the schema the changeset will produce. A mismatch is a deploy warning, not a denial. The unresolved warning becomes a hard blocker on the changeset. A bound release does not go live on deploy. When the changeset applies, the schema and the newest bound release of each applet become active together. Schema and code change together.
97
+
98
+ Adoption folds one pending changeset into another and abandons the source. The source must have no bound releases.
99
+
100
+ The applet manifest lists the objects the applet uses, with create and delete flags, read-only attributes, and read-write attributes. That list is the access declaration. It caps what the applet identity can do: the object actions, and the attributes it can update. It does not change what a user can see.
101
+
102
+ ## 7. Identity and access
103
+
104
+ Hitch enforces access control on every Hitch API call. An applet never re-implements it. A UI filter or a hidden control is convenience, never a boundary. An applet's own server procedure is applet code, so it must check the business rules it introduces. Every call carries a principal and Hitch applies that principal's grants.
105
+
106
+ Principals:
107
+
108
+ - A person, through a session or a personal API key. A personal API key inherits the member's capabilities.
109
+ - An impersonated person. The target's capabilities must be a proper subset of the caller's.
110
+ - An applet. The applet identity has no role and no bypass. Its authority is exactly the grants projected from its manifest.
111
+
112
+ The organization roles are member and owner. There is no admin role. The owner holds every capability, bypasses every grant, and cannot be removed or impersonated. Capabilities are ten named org-wide powers: manage members, teams, objects, permissions, applets, and the organization; read all records; write all records; use applets; impersonate members. A capability grant goes to a team only, never to a person or to everyone. When one person needs a capability, put them in a one-person team. A caller can grant only a capability the caller holds.
113
+
114
+ Object grants are the matrix: one row per object and grantee with create, read, update, and delete flags. A grantee is a team, an applet, or the default row that every member holds. A member's access is the union of the default row and their team rows. The read-all and write-all capabilities bypass object grants. Object grants set the verbs. Conditions narrow rows. Attribute grants restrict updates to protected attributes and applet writes. Reads are never masked per attribute.
115
+
116
+ An object grant can delegate through one single-valued owner reference. The linked record then uses the same grantee's grant on the target object.
117
+
118
+ Grant conditions scope rows to the current user. A condition path is a user attribute on the object, or one reference followed by a user attribute on the target object. A record whose user attribute is empty is invisible to that grant. An invisible record behaves as missing. A condition applies to read, update, and delete, never to create. A condition cannot compare a value, such as "where status equals X".
119
+
120
+ Who a call acts as:
121
+
122
+ - A client applet call acts as the current user. The manifest grants do not apply.
123
+ - A schedule run acts as the applet, capped by the manifest.
124
+ - A workflow run acts as whoever started it: the invoking user, or the applet when a schedule started it.
125
+
126
+ Because Hitch enforces access, a client applet needs no server proxy for filtering or access control. Add a server only for secrets, external systems, schedules, workflows, or bounded fan-in.
127
+
128
+ ## 8. What makes a good applet
129
+
130
+ A good applet:
131
+
132
+ - Solves one related workflow for one clear group of users.
133
+ - Uses the organization's existing Hitch data, not a private copy.
134
+ - Presents the smallest useful set of decisions and actions. It does not reproduce a legacy product screen for screen.
135
+ - Uses Hitch primitives before custom infrastructure.
136
+ - Feels native through semantic theme tokens, shared interaction patterns, useful empty, loading, and error states, and viewport-safe layouts.
137
+ - Has explicit access, performance, failure, and recovery behavior.
138
+ - Can be replaced or rolled back with no move or rewrite of the canonical data.
139
+
140
+ Split an applet when workflows, audiences, privileges, release cadence, or failure modes differ. Do not split because the UI has another page. A sync applet and a reporting applet are a good split. Ingestion has secrets, schedules, broad write grants, retries, and external failure modes. Reports read already-synced data with narrow grants.
141
+
142
+ ## 9. Product-design principles
143
+
144
+ - Build to user outcomes, not to the screens or tables of an older system.
145
+ - Start with the important nouns, relationships, permissions, and failure cases. Design screens after that.
146
+ - Simplify critically. Defer speculative channels and infrastructure.
147
+ - Ship the smallest complete workflow. Add nothing unrequested.
148
+ - Treat thresholds, status rules, timing, channels, and recipients as product decisions for the user.
149
+ - Confirm destructive or external writes. Show the target, the intended change, the stale-source check, and the outcome. A Hitch mutation and a mutation of an external source are not equivalent.
150
+ - Record rejected designs and the reason. Do not rebuild what was deliberately cut.
@@ -2,12 +2,6 @@ name: Deploy Hitch applets
2
2
 
3
3
  on:
4
4
  push:
5
- paths:
6
- - "applets/**"
7
- - ".github/workflows/hitch.yml"
8
- - "hitch.config.ts"
9
- - "pnpm-lock.yaml"
10
- - "pnpm-workspace.yaml"
11
5
 
12
6
  permissions: {}
13
7
 
@@ -18,4 +12,4 @@ jobs:
18
12
  contents: read
19
13
  id-token: write
20
14
  # Hitch verifies the called workflow's commit SHA from the OIDC token.
21
- uses: hitch-42/actions/.github/workflows/deploy.yml@main
15
+ uses: hitch-42/actions/.github/workflows/deploy.yml@1a76e4936f94730e20710b455fa0a06564929f39
@@ -1,2 +0,0 @@
1
- import { n as runBuild } from "./build-Ch3pALLA.js";
2
- export { runBuild };
@@ -1,2 +0,0 @@
1
- import { n as runGenerate } from "./generate-BuszZ9-U.js";
2
- export { runGenerate };
@@ -1,56 +0,0 @@
1
- import { a as ORG_CODE_PATTERN, r as appletSourceDefinitionSchema } from "./manifest-DyoJ1MIL.js";
2
- import { createRequire } from "node:module";
3
- import { z } from "zod";
4
- import { existsSync } from "node:fs";
5
- import { basename, dirname, resolve } from "node:path";
6
- import { loadConfigFromFile } from "vite";
7
- //#region src/workspace.ts
8
- const hitchConfigSchema = z.object({
9
- sdk: z.string().min(1),
10
- org: z.string().regex(ORG_CODE_PATTERN)
11
- });
12
- const require = createRequire(import.meta.url);
13
- function readWorkspaceConfig(dir) {
14
- const path = resolve(dir, "hitch.config.ts");
15
- if (!existsSync(path)) return void 0;
16
- let module;
17
- try {
18
- module = require(path);
19
- } catch (error) {
20
- throw new Error(`Failed to load ${path}.`, { cause: error });
21
- }
22
- const config = hitchConfigSchema.safeParse(module !== null && typeof module === "object" && "default" in module ? module.default : module);
23
- if (!config.success) throw new Error(`Invalid Hitch config in ${path}: ${config.error.message}`);
24
- return config.data;
25
- }
26
- function resolveWorkspaceOrg(appletRoot) {
27
- const appletsDir = dirname(appletRoot);
28
- if (basename(appletsDir) === "applets") {
29
- const config = readWorkspaceConfig(dirname(appletsDir));
30
- if (config !== void 0) return config.org;
31
- }
32
- 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.`);
33
- }
34
- //#endregion
35
- //#region src/typegen/load-config.ts
36
- async function loadAppletSourceDefinition(root) {
37
- const path = resolve(root, "applet.manifest.ts");
38
- if (!existsSync(path)) throw new Error(`No applet manifest found at ${path}.`);
39
- const loaded = await loadConfigFromFile({
40
- command: "build",
41
- mode: "production"
42
- }, path, root);
43
- if (loaded === null) throw new Error(`No applet manifest found at ${path}.`);
44
- const parsed = appletSourceDefinitionSchema.safeParse(loaded.config);
45
- if (!parsed.success) throw new Error(`Invalid applet manifest at ${loaded.path}: ${parsed.error.message}`);
46
- return parsed.data;
47
- }
48
- async function loadAppletConfig(root) {
49
- const { dependencies: _dependencies, devDependencies: _devDependencies, ...definition } = await loadAppletSourceDefinition(root);
50
- return {
51
- org: resolveWorkspaceOrg(root),
52
- ...definition
53
- };
54
- }
55
- //#endregion
56
- export { resolveWorkspaceOrg as i, loadAppletSourceDefinition as n, readWorkspaceConfig as r, loadAppletConfig as t };