@voxgig/station 0.0.2 → 0.2.0

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 (55) hide show
  1. package/README.md +26 -32
  2. package/dist/src/Station.d.ts +1 -133
  3. package/dist/src/Station.js +46 -434
  4. package/dist/src/Station.js.map +1 -1
  5. package/dist/src/adapter.js +0 -54
  6. package/dist/src/adapter.js.map +1 -1
  7. package/dist/src/config-shape.js +0 -11
  8. package/dist/src/config-shape.js.map +1 -1
  9. package/dist/src/descriptor.js +0 -36
  10. package/dist/src/descriptor.js.map +1 -1
  11. package/dist/src/error.js +0 -5
  12. package/dist/src/error.js.map +1 -1
  13. package/dist/src/factory.d.ts +0 -9
  14. package/dist/src/factory.js +0 -37
  15. package/dist/src/factory.js.map +1 -1
  16. package/dist/src/feature.d.ts +0 -75
  17. package/dist/src/feature.js +0 -87
  18. package/dist/src/feature.js.map +1 -1
  19. package/dist/src/index.d.ts +1 -2
  20. package/dist/src/index.js +11 -22
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/loader.d.ts +0 -29
  23. package/dist/src/loader.js +0 -66
  24. package/dist/src/loader.js.map +1 -1
  25. package/dist/src/profile.d.ts +10 -3
  26. package/dist/src/profile.js +97 -23
  27. package/dist/src/profile.js.map +1 -1
  28. package/dist/src/secrets.d.ts +5 -0
  29. package/dist/src/secrets.js +106 -32
  30. package/dist/src/secrets.js.map +1 -1
  31. package/dist/src/shape.d.ts +0 -22
  32. package/dist/src/shape.js +0 -113
  33. package/dist/src/shape.js.map +1 -1
  34. package/package.json +3 -4
  35. package/src/Station.ts +34 -441
  36. package/src/adapter.ts +0 -57
  37. package/src/config-shape.ts +0 -11
  38. package/src/descriptor.ts +0 -36
  39. package/src/error.ts +0 -5
  40. package/src/factory.ts +0 -37
  41. package/src/feature.ts +0 -87
  42. package/src/index.ts +1 -14
  43. package/src/loader.ts +0 -66
  44. package/src/profile.ts +115 -23
  45. package/src/secrets.ts +104 -32
  46. package/src/shape.ts +0 -113
  47. package/src/types.ts +0 -59
  48. package/dist/src/index.browser.d.ts +0 -14
  49. package/dist/src/index.browser.js +0 -64
  50. package/dist/src/index.browser.js.map +0 -1
  51. package/dist/src/profilecore.d.ts +0 -10
  52. package/dist/src/profilecore.js +0 -180
  53. package/dist/src/profilecore.js.map +0 -1
  54. package/src/index.browser.ts +0 -56
  55. package/src/profilecore.ts +0 -201
package/README.md CHANGED
@@ -16,42 +16,37 @@ validates through [voxgig/struct](https://github.com/voxgig/struct).
16
16
  Those are the only two runtime dependencies a station library takes
17
17
  (design §10).
18
18
 
19
- ## Two entries, one surface
20
-
21
- `package.json` `exports` selects between them by condition:
22
-
23
- | condition | entry | |
24
- |---|---|---|
25
- | default | `dist/src/index.js` | the node entry — the whole surface |
26
- | `browser` | `dist/src/index.browser.js` | the same surface **minus** the file-loading half |
27
-
28
- `loadConfig`, `findConfigFile` and `configScope` are node-only: they live
29
- in `profile.ts`, the one module here with top-level `node:` imports, and
30
- the browser entry simply does not carry them. So the browser graph pulls
31
- no `node:` builtin at all — `test/browser.test.ts` asserts it by walking
32
- the *compiled* `dist` files, the same requires a bundler follows, rather
33
- than trusting the source. The boundary, as that test states it: the walk
34
- covers this package's own modules; `@voxgig/sekreto` and
35
- `@voxgig/struct` are recorded but not entered, since their browser
36
- posture is their own.
37
-
38
- That is why `profilecore.ts` exists: the pure half of profile handling
39
- (selection, the ref grammar, the §3.3 merge) is data-in/data-out and the
40
- browser entry can reach it. `profile.ts` re-exports it, so no import path
41
- in the node half changes.
42
-
43
- On the browser entry `Station.open()` takes `config` as an object — the
44
- explicit-config path, which skips file loading entirely. With no `config`
45
- it behaves as node does when no `station.json` exists: null config,
46
- repo-scoped.
19
+ ## One entry
20
+
21
+ `package.json` `exports` names a single entry, `dist/src/index.js`.
22
+
23
+ There used to be two. A `browser` condition selected
24
+ `dist/src/index.browser.js` — the same surface **minus** the file-loading
25
+ half, so that no `node:` builtin was reachable from it — and three pieces
26
+ of machinery existed to keep that true: `profilecore.ts` held the pure
27
+ half of profile handling so the browser entry could reach it without
28
+ `node:fs`; `Station.ts` took `loadConfig`/`configScope` through a
29
+ `ConfigFileIO` seam that only the node entry registered; and
30
+ `test/browser.test.ts` walked the compiled `dist` graph asserting no
31
+ `node:` builtin appeared.
32
+
33
+ **Station is server-side only**, so all of it is gone: the second entry,
34
+ the condition, the seam, the split and the graph-walk test.
35
+ `profilecore.ts` is merged back into `profile.ts`, and `Station.ts`
36
+ imports `loadConfig` and `configScope` directly. Every import of
37
+ `./profile` is unchanged — that path always re-exported the pure half.
38
+
39
+ `setConfigFileIO` and its `ConfigFileIO` type were exported from this
40
+ package and are removed with the seam. Nothing in this repository or any
41
+ other voxgig port used them; the seam existed for the browser entry and
42
+ for nothing else.
47
43
 
48
44
  ## Layout
49
45
 
50
46
  | | |
51
47
  |---|---|
52
48
  | `src/Station.ts` | the core: open/connect/adopt/options, sdk/create/check/warm, registry, transport middleware, the ref grammar |
53
- | `src/index.ts` | the node entry, and the registration that hands Station the file half |
54
- | `src/index.browser.ts` | the browser entry (above) |
49
+ | `src/index.ts` | the entry: the public surface |
55
50
  | `src/adapter.ts` | `featureBinding` (the one binding entry) + the carried adapter |
56
51
  | `src/shape.ts` | `normalizeConfig`/`validateConfig`: the §4 grammar, the §4.4 workarounds, the §5.2 credential scans |
57
52
  | `src/config-shape.ts` | a verbatim mirror of [`spec/config-shape.json`](../spec/config-shape.json) — generated by `make sync-shape`, never edited by hand |
@@ -60,8 +55,7 @@ repo-scoped.
60
55
  | `src/loader.ts` | `loadSync`/`loadAsync`: CommonJS require + guarded dynamic import (§6.3) |
61
56
  | `src/descriptor.ts` | descriptor v1 normalizer + canonical serializer |
62
57
  | `src/secrets.ts` | the sekreto-backed broker + the placeholder |
63
- | `src/profile.ts` | the node-only file half: `station.json` lookup, config scope |
64
- | `src/profilecore.ts` | the pure half: profile selection, ref grammar, the §3.3 merge |
58
+ | `src/profile.ts` | profile selection, the ref grammar, the §3.3 merge, `station.json` lookup and config scope |
65
59
  | `src/events.ts` | the bounded event ring + tap |
66
60
  | `src/error.ts` | the error-code catalog |
67
61
  | `src/types.ts` | shared types; the descriptor and event shapes are pinned by the corpus, so evolve them additively |
@@ -1,51 +1,10 @@
1
1
  import { Binding, PluginEntry, ResolvedInstance, SdkBlock, StationEvent, StationOptions } from './types';
2
- /** The FILE half of config loading, behind a seam so this module never
3
- * imports it (§2.2, §17 Phase 1: no top-level `node:` imports reachable
4
- * from the browser entry). The NODE entry (`index.ts`) registers
5
- * `profile.ts`'s loadConfig/configScope at module load; the browser
6
- * entry registers nothing, so a browser `open()` with no explicit
7
- * `config` behaves exactly as node does when no station.json exists -
8
- * null config, repo-scoped - rather than inventing a third state.
9
- * Passing `config` (the documented browser spelling, §2.2) skips the
10
- * seam entirely on both entries, which is the pre-existing
11
- * explicit-config path unchanged. */
12
- export type ConfigFileIO = {
13
- loadConfig: (from?: string) => any;
14
- configScope: (from?: string) => 'repo' | 'user' | 'none';
15
- };
16
- export declare function setConfigFileIO(io: ConfigFileIO): void;
17
- /** §6.1: `as` IS A TAG, NOT A FREE NAME.
18
- *
19
- * The api comes from the SDK being passed, so the resulting ref is
20
- * `<api>$<tag>` and multi-instance works imperatively too. A full ref is
21
- * also accepted and is VALIDATED: its name must equal the SDK's api
22
- * slug, or it is `station_instance_api`.
23
- *
24
- * Both forms are needed because the imperative path is where the api is
25
- * implicit in an argument rather than written in a key. An `as` that
26
- * took an arbitrary name would reintroduce exactly the second-identity
27
- * problem the ref re-key removed: under the ref invariant
28
- * `as: 'solar-eu'` would denote the untagged `solar-eu` DEFINITION, not
29
- * an instance of the SDK just handed in, and registry grouping, api
30
- * defaults and every ref consumer would disagree about what it is.
31
- *
32
- * A bare connect(SDK) with no name falls back to the descriptor slug,
33
- * which is today's behaviour and why the single-instance case is
34
- * unchanged to the byte. */
35
2
  export declare function instanceRef(api: string, fopts?: any): string;
36
3
  export declare function checkInstanceName(name: string): boolean;
37
4
  export declare function checkInstanceTag(tag: string): boolean;
38
5
  export declare class Station {
39
6
  private static ambient;
40
7
  private static ambientOpts;
41
- /** §6.2's second path, and the front door the docs name.
42
- *
43
- * The package exported only the free `provide()` and the class had no
44
- * static — so the documented `Station.provide(...)` call failed, and
45
- * so did the remediation `station_no_factory` prints, which tells the
46
- * user to make exactly that call. Delegates to the same
47
- * process-global table the free function fills; there is one
48
- * registry, not two. */
49
8
  static provide(api: string, factory: any): void;
50
9
  private opts;
51
10
  private profile;
@@ -71,51 +30,17 @@ export declare class Station {
71
30
  private construct;
72
31
  options(a?: any, b?: any): any;
73
32
  _boundEntry(client: any): PluginEntry | null;
74
- /** The profile block that governs an instance — its own if the
75
- * profile declares it, otherwise its **api's**.
76
- *
77
- * `resolveProfile` builds `profile.sdk` from the declared refs alone
78
- * ("an api block declares no instance, so the ref set comes from the
79
- * two `sdk` maps"), shallow-merging `profile.api[a]` into each. That
80
- * is right for a declared instance and leaves an IMPERATIVE one —
81
- * `connect(SDK, { as: 'test' })`, named but never written into
82
- * config — with no block at all. The api-level `secret`, `base` and
83
- * most seriously `policy.hosts` then did not reach it, so a profile
84
- * that denies egress everywhere denied nothing for a tagged client.
85
- *
86
- * ONE RULE, ONE PLACE: registration and the transport seam both ask
87
- * here, because they disagreeing is how the credential and the
88
- * allowlist came apart in the first place. */
89
33
  private blockFor;
90
- /** The DECLARED instance an assigned tag stands for, or the name
91
- * itself. `create('stripe$prod')` registers under `stripe$1`, and
92
- * every question about that client's configuration — its secret, its
93
- * base, its egress policy — is a question about `stripe$prod`. */
94
34
  private declaredRef;
95
35
  _register(client: any, config: any, options: any, _calleropts: any, fopts?: any): {
96
36
  binding: Binding;
97
37
  profilePlugin?: SdkBlock;
98
38
  };
99
39
  private descriptorCache;
100
- /** The descriptor describes the API, and is SHARED by every instance
101
- * of it — which is §7.4's own reasoning for why the effective secret
102
- * name moved to `Binding` and is not read off the descriptor.
103
- *
104
- * The same argument applies to feature activation, and this did not
105
- * follow it: the cache is keyed by slug alone while
106
- * `normalizeDescriptor`'s second argument decides each feature row's
107
- * `active`. So two instances of one api constructed with different
108
- * feature maps shared the FIRST one's feature state, and
109
- * `descriptorOf()` / `canonicalDescriptor()` became
110
- * construction-order-dependent.
111
- *
112
- * Normalized with no per-instance features, so the shared value holds
113
- * only API-stable metadata — which is what `factory.ts` already does
114
- * when it normalizes at provide time. Per-instance activation is
115
- * `featuresOf(name)`'s answer, and always was. */
116
40
  private describe;
117
41
  _hoist(name: string, value: string): void;
118
42
  _transport(name: string, inner: any, fctx: any, fullurl: string, fetchdef: any): Promise<any>;
43
+ private scrubbed;
119
44
  private emitHttp;
120
45
  private emitErr;
121
46
  _opEvent(name: string, ctx: any, outcome: string): void;
@@ -128,63 +53,16 @@ export declare class Station {
128
53
  secretname?: string;
129
54
  warnings: string[];
130
55
  }[];
131
- /** The instance, constructed on first call and CACHED: same name ->
132
- * same object. That caching is what makes "get it where you need it"
133
- * a real instruction - call it in a request handler, in a worker, in
134
- * a test, and the first call pays construction while the rest are a
135
- * map lookup.
136
- *
137
- * SYNCHRONOUS (§6.3), which is what bounds the loader to CommonJS. */
138
56
  sdk(name: string): any;
139
- /** An UNCACHED client from the same resolved config plus overrides,
140
- * for the case that genuinely wants a distinct one - a per-request
141
- * credential scope, a test double. Deliberately the longer name.
142
- *
143
- * It registers under an AUTO-ASSIGNED TAG, because §7.5 registers
144
- * every constructed adapter under its instance name and
145
- * `station_bound_twice` fires on a second binding of one name: a
146
- * second `create('stripe')` would otherwise throw, which is exactly
147
- * the per-request case this exists for. The tag is the lowest unused
148
- * positive integer, so an auto-tagged instance is an ORDINARY
149
- * instance rather than a parallel identity scheme - `plugins()`, the
150
- * placeholder, the event stream and `station_bound_twice` all keep
151
- * working on one identity model.
152
- *
153
- * The SECRET NAME does not follow the assigned tag: it resolves from
154
- * the DECLARED instance the tag was assigned under, so every client
155
- * of one instance shares one broker cache entry rather than
156
- * re-resolving per request (§5.3). */
157
57
  create(name: string, overrides?: any): any;
158
- /** The lowest positive integer tag not already taken, by a LIVE
159
- * instance or a DECLARED one.
160
- *
161
- * The registry alone was not enough: a profile may declare
162
- * `stripe$1`, and until something constructs it `registry.has` says
163
- * false — so `create('stripe$prod')` took that identity for a client
164
- * built from the `stripe$prod` block. `instances()` then reported the
165
- * declared `stripe$1` as live with the wrong client, and a later
166
- * `sdk('stripe$1')` failed `station_bound_twice` against a binding
167
- * that was never its own. Declaration reserves the name whether or
168
- * not it has been built. */
169
58
  private autotag;
170
59
  private build;
171
- /** §6.2's three paths, in order of preference: self-registration,
172
- * `Station.provide`, then the loader. */
173
60
  private resolveFactory;
174
- /** `package` is honoured only from repo-scoped config (§6.3), and a
175
- * user-level one is IGNORED WITH A WARNING rather than imported - it
176
- * names code to load and sits outside the repo's review boundary. */
177
61
  private loaderPackage;
178
62
  /** ts/js only: preload ESM packages into the factory table, after
179
63
  * which `sdk()` is synchronous again for everything (§6.3). One
180
64
  * `await` at startup rather than one per call site. */
181
65
  load(): Promise<void>;
182
- /** The merged, ordered feature set for one instance, WITH
183
- * PROVENANCE (§8.7): which config level set each value.
184
- *
185
- * Provenance is the half that makes a fleet view usable rather than
186
- * merely correct - at 26 instances "why is retry off here" is the
187
- * question, and a merged map alone cannot answer it. */
188
66
  featuresOf(name: string): {
189
67
  ordered: string[];
190
68
  merged: {
@@ -203,10 +81,6 @@ export declare class Station {
203
81
  api?: string;
204
82
  feature?: string;
205
83
  }): any[];
206
- /** Eagerly resolve and construct every ACTIVE instance - for CI
207
- * (§6.6). The point is to turn availability errors, which are
208
- * deliberately deferred to first use, into one failure at a moment
209
- * somebody is watching. */
210
84
  check(): {
211
85
  ok: string[];
212
86
  failed: {
@@ -215,12 +89,6 @@ export declare class Station {
215
89
  message: string;
216
90
  }[];
217
91
  };
218
- /** Batch-resolve secrets for ACTIVE instances (§5.5).
219
- *
220
- * With no argument it warms the active ones only, because reaching
221
- * for a credential belonging to a disabled integration is the wrong
222
- * default. `warm(names)` warms exactly what it is given, inactive
223
- * included, because an explicit name is an explicit request. */
224
92
  warm(names?: string[]): Promise<{
225
93
  warmed: string[];
226
94
  missed: string[];