@c9up/station 0.1.5
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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/ResourceRegistry.d.ts +18 -0
- package/dist/ResourceRegistry.d.ts.map +1 -0
- package/dist/ResourceRegistry.js +38 -0
- package/dist/ResourceRegistry.js.map +1 -0
- package/dist/StationProvider.d.ts +109 -0
- package/dist/StationProvider.d.ts.map +1 -0
- package/dist/StationProvider.js +1144 -0
- package/dist/StationProvider.js.map +1 -0
- package/dist/casing.d.ts +27 -0
- package/dist/casing.d.ts.map +1 -0
- package/dist/casing.js +75 -0
- package/dist/casing.js.map +1 -0
- package/dist/defineResource.d.ts +9 -0
- package/dist/defineResource.d.ts.map +1 -0
- package/dist/defineResource.js +84 -0
- package/dist/defineResource.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/services/main.d.ts +18 -0
- package/dist/services/main.d.ts.map +1 -0
- package/dist/services/main.js +31 -0
- package/dist/services/main.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/views/errors/404.d.ts +12 -0
- package/dist/views/errors/404.d.ts.map +1 -0
- package/dist/views/errors/404.js +19 -0
- package/dist/views/errors/404.js.map +1 -0
- package/dist/views/escape.d.ts +30 -0
- package/dist/views/escape.d.ts.map +1 -0
- package/dist/views/escape.js +34 -0
- package/dist/views/escape.js.map +1 -0
- package/dist/views/form.d.ts +34 -0
- package/dist/views/form.d.ts.map +1 -0
- package/dist/views/form.js +139 -0
- package/dist/views/form.js.map +1 -0
- package/dist/views/layout.d.ts +24 -0
- package/dist/views/layout.d.ts.map +1 -0
- package/dist/views/layout.js +85 -0
- package/dist/views/layout.js.map +1 -0
- package/dist/views/list.d.ts +22 -0
- package/dist/views/list.d.ts.map +1 -0
- package/dist/views/list.js +85 -0
- package/dist/views/list.js.map +1 -0
- package/dist/views/login.d.ts +25 -0
- package/dist/views/login.d.ts.map +1 -0
- package/dist/views/login.js +44 -0
- package/dist/views/login.js.map +1 -0
- package/dist/views/show.d.ts +17 -0
- package/dist/views/show.d.ts.map +1 -0
- package/dist/views/show.js +24 -0
- package/dist/views/show.js.map +1 -0
- package/package.json +63 -0
- package/src/ResourceRegistry.ts +49 -0
- package/src/StationProvider.ts +1579 -0
- package/src/casing.ts +86 -0
- package/src/defineResource.ts +126 -0
- package/src/index.ts +14 -0
- package/src/services/main.ts +39 -0
- package/src/types.ts +108 -0
- package/src/views/errors/404.ts +27 -0
- package/src/views/escape.ts +46 -0
- package/src/views/form.ts +191 -0
- package/src/views/layout.ts +90 -0
- package/src/views/list.ts +121 -0
- package/src/views/login.ts +65 -0
- package/src/views/show.ts +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C9up
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# @c9up/station
|
|
2
|
+
|
|
3
|
+
Station is the admin-scaffolding module for the [Ream](https://ream.dev) framework: declare a resource for an Atlas entity in one file and Station owns the route/UI/audit/policy derivation. It centralises the resource catalogue (`defineResource`, `ResourceRegistry`) so every later concern reads from a single declaration. See [docs/en/modules/station.md](https://ream.dev/modules/station) for the full guide.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Resource } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Central catalogue of declared resources for a Station-powered admin surface.
|
|
4
|
+
*
|
|
5
|
+
* Plain data structure: no boot()/start() lifecycle here — provider wiring
|
|
6
|
+
* lands in story 54.7. Iteration order is registration order, which is what
|
|
7
|
+
* the admin sidebar wants.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ResourceRegistry {
|
|
10
|
+
#private;
|
|
11
|
+
register<T>(resource: Resource<T>): void;
|
|
12
|
+
get(name: string): Resource | undefined;
|
|
13
|
+
getOrThrow(name: string): Resource;
|
|
14
|
+
has(name: string): boolean;
|
|
15
|
+
count(): number;
|
|
16
|
+
all(): ReadonlyArray<Resource>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=ResourceRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResourceRegistry.d.ts","sourceRoot":"","sources":["../src/ResourceRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;GAMG;AACH,qBAAa,gBAAgB;;IAG5B,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IAUxC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAIvC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ;IAUlC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,KAAK,IAAI,MAAM;IAIf,GAAG,IAAI,aAAa,CAAC,QAAQ,CAAC;CAI9B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central catalogue of declared resources for a Station-powered admin surface.
|
|
3
|
+
*
|
|
4
|
+
* Plain data structure: no boot()/start() lifecycle here — provider wiring
|
|
5
|
+
* lands in story 54.7. Iteration order is registration order, which is what
|
|
6
|
+
* the admin sidebar wants.
|
|
7
|
+
*/
|
|
8
|
+
export class ResourceRegistry {
|
|
9
|
+
#map = new Map();
|
|
10
|
+
register(resource) {
|
|
11
|
+
const existing = this.#map.get(resource.name);
|
|
12
|
+
if (existing !== undefined) {
|
|
13
|
+
throw new Error(`[station] ResourceRegistry: duplicate resource name '${resource.name}' (already registered for ${existing.entity.name})`);
|
|
14
|
+
}
|
|
15
|
+
this.#map.set(resource.name, resource);
|
|
16
|
+
}
|
|
17
|
+
get(name) {
|
|
18
|
+
return this.#map.get(name);
|
|
19
|
+
}
|
|
20
|
+
getOrThrow(name) {
|
|
21
|
+
const found = this.#map.get(name);
|
|
22
|
+
if (found === undefined) {
|
|
23
|
+
throw new Error(`[station] ResourceRegistry: no resource named '${name}'`);
|
|
24
|
+
}
|
|
25
|
+
return found;
|
|
26
|
+
}
|
|
27
|
+
has(name) {
|
|
28
|
+
return this.#map.has(name);
|
|
29
|
+
}
|
|
30
|
+
count() {
|
|
31
|
+
return this.#map.size;
|
|
32
|
+
}
|
|
33
|
+
all() {
|
|
34
|
+
const snapshot = [...this.#map.values()];
|
|
35
|
+
return Object.freeze(snapshot);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=ResourceRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResourceRegistry.js","sourceRoot":"","sources":["../src/ResourceRegistry.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IACnB,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE5C,QAAQ,CAAI,QAAqB;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CACd,wDAAwD,QAAQ,CAAC,IAAI,6BAA6B,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,CACzH,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,GAAG,CAAC,IAAY;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,UAAU,CAAC,IAAY;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACd,kDAAkD,IAAI,GAAG,CACzD,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IAED,GAAG,CAAC,IAAY;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK;QACJ,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,GAAG;QACF,MAAM,QAAQ,GAAe,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACrD,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;CACD"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StationProvider — Ream provider that wires Station's `ResourceRegistry`
|
|
3
|
+
* into the host container and mounts the list + show routes for every
|
|
4
|
+
* registered resource.
|
|
5
|
+
*
|
|
6
|
+
* Story 54.7 EXTENDS this provider with Warden integration (login
|
|
7
|
+
* surface + `/_assets/station/*` mount). The class shape and lifecycle
|
|
8
|
+
* stay; only `start()` grows.
|
|
9
|
+
*
|
|
10
|
+
* Mirror of `packages/aurora/src/AuroraProvider.ts` — register binds a
|
|
11
|
+
* singleton + sets the `services/main` proxy backing instance, start
|
|
12
|
+
* dynamically imports BOTH `@c9up/ream/services/router` AND `@c9up/atlas`
|
|
13
|
+
* inside try/catch so non-Ream hosts AND Station-without-Atlas consumers
|
|
14
|
+
* are silently tolerated. Once both modules resolve, the per-resource
|
|
15
|
+
* repository + column metadata is built ONCE (cached on the instance)
|
|
16
|
+
* and re-used by every request, then route registration runs OUTSIDE
|
|
17
|
+
* the catch — real bugs in route registration surface instead of being
|
|
18
|
+
* swallowed.
|
|
19
|
+
*/
|
|
20
|
+
import type { ColumnMetadata } from "@c9up/atlas";
|
|
21
|
+
/**
|
|
22
|
+
* Duck-typed slice of the host's IoC container — Station MUST stay
|
|
23
|
+
* publishable without importing `@c9up/ream` directly (memory
|
|
24
|
+
* `project_package_extraction`). The Ream container fulfils this shape.
|
|
25
|
+
*/
|
|
26
|
+
interface StationContainer {
|
|
27
|
+
singleton<T>(key: unknown, factory: () => T): void;
|
|
28
|
+
resolve<T>(key: unknown): T;
|
|
29
|
+
has(key: unknown): boolean;
|
|
30
|
+
}
|
|
31
|
+
interface StationConfigStore {
|
|
32
|
+
get<T>(key: string): T | undefined;
|
|
33
|
+
}
|
|
34
|
+
export interface StationAppContext {
|
|
35
|
+
container: StationContainer;
|
|
36
|
+
config: StationConfigStore;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Config block read from `app.config.get<StationConfig>('station')`.
|
|
40
|
+
* Every field is optional — the defaults match the 54.2 / 54.3 / 54.4
|
|
41
|
+
* conventions so an app can leave the config out entirely.
|
|
42
|
+
*/
|
|
43
|
+
export interface StationConfig {
|
|
44
|
+
/**
|
|
45
|
+
* When true (default `true` if `@c9up/warden` is installed),
|
|
46
|
+
* Station mounts a login surface at `/admin/login` and gates every
|
|
47
|
+
* other `/admin/*` route behind `auth.verify(token)`. Setting this
|
|
48
|
+
* to `false` keeps the old open-by-default behaviour from 54.2.
|
|
49
|
+
*/
|
|
50
|
+
requireAuth?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Role required to pass the auth gate. When omitted, any
|
|
53
|
+
* authenticated user can access `/admin/*` (the per-action
|
|
54
|
+
* `<resource>.<action>` permission gate still applies).
|
|
55
|
+
*/
|
|
56
|
+
requireRole?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Where to redirect on a failed auth check. Defaults to
|
|
59
|
+
* `/admin/login`.
|
|
60
|
+
*/
|
|
61
|
+
loginPath?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Cookie name carrying the auth token. Defaults to `station_auth`
|
|
64
|
+
* to avoid colliding with app-level session cookies.
|
|
65
|
+
*/
|
|
66
|
+
cookieName?: string;
|
|
67
|
+
}
|
|
68
|
+
/** @internal Reset module-level flags between tests. */
|
|
69
|
+
export declare function resetStationProviderFlags(): void;
|
|
70
|
+
/**
|
|
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.
|
|
82
|
+
*
|
|
83
|
+
* The `_method` synthetic field from browser-form method-overrides is
|
|
84
|
+
* dropped automatically because it never matches a column propertyKey.
|
|
85
|
+
*
|
|
86
|
+
* Returning a fresh object — never the caller's reference — so a
|
|
87
|
+
* downstream mutation can't poison the audit snapshot.
|
|
88
|
+
*/
|
|
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>;
|
|
91
|
+
export default class StationProvider {
|
|
92
|
+
#private;
|
|
93
|
+
protected app: StationAppContext;
|
|
94
|
+
constructor(app: StationAppContext);
|
|
95
|
+
register(): void;
|
|
96
|
+
boot(): Promise<void>;
|
|
97
|
+
start(): Promise<void>;
|
|
98
|
+
ready(): Promise<void>;
|
|
99
|
+
shutdown(): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Node's ERR_MODULE_NOT_FOUND surfaces on an Error subclass with `code`.
|
|
103
|
+
* Exported for the 54.8 agnostic-peer-missing unit test, which can't
|
|
104
|
+
* realistically simulate the dynamic-import failure path inside vitest's
|
|
105
|
+
* mock graph.
|
|
106
|
+
*/
|
|
107
|
+
export declare function isModuleNotFound(err: unknown): boolean;
|
|
108
|
+
export {};
|
|
109
|
+
//# sourceMappingURL=StationProvider.d.ts.map
|
|
@@ -0,0 +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"}
|