@voxgig/station 0.0.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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/dist/src/Station.d.ts +238 -0
  4. package/dist/src/Station.js +1213 -0
  5. package/dist/src/Station.js.map +1 -0
  6. package/dist/src/adapter.d.ts +9 -0
  7. package/dist/src/adapter.js +171 -0
  8. package/dist/src/adapter.js.map +1 -0
  9. package/dist/src/config-shape.d.ts +1 -0
  10. package/dist/src/config-shape.js +329 -0
  11. package/dist/src/config-shape.js.map +1 -0
  12. package/dist/src/descriptor.d.ts +8 -0
  13. package/dist/src/descriptor.js +170 -0
  14. package/dist/src/descriptor.js.map +1 -0
  15. package/dist/src/error.d.ts +8 -0
  16. package/dist/src/error.js +60 -0
  17. package/dist/src/error.js.map +1 -0
  18. package/dist/src/events.d.ts +15 -0
  19. package/dist/src/events.js +46 -0
  20. package/dist/src/events.js.map +1 -0
  21. package/dist/src/factory.d.ts +24 -0
  22. package/dist/src/factory.js +79 -0
  23. package/dist/src/factory.js.map +1 -0
  24. package/dist/src/feature.d.ts +106 -0
  25. package/dist/src/feature.js +324 -0
  26. package/dist/src/feature.js.map +1 -0
  27. package/dist/src/index.browser.d.ts +14 -0
  28. package/dist/src/index.browser.js +64 -0
  29. package/dist/src/index.browser.js.map +1 -0
  30. package/dist/src/index.d.ts +14 -0
  31. package/dist/src/index.js +56 -0
  32. package/dist/src/index.js.map +1 -0
  33. package/dist/src/loader.d.ts +45 -0
  34. package/dist/src/loader.js +181 -0
  35. package/dist/src/loader.js.map +1 -0
  36. package/dist/src/profile.d.ts +6 -0
  37. package/dist/src/profile.js +77 -0
  38. package/dist/src/profile.js.map +1 -0
  39. package/dist/src/profilecore.d.ts +10 -0
  40. package/dist/src/profilecore.js +180 -0
  41. package/dist/src/profilecore.js.map +1 -0
  42. package/dist/src/secrets.d.ts +12 -0
  43. package/dist/src/secrets.js +93 -0
  44. package/dist/src/secrets.js.map +1 -0
  45. package/dist/src/shape.d.ts +42 -0
  46. package/dist/src/shape.js +444 -0
  47. package/dist/src/shape.js.map +1 -0
  48. package/dist/src/types.d.ts +138 -0
  49. package/dist/src/types.js +5 -0
  50. package/dist/src/types.js.map +1 -0
  51. package/package.json +60 -0
  52. package/src/Station.ts +1310 -0
  53. package/src/adapter.ts +183 -0
  54. package/src/config-shape.ts +326 -0
  55. package/src/descriptor.ts +179 -0
  56. package/src/error.ts +64 -0
  57. package/src/events.ts +45 -0
  58. package/src/factory.ts +93 -0
  59. package/src/feature.ts +321 -0
  60. package/src/index.browser.ts +56 -0
  61. package/src/index.ts +50 -0
  62. package/src/loader.ts +188 -0
  63. package/src/profile.ts +67 -0
  64. package/src/profilecore.ts +201 -0
  65. package/src/secrets.ts +96 -0
  66. package/src/shape.ts +435 -0
  67. package/src/types.ts +185 -0
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ /* The loader (design §6.3), where the language allows it.
3
+ *
4
+ * In ts, js, py, rb, php, perl, lua, elixir and clojure a module can be
5
+ * imported by name at runtime, so `api.<slug>.package` closes the loop:
6
+ * station imports the package (which triggers self-registration, §6.2
7
+ * path 1) and then looks up the factory.
8
+ *
9
+ * `sdk()` IS SYNCHRONOUS, AND IN TS/JS THAT BOUNDS WHAT THE LOADER CAN
10
+ * IMPORT. A CommonJS package loads synchronously through `require`; an
11
+ * ESM-only package only loads through `import()`, which is a promise.
12
+ * Making `sdk()` async to cover that would put an `await` in front of
13
+ * every call site in every language for a cost only two of them pay.
14
+ * So the rule is explicit rather than discovered at runtime:
15
+ *
16
+ * - the synchronous loader handles CommonJS, which is every SDK sdkgen
17
+ * generates (the ts target is `"type": "commonjs"`);
18
+ * - ESM packages load through an explicit `await station.load()`
19
+ * preload, after which `sdk()` is synchronous again for everything —
20
+ * one `await` at startup rather than one per call site;
21
+ * - a package only `import()` can load, reached through `sdk()`
22
+ * without a preload, is `station_sdk_load` WITH THE REMEDY IN THE
23
+ * MESSAGE. Never a silent half-loaded client.
24
+ *
25
+ * THIS IS A CODE-LOADING SURFACE DRIVEN BY A CONFIG FILE, so it has
26
+ * rules, and they are enforced here rather than documented and hoped
27
+ * for. See `checkPackage` and the `repoScoped` argument. */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.nativeImport = exports.DEFAULT_EXPORT = void 0;
30
+ exports.camelify = camelify;
31
+ exports.checkPackage = checkPackage;
32
+ exports.factoryFromModule = factoryFromModule;
33
+ exports.loadSync = loadSync;
34
+ exports.loadAsync = loadAsync;
35
+ const error_1 = require("./error");
36
+ const factory_1 = require("./factory");
37
+ /** The fixed alias every generated package exports.
38
+ *
39
+ * `export` defaults to this rather than to a derived class name because
40
+ * it is the same identifier in every generated package, where
41
+ * `camelify(slug) + 'SDK'` is a rule that has to be recomputed and can
42
+ * be wrong. The derived name is the SECOND attempt and an explicit
43
+ * `export` the third. */
44
+ exports.DEFAULT_EXPORT = 'SDK';
45
+ /** `stripe-eu` -> `StripeEu`, for the second-attempt export name. */
46
+ function camelify(slug) {
47
+ return String(slug).split(/[^A-Za-z0-9]+/).filter((s) => '' !== s)
48
+ .map((s) => s[0].toUpperCase() + s.slice(1))
49
+ .join('');
50
+ }
51
+ /** Only MODULE NAMES, resolved by the host language's ordinary
52
+ * resolution from the application root. Never a filesystem path, never
53
+ * a URL, never anything relative — a config file naming a path is a
54
+ * config file reaching outside the dependency graph it is allowed to
55
+ * name. */
56
+ function checkPackage(api, pkg) {
57
+ const p = String(pkg);
58
+ // A TRAVERSAL SEGMENT IS NOT A LEADING MARKER, and checking only the
59
+ // first character missed it: `pkg/../../escape` starts with neither
60
+ // `.` nor `/`, so it passed — and node resolves it through
61
+ // `node_modules/pkg/../../escape`, importing application-local code
62
+ // from outside the named dependency. The whole point of this function
63
+ // is that a configured package stays inside the dependency graph a
64
+ // reviewer can see.
65
+ const seg = p.split('/').some((x) => '.' === x || '..' === x);
66
+ const bad = '' === p ||
67
+ p.startsWith('.') ||
68
+ p.startsWith('/') ||
69
+ p.startsWith('~') ||
70
+ seg ||
71
+ -1 !== p.indexOf('://') ||
72
+ -1 !== p.indexOf('\\');
73
+ if (bad) {
74
+ throw new error_1.StationError('station_sdk_load', 'api "' + api + '": `package` must be a module name resolved from ' +
75
+ 'the application root, not a path or URL: ' + JSON.stringify(pkg));
76
+ }
77
+ return p;
78
+ }
79
+ /** Build a `{construct, config}` pair from a module that self-registered
80
+ * nothing — the retrofit path for a package whose SDK predates the
81
+ * station feature. It is not descriptor-blind: a generated main module
82
+ * exports its constructor AND the `config` singleton beside it. */
83
+ function factoryFromModule(api, mod, exportName) {
84
+ const tried = [];
85
+ const pick = (n) => {
86
+ tried.push(n);
87
+ return null == mod ? undefined : mod[n];
88
+ };
89
+ let ctor = null == exportName || '' === exportName
90
+ ? undefined : pick(exportName);
91
+ if (null == ctor) {
92
+ ctor = pick(exports.DEFAULT_EXPORT);
93
+ }
94
+ if (null == ctor) {
95
+ ctor = pick(camelify(api) + 'SDK');
96
+ }
97
+ if ('function' !== typeof ctor) {
98
+ throw new error_1.StationError('station_sdk_load', 'api "' + api + '": no SDK constructor found on the module; tried [' +
99
+ tried.join(', ') + ']. Set `export` to the exported name.');
100
+ }
101
+ const config = mod.config ?? mod.CONFIG;
102
+ if (null == config) {
103
+ throw new error_1.StationError('station_sdk_load', 'api "' + api + '": the module exports a constructor but no `config` ' +
104
+ 'singleton, so its feature schema and transport roles cannot be read ' +
105
+ 'before construction (§6.2)');
106
+ }
107
+ const construct = (options) => new ctor(options);
108
+ return { construct, config };
109
+ }
110
+ /** Synchronous load, for CommonJS. Returns true when the api has a
111
+ * factory afterwards — either because importing the package triggered
112
+ * self-registration, or because one was built from its exports. */
113
+ function loadSync(api, pkg, exportName) {
114
+ checkPackage(api, pkg);
115
+ if (null != (0, factory_1.factoryFor)(api)) {
116
+ return true;
117
+ }
118
+ let mod;
119
+ try {
120
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
121
+ mod = require(pkg);
122
+ }
123
+ catch (e) {
124
+ // ERR_REQUIRE_ESM is the case the preload exists for, and the
125
+ // message has to name the remedy rather than the symptom.
126
+ const code = String(e?.code || '');
127
+ if ('ERR_REQUIRE_ESM' === code) {
128
+ throw new error_1.StationError('station_sdk_load', 'api "' + api + '": package "' + pkg + '" is ESM-only and cannot be ' +
129
+ 'loaded synchronously; `await station.load()` at startup, then ' +
130
+ 'sdk() is synchronous again for everything (§6.3)');
131
+ }
132
+ throw new error_1.StationError('station_sdk_load', 'api "' + api + '": package "' + pkg + '" could not be imported: ' +
133
+ String(e?.message || e));
134
+ }
135
+ // Path 1: the module self-registered while being imported.
136
+ if (null != (0, factory_1.factoryFor)(api)) {
137
+ return true;
138
+ }
139
+ (0, factory_1.provide)(api, factoryFromModule(api, mod, exportName));
140
+ return true;
141
+ }
142
+ /** The ESM preload behind `await station.load()`. Imports the package
143
+ * and fills the SAME table the synchronous path fills. */
144
+ /** A dynamic `import()` that SURVIVES THE COMMONJS EMIT.
145
+ *
146
+ * This package compiles with `module: "commonjs"`, and TypeScript
147
+ * rewrites a literal `import(pkg)` into a promise around `require(pkg)`
148
+ * — so `await station.load()` threw `ERR_REQUIRE_ESM` for exactly the
149
+ * ESM-only packages it exists to preload, leaving no working path to
150
+ * those SDKs at all. Verified against the emitted `dist/src/loader.js`,
151
+ * which read `mod = require(pkg)`.
152
+ *
153
+ * `new Function` is the seam the compiler does not look inside. It is
154
+ * deliberately the only one in this file, and it takes no caller input:
155
+ * `pkg` has already been through `checkPackage`, and the body is a
156
+ * constant. Changing the package to `module: "node16"` would fix it
157
+ * more cleanly and is a whole-package decision rather than a defect
158
+ * fix. */
159
+ exports.nativeImport = new Function('p', 'return import(p)');
160
+ async function loadAsync(api, pkg, exportName) {
161
+ checkPackage(api, pkg);
162
+ if (null != (0, factory_1.factoryFor)(api)) {
163
+ return true;
164
+ }
165
+ let mod;
166
+ try {
167
+ mod = await (0, exports.nativeImport)(pkg);
168
+ }
169
+ catch (e) {
170
+ throw new error_1.StationError('station_sdk_load', 'api "' + api + '": package "' + pkg + '" could not be imported: ' +
171
+ String(e?.message || e));
172
+ }
173
+ if (null != (0, factory_1.factoryFor)(api)) {
174
+ return true;
175
+ }
176
+ // An ESM namespace puts a CommonJS module's exports under `default`.
177
+ const flat = null != mod?.default && null == mod?.config ? mod.default : mod;
178
+ (0, factory_1.provide)(api, factoryFromModule(api, flat, exportName));
179
+ return true;
180
+ }
181
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { StationConfig } from './types';
2
+ export { refapi, resolveProfile, selectProfile, } from './profilecore';
3
+ export type { ResolvedProfile } from './profilecore';
4
+ export declare function findConfigFile(from?: string): string | null;
5
+ export declare function loadConfig(from?: string): StationConfig | null;
6
+ export declare function configScope(from?: string): 'repo' | 'user' | 'none';
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /* The FILE half of profile handling - finding and reading
3
+ * station.json - and the scope rule that hangs off where it was found.
4
+ * NODE-ONLY by construction (fs/os/path), which is the §2.2 / §17
5
+ * Phase 1 split: the browser entry never imports this module, and
6
+ * `Station` reaches it through the ConfigFileIO seam the node entry
7
+ * registers. The pure half (selectProfile, refapi, resolveProfile)
8
+ * lives in `profilecore.ts` and is re-exported here so every existing
9
+ * import of `./profile` keeps working. */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.selectProfile = exports.resolveProfile = exports.refapi = void 0;
15
+ exports.findConfigFile = findConfigFile;
16
+ exports.loadConfig = loadConfig;
17
+ exports.configScope = configScope;
18
+ const node_fs_1 = __importDefault(require("node:fs"));
19
+ const node_os_1 = __importDefault(require("node:os"));
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const error_1 = require("./error");
22
+ var profilecore_1 = require("./profilecore");
23
+ Object.defineProperty(exports, "refapi", { enumerable: true, get: function () { return profilecore_1.refapi; } });
24
+ Object.defineProperty(exports, "resolveProfile", { enumerable: true, get: function () { return profilecore_1.resolveProfile; } });
25
+ Object.defineProperty(exports, "selectProfile", { enumerable: true, get: function () { return profilecore_1.selectProfile; } });
26
+ // station.json lookup: cwd upward to the repo root, then
27
+ // ~/.voxgig/station.json (design §3.5). A repo root is where .git lives;
28
+ // with no repo the walk stops at the filesystem root.
29
+ function findConfigFile(from) {
30
+ let dir = node_path_1.default.resolve(from || process.cwd());
31
+ for (;;) {
32
+ const candidate = node_path_1.default.join(dir, 'station.json');
33
+ if (node_fs_1.default.existsSync(candidate)) {
34
+ return candidate;
35
+ }
36
+ const atRepoRoot = node_fs_1.default.existsSync(node_path_1.default.join(dir, '.git'));
37
+ const parent = node_path_1.default.dirname(dir);
38
+ if (atRepoRoot || parent === dir) {
39
+ break;
40
+ }
41
+ dir = parent;
42
+ }
43
+ const home = node_path_1.default.join(node_os_1.default.homedir(), '.voxgig', 'station.json');
44
+ return node_fs_1.default.existsSync(home) ? home : null;
45
+ }
46
+ function loadConfig(from) {
47
+ const file = findConfigFile(from);
48
+ if (null == file) {
49
+ return null;
50
+ }
51
+ const text = node_fs_1.default.readFileSync(file, 'utf8');
52
+ // A file that is not JSON is a config error, not a raw SyntaxError
53
+ // escaping open(): the reader found station.json and could not use
54
+ // it, which is exactly what station_config_invalid exists to say.
55
+ try {
56
+ return JSON.parse(text);
57
+ }
58
+ catch (err) {
59
+ throw new error_1.StationError('station_config_invalid', 'station.json at ' + file + ' is not valid JSON: ' + err.message);
60
+ }
61
+ }
62
+ // Which side of the review boundary the config came from (§6.3).
63
+ //
64
+ // `package` and `export` are honoured only from REPO-SCOPED config,
65
+ // because a user-level file is outside the repo's review boundary and a
66
+ // `package` key arriving from it names code to import. Everything else
67
+ // in a user-level config still applies - this narrows one key rather
68
+ // than distrusting the file.
69
+ function configScope(from) {
70
+ const file = findConfigFile(from);
71
+ if (null == file) {
72
+ return 'none';
73
+ }
74
+ const home = node_path_1.default.join(node_os_1.default.homedir(), '.voxgig', 'station.json');
75
+ return file === home ? 'user' : 'repo';
76
+ }
77
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/profile.ts"],"names":[],"mappings":";AAAA;;;;;;;0CAO0C;;;;;;AAiB1C,wCAYC;AAED,gCAcC;AASD,kCAKC;AAzDD,sDAAwB;AACxB,sDAAwB;AACxB,0DAA4B;AAE5B,mCAAsC;AAGtC,6CAEsB;AADpB,qGAAA,MAAM,OAAA;AAAE,6GAAA,cAAc,OAAA;AAAE,4GAAA,aAAa,OAAA;AAIvC,yDAAyD;AACzD,yEAAyE;AACzE,sDAAsD;AACtD,SAAgB,cAAc,CAAC,IAAa;IAC1C,IAAI,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC7C,SAAU,CAAC;QACT,MAAM,SAAS,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;QAChD,IAAI,iBAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAAC,OAAO,SAAS,CAAA;QAAC,CAAC;QAClD,MAAM,UAAU,GAAG,iBAAE,CAAC,UAAU,CAAC,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;QACxD,MAAM,MAAM,GAAG,mBAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,UAAU,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YAAC,MAAK;QAAC,CAAC;QAC3C,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;IACD,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;IAC/D,OAAO,iBAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED,SAAgB,UAAU,CAAC,IAAa;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QAAC,OAAO,IAAI,CAAA;IAAC,CAAC;IACjC,MAAM,IAAI,GAAG,iBAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC1C,mEAAmE;IACnE,mEAAmE;IACnE,kEAAkE;IAClE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,OAAO,GAAQ,EAAE,CAAC;QAChB,MAAM,IAAI,oBAAY,CAAC,wBAAwB,EAC7C,kBAAkB,GAAG,IAAI,GAAG,sBAAsB,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;IACrE,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,EAAE;AACF,oEAAoE;AACpE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,6BAA6B;AAC7B,SAAgB,WAAW,CAAC,IAAa;IACvC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QAAC,OAAO,MAAM,CAAA;IAAC,CAAC;IACnC,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;IAC/D,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;AACxC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { StationConfig, SdkBlock } from './types';
2
+ export declare function selectProfile(optProfile?: string): string;
3
+ export declare function refapi(ref: string): string;
4
+ export type ResolvedProfile = {
5
+ name: string;
6
+ providers: any[];
7
+ api: Record<string, SdkBlock>;
8
+ sdk: Record<string, SdkBlock>;
9
+ };
10
+ export declare function resolveProfile(config: StationConfig | null, profileName: string): ResolvedProfile;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ /* The pure half of profile handling: selection, the ref grammar, and
3
+ * the §3.3 merge. Split from `profile.ts` so the BROWSER ENTRY can
4
+ * reach it (§2.2, §17 Phase 1): everything here is data-in/data-out,
5
+ * while the file half (findConfigFile/loadConfig/configScope) is
6
+ * node-only and stays in `profile.ts`, imported by the node entry
7
+ * alone. `profile.ts` re-exports these, so every existing import path
8
+ * is unchanged. */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.selectProfile = selectProfile;
11
+ exports.refapi = refapi;
12
+ exports.resolveProfile = resolveProfile;
13
+ const sekreto_1 = require("@voxgig/sekreto");
14
+ const descriptor_1 = require("./descriptor");
15
+ const error_1 = require("./error");
16
+ const shape_1 = require("./shape");
17
+ // Profile selection: VOXGIG_STATION_PROFILE, else the open() option,
18
+ // else 'default' (design §3.5 - env vars rank above station.json but
19
+ // below open() opts; profile NAME selection follows the same order with
20
+ // open() opts winning).
21
+ //
22
+ // `process` is reached through a typeof guard rather than an import:
23
+ // in the browser there is no process and no environment, so the answer
24
+ // is simply the option or the default - which is the same degradation
25
+ // "no env var set" already means.
26
+ function selectProfile(optProfile) {
27
+ if (null != optProfile && '' !== optProfile) {
28
+ return optProfile;
29
+ }
30
+ const env = 'undefined' !== typeof process
31
+ ? process.env.VOXGIG_STATION_PROFILE : undefined;
32
+ if (null != env && '' !== env) {
33
+ return env;
34
+ }
35
+ return 'default';
36
+ }
37
+ // The api half of a ref is the substring before the first `$`, and an
38
+ // untagged ref IS an api slug (design §3.4) - which is why the whole
39
+ // `plugin` -> `sdk` migration is a key rename and nothing else.
40
+ //
41
+ // LEXICAL, and that is the point: under the old free-form identity
42
+ // which api an instance used was itself a merged value, so the merge
43
+ // could not be a flat pass over both maps and a port that got the
44
+ // phasing wrong silently picked another api's defaults (§3.3).
45
+ function refapi(ref) {
46
+ const at = String(ref).indexOf('$');
47
+ return -1 === at ? String(ref) : String(ref).slice(0, at);
48
+ }
49
+ // Shallow merge, per key, left to right - each source over the one
50
+ // before it. Declared rather than assumed: plugin's library default is
51
+ // a deep map merge, and an allowlist that widens because two precedence
52
+ // levels merged is the failure this rule exists to prevent
53
+ // (station-and-plugin.md §2.5). The visible consequence: an overlay's
54
+ // `policy` REPLACES the base's entirely rather than merging `hosts`
55
+ // into it. That is also the safer reading for an allowlist.
56
+ function shallow(...sources) {
57
+ const out = {};
58
+ for (const src of sources) {
59
+ if (null == src || 'object' !== typeof src || Array.isArray(src)) {
60
+ continue;
61
+ }
62
+ for (const k of Object.keys(src)) {
63
+ out[k] = src[k];
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ // Merge the base profile ('default') with the selected overlay.
69
+ //
70
+ // §3.3's total order, for the two block levels - lowest precedence
71
+ // first:
72
+ //
73
+ // base.api[<api>] ⊕ base.sdk[<ref>] ⊕ overlay.api[<api>] ⊕ overlay.sdk[<ref>]
74
+ //
75
+ // PROFILE SPECIFICITY OUTRANKS BLOCK SPECIFICITY. A `prod` api-level
76
+ // `base` beats a `default` instance-level `base`, because that is what
77
+ // an environment overlay is for; within one profile the instance beats
78
+ // the api default, because that is what an instance is for.
79
+ //
80
+ // THIS IS ONE FLAT LEFT-TO-RIGHT MERGE AND MUST NOT BE REORGANIZED INTO
81
+ // "collapse each namespace, then put instance over api". Collapsing
82
+ // first computes (base.api ⊕ overlay.api) and (base.sdk ⊕ overlay.sdk)
83
+ // and then lets EVERY instance value beat EVERY api value - the exact
84
+ // opposite. A production `api.stripe.policy` would then fail to
85
+ // override a default profile's `sdk.stripe$test.policy`, silently
86
+ // keeping the wider allowlist in production.
87
+ //
88
+ // `secrets.providers` replaces wholesale, never merges (§3.5, §5.2):
89
+ // chain order decides which store wins, so a positional merge would be
90
+ // actively dangerous.
91
+ function resolveProfile(config, profileName) {
92
+ const profiles = config?.profiles || {};
93
+ const base = profiles['default'] || {};
94
+ const overlay = 'default' === profileName
95
+ ? {} : (profiles[profileName] || {});
96
+ const providers = overlay.secrets?.providers ?? base.secrets?.providers ??
97
+ [{ kind: 'env' }];
98
+ const api = {};
99
+ for (const slug of sortedkeys(base.api, overlay.api)) {
100
+ api[slug] = shallow(base.api?.[slug], overlay.api?.[slug]);
101
+ }
102
+ // An api block declares no instance, so the ref set comes from the
103
+ // two `sdk` maps alone.
104
+ const sdk = {};
105
+ for (const ref of sortedkeys(base.sdk, overlay.sdk)) {
106
+ const a = refapi(ref);
107
+ const merged = shallow(base.api?.[a], base.sdk?.[ref], overlay.api?.[a], overlay.sdk?.[ref]);
108
+ // Defaults are applied ONCE, to the fully merged instance, and this
109
+ // is the rule §4.2's normalization must not be confused with. Had
110
+ // the overlay block carried a synthesized `active: true` into the
111
+ // merge, a one-key environment override would silently re-enable an
112
+ // integration the base declared `active: false` - live in
113
+ // production despite being declared inactive. The `instance` corpus
114
+ // section carries exactly this case.
115
+ for (const k of Object.keys(shape_1.BLOCK_DEFAULTS)) {
116
+ if (undefined === merged[k]) {
117
+ merged[k] = shape_1.BLOCK_DEFAULTS[k]();
118
+ }
119
+ }
120
+ sdk[ref] = merged;
121
+ }
122
+ checksecrets(sdk, profileName);
123
+ return { name: profileName, providers, api, sdk };
124
+ }
125
+ // A configured secret name sekreto would reject is caught at profile
126
+ // load, not first request (§14 station_secret_name) - and then the
127
+ // derived names are checked for uniqueness, because envtoken is LOSSY.
128
+ //
129
+ // It collapses any run of non-alphanumerics to `_`, so `$` and `-` are
130
+ // indistinguishable downstream: the ref `stripe$test` and an untagged
131
+ // instance of an api slugged `stripe-test` both derive
132
+ // `stripe_test.apikey`. That pairing is the realistic one rather than a
133
+ // contrived one, because api slugs are hyphenated by construction - so
134
+ // a fleet with both a `stripe-test` api and a `test` instance of
135
+ // `stripe` collides by default. Since the resolution cache is keyed by
136
+ // secret name (§5.3), the second instance would silently receive the
137
+ // first's value.
138
+ //
139
+ // Two instances that EXPLICITLY name the same secret are not a
140
+ // collision - that is the shared-key case the api-level `secret` exists
141
+ // for, and saying so out loud is how you ask for it.
142
+ function checksecrets(sdk, profileName) {
143
+ const refs = Object.keys(sdk).sort();
144
+ for (const ref of refs) {
145
+ const name = sdk[ref].secret;
146
+ if (null != name && !(0, sekreto_1.validname)(name)) {
147
+ throw new error_1.StationError('station_secret_name', 'profile "' + profileName + '" sdk "' + ref +
148
+ '": secret name rejected by sekreto: ' + JSON.stringify(name));
149
+ }
150
+ }
151
+ const seen = new Map();
152
+ for (const ref of refs) {
153
+ const written = sdk[ref].secret;
154
+ const derived = null == written || '' === written;
155
+ const name = derived ? (0, descriptor_1.secretnameDefault)(ref) : written;
156
+ const prior = seen.get(name);
157
+ if (undefined !== prior && (derived || prior.derived)) {
158
+ throw new error_1.StationError('station_secret_collision', 'profile "' + profileName + '": instances "' + prior.ref + '" and "' +
159
+ ref + '" both resolve to secret name "' + name +
160
+ '", so they would share one credential; name it explicitly on ' +
161
+ 'each, or at the api level to share it deliberately (§5.1)');
162
+ }
163
+ if (undefined === prior) {
164
+ seen.set(name, { ref, derived });
165
+ }
166
+ }
167
+ }
168
+ function sortedkeys(...maps) {
169
+ const keys = new Set();
170
+ for (const m of maps) {
171
+ if (null == m || 'object' !== typeof m || Array.isArray(m)) {
172
+ continue;
173
+ }
174
+ for (const k of Object.keys(m)) {
175
+ keys.add(k);
176
+ }
177
+ }
178
+ return Array.from(keys).sort();
179
+ }
180
+ //# sourceMappingURL=profilecore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profilecore.js","sourceRoot":"","sources":["../../src/profilecore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare function placeholderFor(slug: string): string;
2
+ export declare class SecretBroker {
3
+ private sekreto;
4
+ private overrides;
5
+ private cache;
6
+ private held;
7
+ constructor(providers: any[]);
8
+ hoist(slug: string, value: string): void;
9
+ value(slug: string, name: string): Promise<string>;
10
+ scrub(text: string): string;
11
+ refresh(): void;
12
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SecretBroker = void 0;
4
+ exports.placeholderFor = placeholderFor;
5
+ const sekreto_1 = require("@voxgig/sekreto");
6
+ const error_1 = require("./error");
7
+ // The secret broker (design §5): sekreto resolves, station places. The
8
+ // broker holds resolved values privately - they never enter options,
9
+ // events, or captures; the SDK sees only the placeholder.
10
+ // §7.2: keyed by INSTANCE. Two live instances of one api must have
11
+ // distinct placeholders or the injection seam cannot tell which
12
+ // credential a header wants. For an untagged instance this is the api
13
+ // slug, so the single-instance case is unchanged.
14
+ function placeholderFor(slug) {
15
+ return '[station:' + slug + ']';
16
+ }
17
+ class SecretBroker {
18
+ constructor(providers) {
19
+ // Values hoisted by adopt() from resident options.apikey (design §3.1).
20
+ this.overrides = new Map();
21
+ this.cache = new Map();
22
+ // Every value this broker ever held, for the exact-value scrub.
23
+ this.held = [];
24
+ this.sekreto = new sekreto_1.Sekreto({ providers });
25
+ }
26
+ hoist(slug, value) {
27
+ this.overrides.set(slug, value);
28
+ this.held.push(value);
29
+ }
30
+ // Resolve the value for a plugin's secret name. Misses and store
31
+ // errors keep sekreto's distinction (design §5.2): a miss is
32
+ // station_secret_no_value, a store that could not answer is
33
+ // station_secret_error with sekreto's message intact - and never a
34
+ // retry against a weaker store (sekreto owns the chain).
35
+ // OVERRIDES ARE KEYED BY INSTANCE; THE RESOLUTION CACHE IS KEYED BY
36
+ // SECRET NAME (§5.3). A hoisted credential belongs to the one instance
37
+ // it was resident in, but a resolved VALUE belongs to the name it was
38
+ // resolved for - so several instances sharing one api-level `secret`
39
+ // cost one lookup rather than one each, and every client an
40
+ // auto-tagged `create()` produces shares the declared instance's entry
41
+ // instead of re-resolving per request.
42
+ //
43
+ // Keying the cache by instance instead is the defect this replaces: at
44
+ // 26 instances over 20 apis it turns one store round-trip into 26.
45
+ async value(slug, name) {
46
+ const override = this.overrides.get(slug);
47
+ if (null != override) {
48
+ return override;
49
+ }
50
+ const cached = this.cache.get(name);
51
+ if (null != cached) {
52
+ return cached;
53
+ }
54
+ let value;
55
+ try {
56
+ value = await this.sekreto.get(name);
57
+ }
58
+ catch (e) {
59
+ if (e instanceof sekreto_1.SekretoError && e.message.includes('unknown secret')) {
60
+ throw new error_1.StationError('station_secret_no_value', 'no store had "' + name + '" for plugin "' + slug + '"');
61
+ }
62
+ throw new error_1.StationError('station_secret_error', String(e?.message || e));
63
+ }
64
+ this.cache.set(name, value);
65
+ this.held.push(value);
66
+ return value;
67
+ }
68
+ // Exact-value scrub, deliberately WITHOUT sekreto's four-character
69
+ // readability floor (design §7 as revised): on boundaries where the
70
+ // promise is absolute, every held value is scrubbed whatever its
71
+ // length. sekreto's own redact() runs too, covering values resolved
72
+ // by the underlying instance that station never held.
73
+ scrub(text) {
74
+ let out = this.sekreto.redact(text);
75
+ for (const value of this.held) {
76
+ if ('' !== value) {
77
+ out = out.split(value).join('[redacted]');
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ // Drop caches so the next resolve asks the stores again (rotation
83
+ // support rides on sekreto's refresh, design §5.3).
84
+ refresh() {
85
+ this.cache.clear();
86
+ if ('function' === typeof this.sekreto.refresh) {
87
+ ;
88
+ this.sekreto.refresh();
89
+ }
90
+ }
91
+ }
92
+ exports.SecretBroker = SecretBroker;
93
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/secrets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /** Profile-level containers. Safe to materialize early either way:
2
+ * they are containers, and a missing one merges as empty regardless. */
3
+ export declare const PROFILE_DEFAULTS: {
4
+ [k: string]: () => any;
5
+ };
6
+ /** Block-level. `feature` is a container and safe early.
7
+ *
8
+ * `active` IS NOT, and that is the whole timing rule: a default
9
+ * synthesized into an OVERLAY block overwrites the base's real value
10
+ * and silently reactivates an integration the base deliberately barred
11
+ * (§3.3). So the two consumers read this same table at different
12
+ * moments - validateConfig before, to every block, because a block with
13
+ * no present keys is an open map; the resolver AFTER, to the merged
14
+ * instance, because an absent key must stay absent through the merge. */
15
+ export declare const BLOCK_DEFAULTS: {
16
+ [k: string]: () => any;
17
+ };
18
+ /** The one block key carrying the timing rule. Named rather than
19
+ * inferred, so a reader does not have to work out which of the two it
20
+ * is, and so a port can assert it. */
21
+ export declare const MERGE_SENSITIVE: string[];
22
+ /** Materialize every documented default, DEFENSIVELY: a node that is
23
+ * not the kind it expects is left alone for validate to reject with a
24
+ * proper message. Pure data-in/data-out, which is what makes it
25
+ * portable to 22 languages and expressible in the corpus.
26
+ *
27
+ * THE NORMALIZED FORM IS AN INPUT TO VALIDATION AND TO NOTHING ELSE. */
28
+ export declare function normalizeConfig(raw: any): any;
29
+ /** `spec/config-shape.json`, §4.3 verbatim, through the shipped mirror.
30
+ * Every validate gets a CLONE: struct's validate consumes the spec it
31
+ * walks (it deletes satisfied `$ONE` branches as it goes), so handing it
32
+ * the module constant twice would validate the second config against a
33
+ * spec the first had already eaten. */
34
+ export declare function configShape(): any;
35
+ /** Normalize, then validate (§4.2). Raises `station_config_invalid`
36
+ * with EVERY struct error at once - an eighteen-instance config that
37
+ * touches three of them must not die because the eighteenth has a
38
+ * typo'd package name - then the §5.2 scans.
39
+ *
40
+ * Takes the NORMALIZED form. Handing it a raw config is the mistake
41
+ * §4.2 exists to prevent, so callers go through open()/loadConfig. */
42
+ export declare function validateConfig(normalized: any): any;