@jskit-ai/agent-docs 0.1.256 → 0.1.258
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/guide/agent/app-extras/assistant-conversation.md +88 -35
- package/guide/agent/app-extras/assistant.md +28 -28
- package/guide/agent/app-setup/authentication.md +36 -17
- package/guide/agent/app-setup/database-layer.md +116 -17
- package/guide/agent/app-setup/existing-application-migration.md +29 -25
- package/guide/agent/app-setup/multi-homing.md +94 -48
- package/guide/agent/app-setup/run-an-accounts-hub.md +6 -4
- package/guide/agent/framework/client-features.md +496 -0
- package/guide/agent/framework/crud-operations.md +5 -5
- package/guide/agent/framework/translation.md +4 -3
- package/guide/agent/framework/typed-features.md +1104 -0
- package/guide/agent/framework/ui-operations.md +2 -2
- package/guide/agent/index.md +6 -1
- package/package.json +1 -1
- package/patterns/feature-package/PATTERN.md +104 -10
- package/patterns/feature-package/example/booking-engine/package.json +9 -4
- package/patterns/feature-package/example/booking-engine/src/server/BookingEngineFeature.js +24 -0
- package/patterns/feature-package/example/booking-engine/src/server/bookingAvailability.js +10 -0
- package/patterns/feature-package/example/booking-engine/src/server/bookingEngine.js +29 -0
- package/patterns/feature-package/example/booking-engine/src/server/bookingEngineActions.js +26 -0
- package/patterns/feature-package/example/booking-engine/src/server/inputSchemas.js +1 -2
- package/patterns/feature-package/example/booking-engine/src/server/types.js +12 -0
- package/patterns/feature-package/example/variations/AvailabilityEngineFeature.js +19 -0
- package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +23 -0
- package/patterns/feature-package/example/variations/availabilityEngineActions.js +26 -0
- package/patterns/feature-package/example/variations/customKnexRepository.js +32 -26
- package/patterns/feature-package/example/variations/invoiceRollupActions.js +26 -0
- package/patterns/feature-package/example/variations/orchestratorService.js +24 -19
- package/patterns/feature-package/example/variations/types.js +7 -0
- package/patterns/minimal-foundation/PATTERN.md +30 -1
- package/patterns/minimal-foundation/example/AGENTS.md +14 -0
- package/patterns/minimal-foundation/example/bin/develop.js +10 -5
- package/patterns/minimal-foundation/example/eslint.config.mjs +21 -2
- package/patterns/minimal-foundation/example/package.json +5 -4
- package/patterns/minimal-foundation/example/playwright.config.mjs +1 -1
- package/patterns/minimal-foundation/example/server/lib/envFile.js +19 -0
- package/patterns/minimal-foundation/example/server/lib/loggerOptions.js +15 -0
- package/patterns/minimal-foundation/example/server/lib/runtimeEnv.js +17 -17
- package/patterns/minimal-foundation/example/server.js +32 -29
- package/patterns/minimal-foundation/example/tests/server/smoke.test.js +2 -1
- package/patterns/shell-foundation/PATTERN.md +29 -0
- package/patterns/shell-foundation/example/AGENTS.md +14 -0
- package/patterns/shell-foundation/example/bin/develop.js +10 -5
- package/patterns/shell-foundation/example/eslint.config.mjs +21 -2
- package/patterns/shell-foundation/example/package.json +6 -5
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +1 -0
- package/patterns/shell-foundation/example/playwright.config.mjs +1 -1
- package/patterns/shell-foundation/example/server/lib/envFile.js +19 -0
- package/patterns/shell-foundation/example/server/lib/loggerOptions.js +15 -0
- package/patterns/shell-foundation/example/server/lib/runtimeEnv.js +17 -17
- package/patterns/shell-foundation/example/server.js +32 -29
- package/patterns/shell-foundation/example/tests/server/smoke.test.js +2 -1
- package/reference/autogen/KERNEL_MAP.md +14 -8
- package/reference/autogen/PATTERN_INDEX.md +37 -37
- package/reference/autogen/README.md +2 -0
- package/reference/autogen/packages/agent-docs.md +70 -26
- package/reference/autogen/packages/agent-runtime.md +129 -0
- package/reference/autogen/packages/agent.md +149 -0
- package/reference/autogen/packages/architecture-inspector.md +97 -44
- package/reference/autogen/packages/assistant-core.md +786 -292
- package/reference/autogen/packages/assistant-runtime.md +182 -57
- package/reference/autogen/packages/assistant-voice-sherpa.md +4 -0
- package/reference/autogen/packages/assistant-voice.md +54 -8
- package/reference/autogen/packages/auth-core.md +44 -13
- package/reference/autogen/packages/auth-provider-local-core.md +86 -34
- package/reference/autogen/packages/auth-provider-local-db-core.md +1 -2
- package/reference/autogen/packages/auth-provider-oidc-core.md +10 -4
- package/reference/autogen/packages/auth-provider-supabase-core.md +52 -66
- package/reference/autogen/packages/auth-web.md +19 -11
- package/reference/autogen/packages/connectors-catalog.md +71 -43
- package/reference/autogen/packages/connectors-core.md +42 -4
- package/reference/autogen/packages/console-core.md +27 -14
- package/reference/autogen/packages/crud-core.md +178 -170
- package/reference/autogen/packages/database-runtime-mysql.md +0 -2
- package/reference/autogen/packages/database-runtime-postgres.md +0 -2
- package/reference/autogen/packages/database-runtime.md +39 -10
- package/reference/autogen/packages/http-runtime.md +79 -57
- package/reference/autogen/packages/json-rest-api-core.md +11 -8
- package/reference/autogen/packages/kernel.md +144 -83
- package/reference/autogen/packages/oidc-issuer-core.md +49 -20
- package/reference/autogen/packages/payments-core.md +20 -3
- package/reference/autogen/packages/realtime.md +47 -22
- package/reference/autogen/packages/resource-core.md +1 -1
- package/reference/autogen/packages/resource-crud-core.md +64 -26
- package/reference/autogen/packages/rewarded-core.md +47 -36
- package/reference/autogen/packages/shell-web.md +7 -10
- package/reference/autogen/packages/storage-runtime.md +6 -2
- package/reference/autogen/packages/uploads-runtime.md +10 -2
- package/reference/autogen/packages/users-core.md +73 -71
- package/reference/autogen/packages/workspaces-core.md +121 -106
- package/reference/autogen/packages/workspaces-web.md +1 -2
- package/reference/autogen/tooling/config-eslint.md +302 -0
- package/reference/autogen/tooling/jskit-catalog.md +28 -18
- package/reference/autogen/tooling/jskit-release.md +35 -20
- package/reference/autogen/tooling/testUtils.md +3 -1
- package/skills/jskit/SKILL.md +3 -2
- package/skills/jskit/references/app-operations.md +1 -1
- package/skills/jskit/references/client-features.md +491 -0
- package/skills/jskit/references/crud-form-actions.md +1 -1
- package/skills/jskit/references/crud-operations.md +7 -7
- package/skills/jskit/references/existing-application-migration.md +31 -27
- package/skills/jskit/references/heuristics/INDEX.md +5 -1
- package/skills/jskit/references/heuristics/child-cruds.md +1 -1
- package/skills/jskit/references/heuristics/client-features.md +66 -0
- package/skills/jskit/references/heuristics/client-requests.md +1 -1
- package/skills/jskit/references/heuristics/crud-authoring.md +12 -2
- package/skills/jskit/references/heuristics/crud-links.md +1 -1
- package/skills/jskit/references/heuristics/crud-repository-mapping.md +3 -3
- package/skills/jskit/references/heuristics/filters.md +1 -1
- package/skills/jskit/references/heuristics/live-actions.md +1 -1
- package/skills/jskit/references/heuristics/page-redirects.md +1 -1
- package/skills/jskit/references/heuristics/page-scaffolding.md +1 -1
- package/skills/jskit/references/heuristics/placements.md +1 -1
- package/skills/jskit/references/heuristics/row-policies.md +1 -1
- package/skills/jskit/references/heuristics/server-search.md +1 -1
- package/skills/jskit/references/heuristics/surfaces.md +1 -1
- package/skills/jskit/references/heuristics/typed-features.md +100 -0
- package/skills/jskit/references/heuristics/ui-contract.md +1 -1
- package/skills/jskit/references/heuristics/ui-testing.md +4 -4
- package/skills/jskit/references/material-3.md +2 -2
- package/skills/jskit/references/multi-homing.md +96 -50
- package/skills/jskit/references/pattern-index.md +37 -37
- package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +30 -1
- package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +14 -0
- package/skills/jskit/references/patterns/app/minimal-foundation/example/bin/develop.js +10 -5
- package/skills/jskit/references/patterns/app/minimal-foundation/example/eslint.config.mjs +21 -2
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +5 -4
- package/skills/jskit/references/patterns/app/minimal-foundation/example/playwright.config.mjs +1 -1
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server/lib/envFile.js +19 -0
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server/lib/loggerOptions.js +15 -0
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server/lib/runtimeEnv.js +17 -17
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +32 -29
- package/skills/jskit/references/patterns/app/minimal-foundation/example/tests/server/smoke.test.js +2 -1
- package/skills/jskit/references/patterns/app/shell-foundation/PATTERN.md +29 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +14 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/bin/develop.js +10 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/eslint.config.mjs +21 -2
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +6 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +1 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/playwright.config.mjs +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/server/lib/envFile.js +19 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/server/lib/loggerOptions.js +15 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/server/lib/runtimeEnv.js +17 -17
- package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +32 -29
- package/skills/jskit/references/patterns/app/shell-foundation/example/tests/server/smoke.test.js +2 -1
- package/skills/jskit/references/patterns/assistant/assistant-surface/example/src/ApplicationAiFeature.js +4 -2
- package/skills/jskit/references/patterns/auth/accounts-hub/PATTERN.md +2 -1
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/accounts-hub/example/server/hubPaths.js +9 -3
- package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +25 -12
- package/skills/jskit/references/patterns/auth/auth-surface/example/tools/preview-identity +7 -1
- package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/ai-connections/PATTERN.md +16 -1
- package/skills/jskit/references/patterns/connectors/ai-connections/example/ai-model.js +12 -3
- package/skills/jskit/references/patterns/connectors/ai-connections/example/perplexity-answer.js +15 -4
- package/skills/jskit/references/patterns/connectors/api-key-connection/PATTERN.md +9 -1
- package/skills/jskit/references/patterns/connectors/api-key-connection/example/connections.js +5 -2
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/PATTERN.md +13 -3
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/s3-transfer.js +14 -3
- package/skills/jskit/references/patterns/connectors/calendar-cli/PATTERN.md +8 -0
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/knexfile.js +1 -1
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/scripts/calendar.js +8 -4
- package/skills/jskit/references/patterns/connectors/event-delivery/PATTERN.md +1 -1
- package/skills/jskit/references/patterns/connectors/firebase-messaging/PATTERN.md +10 -0
- package/skills/jskit/references/patterns/connectors/firebase-messaging/example/server/notifications.js +8 -0
- package/skills/jskit/references/patterns/connectors/google-ads-search/PATTERN.md +18 -7
- package/skills/jskit/references/patterns/connectors/google-ads-search/example/ads-setup.js +13 -5
- package/skills/jskit/references/patterns/connectors/oauth-connection/PATTERN.md +1 -1
- package/skills/jskit/references/patterns/connectors/paddle-catalogue/PATTERN.md +11 -2
- package/skills/jskit/references/patterns/connectors/paddle-catalogue/example/create-products.js +5 -2
- package/skills/jskit/references/patterns/connectors/public-image/PATTERN.md +10 -1
- package/skills/jskit/references/patterns/connectors/public-image/example/logo-url.js +4 -1
- package/skills/jskit/references/patterns/connectors/redshift-queries/PATTERN.md +1 -1
- package/skills/jskit/references/patterns/connectors/source-scanning/PATTERN.md +13 -2
- package/skills/jskit/references/patterns/connectors/source-scanning/example/source-scanner.js +9 -0
- package/skills/jskit/references/patterns/crud/json-api-resource-package/PATTERN.md +102 -23
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +3 -2
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/types.js +5 -0
- package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +54 -1
- package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +16 -5
- package/skills/jskit/references/patterns/database/mysql-application/example/knexfile.js +2 -2
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
- package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +1 -1
- package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +16 -5
- package/skills/jskit/references/patterns/database/postgres-application/example/knexfile.js +2 -2
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
- package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +1 -1
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/rewards/google-rewarded/example/GoogleRewardedDeliveryProvider.js +1 -1
- package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +5 -11
- package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +104 -10
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +9 -4
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/BookingEngineFeature.js +24 -0
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/bookingAvailability.js +10 -0
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/bookingEngine.js +29 -0
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/bookingEngineActions.js +26 -0
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/inputSchemas.js +1 -2
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/types.js +12 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/AvailabilityEngineFeature.js +19 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +23 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/availabilityEngineActions.js +26 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +32 -26
- package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +26 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/orchestratorService.js +24 -19
- package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +7 -0
- package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +16 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +3 -2
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/types.js +5 -0
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +4 -3
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +2 -9
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/types.js +5 -0
- package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +42 -17
- package/skills/jskit/references/patterns/workspaces/workspace-server/example/packages/main/src/server/WorkspaceInvitationsFeature.js +27 -0
- package/skills/jskit/references/patterns/workspaces/workspace-server/example/packages/main/src/server/email/workspaceInviteEmail.js +11 -10
- package/skills/jskit/references/patterns/workspaces/workspace-server/example/packages/main/src/server/email/workspaceInviteMailer.js +27 -0
- package/skills/jskit/references/translation.md +5 -4
- package/skills/jskit/references/typed-features.md +1099 -0
- package/skills/jskit/references/ui-operations.md +3 -3
- package/patterns/feature-package/example/booking-engine/src/server/BookingEngineProvider.js +0 -33
- package/patterns/feature-package/example/booking-engine/src/server/actions.js +0 -24
- package/patterns/feature-package/example/variations/AvailabilityEngineProvider.js +0 -31
- package/patterns/feature-package/example/variations/InvoiceRollupProvider.js +0 -36
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/BookingEngineProvider.js +0 -33
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/src/server/actions.js +0 -24
- package/skills/jskit/references/patterns/server/feature-package/example/variations/AvailabilityEngineProvider.js +0 -31
- package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupProvider.js +0 -36
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
<!-- Generated by `npm run agent-docs:build` from `packages/agent-docs/site/guide/framework/client-features.md`. Do not edit manually. -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
title: Client features
|
|
5
|
+
description: How JSKIT client code is written (composables, components, client factories and providers), rule by rule with a good and a bad example, and the lint rules that check it.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Client features
|
|
9
|
+
|
|
10
|
+
Read this page before writing or reviewing JSKIT client code: a composable, a
|
|
11
|
+
Vue component, a client factory or a client provider. It is the client half of
|
|
12
|
+
[typed features](/guide/framework/typed-features): the same rule, the same
|
|
13
|
+
`readable-check: allow` vocabulary and the same size limits, applied to Vue.
|
|
14
|
+
`clientFeaturesConfig` makes the rules a machine can see errors
|
|
15
|
+
([checks](#checks)); the others are checked by reading.
|
|
16
|
+
|
|
17
|
+
The [client features heuristic](https://github.com/mobily-enterprises/jskit-ai/blob/main/packages/agent-docs/heuristics/client-features.md)
|
|
18
|
+
maps keywords and lint findings to the section that fixes them.
|
|
19
|
+
|
|
20
|
+
## The rule
|
|
21
|
+
|
|
22
|
+
> Every collaborator client code uses arrives through its options, props,
|
|
23
|
+
> injection or provider capabilities, typed where it is received and required,
|
|
24
|
+
> never probed, so each call has one visible destination.
|
|
25
|
+
|
|
26
|
+
- A **collaborator** is an object or function with behaviour that code calls:
|
|
27
|
+
an adapter, an API client, a state object with methods, a callback, a router.
|
|
28
|
+
Plain data (a label, a list, a record) is not one.
|
|
29
|
+
- A **composable** is a `use<Name>()` function that holds Vue state; a **client
|
|
30
|
+
factory** is a `create<Name>()` that holds none.
|
|
31
|
+
|
|
32
|
+
## Simplicity
|
|
33
|
+
|
|
34
|
+
### Simple to reason about
|
|
35
|
+
|
|
36
|
+
**A reader follows each flow top to bottom: plain functions, direct calls,
|
|
37
|
+
names that say what they do.** Satisfying a rule by adding a layer, a
|
|
38
|
+
registry, a pass-through wrapper, meta-programming or a one-use abstraction is
|
|
39
|
+
wrong, and so is splitting a file into many tiny modules to meet a size limit.
|
|
40
|
+
|
|
41
|
+
Signs of over-cleverness: a `create*` that only forwards to another; handlers
|
|
42
|
+
built in a loop or from strings; `Object.fromEntries(… .map(…))` to build an
|
|
43
|
+
API; a `Proxy`; a factory used once; a registry for two entries; a module of
|
|
44
|
+
one three-line helper; files named `utils2`, `part1` or `…Helpers`.
|
|
45
|
+
|
|
46
|
+
Why: the rules exist so code is easy to follow; a contortion that silences one
|
|
47
|
+
defeats it.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// good: the contract is required, so the calls are direct
|
|
51
|
+
function resend(messageId) {
|
|
52
|
+
return controller.resend(messageId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// bad: a computed layer that hides two probes
|
|
56
|
+
const operations = Object.fromEntries(["find", "resend"].map((name) => [name, (...args) => controller.value?.[name]?.(...args)]));
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
When a rule seems to force a contortion, take the direct reading that meets
|
|
60
|
+
its intent, and report the case so the rule can be narrowed.
|
|
61
|
+
|
|
62
|
+
## Types and collaborators
|
|
63
|
+
|
|
64
|
+
### Client types
|
|
65
|
+
|
|
66
|
+
**A package's client types live in `src/client/types.js`, exported as
|
|
67
|
+
`./client/types` when another package uses them: one `@typedef` per value its
|
|
68
|
+
client code hands out or receives across files.** That is a composable's
|
|
69
|
+
result (`ReturnType<typeof useX>`), a client factory's result, and each
|
|
70
|
+
contract an application implements, written once with its optional members
|
|
71
|
+
marked `?`. Kernel capabilities come from `@jskit-ai/kernel/client/types`:
|
|
72
|
+
`ClientComponents`, `ClientVueApp`, `ClientRouter`, `ClientPinia`,
|
|
73
|
+
`ClientQueryClient`, `ClientEnv`, `ClientSurfaceRuntime`, `ClientSurfaceMode`
|
|
74
|
+
and `ClientLogger`.
|
|
75
|
+
|
|
76
|
+
Why: a parameter that names its type sends a reader to one file.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
// good: src/client/types.js
|
|
80
|
+
/** @typedef {ReturnType<typeof import("./useAttachmentQueue.js").useAttachmentQueue>} AttachmentQueue */
|
|
81
|
+
/**
|
|
82
|
+
* What an application supplies to render a conversation.
|
|
83
|
+
* @typedef {object} ConversationAdapter
|
|
84
|
+
* @property {(text: string) => Promise<void>} send
|
|
85
|
+
* @property {MessageDelivery} [delivery] Present when the application tracks delivery.
|
|
86
|
+
*/
|
|
87
|
+
export {};
|
|
88
|
+
|
|
89
|
+
// bad: the shape is whatever the caller passed
|
|
90
|
+
function useConversationActions({ adapter }) {
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Typed options
|
|
94
|
+
|
|
95
|
+
**A composable, client factory or helper destructures its options in its
|
|
96
|
+
signature and types them with a JSDoc `@param`.** An options object's `= {}`
|
|
97
|
+
stays only where a real caller passes nothing and every member has its own
|
|
98
|
+
default.
|
|
99
|
+
|
|
100
|
+
Why: the same one-step reading as server code; `useX(options)` read in the
|
|
101
|
+
body hides what it needs.
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
// good
|
|
105
|
+
/** @param {{ uploadAttachment: AttachmentUpload, onUploaded: (attachment: Attachment) => void }} options */
|
|
106
|
+
function useAttachments({ uploadAttachment, onUploaded }) {
|
|
107
|
+
|
|
108
|
+
// bad
|
|
109
|
+
function useAttachments(options = {}) {
|
|
110
|
+
const upload = options.uploadAttachment;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Required options
|
|
114
|
+
|
|
115
|
+
**A collaborator every real caller passes is required: no `= null`, `= {}` or
|
|
116
|
+
no-op default, no `typeof x === "function"` probe and no `x?.()` call.** A
|
|
117
|
+
default that is the production implementation stays (`now = Date.now`).
|
|
118
|
+
|
|
119
|
+
Why: a probe gives each call a second outcome that production never takes.
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
// good
|
|
123
|
+
function useAttachments({ uploadAttachment }) {
|
|
124
|
+
|
|
125
|
+
// bad: every caller passes uploadAttachment, yet it is probed
|
|
126
|
+
function useAttachments({ uploadAttachment = null } = {}) {
|
|
127
|
+
if (typeof uploadAttachment !== "function") return;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Absent options
|
|
131
|
+
|
|
132
|
+
**What a real caller leaves out takes one of the server's two forms.** A
|
|
133
|
+
per-call callback (`onError`, `onProgress`) defaults to a named no-op and is
|
|
134
|
+
called plainly. An optional operation, a contract member some real caller
|
|
135
|
+
omits, is typed `?`, received with no default, probed once with an
|
|
136
|
+
`optional operation:` allow comment, and branched on that one boolean.
|
|
137
|
+
|
|
138
|
+
- When the member is read live (an application binding behind a getter), probe
|
|
139
|
+
it where it is called, with one allow comment, through one local reader
|
|
140
|
+
function when several sites call it.
|
|
141
|
+
- In a template, call it in the binding, `adapter.actions.reload?.()`, with one
|
|
142
|
+
HTML allow comment directly above the element's opening tag; it covers that
|
|
143
|
+
element's bindings, not its children's. Never `a?.b && a.b()`.
|
|
144
|
+
- A maybe-ref input (`MaybeRefOrGetter<T>`) is read with Vue's `toValue()`.
|
|
145
|
+
- A function whose one job is normalizing a documented external value of
|
|
146
|
+
several shapes carries one `input validation:` allow comment directly above
|
|
147
|
+
it, which covers the probes in its own body. Nowhere else: never over a
|
|
148
|
+
composable, a `setup()` or a function that does more than normalize.
|
|
149
|
+
|
|
150
|
+
A test-only omission counts for none of these: complete the double.
|
|
151
|
+
|
|
152
|
+
Why: one spelling for "may be absent" across server and client, and one probe
|
|
153
|
+
instead of one per call.
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
// good
|
|
157
|
+
function ignoreProgress() {}
|
|
158
|
+
function useUpload({ onProgress = ignoreProgress }) {
|
|
159
|
+
onProgress(0);
|
|
160
|
+
}
|
|
161
|
+
/** @param {{ deleteAttachment?: AttachmentDelete }} options */
|
|
162
|
+
function useAttachments({ deleteAttachment }) {
|
|
163
|
+
// readable-check: allow method-probe - optional operation: a read-only queue has no deleteAttachment, so removal stays local
|
|
164
|
+
const canDelete = typeof deleteAttachment === "function";
|
|
165
|
+
}
|
|
166
|
+
const text = toValue(source);
|
|
167
|
+
|
|
168
|
+
// bad
|
|
169
|
+
function useUpload({ onProgress = null }) {
|
|
170
|
+
onProgress?.(0);
|
|
171
|
+
}
|
|
172
|
+
const text = typeof source === "function" ? source() : source.value;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```html
|
|
176
|
+
<!-- good -->
|
|
177
|
+
<!-- readable-check: allow optional-call - optional operation: a read-only transcript has no reload -->
|
|
178
|
+
<v-btn :disabled="loading" @click="adapter.actions.reload?.()" />
|
|
179
|
+
|
|
180
|
+
<!-- bad -->
|
|
181
|
+
<v-btn v-if="adapter.actions?.reload" @click="adapter.actions.reload()" />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Platform probes
|
|
185
|
+
|
|
186
|
+
**A probe of a host object lives in one helper per package,
|
|
187
|
+
`hasWindowFunction(name)`, with one `platform object:` allow comment, and is
|
|
188
|
+
imported.**
|
|
189
|
+
|
|
190
|
+
Why: one treatment per probe kind; copies of a `window` probe drift.
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
// good: src/client/browser.js
|
|
194
|
+
/** Whether the page's window has the named function; outside a browser there is no window. */
|
|
195
|
+
function hasWindowFunction(name) {
|
|
196
|
+
// readable-check: allow method-probe - platform object: window and its functions exist only in a browser
|
|
197
|
+
return typeof window !== "undefined" && typeof window[name] === "function";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// bad, in five places
|
|
201
|
+
if (typeof window !== "undefined" && typeof window.clearTimeout === "function") window.clearTimeout(timer);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Client providers
|
|
205
|
+
|
|
206
|
+
**A client provider follows the server's token rules: `requires`, `optional`
|
|
207
|
+
and `provides` one per line, hooks typed with the kernel's or the owner's
|
|
208
|
+
types, registration in `setup()`, a replaceable value as an `optional` token
|
|
209
|
+
with a named default, and no module-level registry.**
|
|
210
|
+
|
|
211
|
+
Why: client providers are capabilities like server features; a slot filled by
|
|
212
|
+
a setter or a module map has no visible owner.
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
// good
|
|
216
|
+
const ConversationClientProvider = defineProvider({
|
|
217
|
+
id: "conversation.web.client",
|
|
218
|
+
requires: {
|
|
219
|
+
components: "client.components"
|
|
220
|
+
},
|
|
221
|
+
/** @param {{ components: import("@jskit-ai/kernel/client/types").ClientComponents }} capabilities */
|
|
222
|
+
setup({ components }) {
|
|
223
|
+
components.register("conversation.web.settings.element", ConversationSettingsElement);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// bad
|
|
228
|
+
const components = new Map();
|
|
229
|
+
export function registerComponent(id, component) { components.set(id, component); }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Injected values
|
|
233
|
+
|
|
234
|
+
**`inject()` takes the owner's exported key, never a literal. A required value
|
|
235
|
+
is `inject(KEY)`; one that may be absent is `inject(KEY, NAMED_DEFAULT)` with
|
|
236
|
+
the named default exported beside the key. The binding names its type.** A
|
|
237
|
+
key another package owns is imported, with its type and disabled default, from
|
|
238
|
+
that package; a consumer that cannot depend on it writes the key in place,
|
|
239
|
+
typed, with a named `NO_*` default.
|
|
240
|
+
|
|
241
|
+
Why: injection is the client's capability lookup: one key, one type, one
|
|
242
|
+
resolution.
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
// good
|
|
246
|
+
/** @type {import("@jskit-ai/workspaces-web/client/types").WorkspaceScopeSupport} */
|
|
247
|
+
const support = inject(WORKSPACE_SCOPE_KEY, NO_WORKSPACE_SCOPE);
|
|
248
|
+
|
|
249
|
+
// bad
|
|
250
|
+
const support = inject("workspaceScope", null);
|
|
251
|
+
if (!support || typeof support.readRouteScope !== "function") return FALLBACK;
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Components
|
|
255
|
+
|
|
256
|
+
### Collaborator props
|
|
257
|
+
|
|
258
|
+
**Every prop has a `type` and is required or has a real default. A prop the
|
|
259
|
+
component calls is `required: true`, typed with a `PropType` cast to its
|
|
260
|
+
typedef, and called without `?.`.** A collaborator prop that real callers
|
|
261
|
+
genuinely leave out keeps `default: null`, is typed `PropType<X | null>`,
|
|
262
|
+
carries one `optional operation:` allow comment and is branched on once.
|
|
263
|
+
|
|
264
|
+
Why: a `default: null` collaborator is a probe at every use; a chain of `?.`
|
|
265
|
+
calls on one adapter hides which parts are really optional.
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
// good
|
|
269
|
+
const props = defineProps({
|
|
270
|
+
state: { type: /** @type {import("vue").PropType<GoalState>} */ (Object), required: true }
|
|
271
|
+
});
|
|
272
|
+
const canSet = computed(() => !props.state.loading && props.state.available);
|
|
273
|
+
|
|
274
|
+
// bad
|
|
275
|
+
const props = defineProps({ state: { type: Object, default: null } });
|
|
276
|
+
const canSet = computed(() => typeof props.state?.set === "function");
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Declared emits
|
|
280
|
+
|
|
281
|
+
**`defineEmits()` lists every event the component emits, including those a
|
|
282
|
+
composable emits through the `emit` it receives.**
|
|
283
|
+
|
|
284
|
+
Why: the declaration is the component's event contract, and an undeclared
|
|
285
|
+
event also falls through as a DOM listener.
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
// good
|
|
289
|
+
const emit = defineEmits(["close", "closed"]);
|
|
290
|
+
|
|
291
|
+
// bad
|
|
292
|
+
const emit = defineEmits(["close"]);
|
|
293
|
+
emit("closed");
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Presenting scripts
|
|
297
|
+
|
|
298
|
+
**A component's `<script>` presents: props, emits, element refs, display
|
|
299
|
+
`computed`s and handlers that call a composable or emit.** Requests, state
|
|
300
|
+
machines, timers, parsing and projections live in a composable or a plain
|
|
301
|
+
module, moved verbatim. Split by concern into a few cohesive pieces; a large
|
|
302
|
+
style block moves to `<style scoped src="./X.css">`. Never a subcomponent made
|
|
303
|
+
only to shrink a file: it changes scoped styles and slots.
|
|
304
|
+
|
|
305
|
+
Why: logic in a composable is read and tested without mounting a component.
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
good: Transcript.vue (template and presentation), useTranscriptScroll.js,
|
|
309
|
+
transcriptEntries.js (pure projection), Transcript.css via <style scoped src>
|
|
310
|
+
bad: Transcript.vue, 1,600 lines: rendering, scroll anchoring, delivery state and 500 lines of CSS
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Composables
|
|
314
|
+
|
|
315
|
+
### Fixed result shape
|
|
316
|
+
|
|
317
|
+
**A composable returns one object literal of named members, the same on every
|
|
318
|
+
path: nothing assigned after construction, no spread of another composable's
|
|
319
|
+
result, no conditional keys, and never `Object.freeze`.** That includes a
|
|
320
|
+
result a callback builds: a wrapper passes what the result needs into the
|
|
321
|
+
callback instead of patching what it returned. An extension that needs the
|
|
322
|
+
object it extends is listed in the literal (`application: null`) and filled
|
|
323
|
+
once right after construction, with a `cycle:` allow comment. A plain
|
|
324
|
+
`create*` that holds no Vue state keeps `Object.freeze`.
|
|
325
|
+
|
|
326
|
+
Why: a fixed shape keeps the type true; and `reactive()` leaves a frozen
|
|
327
|
+
object unproxied while `proxyRefs()` (applied to a `setup()` return and to
|
|
328
|
+
`defineExpose()`) throws on one, so Vue state is never frozen.
|
|
329
|
+
|
|
330
|
+
```js
|
|
331
|
+
// good
|
|
332
|
+
const suggestions = useSuggestions();
|
|
333
|
+
return { messages, send, suggestions };
|
|
334
|
+
const runtime = app.runWithContext(() => createRuntime(readers, retain));
|
|
335
|
+
|
|
336
|
+
// bad
|
|
337
|
+
return Object.freeze({ messages, send, ...useSuggestions() });
|
|
338
|
+
const runtime = app.runWithContext(() => createRuntime(readers));
|
|
339
|
+
runtime.retain = retain;
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Effect order
|
|
343
|
+
|
|
344
|
+
**Splitting a composable keeps its `watch()` and `watchEffect()` calls in the
|
|
345
|
+
original composable, in their creation order, calling the extracted module's
|
|
346
|
+
functions.**
|
|
347
|
+
|
|
348
|
+
Why: pre-flush watchers in one scope run in trigger order, so moving them into
|
|
349
|
+
a helper composable can reorder an application's callbacks.
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
// good: useConversation.js keeps its watchers in order
|
|
353
|
+
watch(messages, (next) => scroll.follow(next));
|
|
354
|
+
watch(draft, (text) => drafts.save(text));
|
|
355
|
+
|
|
356
|
+
// bad: one watcher moved into useDrafts(), now created before the scroll watcher
|
|
357
|
+
const drafts = useDrafts(draft);
|
|
358
|
+
watch(messages, (next) => scroll.follow(next));
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Released listeners
|
|
362
|
+
|
|
363
|
+
**Everything client code starts, it stops: each `addEventListener`,
|
|
364
|
+
`setInterval`, observer and `.on()` is released in `onScopeDispose` or
|
|
365
|
+
`onBeforeUnmount`, or registered with `{ once: true }` or an `AbortSignal`
|
|
366
|
+
aborted there.** A listener on an object the code creates with `new` and drops
|
|
367
|
+
with it (its own socket, closed in `disconnect`) goes with that object.
|
|
368
|
+
|
|
369
|
+
Why: a listener that outlives its component leaks memory and fires against
|
|
370
|
+
unmounted state.
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
// good
|
|
374
|
+
onMounted(() => window.addEventListener("resize", measure));
|
|
375
|
+
onBeforeUnmount(() => window.removeEventListener("resize", measure));
|
|
376
|
+
|
|
377
|
+
// bad
|
|
378
|
+
onMounted(() => window.addEventListener("resize", () => measure()));
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Module state
|
|
382
|
+
|
|
383
|
+
**State lives inside the composable or factory that owns it. A module-level
|
|
384
|
+
`let`, `ref()` or `reactive()` exists only when every instance on the page
|
|
385
|
+
must share it, with a `shared resource:` allow comment.** A private memo
|
|
386
|
+
cache nothing fills from outside stays.
|
|
387
|
+
|
|
388
|
+
Why: module scope is shared by every component instance and every application
|
|
389
|
+
on the page, and survives unmounting.
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
// good: each composer owns its draft
|
|
393
|
+
function useComposer() {
|
|
394
|
+
const draft = ref("");
|
|
395
|
+
}
|
|
396
|
+
// readable-check: allow module-state - shared resource: every attachment queue on the page draws ids from one sequence
|
|
397
|
+
let attachmentClientSequence = 0;
|
|
398
|
+
|
|
399
|
+
// bad: every composer on the page shares one draft
|
|
400
|
+
let pendingDraft = "";
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Helpers and sizes
|
|
404
|
+
|
|
405
|
+
### One helper per package
|
|
406
|
+
|
|
407
|
+
**A pure helper exists once in a package and is imported; one the kernel owns
|
|
408
|
+
(`isRecord`, `normalizeText`) comes from
|
|
409
|
+
`@jskit-ai/kernel/shared/support/normalize`; one client and server code both
|
|
410
|
+
need lives in the package's `src/shared`.**
|
|
411
|
+
|
|
412
|
+
Why: copies drift, and the next reader cannot tell which is right.
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
// good
|
|
416
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
417
|
+
|
|
418
|
+
// bad: a copy in src/client/lib/api.js
|
|
419
|
+
function isRecord(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); }
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Size limits
|
|
423
|
+
|
|
424
|
+
**Files stay under 750 lines and functions under 600; a `.vue` file counts
|
|
425
|
+
whole: template, script and style.** Split by concern
|
|
426
|
+
([presenting scripts](#presenting-scripts)), never into tiny modules
|
|
427
|
+
([simple to reason about](#simple-to-reason-about)).
|
|
428
|
+
|
|
429
|
+
Why: a reader holds one concern at a time.
|
|
430
|
+
|
|
431
|
+
```text
|
|
432
|
+
good: Transcript.vue 420 lines, useTranscriptScroll.js 180, transcriptEntries.js 240
|
|
433
|
+
bad: Transcript.vue 1,600 lines; or eight 90-line files named transcriptPart1.js to transcriptPart8.js
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Checks
|
|
437
|
+
|
|
438
|
+
### Running the checks
|
|
439
|
+
|
|
440
|
+
`clientFeaturesConfig(globs)` makes the client rules errors for the client
|
|
441
|
+
code the globs select, Vue components included and tests excluded. A new
|
|
442
|
+
application lists all of it; an existing one lists each client tree as it
|
|
443
|
+
converts it, and nothing else changes until it does:
|
|
444
|
+
|
|
445
|
+
```js
|
|
446
|
+
import { baseConfig, clientFeaturesConfig, typedFeaturesConfig, vueConfig } from "@jskit-ai/config-eslint/server";
|
|
447
|
+
|
|
448
|
+
export default [
|
|
449
|
+
...baseConfig,
|
|
450
|
+
...typedFeaturesConfig(["packages/*/src/server/**", "packages/*/src/shared/**"]),
|
|
451
|
+
...vueConfig,
|
|
452
|
+
...clientFeaturesConfig(["packages/*/src/client/**", "src/**"])
|
|
453
|
+
];
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
It runs the typed-features rules that read client code, with `use*`
|
|
457
|
+
composables counted as factories and templates read for probes, and the
|
|
458
|
+
client-only rules below. The server's action and error rules do not apply.
|
|
459
|
+
|
|
460
|
+
### Lint rules
|
|
461
|
+
|
|
462
|
+
| Rule | Reports | Section |
|
|
463
|
+
| --- | --- | --- |
|
|
464
|
+
| `jskit/typed-inject` | `inject()` with a literal key, an untyped binding, or a `null` / `{}` default | [Injected values](#injected-values) |
|
|
465
|
+
| `jskit/collaborator-props` | a prop the component calls that is optional with no named default, or typed only `Object` | [Collaborator props](#collaborator-props) |
|
|
466
|
+
| `jskit/fixed-result-shape` | behaviour assigned to a `use*` / `create*` result after it is built, also one a wrapper's callback built | [Fixed result shape](#fixed-result-shape) |
|
|
467
|
+
| `jskit/no-frozen-vue-state` | `Object.freeze` over refs, computeds or reactive state | [Fixed result shape](#fixed-result-shape) |
|
|
468
|
+
| `jskit/released-listeners` | a listener, interval, observer or subscription the file starts and never releases | [Released listeners](#released-listeners) |
|
|
469
|
+
| `jskit/no-module-state` | a module-level `let`, `ref()` or `reactive()` | [Module state](#module-state) |
|
|
470
|
+
| `vue/require-prop-types`, `vue/require-default-prop`, `vue/require-valid-default-prop`, `vue/no-required-prop-with-default` | a prop without a type, optional without a default, or both | [Collaborator props](#collaborator-props) |
|
|
471
|
+
| `vue/require-explicit-emits` | an emitted event the component does not declare | [Declared emits](#declared-emits) |
|
|
472
|
+
|
|
473
|
+
The typed-features rules keep their own sections:
|
|
474
|
+
[`typed-dependencies`](/guide/framework/typed-features#typed-collaborators),
|
|
475
|
+
[`required-collaborators`](/guide/framework/typed-features#required-collaborators),
|
|
476
|
+
[`no-duplicate-helper`](/guide/framework/typed-features#one-helper-per-package),
|
|
477
|
+
[`no-factory-spread`](/guide/framework/typed-features#named-members) and the
|
|
478
|
+
rest. In client code, `no-slot-setter` reports only a setter named like a slot
|
|
479
|
+
(`setProvider`), `frozen-factory-result` leaves Vue state unfrozen, and
|
|
480
|
+
`no-duplicate-helper` compares a client helper with the whole package, server
|
|
481
|
+
and shared code included.
|
|
482
|
+
|
|
483
|
+
Simplicity, effect order and presenting scripts are checked by reading.
|
|
484
|
+
|
|
485
|
+
### Exceptions
|
|
486
|
+
|
|
487
|
+
Client exceptions use the
|
|
488
|
+
[typed features exceptions](/guide/framework/typed-features#exceptions): a
|
|
489
|
+
comment on the line before the finding, naming the rule or the finding, with a
|
|
490
|
+
reason prefix. Client code adds three placements:
|
|
491
|
+
|
|
492
|
+
| Placement | The exception |
|
|
493
|
+
| --- | --- |
|
|
494
|
+
| `// readable-check: allow module-state - shared resource: <what the page shares>` | module state every instance on the page shares on purpose |
|
|
495
|
+
| `<!-- readable-check: allow optional-call - optional operation: <what lacks it> -->` above an element | that element's bindings call an optional operation |
|
|
496
|
+
| `// readable-check: allow method-probe - input validation: <the shapes>` above a function | the probes of a function whose one job is normalizing an external value of several shapes |
|
|
@@ -338,11 +338,11 @@ make a live table or a generator the sole source of truth. Inspect schemas for
|
|
|
338
338
|
adoption and diagnosis; inspection is optional.
|
|
339
339
|
|
|
340
340
|
Read [ordering and recovery](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/database-layer#directory-order-comes-before-filenames)
|
|
341
|
-
before schema work; check Knex configuration and ledger.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
341
|
+
before schema work; check Knex configuration and ledger. Package table
|
|
342
|
+
migrations run first, then root `migrations/`, then the constraints phases.
|
|
343
|
+
Timestamps order only within directories. Root migrations may reference package
|
|
344
|
+
tables; keep tables/columns with their package and put foreign keys between
|
|
345
|
+
packages in a constraints phase.
|
|
346
346
|
|
|
347
347
|
MySQL/MariaDB can leave committed DDL without a ledger entry. Check columns and
|
|
348
348
|
keys separately; `hasColumn()` is insufficient. Validate existing definitions,
|
|
@@ -140,9 +140,10 @@ the English `message`:
|
|
|
140
140
|
`messageParams` for the exact sentence.
|
|
141
141
|
|
|
142
142
|
The client translates a code only when the English the server sent is JSKIT's
|
|
143
|
-
English for that code. An app's own sentence under
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
English for that code. An app's own sentence under JSKIT's shared code is shown
|
|
144
|
+
exactly as written, and so is any text whose code has no catalog entry. That is
|
|
145
|
+
why each refusal an app throws
|
|
146
|
+
[carries its own code](/guide/framework/typed-features#refusals-carry-their-own-code).
|
|
146
147
|
|
|
147
148
|
App code that shows errors uses the same helper JSKIT's components use:
|
|
148
149
|
|