@compean/core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RULES.md +2 -2
- package/dist/eslint/index.js +7 -2
- package/dist/registry.d.ts +9 -2
- package/dist/registry.js +4 -1
- package/package.json +1 -1
package/RULES.md
CHANGED
|
@@ -83,7 +83,7 @@ packages/client/ features/<feature>/: controllers, client services, shared vie
|
|
|
83
83
|
## Controllers
|
|
84
84
|
|
|
85
85
|
- A controller extends `Controller<TState>`. It exposes `state`, `initialize()` and its actions; the base class provides `start()`, `subscribe`, `dispose`, `notify()`, `track(cleanup)`, `load(task)` and `run(task)`.
|
|
86
|
-
- `state` holds display-ready values only. Building them is private mapping
|
|
86
|
+
- `state` holds display-ready values only. Building them is done by the controller's private methods, including helpers that don't use `this` (such as formatting a date), so all of a screen's mapping is found in one place. Screens and components import only view-model types, never the functions that build them. Decisions call domain rules, not view-model fields. [§7.4, §9.1]
|
|
87
87
|
- Loading the screen's data goes through `load()`: before the screen is first ready, the status settles when every load started so far has finished ('ready', or 'error' with a retry); once ready it's a refresh whose failure shows as a toast. A screen that loads nothing never calls it and starts ready. Every action goes through `run()`, which never changes the status and shows failures as toasts. Both show a `UserError`'s message, show a generic message for anything else, and report bugs. [§10.7, §18]
|
|
88
88
|
- An action must add something: `run()`, a state change, or combining services. One that only forwards a call is a pass-through. [§7.1]
|
|
89
89
|
- Every decision that reads app state is made in the controller, in a method called directly where the data arrives (for example at the end of `load()`). Subscribe to a service only when the change comes from outside. [§18.2]
|
|
@@ -117,7 +117,7 @@ packages/client/ features/<feature>/: controllers, client services, shared vie
|
|
|
117
117
|
- A service extends `Observable` only when screens must react to its changes live; otherwise it's a plain class. [§19.1]
|
|
118
118
|
- Its interface speaks the domain: one named snapshot for displayed data, intent methods instead of setters, question methods only for domain rules several callers share. A getter per field is a red flag. [§19.6, §5.1]
|
|
119
119
|
- A library (Zustand, query-core, Better Auth) may live inside a service, never in screens or controllers. [§5.1]
|
|
120
|
-
- Dependencies come through the constructor, every argument named, no `undefined` placeholders. A default must be what production wants, so only `{ clock, random }` default (to the real ones) and a dependency never defaults. Product constants live in the module that owns the rule. [§8.2, §10.9]
|
|
120
|
+
- Dependencies come through the constructor, every argument named, no `undefined` placeholders. Declare each as a field and assign it in the constructor; apps never use parameter properties (`constructor(private readonly x: X)`), because Node runs the API by stripping types only. `erasableSyntaxOnly` enforces it. A default must be what production wants, so only `{ clock, random }` default (to the real ones) and a dependency never defaults. Product constants live in the module that owns the rule. [§8.2, §10.9]
|
|
121
121
|
|
|
122
122
|
### Server data on the client
|
|
123
123
|
|
package/dist/eslint/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
const PLATFORM_IMPORT = /^(react|react-dom|react-native|react-router|expo)(\/|$)|^react-native-|^react-router-|^@tanstack\/react-|^expo-|^@expo\/|^@react-navigation\//;
|
|
3
3
|
/** What packages/domain may import: its own files and zod. */
|
|
4
4
|
const DOMAIN_ALLOWED = /^(\.{1,2}\/|zod(\/|$))/;
|
|
5
|
+
/** Tests may import their runner and test libraries, which the platform and purity rules would flag. */
|
|
6
|
+
const TEST_FILES = ['**/*.test.{ts,tsx}'];
|
|
5
7
|
/** TanStack Router calls whose options object defines a route. */
|
|
6
8
|
const ROUTE_FACTORIES = new Set([
|
|
7
9
|
'createFileRoute',
|
|
@@ -14,8 +16,8 @@ const ROUTE_FACTORIES = new Set([
|
|
|
14
16
|
const LIBRARY_OWNERS = [
|
|
15
17
|
{
|
|
16
18
|
library: /^@tanstack\/(query-core|react-query)(\/|$)/,
|
|
17
|
-
owner:
|
|
18
|
-
ownerName: 'a client service (*.service.ts)',
|
|
19
|
+
owner: /(\.service|(^|[\\/])boot)\.tsx?$/,
|
|
20
|
+
ownerName: 'a client service (*.service.ts) or boot.ts',
|
|
19
21
|
},
|
|
20
22
|
{ library: /^better-auth(\/|$)/, owner: /(^|[\\/])auth\.service\.tsx?$/, ownerName: 'the auth service (auth.service.ts)' },
|
|
21
23
|
];
|
|
@@ -135,10 +137,12 @@ export function compeanRules() {
|
|
|
135
137
|
{ plugins: { compean: plugin } },
|
|
136
138
|
{
|
|
137
139
|
files: ['packages/client/**/*.{ts,tsx}', '**/*.controller.{ts,tsx}'],
|
|
140
|
+
ignores: TEST_FILES,
|
|
138
141
|
rules: { 'compean/platform-free': 'error' },
|
|
139
142
|
},
|
|
140
143
|
{
|
|
141
144
|
files: ['packages/domain/**/*.{ts,tsx}'],
|
|
145
|
+
ignores: TEST_FILES,
|
|
142
146
|
rules: { 'compean/domain-pure': 'error' },
|
|
143
147
|
},
|
|
144
148
|
{
|
|
@@ -147,6 +151,7 @@ export function compeanRules() {
|
|
|
147
151
|
},
|
|
148
152
|
{
|
|
149
153
|
files: ['packages/client/**/*.{ts,tsx}', 'apps/web/**/*.{ts,tsx}', 'apps/mobile/**/*.{ts,tsx}'],
|
|
154
|
+
ignores: TEST_FILES,
|
|
150
155
|
rules: { 'compean/library-owners': 'error' },
|
|
151
156
|
},
|
|
152
157
|
];
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
type Token<T> = abstract new (...args: never[]) => T;
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/** What callers of `$S(token)` can use: the class's public members, so a test fake needs only those. */
|
|
3
|
+
type PublicShape<T> = {
|
|
4
|
+
[K in keyof T]: T[K];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Makes `instance` the one `$S(token)` returns. Called from boot.ts and from tests. The type comes
|
|
8
|
+
* from the class alone; `instance` must offer its public members, so a fake with extra fields fits.
|
|
9
|
+
*/
|
|
10
|
+
export declare function register<T>(token: Token<T>, instance: NoInfer<PublicShape<T>>): void;
|
|
4
11
|
/**
|
|
5
12
|
* The service registered for this class, or a framework default (ToastService, ErrorReporter).
|
|
6
13
|
*
|
package/dist/registry.js
CHANGED
|
@@ -9,7 +9,10 @@ const defaults = new Map([
|
|
|
9
9
|
[ToastService, () => new ToastService()],
|
|
10
10
|
[ErrorReporter, () => new ConsoleReporter()],
|
|
11
11
|
]);
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Makes `instance` the one `$S(token)` returns. Called from boot.ts and from tests. The type comes
|
|
14
|
+
* from the class alone; `instance` must offer its public members, so a fake with extra fields fits.
|
|
15
|
+
*/
|
|
13
16
|
export function register(token, instance) {
|
|
14
17
|
services.set(token, instance);
|
|
15
18
|
}
|