@hitch42/applet 0.1.1-beta.8 → 0.1.1-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.
- package/dist/_internal/client.js +120 -11
- package/dist/_internal/log-context.d.ts +34 -0
- package/dist/_internal/log-context.js +2 -0
- package/dist/_internal/server.d.ts +1 -1
- package/dist/_internal/server.js +112 -57
- package/dist/applet.js +12 -12
- package/dist/build-Bh_S5BUv.js +2 -0
- package/dist/{build-DANx9Hgg.js → build-CEfFoIZZ.js} +1 -1
- package/dist/{ci-5qWl7lT3.js → ci-BKw14X2h.js} +14 -13
- package/dist/client.d.ts +3 -3
- package/dist/{contract-rP_eV9PK.d.ts → contract-CsUpTBe0.d.ts} +1 -1
- package/dist/{deploy-MQWLG2EA.js → deploy-wGYjwXrx.js} +139 -88
- package/dist/{generate-BxYHdoAQ.js → generate-BEIYs17x.js} +65 -28
- package/dist/generate-Dox-OYt6.js +2 -0
- package/dist/headers-DkNr9MLJ.js +17 -0
- package/dist/index.d.ts +23 -22
- package/dist/index.js +2 -1
- package/dist/{init-jDEgYIXg.js → init-6oGPM46R.js} +15 -17
- package/dist/{load-config-BYSxDVAl.js → load-config-xEdTXs0y.js} +2 -2
- package/dist/log-context-zu8U9Sjm.js +73 -0
- package/dist/{login-C7RKmmTe.js → login-CUx3TmFQ.js} +1 -1
- package/dist/{logs-DgjgAxj2.js → logs-DSjuEN0Q.js} +20 -22
- package/dist/{manifest-DD_SGkS6.d.ts → manifest-C6_F4bL8.d.ts} +5 -1
- package/dist/{manifest-Bp87Uz0a.js → manifest-CJSL9GuC.js} +20 -4
- package/dist/{project-CbYPtpPq.js → project-BRZGd6xq.js} +199 -18
- package/dist/query-cache-CXmAyLv3.js +82 -0
- package/dist/react.d.ts +3 -2
- package/dist/react.js +98 -80
- package/dist/{schedules-dhU1yyFr.js → schedules-C8_Svr4h.js} +1 -1
- package/dist/server.d.ts +9 -4
- package/dist/server.js +29 -8
- package/dist/{telephony-protocol-DRgiYjqQ.d.ts → telephony-protocol-D-nf3Gca.d.ts} +15 -13
- package/dist/telephony.d.ts +24 -4
- package/dist/telephony.js +135 -16
- package/dist/{upload-YbRKEqR6.d.ts → upload-DKZzz5kW.d.ts} +6 -6
- package/dist/{variables-Bwnr7oxf.js → variables-DT5RyHdH.js} +13 -13
- package/dist/vite.d.ts +3 -60
- package/dist/vite.js +50 -15
- package/dist/{workflow-engine-MogejNfF.js → workflow-engine-BOTzURKM.js} +1 -7
- package/dist/{workspace-XVtM12ZC.js → workspace-D0dnnlxL.js} +33 -38
- package/package.json +13 -5
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +30 -4
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +8 -0
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +2 -0
- package/scaffold/.agents/skills/hitch-applets/references/records.md +23 -1
- package/scaffold/.agents/skills/hitch-data/SKILL.md +2 -1
- package/scaffold/.agents/skills/hitch-data/references/attributes.md +7 -1
- package/scaffold/.agents/skills/hitch-data/references/limits.md +13 -13
- package/scaffold/.agents/skills/hitch-theory/SKILL.md +3 -2
- package/scaffold/.agents/skills/react-best-practices/SKILL.md +127 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-effect-event-deps.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-defer-await.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-dependencies.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-parallel.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-analyzable-paths.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-conditional.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +46 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-preload.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-event-listeners.md +78 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-function-results.md +58 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-storage.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-flatmap-filter.md +51 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-length-check-first.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-request-idle-callback.md +104 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-resource-hints.md +81 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-script-defer-async.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-usetransition-loading.md +43 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-no-inline-components.md +75 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/scaffold/.agents/skills/tanstack-query/SKILL.md +136 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/automatic-invalidation-after-mutations.md +124 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/broadcast-realtime-and-multi-tab-synchronization.md +150 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/compose-query-with-tanstack-router-and-start.md +248 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/enforce-query-best-practices-with-eslint.md +167 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/persist-offline-and-restore-caches.md +181 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/query-data-and-forms.md +156 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/build-query-abstractions.md +130 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/cancel-queries-and-consume-abort-signals.md +176 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/concurrent-optimistic-updates.md +135 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/coordinate-query-execution.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/design-query-keys-and-options.md +182 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/fetch-and-observe-queries.md +193 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/implement-optimistic-updates-and-cache-writes.md +168 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/paginate-and-build-infinite-queries.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/seed-placeholder-select-and-transform-data.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/selectors-and-derived-state.md +145 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/tune-defaults-freshness-retries-and-refetching.md +172 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/understand-query-internals-and-observers.md +104 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/write-mutations-and-invalidate-related-queries.md +183 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/debug-with-devtools.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/handle-status-and-errors.md +119 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/shape-data-and-render-efficiently.md +184 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-framework-adapter-reactivity.md +185 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-suspense-and-error-boundaries.md +208 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/migrate-major-versions-and-codemods.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/prefetch-and-remove-request-waterfalls.md +228 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/setup-query-client-and-providers.md +161 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/ssr-hydration-and-streaming.md +209 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/test-query-code.md +160 -0
- package/scaffold/.agents/skills/tanstack-router/SKILL.md +134 -0
- package/scaffold/.agents/skills/tanstack-router/references/compositions/router-query.md +412 -0
- package/scaffold/.agents/skills/tanstack-router/references/react-router.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/auth-and-guards.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/code-splitting.md +318 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/data-loading.md +490 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/index.md +158 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/navigation.md +433 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/not-found-and-errors.md +436 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/path-params.md +383 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/search-params.md +343 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/ssr.md +462 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/type-safety.md +493 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-plugin.md +250 -0
- package/scaffold/.agents/skills/tanstack-router/references/virtual-file-routes.md +215 -0
- package/scaffold/.agents/skills/web-design-guidelines/SKILL.md +184 -0
- package/scaffold/.github/workflows/hitch.yml +1 -1
- package/scaffold/AGENTS.md +2 -2
- package/scaffold/applet/src/client/components/ui/alert.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/button.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/checkbox.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/dialog.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/input-group.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/select.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/sheet.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/spinner.tsx +2 -1
- package/scaffold/applet/src/client/components/ui/tabs.tsx +1 -1
- package/scaffold/applet/src/client/lib/utils.ts +1 -6
- package/scaffold/applet/src/client/styles/globals.css +2 -0
- package/dist/build-CFCKKMn7.js +0 -2
- package/dist/generate-CQ07Hdpo.js +0 -2
|
@@ -45,8 +45,8 @@ async function healCredentialStore(credentialsPath, source, store, unrecognizedC
|
|
|
45
45
|
console.error(unrecognizedCount === void 0 ? `hitch: reset unreadable credentials file (backup: ${backupPath}).` : unrecognizedCount === 0 ? `hitch: migrated credentials to per-organization storage (backup: ${backupPath}).` : `hitch: removed ${unrecognizedCount} unrecognized credential ${unrecognizedCount === 1 ? "entry" : "entries"} (backup: ${backupPath}).`);
|
|
46
46
|
return store;
|
|
47
47
|
}
|
|
48
|
-
async function readRawCredentialStore(
|
|
49
|
-
const { credentialsPath } = storePaths(
|
|
48
|
+
async function readRawCredentialStore() {
|
|
49
|
+
const { credentialsPath } = storePaths();
|
|
50
50
|
let source;
|
|
51
51
|
try {
|
|
52
52
|
source = await readFile(credentialsPath, "utf8");
|
|
@@ -113,9 +113,9 @@ function organizationInstanceUrl(instanceUrl, orgCode) {
|
|
|
113
113
|
url.hostname = [orgCode, ...labels.slice(1)].join(".");
|
|
114
114
|
return url.toString().replace(/\/$/, "");
|
|
115
115
|
}
|
|
116
|
-
function storePaths(
|
|
117
|
-
const env =
|
|
118
|
-
const home =
|
|
116
|
+
function storePaths() {
|
|
117
|
+
const env = process.env;
|
|
118
|
+
const home = homedir();
|
|
119
119
|
const configDir = join(env.XDG_CONFIG_HOME || join(home, ".config"), "hitch");
|
|
120
120
|
return {
|
|
121
121
|
configDir,
|
|
@@ -124,28 +124,28 @@ function storePaths(options = {}) {
|
|
|
124
124
|
credentialsPath: join(configDir, "credentials.json")
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
-
async function resolveInstanceUrl(override
|
|
127
|
+
async function resolveInstanceUrl(override) {
|
|
128
128
|
if (override !== void 0) return normalizeInstanceUrl(override);
|
|
129
|
-
const envInstanceUrl =
|
|
129
|
+
const envInstanceUrl = process.env.HITCH_API_URL;
|
|
130
130
|
if (envInstanceUrl !== void 0 && envInstanceUrl.length > 0) return normalizeInstanceUrl(envInstanceUrl);
|
|
131
|
-
const { configPath } = storePaths(
|
|
131
|
+
const { configPath } = storePaths();
|
|
132
132
|
const config = await readJson(configPath);
|
|
133
133
|
if (config === void 0) return DEFAULT_INSTANCE_URL;
|
|
134
134
|
if (!isObject(config) || config.instanceUrl !== void 0 && typeof config.instanceUrl !== "string") throw new Error(`${configPath}: expected { instanceUrl?: string }.`);
|
|
135
135
|
return normalizeInstanceUrl(config.instanceUrl ?? "https://app.hitch42.com");
|
|
136
136
|
}
|
|
137
|
-
async function readCredentialStore(
|
|
138
|
-
const store = await readRawCredentialStore(
|
|
137
|
+
async function readCredentialStore() {
|
|
138
|
+
const store = await readRawCredentialStore();
|
|
139
139
|
return { instances: Object.fromEntries(Object.entries(store.instances).map(([instanceUrl, credentialsByOrg]) => [instanceUrl, Object.fromEntries(Object.entries(credentialsByOrg).flatMap(([orgCode, value]) => {
|
|
140
140
|
const credentials = parseCredentials(value);
|
|
141
141
|
return credentials === void 0 ? [] : [[orgCode, credentials]];
|
|
142
142
|
}))])) };
|
|
143
143
|
}
|
|
144
|
-
async function getCredentialsForOrg(instanceUrl, orgCode
|
|
145
|
-
return (await readCredentialStore(
|
|
144
|
+
async function getCredentialsForOrg(instanceUrl, orgCode) {
|
|
145
|
+
return (await readCredentialStore()).instances[normalizeInstanceUrl(instanceUrl)]?.[orgCode];
|
|
146
146
|
}
|
|
147
|
-
function environmentAuth(
|
|
148
|
-
const apiKey =
|
|
147
|
+
function environmentAuth() {
|
|
148
|
+
const apiKey = process.env.HITCH_API_KEY;
|
|
149
149
|
return apiKey !== void 0 && apiKey.length > 0 ? {
|
|
150
150
|
accessToken: apiKey,
|
|
151
151
|
source: "environment"
|
|
@@ -169,11 +169,11 @@ function resolveStoredAuthForOrg(credentialsByOrg, orgCode) {
|
|
|
169
169
|
const apiKeyOrgs = Object.values(credentialsByOrg).filter((credentials) => credentials.type === "apiKey").map(({ orgCode: storedOrg }) => storedOrg).sort();
|
|
170
170
|
if (apiKeyOrgs.length > 0) throw new AuthenticationError(`Stored API ${apiKeyOrgs.length === 1 ? "key is" : "keys are"} scoped to ${apiKeyOrgs.map((storedOrg) => `"${storedOrg}"`).join(", ")}, not "${orgCode}".`);
|
|
171
171
|
}
|
|
172
|
-
async function resolveAuthForOrg(instanceUrl, orgCode
|
|
173
|
-
const auth = environmentAuth(
|
|
172
|
+
async function resolveAuthForOrg(instanceUrl, orgCode) {
|
|
173
|
+
const auth = environmentAuth();
|
|
174
174
|
if (auth !== void 0) return auth;
|
|
175
175
|
const normalizedUrl = normalizeInstanceUrl(instanceUrl);
|
|
176
|
-
const credentialsByOrg = Object.entries((await readCredentialStore(
|
|
176
|
+
const credentialsByOrg = Object.entries((await readCredentialStore()).instances).find(([instance]) => instance === normalizedUrl)?.[1];
|
|
177
177
|
return resolveStoredAuthForOrg(credentialsByOrg ?? {}, orgCode);
|
|
178
178
|
}
|
|
179
179
|
function rejectEnvironmentKey(response, auth) {
|
|
@@ -182,9 +182,9 @@ function rejectEnvironmentKey(response, auth) {
|
|
|
182
182
|
function bearerToken(credentials) {
|
|
183
183
|
return "apiKey" in credentials ? credentials.apiKey : credentials.accessToken;
|
|
184
184
|
}
|
|
185
|
-
async function setCredentials(instanceUrl, credentials
|
|
186
|
-
const paths = storePaths(
|
|
187
|
-
const store = await readRawCredentialStore(
|
|
185
|
+
async function setCredentials(instanceUrl, credentials) {
|
|
186
|
+
const paths = storePaths();
|
|
187
|
+
const store = await readRawCredentialStore();
|
|
188
188
|
const normalizedUrl = normalizeInstanceUrl(instanceUrl);
|
|
189
189
|
const credentialsByOrg = store.instances[normalizedUrl];
|
|
190
190
|
store.instances[normalizedUrl] = {
|
|
@@ -291,17 +291,17 @@ async function pollForToken(fetcher, instanceUrl, device, now, sleep) {
|
|
|
291
291
|
throw new AuthenticationError("The sign-in request expired. Run hitch login again.");
|
|
292
292
|
}
|
|
293
293
|
async function loginWithDevice(instanceUrl, options = {}) {
|
|
294
|
-
const fetcher =
|
|
295
|
-
const now =
|
|
294
|
+
const fetcher = globalThis.fetch;
|
|
295
|
+
const now = Date.now;
|
|
296
296
|
const { body, response } = await postJson(fetcher, `${instanceUrl}/api/auth/device/code`, { client_id: CLIENT_ID });
|
|
297
297
|
if (!response.ok) throw new AuthenticationError(typeof body.error_description === "string" ? body.error_description : `Could not start sign-in (${response.status}).`);
|
|
298
298
|
const device = parseDeviceCode(body);
|
|
299
299
|
const verificationUrl = options.expectedOrg === void 0 ? device.verification_uri_complete : `${device.verification_uri_complete}&org=${encodeURIComponent(options.expectedOrg)}`;
|
|
300
|
-
|
|
300
|
+
console.log(`Open ${verificationUrl} and confirm code ${device.user_code}.`);
|
|
301
301
|
if (!options.noBrowser) try {
|
|
302
|
-
|
|
302
|
+
defaultOpenUrl(verificationUrl);
|
|
303
303
|
} catch {}
|
|
304
|
-
const token = await pollForToken(fetcher, instanceUrl, device, now,
|
|
304
|
+
const token = await pollForToken(fetcher, instanceUrl, device, now, (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
305
305
|
const { body: organizationBody, response: organizationResponse } = await postJson(fetcher, `${instanceUrl}/api/auth/device/organization`, { device_code: device.device_code });
|
|
306
306
|
if (!organizationResponse.ok) throw new AuthenticationError("The sign-in did not resolve an organization.");
|
|
307
307
|
const organization = parseDeviceOrganization(organizationBody);
|
|
@@ -319,7 +319,7 @@ async function loginWithDevice(instanceUrl, options = {}) {
|
|
|
319
319
|
organizationId: organization.organizationId,
|
|
320
320
|
orgCode: organization.orgCode
|
|
321
321
|
};
|
|
322
|
-
await setCredentials(instanceUrl, credentials
|
|
322
|
+
await setCredentials(instanceUrl, credentials);
|
|
323
323
|
if (options.expectedOrg !== void 0 && organization.orgCode !== options.expectedOrg) throw new AuthenticationError(`logged in to "${organization.orgCode}" but this applet targets "${options.expectedOrg}" — confirm the right organization on the device page`);
|
|
324
324
|
return {
|
|
325
325
|
credentials,
|
|
@@ -356,23 +356,18 @@ function findWorkspaceConfig(cwd) {
|
|
|
356
356
|
}
|
|
357
357
|
//#endregion
|
|
358
358
|
//#region src/auth.ts
|
|
359
|
-
async function fetchWithOrgAuth(orgCode,
|
|
360
|
-
const
|
|
361
|
-
const instanceUrl = await resolveInstanceUrl(void 0, options.store);
|
|
359
|
+
async function fetchWithOrgAuth(orgCode, request) {
|
|
360
|
+
const instanceUrl = await resolveInstanceUrl();
|
|
362
361
|
const organizationUrl = organizationInstanceUrl(instanceUrl, orgCode);
|
|
363
|
-
const auth = await resolveAuthForOrg(instanceUrl, orgCode
|
|
362
|
+
const auth = await resolveAuthForOrg(instanceUrl, orgCode);
|
|
364
363
|
if (auth !== void 0) {
|
|
365
364
|
const response = await request(organizationUrl, bearerToken(auth));
|
|
366
365
|
rejectEnvironmentKey(response, auth);
|
|
367
366
|
if (response.status !== 401) return response;
|
|
368
367
|
}
|
|
369
|
-
if (
|
|
370
|
-
await
|
|
371
|
-
|
|
372
|
-
fetch: fetcher,
|
|
373
|
-
store: options.store
|
|
374
|
-
});
|
|
375
|
-
const credentials = await getCredentialsForOrg(instanceUrl, orgCode, options.store);
|
|
368
|
+
if (process.stdin.isTTY !== true) throw new AuthenticationError("Not logged in. Run applet login, or set HITCH_API_KEY.");
|
|
369
|
+
await loginWithDevice(instanceUrl, { expectedOrg: orgCode });
|
|
370
|
+
const credentials = await getCredentialsForOrg(instanceUrl, orgCode);
|
|
376
371
|
if (credentials === void 0) throw new Error(`login did not provide credentials for organization "${orgCode}"`);
|
|
377
372
|
return request(organizationUrl, bearerToken(credentials));
|
|
378
373
|
}
|
|
@@ -387,4 +382,4 @@ function resolveWorkspaceOrg(appletRoot) {
|
|
|
387
382
|
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
383
|
}
|
|
389
384
|
//#endregion
|
|
390
|
-
export { loginWithDevice as a,
|
|
385
|
+
export { loginWithDevice as a, bearerToken as c, resolveAuthForOrg as d, resolveInstanceUrl as f, readWorkspaceConfig as i, organizationInstanceUrl as l, fetchWithOrgAuth as n, AuthenticationError as o, findWorkspaceConfig as r, DEFAULT_INSTANCE_URL as s, resolveWorkspaceOrg as t, rejectEnvironmentKey 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.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"applet": "./dist/applet.js"
|
|
@@ -42,10 +42,15 @@
|
|
|
42
42
|
"./_internal/server": {
|
|
43
43
|
"source": "./src/internal/server.ts",
|
|
44
44
|
"default": "./dist/_internal/server.js"
|
|
45
|
+
},
|
|
46
|
+
"./_internal/log-context": {
|
|
47
|
+
"source": "./src/server/log-context.ts",
|
|
48
|
+
"default": "./dist/_internal/log-context.js"
|
|
45
49
|
}
|
|
46
50
|
},
|
|
47
51
|
"dependencies": {
|
|
48
52
|
"@cloudflare/vite-plugin": "^1.51.1",
|
|
53
|
+
"@fontsource-variable/geist-mono": "^5.3.0",
|
|
49
54
|
"@fontsource-variable/inter": "^5.3.0",
|
|
50
55
|
"@hey-api/openapi-ts": "0.0.0-next-20260824173136",
|
|
51
56
|
"@orpc/client": "^1.15.0",
|
|
@@ -53,7 +58,6 @@
|
|
|
53
58
|
"@orpc/openapi-client": "^1.15.0",
|
|
54
59
|
"@orpc/tanstack-query": "^1.15.0",
|
|
55
60
|
"@tailwindcss/vite": "^4.3.0",
|
|
56
|
-
"@tanstack/query-async-storage-persister": "5.101.4",
|
|
57
61
|
"@tanstack/react-query-persist-client": "5.101.4",
|
|
58
62
|
"@tanstack/router-cli": "^1.167.24",
|
|
59
63
|
"@tanstack/router-plugin": "^1.168.23",
|
|
@@ -64,18 +68,22 @@
|
|
|
64
68
|
"@tiptap/extension-table": "^3.30.3",
|
|
65
69
|
"@tiptap/extensions": "^3.30.3",
|
|
66
70
|
"@tiptap/markdown": "^3.30.3",
|
|
71
|
+
"@tiptap/pm": "^3.30.3",
|
|
67
72
|
"@tiptap/react": "^3.30.3",
|
|
68
73
|
"@tiptap/starter-kit": "^3.30.3",
|
|
69
74
|
"@vitejs/plugin-react": "^6.0.3",
|
|
70
75
|
"class-variance-authority": "^0.7.1",
|
|
71
|
-
"
|
|
76
|
+
"cn": "^0.4.0",
|
|
72
77
|
"croner": "^10.0.1",
|
|
73
78
|
"lucide-react": "^1.33.0",
|
|
74
79
|
"oxc-transform-react": "^0.149.0",
|
|
80
|
+
"oxfmt": "^0.72.0",
|
|
81
|
+
"oxlint": "^1.87.0",
|
|
82
|
+
"oxlint-tsgolint": "^7.0.2003",
|
|
75
83
|
"react-aria-components": "^1.20.0",
|
|
76
84
|
"shadcn": "^4.16.1",
|
|
85
|
+
"smol-toml": "^1.9.0",
|
|
77
86
|
"streamdown": "^2.6.0",
|
|
78
|
-
"tailwind-merge": "^3.6.0",
|
|
79
87
|
"tailwindcss": "^4.3.0",
|
|
80
88
|
"tw-animate-css": "^1.4.0",
|
|
81
89
|
"vite": "8.3.0",
|
|
@@ -105,6 +113,6 @@
|
|
|
105
113
|
"publishConfig": {
|
|
106
114
|
"access": "public",
|
|
107
115
|
"registry": "https://registry.npmjs.org",
|
|
108
|
-
"tag": "
|
|
116
|
+
"tag": "latest"
|
|
109
117
|
}
|
|
110
118
|
}
|
|
@@ -28,6 +28,10 @@ This file holds the common rules. Read each matching reference before you edit a
|
|
|
28
28
|
|
|
29
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`.
|
|
30
30
|
|
|
31
|
+
Read `../tanstack-router/SKILL.md` for route work and `../tanstack-query/SKILL.md` for query and cache work.
|
|
32
|
+
Read `../react-best-practices/SKILL.md` for React performance and `../web-design-guidelines/SKILL.md` for requested interface reviews.
|
|
33
|
+
Follow the Hitch applet rules for SDK-owned configuration, providers, and generated files.
|
|
34
|
+
|
|
31
35
|
## Work with the workspace
|
|
32
36
|
|
|
33
37
|
- Run package commands with `pnpm`.
|
|
@@ -50,17 +54,18 @@ For attribute types, filters, and API limits, read `../hitch-data/SKILL.md`. To
|
|
|
50
54
|
- The dev server runs as you in the production org. Reads and writes use real data.
|
|
51
55
|
- The dev server prints a preview link. Open the link to see the local applet inside Hitch.
|
|
52
56
|
- Chrome asks once to allow local network access for the Hitch site.
|
|
53
|
-
- The preview lasts until you select
|
|
57
|
+
- The preview lasts until you select Exit or close the tab.
|
|
54
58
|
- A pending `requiresChangeset` schema is not available in dev. Deploy to test it.
|
|
55
59
|
- Run `pnpm --dir applets/<code> run deploy` only when the user requests a deployment.
|
|
56
|
-
-
|
|
57
|
-
-
|
|
60
|
+
- The CLI creates missing MCP files and adds missing Hitch and Chrome DevTools entries. Existing entries and other settings belong to the user.
|
|
61
|
+
- The applet CLI replaces its generated support files. 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.
|
|
58
62
|
- Remember that generation refreshes the local theme copy. Deployed applets load the public org theme from its Hitch `.com` URL.
|
|
59
63
|
|
|
60
64
|
## Use Vite and Vitest
|
|
61
65
|
|
|
62
66
|
- Import configuration APIs from `vite`.
|
|
63
67
|
- Import test APIs from `vitest`.
|
|
68
|
+
- Put tests in the applet's `test` folder and name them `*.test.ts` or `*.test.tsx`. Vitest does not run test files in other folders.
|
|
64
69
|
- Do not edit `vite.config.ts`. The applet CLI rewrites it. Do not add another Vite config.
|
|
65
70
|
- Add applet packages to `dependencies` or `devDependencies` in `applet.manifest.ts`.
|
|
66
71
|
|
|
@@ -132,6 +137,25 @@ Read `references/files.md` before download links, attachment attributes, or serv
|
|
|
132
137
|
- 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.
|
|
133
138
|
- Do not log secrets, tokens, or personal data.
|
|
134
139
|
|
|
140
|
+
## Add request context with `log.set()`
|
|
141
|
+
|
|
142
|
+
Each server request gets one request entry with the route, status, outcome, CPU time, and wall time. Add facts about the request to that entry:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { log } from "@hitch42/applet/server";
|
|
146
|
+
|
|
147
|
+
log.set({ invoiceId, processedCount });
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- Set the IDs, counts, and decisions that explain what the request did. One entry with many fields is easier to filter than many `console.log` lines.
|
|
151
|
+
- A later call replaces an earlier value with the same top-level key.
|
|
152
|
+
- To fail a request, throw. The request entry records the error and its stack.
|
|
153
|
+
- To record a problem that the code recovered from, set a field, for example `log.set({ fallbackUsed: true })`.
|
|
154
|
+
- Use `console.<level>` for a separate line with its own level.
|
|
155
|
+
- Outside a request, for example at module scope, `log.set()` warns and drops the fields.
|
|
156
|
+
- A single call larger than 16 KB is dropped. The entry then shows `noteDropped`.
|
|
157
|
+
- Run `pnpm --dir applets/<code> exec applet logs --filter '{"type":"invocation"}'` to read request entries. The CLI prints the fields and any error stack.
|
|
158
|
+
|
|
135
159
|
## Verify changes
|
|
136
160
|
|
|
137
161
|
Run these commands from the workspace root before deployment:
|
|
@@ -139,6 +163,8 @@ Run these commands from the workspace root before deployment:
|
|
|
139
163
|
```sh
|
|
140
164
|
pnpm --dir applets/<code> run generate
|
|
141
165
|
pnpm --dir applets/<code> run typecheck
|
|
166
|
+
pnpm --dir applets/<code> run fmt
|
|
167
|
+
pnpm --dir applets/<code> run lint
|
|
142
168
|
pnpm --dir applets/<code> run test # Only when the applet contains tests.
|
|
143
169
|
pnpm --dir applets/<code> run build
|
|
144
170
|
```
|
|
@@ -150,7 +176,7 @@ If a command fails, report the exact blocker. Do not deploy as a verification st
|
|
|
150
176
|
## Deploy from GitHub
|
|
151
177
|
|
|
152
178
|
- A push to the production branch runs `applet deploy` in the workspace root through GitHub Actions.
|
|
153
|
-
- An applet deploys when its
|
|
179
|
+
- An applet deploys when its folder or a file outside `applets/` differs from its current release, including a local `applet deploy`. Uncommitted and untracked files count. Gitignored files do not count. An applet whose current release has no source hash always deploys.
|
|
154
180
|
- The deploy skips other applets and logs the reason. A skip is expected, not a failure.
|
|
155
181
|
- A schema change in Hitch alone does not trigger CI.
|
|
156
182
|
- Do not import from a sibling applet folder. The change check does not follow such imports.
|
|
@@ -83,6 +83,14 @@ Each object entry has five required fields.
|
|
|
83
83
|
- `readOnly` lists attribute codes that the applet can read but cannot write.
|
|
84
84
|
- `readWrite` lists attribute codes that the applet can read and write.
|
|
85
85
|
|
|
86
|
+
Set `optional: true` when the applet works for a viewer without access to this object.
|
|
87
|
+
List codes from `readOnly` or `readWrite` in `optionalAttributes` when some viewers cannot see their values.
|
|
88
|
+
Typegen makes these record fields nullable for every viewer.
|
|
89
|
+
These fields never grant or remove access.
|
|
90
|
+
They matter when an org shares the applet with a connected organisation.
|
|
91
|
+
Hitch refuses a share if the connected org lacks access to a required object.
|
|
92
|
+
Hitch also refuses a share if a required object lists a required user attribute outside `optionalAttributes`.
|
|
93
|
+
|
|
86
94
|
- Use the applet code format for each object code.
|
|
87
95
|
- Do not declare the same object code more than once.
|
|
88
96
|
- Use the applet code format for each attribute code.
|
|
@@ -15,6 +15,8 @@ import { MarkdownEditor } from "@hitch42/applet/react";
|
|
|
15
15
|
<MarkdownEditor value={value} onChange={setValue} files={hitch.files} />;
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
Pass `autoFocus` to put the cursor at the end of the text when the editor mounts. It is off by default, so the editor never takes focus from another field.
|
|
19
|
+
|
|
18
20
|
The editor uploads pasted or dropped images. It stores each image as ``.
|
|
19
21
|
|
|
20
22
|
The `files` value must supply both `upload(file)` and `download(id)`. The editor uses `download(id)` to show stored Hitch images.
|
|
@@ -16,13 +16,14 @@ Use the generated Hitch contract as the typed data plane.
|
|
|
16
16
|
- `list` sends `GET /v1/<objectCode>` and lists records with query parameters.
|
|
17
17
|
- `create` sends `POST /v1/<objectCode>` and creates one record.
|
|
18
18
|
- `get` sends `GET /v1/<objectCode>/{id}` and gets one record.
|
|
19
|
+
- `history` sends `GET /v1/<objectCode>/{id}/history` and gets record changes.
|
|
19
20
|
- `update` sends `PATCH /v1/<objectCode>/{id}` and updates one record.
|
|
20
21
|
- `delete` sends `DELETE /v1/<objectCode>/{id}` and deletes one record.
|
|
21
22
|
- `query` sends `POST /v1/<objectCode>/query` and lists records with a structured body.
|
|
22
23
|
- `updateWhere` sends `POST /v1/<objectCode>/update` and updates matching records.
|
|
23
24
|
- `aggregate` sends `POST /v1/<objectCode>/aggregate` and calculates aggregate values.
|
|
24
25
|
|
|
25
|
-
- Pass a record ID in `params.id` for `get`, `update`, and `delete`.
|
|
26
|
+
- Pass a record ID in `params.id` for `get`, `history`, `update`, and `delete`.
|
|
26
27
|
- Pass record fields in `body` for `create` and `update`.
|
|
27
28
|
- Pass structured query data in `body` for `query`.
|
|
28
29
|
|
|
@@ -73,6 +74,25 @@ const deal = await hitch.records.deals.get({
|
|
|
73
74
|
- Do not use `.queryOptions(...)` or `.mutationOptions(...)` on the server.
|
|
74
75
|
- Use the server SDK for imperative data access, secrets, or external services.
|
|
75
76
|
|
|
77
|
+
## Read history
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const page = await hitch.records.deals.history({
|
|
81
|
+
params: { id },
|
|
82
|
+
query: { limit: 25, cursor },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
for (const entry of page.data) {
|
|
86
|
+
for (const change of entry.changes) {
|
|
87
|
+
if (change.attributeCode === "stage") console.log(change.to);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can read record history if and only if you can read the record.
|
|
93
|
+
The history uses the current schema. `attributeId` tracks an attribute across renames. `attributeCode` is its current code. Older history cannot follow a rename, so some changes do not appear.
|
|
94
|
+
Use `next_cursor` as the next `cursor`.
|
|
95
|
+
|
|
76
96
|
## Build a query
|
|
77
97
|
|
|
78
98
|
The `query` body accepts `filter`, `sort`, `limit`, `cursor`, and `include`.
|
|
@@ -122,6 +142,8 @@ Each aggregate can have its own `filter` with the same syntax as the top-level `
|
|
|
122
142
|
The array form returns `keys` and `labels` arrays per group.
|
|
123
143
|
The object form orders category groups by an aggregate value (top-N by value).
|
|
124
144
|
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.
|
|
145
|
+
Grouped results include `truncated`. When it is `true`, the limit omitted groups.
|
|
146
|
+
Narrow the filter or split the query.
|
|
125
147
|
|
|
126
148
|
```ts
|
|
127
149
|
const totals = await hitch.records.deals.aggregate({
|
|
@@ -24,8 +24,9 @@ An organization's data lives in Hitch objects. Each object has attributes. Each
|
|
|
24
24
|
## Attributes
|
|
25
25
|
|
|
26
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.
|
|
27
|
+
- Types: text, rich_text, integer, decimal, currency, checkbox, date, datetime, time, domain, url, email, phone, select, reference, user, organization, attachment.
|
|
28
28
|
- A value is a non-empty value or `null`. No type accepts an empty string.
|
|
29
|
+
- An organisation attribute points at a connected organisation; null means internal only; at most one exists per object.
|
|
29
30
|
- Cardinality is configuration. Text, select, reference, user, and attachment can be multi-value.
|
|
30
31
|
- A required attribute rejects `null` and rejects an empty array.
|
|
31
32
|
- 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.
|
|
@@ -13,7 +13,7 @@ Hitch validates and normalizes every attribute value on write. Read this before
|
|
|
13
13
|
- An `immutable` attribute rejects a change once it holds a non-null value. A null immutable attribute can still be set once.
|
|
14
14
|
- A `unique` attribute rejects a duplicate value with HTTP 409. Unique is never allowed on a multi-value attribute.
|
|
15
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`.
|
|
16
|
+
- Every attribute accepts the `$eq` and `$not_empty` filters. Every attribute except checkbox and organization accepts `$in`. Other operators and `sort` need the `indexed` flag. Read `limits.md`.
|
|
17
17
|
|
|
18
18
|
## Text and rich text
|
|
19
19
|
|
|
@@ -91,6 +91,12 @@ Hitch validates and normalizes every attribute value on write. Read this before
|
|
|
91
91
|
- Read: the UUID. With `include`, the expansion is `{ id, firstName, lastName, email }`.
|
|
92
92
|
- Filters: identity operators only. Sort: never.
|
|
93
93
|
|
|
94
|
+
## Organisation
|
|
95
|
+
|
|
96
|
+
- An organisation attribute points at a connected organisation; null means internal only; at most one exists per object.
|
|
97
|
+
- Write an accepted peer organization UUID. The value stays visible after the connection ends.
|
|
98
|
+
- With `include`, the expansion is `{ id, name }`. Filters are `$eq` and `$not_empty` only.
|
|
99
|
+
|
|
94
100
|
## Attachment
|
|
95
101
|
|
|
96
102
|
- 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`.
|
|
@@ -22,19 +22,19 @@ A page is the unit of a read. A screen shows one page and asks for the next one.
|
|
|
22
22
|
|
|
23
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
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 |
|
|
31
|
-
| Distinct keys per dimension, max | 200 |
|
|
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
|
|
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 | Extra groups omitted, `truncated: true` |
|
|
31
|
+
| Distinct keys per dimension, max | 200 | HTTP 400 |
|
|
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
38
|
|
|
39
39
|
## Writes
|
|
40
40
|
|
|
@@ -45,7 +45,7 @@ Applets can be used standalone via their `.app` URL or within the Hitch shell. S
|
|
|
45
45
|
|
|
46
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
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.
|
|
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. The CLI and applet doors are generated from the organization's own schema. The MCP door has a fixed set of record tools and reads each object's live schema at runtime with `get_object_schema`. 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
49
|
|
|
50
50
|
The boundary gives you:
|
|
51
51
|
|
|
@@ -68,7 +68,7 @@ Consequences:
|
|
|
68
68
|
|
|
69
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
70
|
|
|
71
|
-
- Types: text, rich text, integer, decimal, currency, checkbox, date, datetime, time, domain, url, email, phone, select, reference, user, attachment.
|
|
71
|
+
- Types: text, rich text, integer, decimal, currency, checkbox, date, datetime, time, domain, url, email, phone, select, reference, user, organisation, attachment.
|
|
72
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
73
|
- An empty attribute reads as null. A required multi-value attribute rejects an empty array.
|
|
74
74
|
- Uniqueness is a real constraint, per attribute, per organization.
|
|
@@ -83,6 +83,7 @@ References model relationships:
|
|
|
83
83
|
Special cases:
|
|
84
84
|
|
|
85
85
|
- A user attribute points at a membership in the same organization. Removal of the member sweeps those values.
|
|
86
|
+
- An organisation attribute points at a connected organisation; null means internal only; at most one exists per object.
|
|
86
87
|
- 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
|
|
|
88
89
|
## 6. Changesets and releases move together
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-best-practices
|
|
3
|
+
description: Client React performance guidelines. Use when writing, reviewing, or refactoring React components, client data fetching, bundles, rendering, or browser performance.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# React Best Practices
|
|
10
|
+
|
|
11
|
+
Performance guidance for client React applications. The rules focus on browser behavior, client data fetching, bundles, rendering, and re-renders.
|
|
12
|
+
|
|
13
|
+
## When to Apply
|
|
14
|
+
|
|
15
|
+
Reference these guidelines when:
|
|
16
|
+
|
|
17
|
+
- Writing new React components
|
|
18
|
+
- Implementing client data fetching
|
|
19
|
+
- Reviewing code for performance issues
|
|
20
|
+
- Refactoring existing React code
|
|
21
|
+
- Optimizing bundle size or load times
|
|
22
|
+
|
|
23
|
+
## Rule Categories by Priority
|
|
24
|
+
|
|
25
|
+
| Priority | Category | Impact | Prefix |
|
|
26
|
+
| -------- | ------------------------- | ----------- | ------------ |
|
|
27
|
+
| 1 | Eliminating Waterfalls | CRITICAL | `async-` |
|
|
28
|
+
| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |
|
|
29
|
+
| 3 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |
|
|
30
|
+
| 4 | Re-render Optimization | MEDIUM | `rerender-` |
|
|
31
|
+
| 5 | Rendering Performance | MEDIUM | `rendering-` |
|
|
32
|
+
| 6 | JavaScript Performance | LOW-MEDIUM | `js-` |
|
|
33
|
+
| 7 | Advanced Patterns | LOW | `advanced-` |
|
|
34
|
+
|
|
35
|
+
## Quick Reference
|
|
36
|
+
|
|
37
|
+
### 1. Eliminating Waterfalls (CRITICAL)
|
|
38
|
+
|
|
39
|
+
- `async-cheap-condition-before-await` - Check cheap sync conditions before awaiting flags or remote values
|
|
40
|
+
- `async-defer-await` - Move await into branches where actually used
|
|
41
|
+
- `async-parallel` - Use Promise.all() for independent operations
|
|
42
|
+
- `async-dependencies` - Use better-all for partial dependencies
|
|
43
|
+
|
|
44
|
+
### 2. Bundle Size Optimization (CRITICAL)
|
|
45
|
+
|
|
46
|
+
- `bundle-barrel-imports` - Import directly, avoid barrel files
|
|
47
|
+
- `bundle-analyzable-paths` - Prefer statically analyzable imports
|
|
48
|
+
- `bundle-dynamic-imports` - Use React.lazy for heavy components
|
|
49
|
+
- `bundle-defer-third-party` - Split non-critical libraries from the initial bundle
|
|
50
|
+
- `bundle-conditional` - Load modules only when feature is activated
|
|
51
|
+
- `bundle-preload` - Preload on hover/focus for perceived speed
|
|
52
|
+
|
|
53
|
+
### 3. Client-Side Data Fetching (MEDIUM-HIGH)
|
|
54
|
+
|
|
55
|
+
- `client-event-listeners` - Deduplicate global event listeners
|
|
56
|
+
- `client-passive-event-listeners` - Use passive listeners for scroll
|
|
57
|
+
- `client-localstorage-schema` - Version and minimize localStorage data
|
|
58
|
+
|
|
59
|
+
### 4. Re-render Optimization (MEDIUM)
|
|
60
|
+
|
|
61
|
+
- `rerender-defer-reads` - Don't subscribe to state only used in callbacks
|
|
62
|
+
- `rerender-memo` - Extract expensive work into memoized components
|
|
63
|
+
- `rerender-memo-with-default-value` - Hoist default non-primitive props
|
|
64
|
+
- `rerender-dependencies` - Use primitive dependencies in effects
|
|
65
|
+
- `rerender-derived-state` - Subscribe to derived booleans, not raw values
|
|
66
|
+
- `rerender-derived-state-no-effect` - Derive state during render, not effects
|
|
67
|
+
- `rerender-functional-setstate` - Use functional setState for stable callbacks
|
|
68
|
+
- `rerender-lazy-state-init` - Pass function to useState for expensive values
|
|
69
|
+
- `rerender-simple-expression-in-memo` - Avoid memo for simple primitives
|
|
70
|
+
- `rerender-split-combined-hooks` - Split hooks with independent dependencies
|
|
71
|
+
- `rerender-move-effect-to-event` - Put interaction logic in event handlers
|
|
72
|
+
- `rerender-transitions` - Use startTransition for non-urgent updates
|
|
73
|
+
- `rerender-use-deferred-value` - Defer expensive renders to keep input responsive
|
|
74
|
+
- `rerender-use-ref-transient-values` - Use refs for transient frequent values
|
|
75
|
+
- `rerender-no-inline-components` - Don't define components inside components
|
|
76
|
+
|
|
77
|
+
### 5. Rendering Performance (MEDIUM)
|
|
78
|
+
|
|
79
|
+
- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element
|
|
80
|
+
- `rendering-content-visibility` - Use content-visibility for long lists
|
|
81
|
+
- `rendering-hoist-jsx` - Extract static JSX outside components
|
|
82
|
+
- `rendering-svg-precision` - Reduce SVG coordinate precision
|
|
83
|
+
- `rendering-activity` - Use Activity component for show/hide
|
|
84
|
+
- `rendering-conditional-render` - Use ternary, not && for conditionals
|
|
85
|
+
- `rendering-usetransition-loading` - Track async action pending state with useTransition
|
|
86
|
+
- `rendering-resource-hints` - Use React DOM resource hints for preloading
|
|
87
|
+
- `rendering-script-defer-async` - Use defer or async on script tags
|
|
88
|
+
|
|
89
|
+
### 6. JavaScript Performance (LOW-MEDIUM)
|
|
90
|
+
|
|
91
|
+
- `js-batch-dom-css` - Group CSS changes via classes or cssText
|
|
92
|
+
- `js-index-maps` - Build Map for repeated lookups
|
|
93
|
+
- `js-cache-property-access` - Cache object properties in loops
|
|
94
|
+
- `js-cache-function-results` - Cache function results in module-level Map
|
|
95
|
+
- `js-cache-storage` - Cache localStorage/sessionStorage reads
|
|
96
|
+
- `js-combine-iterations` - Combine multiple filter/map into one loop
|
|
97
|
+
- `js-length-check-first` - Check array length before expensive comparison
|
|
98
|
+
- `js-early-exit` - Return early from functions
|
|
99
|
+
- `js-hoist-regexp` - Hoist RegExp creation outside loops
|
|
100
|
+
- `js-min-max-loop` - Use loop for min/max instead of sort
|
|
101
|
+
- `js-set-map-lookups` - Use Set/Map for O(1) lookups
|
|
102
|
+
- `js-tosorted-immutable` - Use toSorted() for immutability
|
|
103
|
+
- `js-flatmap-filter` - Use flatMap to map and filter in one pass
|
|
104
|
+
- `js-request-idle-callback` - Defer non-critical work to browser idle time
|
|
105
|
+
|
|
106
|
+
### 7. Advanced Patterns (LOW)
|
|
107
|
+
|
|
108
|
+
- `advanced-effect-event-deps` - Don't put `useEffectEvent` results in effect deps
|
|
109
|
+
- `advanced-event-handler-refs` - Store event handlers in refs
|
|
110
|
+
- `advanced-init-once` - Initialize app once per app load
|
|
111
|
+
- `advanced-use-latest` - useLatest for stable callback refs
|
|
112
|
+
|
|
113
|
+
## How to Use
|
|
114
|
+
|
|
115
|
+
Read individual rule files for detailed explanations and code examples:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
rules/async-parallel.md
|
|
119
|
+
rules/bundle-barrel-imports.md
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Each rule file contains:
|
|
123
|
+
|
|
124
|
+
- Brief explanation of why it matters
|
|
125
|
+
- Incorrect code example with explanation
|
|
126
|
+
- Correct code example with explanation
|
|
127
|
+
- Additional context and references
|