@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 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 inside the controller. 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]
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
 
@@ -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: /\.service\.tsx?$/,
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
  ];
@@ -1,6 +1,13 @@
1
1
  type Token<T> = abstract new (...args: never[]) => T;
2
- /** Makes `instance` the one `$S(token)` returns. Called from boot.ts and from tests. */
3
- export declare function register<T>(token: Token<T>, instance: T): void;
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
- /** Makes `instance` the one `$S(token)` returns. Called from boot.ts and from tests. */
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compean/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Shared core for Compean apps: controllers, services, errors, forms and the framework rules.",
5
5
  "license": "MIT",
6
6
  "type": "module",