@c9up/station 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/StationProvider.d.ts +94 -18
  2. package/dist/StationProvider.d.ts.map +1 -1
  3. package/dist/StationProvider.js +693 -148
  4. package/dist/StationProvider.js.map +1 -1
  5. package/dist/index.d.ts +7 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +7 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/views/form.d.ts +56 -16
  10. package/dist/views/form.d.ts.map +1 -1
  11. package/dist/views/form.js +53 -75
  12. package/dist/views/form.js.map +1 -1
  13. package/dist/views/list.d.ts +43 -5
  14. package/dist/views/list.d.ts.map +1 -1
  15. package/dist/views/list.js +63 -60
  16. package/dist/views/list.js.map +1 -1
  17. package/dist/views/login.d.ts +20 -8
  18. package/dist/views/login.d.ts.map +1 -1
  19. package/dist/views/login.js +15 -33
  20. package/dist/views/login.js.map +1 -1
  21. package/dist/views/show.d.ts +18 -4
  22. package/dist/views/show.d.ts.map +1 -1
  23. package/dist/views/show.js +29 -15
  24. package/dist/views/show.js.map +1 -1
  25. package/package.json +21 -4
  26. package/src/StationProvider.ts +941 -155
  27. package/src/index.ts +11 -1
  28. package/src/views/form.ts +111 -113
  29. package/src/views/list.ts +108 -75
  30. package/src/views/login.ts +31 -44
  31. package/src/views/show.ts +47 -18
  32. package/templates/errors/404.inker +4 -0
  33. package/templates/form.inker +3 -0
  34. package/{dist/views/layout.js → templates/layout.inker} +10 -35
  35. package/templates/list.inker +8 -0
  36. package/templates/login.inker +4 -0
  37. package/templates/show.inker +4 -0
  38. package/dist/views/errors/404.d.ts +0 -12
  39. package/dist/views/errors/404.d.ts.map +0 -1
  40. package/dist/views/errors/404.js +0 -19
  41. package/dist/views/errors/404.js.map +0 -1
  42. package/dist/views/escape.d.ts +0 -30
  43. package/dist/views/escape.d.ts.map +0 -1
  44. package/dist/views/escape.js +0 -34
  45. package/dist/views/escape.js.map +0 -1
  46. package/dist/views/layout.d.ts +0 -24
  47. package/dist/views/layout.d.ts.map +0 -1
  48. package/dist/views/layout.js.map +0 -1
  49. package/src/views/errors/404.ts +0 -27
  50. package/src/views/escape.ts +0 -46
  51. package/src/views/layout.ts +0 -90
@@ -18,6 +18,7 @@
18
18
  * swallowed.
19
19
  */
20
20
  import type { ColumnMetadata } from "@c9up/atlas";
21
+ import type { Resource } from "./types.js";
21
22
  /**
22
23
  * Duck-typed slice of the host's IoC container — Station MUST stay
23
24
  * publishable without importing `@c9up/ream` directly (memory
@@ -25,7 +26,7 @@ import type { ColumnMetadata } from "@c9up/atlas";
25
26
  */
26
27
  interface StationContainer {
27
28
  singleton<T>(key: unknown, factory: () => T): void;
28
- resolve<T>(key: unknown): T;
29
+ resolve<T>(key: unknown): Promise<T>;
29
30
  has(key: unknown): boolean;
30
31
  }
31
32
  interface StationConfigStore {
@@ -35,6 +36,54 @@ export interface StationAppContext {
35
36
  container: StationContainer;
36
37
  config: StationConfigStore;
37
38
  }
39
+ /** A rune `RuleChain` — only the builders the derivation calls. */
40
+ interface RuneRuleChain {
41
+ optional(): RuneRuleChain;
42
+ nullable(): RuneRuleChain;
43
+ parse(fn: (value: unknown) => unknown): RuneRuleChain;
44
+ }
45
+ /** rune's `rules` factory — the constructors Station maps columns to. */
46
+ interface RuneRules {
47
+ string(): RuneRuleChain;
48
+ number(): RuneRuleChain;
49
+ boolean(): RuneRuleChain;
50
+ /** Type-free chain — a structural (`json`/`jsonb`) column that must accept objects/arrays. */
51
+ any(): RuneRuleChain;
52
+ }
53
+ /** A single rune `ValidationError`. */
54
+ interface RuneValidationError {
55
+ field: string;
56
+ rule: string;
57
+ message: string;
58
+ index?: number;
59
+ meta?: Record<string, unknown>;
60
+ }
61
+ /** Result of `schema.validateResult()` — synchronous, never throws. */
62
+ type RuneValidationResult = {
63
+ valid: true;
64
+ errors: ReadonlyArray<RuneValidationError>;
65
+ data: Record<string, unknown>;
66
+ } | {
67
+ valid: false;
68
+ errors: ReadonlyArray<RuneValidationError>;
69
+ data?: undefined;
70
+ };
71
+ /**
72
+ * A built rune schema — only the result-based check is consumed.
73
+ *
74
+ * `validateResult`, NOT `validate`: rune reserves `validate()` for the VineJS
75
+ * contract (async, throwing). Reading `.valid` off a Promise gives `undefined`,
76
+ * so keeping the old name would have made every admin write fail closed with an
77
+ * empty error list.
78
+ */
79
+ interface RuneSchema {
80
+ validateResult(data: unknown): RuneValidationResult;
81
+ }
82
+ /** Lazy-imported `@c9up/rune` value surface (mirror `AtlasModule`). */
83
+ interface RuneModule {
84
+ schema(fields: Record<string, RuneRuleChain>): RuneSchema;
85
+ rules: RuneRules;
86
+ }
38
87
  /**
39
88
  * Config block read from `app.config.get<StationConfig>('station')`.
40
89
  * Every field is optional — the defaults match the 54.2 / 54.3 / 54.4
@@ -68,26 +117,53 @@ export interface StationConfig {
68
117
  /** @internal Reset module-level flags between tests. */
69
118
  export declare function resetStationProviderFlags(): void;
70
119
  /**
71
- * Mass-assignment guard. Only accepts keys that are:
72
- * 1. A declared `@Column` propertyKey on the resource entity, AND
73
- * 2. Not the primary key (DB-managed), AND
74
- * 3. Not a framework-managed timestamp. Two signals, both honoured:
75
- * (a) the Lucid-style auto flag — `@column.dateTime({ autoCreate })` /
76
- * `{ autoUpdate }` (passed in via `autoManaged`); this is the
77
- * authoritative one and catches custom-named timestamp columns
78
- * (e.g. `registeredAt`) the name list below would miss, AND
79
- * (b) the conventional name fallback (created_at / updated_at /
80
- * deleted_at) for plain `@Column` timestamps declared without the
81
- * auto flag.
120
+ * True when at least one resource mounts a write action that maps a request
121
+ * body onto columns (create / edit). `destroy` needs no body validation. This
122
+ * is the AC7 predicate: it decides whether `@c9up/rune` is REQUIRED at boot —
123
+ * exported so `agnostic-peers-missing.test.ts` can pin the decision directly
124
+ * (the dynamic-import-absent path can't be simulated inside vitest, exactly as
125
+ * for atlas — see that file's header).
126
+ */
127
+ export declare function resourcesNeedValidation(resources: ReadonlyArray<Resource>): boolean;
128
+ /**
129
+ * Derive a `@c9up/rune` validation schema (57.7) from an entity's writable
130
+ * column metadata — the structural replacement for the hand-rolled
131
+ * `filterWritableBody`. The mass-assignment guarantee is now INHERENT:
132
+ * `schema.validateResult()` returns only declared-column keys, so unknown body keys
133
+ * (`role`, `passwordHash`, PK, timestamps) are dropped automatically — no
134
+ * explicit filtering pass.
82
135
  *
83
- * The `_method` synthetic field from browser-form method-overrides is
84
- * dropped automatically because it never matches a column propertyKey.
136
+ * Excluded, exactly as the old guard did: the primary key, framework-managed
137
+ * timestamps (both the Lucid-style `autoManaged` flag AND the `created_at /
138
+ * updated_at / deleted_at` name convention) — plus, new in 57.7, relations and
139
+ * the soft-delete column (`excludedProps`). Metadata is read column-by-column,
140
+ * NEVER `in` on an instance (`@Column() declare` makes `in` always false —
141
+ * mirror `BaseRepository.#hydrate`).
85
142
  *
86
- * Returning a fresh object — never the caller's reference — so a
87
- * downstream mutation can't poison the audit snapshot.
143
+ * Per-column rule mapping tolerates the form-post wire shape (values arrive as
144
+ * strings — rune does NOT coerce): booleans `.parse(isCheckedValue)`, numbers
145
+ * `.parse` string→Number (NaN falls through to a type error), everything else
146
+ * `rules.string()`. `.parse`/`.optional`/`.nullable` force rune's TS path, so
147
+ * no native `.node` is touched.
148
+ *
149
+ * Returns the built schema plus the set of boolean columns — the handler
150
+ * default-fills an ABSENT boolean key to `false` before validating (an
151
+ * unchecked checkbox submits nothing; this preserves the old edit-clears-box
152
+ * behaviour).
153
+ */
154
+ export declare function deriveWritableSchema(columns: ReadonlyArray<ColumnMetadata>, pkColumn: string, autoManaged: ReadonlySet<string>, excludedProps: ReadonlySet<string>, rune: RuneModule): {
155
+ schema: RuneSchema;
156
+ booleanKeys: ReadonlySet<string>;
157
+ };
158
+ /**
159
+ * Validate a request body against a derived schema (57.7). Default-fills any
160
+ * ABSENT boolean column to `false` first (unchecked checkbox = no submission),
161
+ * preserving the old `filterWritableBody` checkbox semantics, then runs rune's
162
+ * synchronous, never-throwing `validateResult()`. On success `result.data` holds only
163
+ * declared-column keys (mass-assignment inherent). Exported for the unit tests
164
+ * that pin the coercion CRUX.
88
165
  */
89
- /** @internal Exported for unit tests — the mass-assignment + checkbox coercion guard. */
90
- export declare function filterWritableBody(body: Record<string, unknown>, columns: ReadonlyArray<ColumnMetadata>, pkColumn: string, autoManaged: ReadonlySet<string>): Record<string, unknown>;
166
+ export declare function validateWritableBody(schema: RuneSchema, booleanKeys: ReadonlySet<string>, body: Record<string, unknown>): RuneValidationResult;
91
167
  export default class StationProvider {
92
168
  #private;
93
169
  protected app: StationAppContext;
@@ -1 +1 @@
1
- {"version":3,"file":"StationProvider.d.ts","sourceRoot":"","sources":["../src/StationProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAEX,cAAc,EAGd,MAAM,aAAa,CAAC;AAYrB;;;;GAIG;AACH,UAAU,gBAAgB;IACzB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACnD,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,CAAC,CAAC;IAC5B,GAAG,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;CAC3B;AAED,UAAU,kBAAkB;IAC3B,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,kBAAkB,CAAC;CAC3B;AA2MD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wDAAwD;AACxD,wBAAgB,yBAAyB,IAAI,IAAI,CAMhD;AAaD;;;;;;;;;;;;;;;;;;GAkBG;AACH,yFAAyF;AACzF,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,EACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzB;AAmDD,MAAM,CAAC,OAAO,OAAO,eAAe;;IAgBvB,SAAS,CAAC,GAAG,EAAE,iBAAiB;gBAAtB,GAAG,EAAE,iBAAiB;IAE5C,QAAQ,IAAI,IAAI;IAWV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0PtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAEtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA+iB/B;AAwED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAItD"}
1
+ {"version":3,"file":"StationProvider.d.ts","sourceRoot":"","sources":["../src/StationProvider.ts"],"names":[],"mappings":"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"}