@lambdot/env 0.1.0 → 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.
- package/CHANGELOG.md +15 -0
- package/README.md +64 -0
- package/package.json +18 -13
- package/src/index.ts +13 -29
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`71e5732`](https://github.com/Embers-of-the-Fire/lambdot/commit/71e57321ad4ec7d1aef3651d104123f8167ec2e7), [`71e5732`](https://github.com/Embers-of-the-Fire/lambdot/commit/71e57321ad4ec7d1aef3651d104123f8167ec2e7)]:
|
|
8
|
+
- @lambdot/core@0.2.0
|
|
9
|
+
|
|
10
|
+
## 0.1.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [#10](https://github.com/Embers-of-the-Fire/lambdot/pull/10) [`19d37e4`](https://github.com/Embers-of-the-Fire/lambdot/commit/19d37e42c7a5514fb62c8f31c65e1aa01916d355) Thanks [@Embers-of-the-Fire](https://github.com/Embers-of-the-Fire)! - Switch inter-package dependency pins from exact versions to `workspace:*` so workspace members always resolve against local sources during development; pnpm rewrites the protocol to exact versions at pack/publish time.
|
|
15
|
+
- Updated dependencies [[`19d37e4`](https://github.com/Embers-of-the-Fire/lambdot/commit/19d37e42c7a5514fb62c8f31c65e1aa01916d355)]:
|
|
16
|
+
- @lambdot/core@0.1.1
|
|
17
|
+
|
|
3
18
|
## [0.1.0](https://github.com/Embers-of-the-Fire/lambdot/compare/env-v0.0.1...env-v0.1.0) (2026-08-29)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @lambdot/env
|
|
2
|
+
|
|
3
|
+
Reads variables from `process.env` into a typed namespace, so deployments
|
|
4
|
+
pass configuration through the environment and plugins consume it through
|
|
5
|
+
the composition's visible context — with the provider-before-consumer
|
|
6
|
+
ordering checked at compile time.
|
|
7
|
+
|
|
8
|
+
## What it provides
|
|
9
|
+
|
|
10
|
+
- **One plugin factory.** `envVars(name, keys)` reads each key from
|
|
11
|
+
`process.env` at activation and emits the snapshot —
|
|
12
|
+
`Readonly<Record<TKey, string>>` — under `name`. A missing or empty
|
|
13
|
+
variable throws during activation, so a misconfigured deployment fails
|
|
14
|
+
loudly before any consumer activates.
|
|
15
|
+
- **A typed namespace value.** Because the keys are a type parameter,
|
|
16
|
+
consumers read `ctx["qq-env"].QQ_BOT_APP_ID` as `string` with no casts.
|
|
17
|
+
The name is a parameter too, so instances multiply: two providers compose
|
|
18
|
+
side by side under distinct names, exactly like `wsTransport` in
|
|
19
|
+
`@lambdot/websocket`.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createKernel, definePlugin } from "@lambdot/core";
|
|
25
|
+
import { envVars } from "@lambdot/env";
|
|
26
|
+
|
|
27
|
+
const report = definePlugin({
|
|
28
|
+
name: "report",
|
|
29
|
+
apply(input: { "qq-env": Readonly<Record<"QQ_BOT_APP_ID" | "QQ_BOT_APP_SECRET", string>> }) {
|
|
30
|
+
const appId: string = input["qq-env"].QQ_BOT_APP_ID;
|
|
31
|
+
// ...
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const kernel = createKernel()
|
|
36
|
+
.use(envVars("qq-env", ["QQ_BOT_APP_ID", "QQ_BOT_APP_SECRET"]))
|
|
37
|
+
.use(report); // before envVars: compile error — the mapping's ctx is
|
|
38
|
+
// typed as what's visible so far, and "qq-env" isn't in it
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Consumers may also declare a wider view — any
|
|
42
|
+
`Readonly<Record<string, string>>` under the same namespace key — since the
|
|
43
|
+
emitted record stays assignable to it; `@lambdot/protocol-qq`'s plugins
|
|
44
|
+
consume `{ env: Readonly<Record<string, string>> }` this way, wired with an
|
|
45
|
+
explicit `mapping: (ctx) => ({ env: ctx["qq-env"] })`. Workers have no
|
|
46
|
+
`process.env`; `@lambdot/host-cloudflare` ships a counterpart `envVars`
|
|
47
|
+
that reads from a worker's bindings object into the same record shape.
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
- `envVars<const TName extends string, const TKey extends string>(name: TName, keys: readonly TKey[])` —
|
|
52
|
+
returns a `Plugin<void, Readonly<Record<TKey, string>>, void, TName>`. The
|
|
53
|
+
snapshot is taken once in `apply`; the plugin takes no config.
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
- [qq-gateway-bot](../../../examples/qq-gateway-bot) — `envVars("qq-env", ...)`
|
|
58
|
+
feeds the qq platform's api plugin through its `mapping`.
|
|
59
|
+
- [qq-webhook-bot](../../../examples/qq-webhook-bot) — the same env wiring over
|
|
60
|
+
webhooks.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
Dual-licensed under [Apache-2.0](../../../LICENSE-APACHE) and [MIT](../../../LICENSE-MIT).
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
"name": "@lambdot/env",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/Embers-of-the-Fire/lambdot",
|
|
7
|
+
"directory": "packages/core/env"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@lambdot/core": "0.2.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Plugin } from "@lambdot/core";
|
|
2
|
+
import { definePlugin } from "@lambdot/core";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* requested variables, keyed by variable name. Distinct names fold side by
|
|
8
|
-
* side (`EnvCapability<"qq-env"> & EnvCapability<"discord-env">`), exactly
|
|
9
|
-
* like `WsCapability` in `@lambdot/websocket`.
|
|
10
|
-
*/
|
|
11
|
-
export type EnvCapability<TCap extends string, TKey extends string> = {
|
|
12
|
-
readonly [K in TCap]: Readonly<Record<TKey, string>>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Read variables from `process.env` and provide them as a typed capability.
|
|
17
|
-
* A missing or empty variable fails activation loudly at kernel start, so a
|
|
5
|
+
* Read variables from `process.env` and emit them as the plugin's namespace
|
|
6
|
+
* value: a snapshot of the requested variables, keyed by variable name. A
|
|
7
|
+
* missing or empty variable fails activation loudly at start, so a
|
|
18
8
|
* misconfigured deployment surfaces before any consumer activates.
|
|
19
9
|
*
|
|
20
10
|
* ```ts
|
|
@@ -22,29 +12,23 @@ export type EnvCapability<TCap extends string, TKey extends string> = {
|
|
|
22
12
|
* // ctx["qq-env"].QQ_BOT_APP_ID: string
|
|
23
13
|
* ```
|
|
24
14
|
*/
|
|
25
|
-
export function envVars<TCap extends string, TKey extends string>(
|
|
15
|
+
export function envVars<const TCap extends string, const TKey extends string>(
|
|
26
16
|
capability: TCap,
|
|
27
17
|
keys: readonly TKey[],
|
|
28
|
-
):
|
|
29
|
-
return {
|
|
30
|
-
name:
|
|
31
|
-
apply(
|
|
18
|
+
): Plugin<void, Readonly<Record<TKey, string>>, void, TCap> {
|
|
19
|
+
return definePlugin({
|
|
20
|
+
name: capability,
|
|
21
|
+
apply() {
|
|
32
22
|
const values: Record<string, string> = {};
|
|
33
23
|
for (const key of keys) {
|
|
34
24
|
const value = process.env[key];
|
|
35
25
|
if (value === undefined || value === "")
|
|
36
26
|
throw new Error(
|
|
37
|
-
|
|
27
|
+
`${capability}: required environment variable "${key}" is not set`,
|
|
38
28
|
);
|
|
39
29
|
values[key] = value;
|
|
40
30
|
}
|
|
41
|
-
|
|
42
|
-
// conditional type that stays deferred for a generic capability
|
|
43
|
-
// name; `EnvCapability<TCap, TKey>` already ties this name to the
|
|
44
|
-
// record, so pin the call down here (same trick as `wsTransport`).
|
|
45
|
-
return (
|
|
46
|
-
ctx.provide as (name: TCap, value: Readonly<Record<TKey, string>>) => Disposer
|
|
47
|
-
).call(ctx, capability, values as Readonly<Record<TKey, string>>);
|
|
31
|
+
return values as Readonly<Record<TKey, string>>;
|
|
48
32
|
},
|
|
49
|
-
};
|
|
33
|
+
});
|
|
50
34
|
}
|