@di-framework/bindings 0.0.1 → 6.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.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # @di-framework/bindings
2
+
3
+ Native [wasmCloud](https://wasmcloud.com) WIT service bindings for DI Framework applications.
4
+
5
+ Each concrete class is a DI token, a named `hostInterfaces` entry, and a build-time WIT
6
+ requirement. The package maps to wasmCloud/WASI interfaces rather than inventing a second
7
+ capability vocabulary.
8
+
9
+ This package **consumes** host-interface bindings. It does not provision PostgreSQL, Redis, NATS,
10
+ or object stores, and it never embeds secret values. ConfigMaps and Secrets stay with Pulumi,
11
+ Terraform, Crossplane, Helm, or another infrastructure layer.
12
+
13
+ ## Bindings
14
+
15
+ ```ts
16
+ import { Component, Container } from '@di-framework/core/decorators';
17
+ import { KeyValue, Postgres, WasmCloudBinding } from '@di-framework/bindings';
18
+
19
+ @WasmCloudBinding('user-database')
20
+ @Container()
21
+ export class UserDatabase extends Postgres {}
22
+
23
+ @WasmCloudBinding('sessions', { interfaces: ['store', 'atomics'] })
24
+ @Container()
25
+ export class Sessions extends KeyValue {}
26
+
27
+ @WasmCloudBinding('cache')
28
+ @Container()
29
+ export class Cache extends KeyValue {}
30
+ ```
31
+
32
+ Put those classes in `src/bindings.ts`. The wasmCloud CLI extension discovers the file, contributes
33
+ each class to the shared WIT requirement graph, generates real WIT guest imports, and renders
34
+ matching `hostInterfaces` entries. PostgreSQL entries omit `hostInterfaces[].name` to match the
35
+ unlabeled import; the binding name still identifies the DI guest and its configuration overlays.
36
+ The compiled guest world is unlabeled (`import wasmcloud:postgres/query@0.2.0`) because the qjs componentizer cannot
37
+ emit `cm-implements` labeled imports yet. Imported `async func`s (postgres, key-value, blobstore,
38
+ messaging, secrets, outgoing HTTP) componentize with `@di-framework/componentize-qjs`
39
+ (wasmtime 48). Set `DI_FRAMEWORK_COMPONENTIZE_QJS` to override that CLI.
40
+ `wasi:config@0.2.0-rc.1` is sync and also componentizes with stock jco.
41
+
42
+ For PostgreSQL, configure the host connection with `WASH_POSTGRES_URL` (or
43
+ `WASMCLOUD_POSTGRES_URL` with the CLI's `platform dev` command), and set
44
+ `config: { database: 'orders' }` on the binding. The unnamed import uses the host's
45
+ connection and the selected database; multiple independently credentialed PostgreSQL
46
+ bindings are not supported by this QuickJS path yet. Guests initialize before the
47
+ application, including bindings constructed at module startup.
48
+
49
+ Secret material is referenced, never inlined:
50
+
51
+ ```ts
52
+ @WasmCloudBinding('user-database', { secretFrom: 'orders-user-database' })
53
+ @Container()
54
+ export class UserDatabase extends Postgres {}
55
+ ```
56
+
57
+ When `secretFrom` is omitted, the default Kubernetes Secret name is `<application>-<binding>`.
58
+
59
+ ## Supported capabilities
60
+
61
+ | Class | WIT package | Version |
62
+ | --- | --- | --- |
63
+ | `Postgres` | `wasmcloud:postgres` | 0.2.0 |
64
+ | `KeyValue` | `wasmcloud:keyvalue` | 0.2.0 |
65
+ | `Blobstore` | `wasmcloud:blobstore` | 0.1.0 |
66
+ | `Messaging` | `wasmcloud:messaging` | 0.3.0 |
67
+ | `Config` | `wasi:config` | 0.2.0-rc.1 |
68
+ | `Secrets` | `wasmcloud:secrets` | 2.1.0 |
69
+ | `OutgoingHttp` | `wasi:http` `client` | 0.3.0 |
70
+
71
+ Package versions are independent of the WASI 0.3 component-model preview.
72
+
73
+ ## Unit tests
74
+
75
+ Replace a binding through the container:
76
+
77
+ ```ts
78
+ class FakeUserDatabase extends UserDatabase {
79
+ override query() {
80
+ return Promise.resolve([]);
81
+ }
82
+ }
83
+
84
+ testContainer.registerValue(UserDatabase, new FakeUserDatabase());
85
+ ```
86
+
87
+ ## Managed PostgreSQL (upcoming release)
88
+
89
+ ```ts
90
+ @WasmCloudBinding('orders-db', { serviceName: 'orders' })
91
+ export class OrdersDatabase extends Postgres {}
92
+ ```
93
+
94
+ Create the service with `di-framework platform service create postgres --name orders --wait`.
95
+ Deploying creates workload-owned ServiceBindings and waits for the controller's
96
+ connection Secrets. Multiple bindings can share a service; separate services use
97
+ independent credentials and PVCs. `serviceName` currently supports PostgreSQL only
98
+ and cannot be combined with manual connection configuration.
99
+
100
+ See [platform PostgreSQL lifecycle and recovery](../di-framework-platform/README.md#dedicated-postgresql-upcoming-release)
101
+ for storage prerequisites, retention, and deletion.
package/catalog.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "Postgres": {
3
+ "kind": "Postgres",
4
+ "package": "wasmcloud:postgres",
5
+ "version": "0.2.0",
6
+ "interfaces": ["query", "prepared", "types"],
7
+ "primaryInterface": "query",
8
+ "namedInstance": true,
9
+ "sharedResources": ["types.pg-value", "types.error"],
10
+ "witDep": "wasmcloud-postgres",
11
+ "usesSecret": true,
12
+ "configKeys": []
13
+ },
14
+ "KeyValue": {
15
+ "kind": "KeyValue",
16
+ "package": "wasmcloud:keyvalue",
17
+ "version": "0.2.0",
18
+ "interfaces": ["store", "atomics", "cas", "batch", "types"],
19
+ "primaryInterface": "store",
20
+ "namedInstance": true,
21
+ "sharedResources": ["types.bucket", "types.error"],
22
+ "witDep": "wasmcloud-keyvalue",
23
+ "usesSecret": false,
24
+ "configKeys": ["backend", "url", "prefix", "root"]
25
+ },
26
+ "Blobstore": {
27
+ "kind": "Blobstore",
28
+ "package": "wasmcloud:blobstore",
29
+ "version": "0.1.0",
30
+ "interfaces": ["blobstore", "container", "types"],
31
+ "primaryInterface": "blobstore",
32
+ "namedInstance": true,
33
+ "sharedResources": ["types.container", "types.error"],
34
+ "witDep": "wasmcloud-blobstore",
35
+ "usesSecret": false,
36
+ "configKeys": ["backend", "url", "root", "buckets"]
37
+ },
38
+ "Messaging": {
39
+ "kind": "Messaging",
40
+ "package": "wasmcloud:messaging",
41
+ "version": "0.3.0",
42
+ "interfaces": ["consumer", "types"],
43
+ "primaryInterface": "consumer",
44
+ "namedInstance": true,
45
+ "sharedResources": ["types.broker-message", "types.error"],
46
+ "witDep": "wasmcloud-messaging",
47
+ "usesSecret": false,
48
+ "configKeys": ["backend", "url", "subscriptions", "consumer_group"]
49
+ },
50
+ "Config": {
51
+ "kind": "Config",
52
+ "package": "wasi:config",
53
+ "version": "0.2.0-rc.1",
54
+ "interfaces": ["store"],
55
+ "primaryInterface": "store",
56
+ "namedInstance": false,
57
+ "sharedResources": [],
58
+ "witDep": "wasi-config",
59
+ "usesSecret": false,
60
+ "configKeys": []
61
+ },
62
+ "Secrets": {
63
+ "kind": "Secrets",
64
+ "package": "wasmcloud:secrets",
65
+ "version": "2.1.0",
66
+ "interfaces": ["store", "reveal"],
67
+ "primaryInterface": "store",
68
+ "namedInstance": true,
69
+ "sharedResources": [],
70
+ "witDep": "wasmcloud-secrets",
71
+ "usesSecret": true,
72
+ "configKeys": []
73
+ },
74
+ "OutgoingHttp": {
75
+ "kind": "OutgoingHttp",
76
+ "package": "wasi:http",
77
+ "version": "0.3.0",
78
+ "interfaces": ["client"],
79
+ "primaryInterface": "client",
80
+ "namedInstance": false,
81
+ "sharedResources": [],
82
+ "witDep": "wasi-http",
83
+ "usesSecret": false,
84
+ "configKeys": ["allowedHosts"]
85
+ }
86
+ }
@@ -0,0 +1,11 @@
1
+ export type BlobstoreGuest = {
2
+ createContainer(name: string): Promise<unknown>;
3
+ getContainer(name: string): Promise<unknown>;
4
+ };
5
+ export declare class Blobstore {
6
+ readonly bindingName: string;
7
+ protected readonly guest: BlobstoreGuest | undefined;
8
+ constructor(guest?: BlobstoreGuest);
9
+ createContainer(name: string): Promise<unknown>;
10
+ getContainer(name: string): Promise<unknown>;
11
+ }
@@ -0,0 +1,16 @@
1
+ import { requireGuest, tryGetGuest } from '../guests.js';
2
+ import { getBindingMetadata } from '../metadata.js';
3
+ export class Blobstore {
4
+ bindingName;
5
+ guest;
6
+ constructor(guest) {
7
+ this.bindingName = getBindingMetadata(this.constructor)?.name ?? 'blobstore';
8
+ this.guest = guest ?? tryGetGuest(this.bindingName);
9
+ }
10
+ createContainer(name) {
11
+ return requireGuest(this.guest, this.bindingName).createContainer(name);
12
+ }
13
+ getContainer(name) {
14
+ return requireGuest(this.guest, this.bindingName).getContainer(name);
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ export type ConfigGuest = {
2
+ get(key: string): Promise<string | undefined> | string | undefined;
3
+ getAll(): Promise<Array<[string, string]>> | Array<[string, string]>;
4
+ };
5
+ export declare class Config {
6
+ readonly bindingName: string;
7
+ protected readonly guest: ConfigGuest | undefined;
8
+ constructor(guest?: ConfigGuest);
9
+ get(key: string): Promise<string | undefined> | string | undefined;
10
+ getAll(): Promise<Array<[string, string]>> | Array<[string, string]>;
11
+ }
@@ -0,0 +1,16 @@
1
+ import { requireGuest, tryGetGuest } from '../guests.js';
2
+ import { getBindingMetadata } from '../metadata.js';
3
+ export class Config {
4
+ bindingName;
5
+ guest;
6
+ constructor(guest) {
7
+ this.bindingName = getBindingMetadata(this.constructor)?.name ?? 'config';
8
+ this.guest = guest ?? tryGetGuest(this.bindingName);
9
+ }
10
+ get(key) {
11
+ return requireGuest(this.guest, this.bindingName).get(key);
12
+ }
13
+ getAll() {
14
+ return requireGuest(this.guest, this.bindingName).getAll();
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ export type KeyValueGuest = {
2
+ open(identifier: string): Promise<unknown>;
3
+ };
4
+ export declare class KeyValue {
5
+ readonly bindingName: string;
6
+ protected readonly guest: KeyValueGuest | undefined;
7
+ constructor(guest?: KeyValueGuest);
8
+ open(identifier: string): Promise<unknown>;
9
+ }
@@ -0,0 +1,13 @@
1
+ import { requireGuest, tryGetGuest } from '../guests.js';
2
+ import { getBindingMetadata } from '../metadata.js';
3
+ export class KeyValue {
4
+ bindingName;
5
+ guest;
6
+ constructor(guest) {
7
+ this.bindingName = getBindingMetadata(this.constructor)?.name ?? 'keyvalue';
8
+ this.guest = guest ?? tryGetGuest(this.bindingName);
9
+ }
10
+ open(identifier) {
11
+ return requireGuest(this.guest, this.bindingName).open(identifier);
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ export type MessagingGuest = {
2
+ publish(message: unknown): Promise<unknown>;
3
+ request(subject: string, body: AsyncIterable<Uint8Array>, timeoutMs?: number): Promise<unknown>;
4
+ };
5
+ export declare class Messaging {
6
+ readonly bindingName: string;
7
+ protected readonly guest: MessagingGuest | undefined;
8
+ constructor(guest?: MessagingGuest);
9
+ publish(message: unknown): Promise<unknown>;
10
+ request(subject: string, body: AsyncIterable<Uint8Array>, timeoutMs?: number): Promise<unknown>;
11
+ }
@@ -0,0 +1,16 @@
1
+ import { requireGuest, tryGetGuest } from '../guests.js';
2
+ import { getBindingMetadata } from '../metadata.js';
3
+ export class Messaging {
4
+ bindingName;
5
+ guest;
6
+ constructor(guest) {
7
+ this.bindingName = getBindingMetadata(this.constructor)?.name ?? 'messaging';
8
+ this.guest = guest ?? tryGetGuest(this.bindingName);
9
+ }
10
+ publish(message) {
11
+ return requireGuest(this.guest, this.bindingName).publish(message);
12
+ }
13
+ request(subject, body, timeoutMs) {
14
+ return requireGuest(this.guest, this.bindingName).request(subject, body, timeoutMs);
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ export type OutgoingHttpGuest = {
2
+ send(request: unknown): Promise<unknown>;
3
+ };
4
+ export declare class OutgoingHttp {
5
+ readonly bindingName: string;
6
+ protected readonly guest: OutgoingHttpGuest | undefined;
7
+ constructor(guest?: OutgoingHttpGuest);
8
+ send(request: unknown): Promise<unknown>;
9
+ }
@@ -0,0 +1,13 @@
1
+ import { requireGuest, tryGetGuest } from '../guests.js';
2
+ import { getBindingMetadata } from '../metadata.js';
3
+ export class OutgoingHttp {
4
+ bindingName;
5
+ guest;
6
+ constructor(guest) {
7
+ this.bindingName = getBindingMetadata(this.constructor)?.name ?? 'outgoing-http';
8
+ this.guest = guest ?? tryGetGuest(this.bindingName);
9
+ }
10
+ send(request) {
11
+ return requireGuest(this.guest, this.bindingName).send(request);
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ export type PostgresGuest = {
2
+ query(query: string, params: readonly unknown[]): Promise<unknown>;
3
+ queryBatch(query: string): Promise<unknown>;
4
+ };
5
+ export declare class Postgres {
6
+ readonly bindingName: string;
7
+ protected readonly guest: PostgresGuest | undefined;
8
+ constructor(guest?: PostgresGuest);
9
+ query(query: string, params?: readonly unknown[]): Promise<unknown>;
10
+ queryBatch(query: string): Promise<unknown>;
11
+ }
@@ -0,0 +1,16 @@
1
+ import { requireGuest, tryGetGuest } from '../guests.js';
2
+ import { getBindingMetadata } from '../metadata.js';
3
+ export class Postgres {
4
+ bindingName;
5
+ guest;
6
+ constructor(guest) {
7
+ this.bindingName = getBindingMetadata(this.constructor)?.name ?? 'postgres';
8
+ this.guest = guest ?? tryGetGuest(this.bindingName);
9
+ }
10
+ query(query, params = []) {
11
+ return requireGuest(this.guest, this.bindingName).query(query, params);
12
+ }
13
+ queryBatch(query) {
14
+ return requireGuest(this.guest, this.bindingName).queryBatch(query);
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ export type SecretsGuest = {
2
+ get(key: string): Promise<unknown>;
3
+ reveal(secret: unknown): Promise<unknown>;
4
+ };
5
+ export declare class Secrets {
6
+ readonly bindingName: string;
7
+ protected readonly guest: SecretsGuest | undefined;
8
+ constructor(guest?: SecretsGuest);
9
+ get(key: string): Promise<unknown>;
10
+ reveal(secret: unknown): Promise<unknown>;
11
+ }
@@ -0,0 +1,16 @@
1
+ import { requireGuest, tryGetGuest } from '../guests.js';
2
+ import { getBindingMetadata } from '../metadata.js';
3
+ export class Secrets {
4
+ bindingName;
5
+ guest;
6
+ constructor(guest) {
7
+ this.bindingName = getBindingMetadata(this.constructor)?.name ?? 'secrets';
8
+ this.guest = guest ?? tryGetGuest(this.bindingName);
9
+ }
10
+ get(key) {
11
+ return requireGuest(this.guest, this.bindingName).get(key);
12
+ }
13
+ reveal(secret) {
14
+ return requireGuest(this.guest, this.bindingName).reveal(secret);
15
+ }
16
+ }
@@ -0,0 +1,17 @@
1
+ export declare const BINDING_KINDS: readonly ["Postgres", "KeyValue", "Blobstore", "Messaging", "Config", "Secrets", "OutgoingHttp"];
2
+ export type BindingKind = (typeof BINDING_KINDS)[number];
3
+ export type CatalogEntry = {
4
+ kind: BindingKind;
5
+ package: string;
6
+ version: string;
7
+ interfaces: string[];
8
+ /** First interface carries the labeled import / hostInterfaces name. */
9
+ primaryInterface: string;
10
+ namedInstance: boolean;
11
+ sharedResources: string[];
12
+ witDep: string;
13
+ usesSecret: boolean;
14
+ configKeys: string[];
15
+ };
16
+ export declare const BINDING_CATALOG: Record<BindingKind, CatalogEntry>;
17
+ export declare function isBindingKind(value: string): value is BindingKind;
@@ -0,0 +1,98 @@
1
+ export const BINDING_KINDS = [
2
+ 'Postgres',
3
+ 'KeyValue',
4
+ 'Blobstore',
5
+ 'Messaging',
6
+ 'Config',
7
+ 'Secrets',
8
+ 'OutgoingHttp',
9
+ ];
10
+ export const BINDING_CATALOG = {
11
+ Postgres: {
12
+ kind: 'Postgres',
13
+ package: 'wasmcloud:postgres',
14
+ version: '0.2.0',
15
+ interfaces: ['query', 'prepared', 'types'],
16
+ primaryInterface: 'query',
17
+ namedInstance: true,
18
+ sharedResources: ['types.pg-value', 'types.error'],
19
+ witDep: 'wasmcloud-postgres',
20
+ usesSecret: true,
21
+ configKeys: [],
22
+ },
23
+ KeyValue: {
24
+ kind: 'KeyValue',
25
+ package: 'wasmcloud:keyvalue',
26
+ version: '0.2.0',
27
+ interfaces: ['store', 'atomics', 'cas', 'batch', 'types'],
28
+ primaryInterface: 'store',
29
+ namedInstance: true,
30
+ sharedResources: ['types.bucket', 'types.error'],
31
+ witDep: 'wasmcloud-keyvalue',
32
+ usesSecret: false,
33
+ configKeys: ['backend', 'url', 'prefix', 'root'],
34
+ },
35
+ Blobstore: {
36
+ kind: 'Blobstore',
37
+ package: 'wasmcloud:blobstore',
38
+ version: '0.1.0',
39
+ interfaces: ['blobstore', 'container', 'types'],
40
+ primaryInterface: 'blobstore',
41
+ namedInstance: true,
42
+ sharedResources: ['types.container', 'types.error'],
43
+ witDep: 'wasmcloud-blobstore',
44
+ usesSecret: false,
45
+ configKeys: ['backend', 'url', 'root', 'buckets'],
46
+ },
47
+ Messaging: {
48
+ kind: 'Messaging',
49
+ package: 'wasmcloud:messaging',
50
+ version: '0.3.0',
51
+ interfaces: ['consumer', 'types'],
52
+ primaryInterface: 'consumer',
53
+ namedInstance: true,
54
+ sharedResources: ['types.broker-message', 'types.error'],
55
+ witDep: 'wasmcloud-messaging',
56
+ usesSecret: false,
57
+ configKeys: ['backend', 'url', 'subscriptions', 'consumer_group'],
58
+ },
59
+ Config: {
60
+ kind: 'Config',
61
+ package: 'wasi:config',
62
+ version: '0.2.0-rc.1',
63
+ interfaces: ['store'],
64
+ primaryInterface: 'store',
65
+ namedInstance: false,
66
+ sharedResources: [],
67
+ witDep: 'wasi-config',
68
+ usesSecret: false,
69
+ configKeys: [],
70
+ },
71
+ Secrets: {
72
+ kind: 'Secrets',
73
+ package: 'wasmcloud:secrets',
74
+ version: '2.1.0',
75
+ interfaces: ['store', 'reveal'],
76
+ primaryInterface: 'store',
77
+ namedInstance: true,
78
+ sharedResources: [],
79
+ witDep: 'wasmcloud-secrets',
80
+ usesSecret: true,
81
+ configKeys: [],
82
+ },
83
+ OutgoingHttp: {
84
+ kind: 'OutgoingHttp',
85
+ package: 'wasi:http',
86
+ version: '0.3.0',
87
+ interfaces: ['client'],
88
+ primaryInterface: 'client',
89
+ namedInstance: false,
90
+ sharedResources: [],
91
+ witDep: 'wasi-http',
92
+ usesSecret: false,
93
+ configKeys: ['allowedHosts'],
94
+ },
95
+ };
96
+ export function isBindingKind(value) {
97
+ return BINDING_KINDS.includes(value);
98
+ }
@@ -0,0 +1,9 @@
1
+ import { type WasmCloudBindingOptions } from './metadata.js';
2
+ export type { WasmCloudBindingOptions };
3
+ /**
4
+ * Declares a named wasmCloud host-interface binding on a concrete class.
5
+ * Class identity is the DI token; `name` is the WIT import and hostInterfaces name.
6
+ */
7
+ export declare function WasmCloudBinding(name: string, options?: WasmCloudBindingOptions): <T extends {
8
+ new (...args: any[]): object;
9
+ }>(ctor: T) => T;
@@ -0,0 +1,34 @@
1
+ import { Postgres } from './bindings/postgres.js';
2
+ import { defineBindingMetadata, isWitIdentifier, rejectsPlaintextSecret, } from './metadata.js';
3
+ /**
4
+ * Declares a named wasmCloud host-interface binding on a concrete class.
5
+ * Class identity is the DI token; `name` is the WIT import and hostInterfaces name.
6
+ */
7
+ export function WasmCloudBinding(name, options = {}) {
8
+ // biome-ignore lint/suspicious/noExplicitAny: class decorator constructor
9
+ return (ctor) => {
10
+ if (!isWitIdentifier(name)) {
11
+ throw new Error(`WasmCloud binding name "${name}" must be a WIT identifier matching /^[a-z][a-z0-9-]*$/`);
12
+ }
13
+ if (options.serviceName !== undefined) {
14
+ if (typeof options.serviceName !== 'string' ||
15
+ !/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(options.serviceName) ||
16
+ options.serviceName.length > 40)
17
+ throw new Error('serviceName must be a DNS label of at most 40 characters');
18
+ if (!(ctor.prototype instanceof Postgres))
19
+ throw new Error('serviceName is currently supported only for Postgres');
20
+ if (options.config !== undefined ||
21
+ options.configFrom !== undefined ||
22
+ options.secretFrom !== undefined)
23
+ throw new Error('serviceName cannot be combined with manual connection configuration');
24
+ if (name.length > 54 || !/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name))
25
+ throw new Error('Managed PostgreSQL binding names must be DNS labels of at most 54 characters');
26
+ }
27
+ const secretProblem = rejectsPlaintextSecret(options.config);
28
+ if (secretProblem !== undefined) {
29
+ throw new Error(`WasmCloud binding "${name}": ${secretProblem}`);
30
+ }
31
+ defineBindingMetadata(ctor, { name, options });
32
+ return ctor;
33
+ };
34
+ }
@@ -0,0 +1,7 @@
1
+ export type GuestModules = Record<string, unknown>;
2
+ /** Well-known key the wasmCloud CLI plugin writes from generated `guests.js`. */
3
+ export declare const WASMCLOUD_GUESTS_GLOBAL = "di-framework.wasmcloud.guests";
4
+ export declare function setGuests(value: GuestModules): void;
5
+ export declare function resetGuests(): void;
6
+ export declare function tryGetGuest<T>(name: string): T | undefined;
7
+ export declare function requireGuest<T>(guest: T | undefined, name: string): T;
package/dist/guests.js ADDED
@@ -0,0 +1,24 @@
1
+ /** Well-known key the wasmCloud CLI plugin writes from generated `guests.js`. */
2
+ export const WASMCLOUD_GUESTS_GLOBAL = 'di-framework.wasmcloud.guests';
3
+ const GUESTS_KEY = Symbol.for(WASMCLOUD_GUESTS_GLOBAL);
4
+ let guests = {};
5
+ function installedGuests() {
6
+ return globalThis[GUESTS_KEY] ?? guests;
7
+ }
8
+ export function setGuests(value) {
9
+ guests = value;
10
+ globalThis[GUESTS_KEY] = value;
11
+ }
12
+ export function resetGuests() {
13
+ guests = {};
14
+ delete globalThis[GUESTS_KEY];
15
+ }
16
+ export function tryGetGuest(name) {
17
+ return installedGuests()[name];
18
+ }
19
+ export function requireGuest(guest, name) {
20
+ if (guest === undefined) {
21
+ throw new Error(`wasmCloud binding "${name}" has no guest implementation. In unit tests, pass a fake to the constructor or call setGuests().`);
22
+ }
23
+ return guest;
24
+ }
@@ -0,0 +1,12 @@
1
+ export { Blobstore, type BlobstoreGuest } from './bindings/blobstore.js';
2
+ export { Config, type ConfigGuest } from './bindings/config.js';
3
+ export { KeyValue, type KeyValueGuest } from './bindings/keyvalue.js';
4
+ export { Messaging, type MessagingGuest } from './bindings/messaging.js';
5
+ export { OutgoingHttp, type OutgoingHttpGuest } from './bindings/outgoing-http.js';
6
+ export { Postgres, type PostgresGuest } from './bindings/postgres.js';
7
+ export { Secrets, type SecretsGuest } from './bindings/secrets.js';
8
+ export { BINDING_CATALOG, BINDING_KINDS, type BindingKind, type CatalogEntry, isBindingKind, } from './catalog.js';
9
+ export { WasmCloudBinding, type WasmCloudBindingOptions } from './decorator.js';
10
+ export { type GuestModules, resetGuests, setGuests, tryGetGuest, WASMCLOUD_GUESTS_GLOBAL, } from './guests.js';
11
+ export { getBindingMetadata, isWitIdentifier, WASMCLOUD_BINDING_KEY, type WasmCloudBindingMetadata, } from './metadata.js';
12
+ export { getWorkload, getWorkloadComponent, getWorkloadService, WORKLOAD_COMPONENT_KEY, WORKLOAD_KEY, WORKLOAD_SERVICE_KEY, Workload, WorkloadComponent, type WorkloadComponentOptions, type WorkloadFetchHandler, type WorkloadMetadata, type WorkloadRunHandler, WorkloadService, type WorkloadServiceOptions, } from './workload.js';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export { Blobstore } from './bindings/blobstore.js';
2
+ export { Config } from './bindings/config.js';
3
+ export { KeyValue } from './bindings/keyvalue.js';
4
+ export { Messaging } from './bindings/messaging.js';
5
+ export { OutgoingHttp } from './bindings/outgoing-http.js';
6
+ export { Postgres } from './bindings/postgres.js';
7
+ export { Secrets } from './bindings/secrets.js';
8
+ export { BINDING_CATALOG, BINDING_KINDS, isBindingKind, } from './catalog.js';
9
+ export { WasmCloudBinding } from './decorator.js';
10
+ export { resetGuests, setGuests, tryGetGuest, WASMCLOUD_GUESTS_GLOBAL, } from './guests.js';
11
+ export { getBindingMetadata, isWitIdentifier, WASMCLOUD_BINDING_KEY, } from './metadata.js';
12
+ export { getWorkload, getWorkloadComponent, getWorkloadService, WORKLOAD_COMPONENT_KEY, WORKLOAD_KEY, WORKLOAD_SERVICE_KEY, Workload, WorkloadComponent, WorkloadService, } from './workload.js';
@@ -0,0 +1,17 @@
1
+ export declare const WASMCLOUD_BINDING_KEY = "di:wasmcloud-binding";
2
+ export type WasmCloudBindingOptions = {
3
+ /** Same-namespace managed PostgreSQL BackingService. */
4
+ serviceName?: string;
5
+ interfaces?: string[];
6
+ secretFrom?: string;
7
+ configFrom?: string;
8
+ config?: Record<string, string>;
9
+ };
10
+ export type WasmCloudBindingMetadata = {
11
+ name: string;
12
+ options: WasmCloudBindingOptions;
13
+ };
14
+ export declare function isWitIdentifier(name: string): boolean;
15
+ export declare function rejectsPlaintextSecret(config: Record<string, string> | undefined): string | undefined;
16
+ export declare function defineBindingMetadata(target: object, metadata: WasmCloudBindingMetadata): void;
17
+ export declare function getBindingMetadata(target: object): WasmCloudBindingMetadata | undefined;
@@ -0,0 +1,33 @@
1
+ import { defineMetadata, getOwnMetadata } from '@di-framework/core/container';
2
+ export const WASMCLOUD_BINDING_KEY = 'di:wasmcloud-binding';
3
+ const SECRET_CONFIG_KEYS = new Set([
4
+ 'password',
5
+ 'secret',
6
+ 'token',
7
+ 'uri',
8
+ 'url',
9
+ 'connectionstring',
10
+ 'connection-string',
11
+ ]);
12
+ export function isWitIdentifier(name) {
13
+ return /^[a-z][a-z0-9-]*$/.test(name);
14
+ }
15
+ export function rejectsPlaintextSecret(config) {
16
+ if (config === undefined)
17
+ return undefined;
18
+ for (const [key, value] of Object.entries(config)) {
19
+ if (SECRET_CONFIG_KEYS.has(key.toLowerCase())) {
20
+ return `config key "${key}" must not carry a secret value; use secretFrom`;
21
+ }
22
+ if (/:\/\//.test(value) || /password=/i.test(value)) {
23
+ return `config value for "${key}" looks like a secret or connection string; use secretFrom`;
24
+ }
25
+ }
26
+ return undefined;
27
+ }
28
+ export function defineBindingMetadata(target, metadata) {
29
+ defineMetadata(WASMCLOUD_BINDING_KEY, metadata, target);
30
+ }
31
+ export function getBindingMetadata(target) {
32
+ return getOwnMetadata(WASMCLOUD_BINDING_KEY, target);
33
+ }
@@ -0,0 +1,40 @@
1
+ export declare const WORKLOAD_KEY = "di:workload";
2
+ export declare const WORKLOAD_COMPONENT_KEY = "di:workload-component";
3
+ export declare const WORKLOAD_SERVICE_KEY = "di:workload-service";
4
+ export type WorkloadFetchHandler = (request: Request) => Response | Promise<Response>;
5
+ export type WorkloadRunHandler = () => Promise<void>;
6
+ export type WorkloadMetadata = {
7
+ name: string;
8
+ };
9
+ export type WorkloadComponentOptions = {
10
+ /** Optional legacy assertion; deployment membership comes from di-framework.config.json. */
11
+ workload?: string;
12
+ /** Path identifying this component within its implicit workload. */
13
+ path: string;
14
+ };
15
+ export type WorkloadServiceOptions = {
16
+ /** Path identifying this service within its implicit workload. */
17
+ path: string;
18
+ /** Optional legacy assertion; deployment membership comes from di-framework.config.json. */
19
+ workload?: string;
20
+ /** Broker subjects delivered to this service by the host. */
21
+ subscriptions?: string[];
22
+ };
23
+ /**
24
+ * Names a colocation namespace. Not an HTTP gateway and not a deployable.
25
+ * Components and services join it via {@link WorkloadComponent} / {@link WorkloadService}.
26
+ */
27
+ export declare function Workload(name: string): <T extends object>(target: T) => T;
28
+ /**
29
+ * Describes an independently deployed component in an implicit workload.
30
+ * `path` identifies the member and is its HTTP route in the generated manifest.
31
+ */
32
+ export declare function WorkloadComponent(options: WorkloadComponentOptions): <T extends object>(target: T) => T;
33
+ /**
34
+ * Describes a service in an implicit workload. The host invokes broker subscriptions
35
+ * when declared; otherwise the service exports a CLI run entrypoint.
36
+ */
37
+ export declare function WorkloadService(options: WorkloadServiceOptions): <T extends object>(target: T) => T;
38
+ export declare function getWorkload(target: object): WorkloadMetadata | undefined;
39
+ export declare function getWorkloadComponent(target: object): WorkloadComponentOptions | undefined;
40
+ export declare function getWorkloadService(target: object): WorkloadServiceOptions | undefined;
@@ -0,0 +1,71 @@
1
+ import { defineMetadata, getOwnMetadata } from '@di-framework/core/container';
2
+ export const WORKLOAD_KEY = 'di:workload';
3
+ export const WORKLOAD_COMPONENT_KEY = 'di:workload-component';
4
+ export const WORKLOAD_SERVICE_KEY = 'di:workload-service';
5
+ function requireName(kind, name) {
6
+ if (!name.trim()) {
7
+ throw new Error(`${kind} name is required`);
8
+ }
9
+ return name;
10
+ }
11
+ /**
12
+ * Names a colocation namespace. Not an HTTP gateway and not a deployable.
13
+ * Components and services join it via {@link WorkloadComponent} / {@link WorkloadService}.
14
+ */
15
+ export function Workload(name) {
16
+ const metadata = { name: requireName('Workload', name) };
17
+ return (target) => {
18
+ defineMetadata(WORKLOAD_KEY, metadata, target);
19
+ return target;
20
+ };
21
+ }
22
+ function requirePath(kind, path) {
23
+ if (typeof path !== 'string' ||
24
+ !path.startsWith('/') ||
25
+ path.startsWith('//') ||
26
+ /[\s?#\\]/.test(path)) {
27
+ throw new Error(`${kind} path is required and must be an absolute path starting with "/"`);
28
+ }
29
+ return path;
30
+ }
31
+ /**
32
+ * Describes an independently deployed component in an implicit workload.
33
+ * `path` identifies the member and is its HTTP route in the generated manifest.
34
+ */
35
+ export function WorkloadComponent(options) {
36
+ const metadata = {
37
+ ...(options.workload === undefined
38
+ ? {}
39
+ : { workload: requireName('WorkloadComponent', options.workload) }),
40
+ path: requirePath('WorkloadComponent', options.path),
41
+ };
42
+ return (target) => {
43
+ defineMetadata(WORKLOAD_COMPONENT_KEY, metadata, target);
44
+ return target;
45
+ };
46
+ }
47
+ /**
48
+ * Describes a service in an implicit workload. The host invokes broker subscriptions
49
+ * when declared; otherwise the service exports a CLI run entrypoint.
50
+ */
51
+ export function WorkloadService(options) {
52
+ const workload = options.workload === undefined ? undefined : requireName('WorkloadService', options.workload);
53
+ const path = requirePath('WorkloadService', options.path);
54
+ return (target) => {
55
+ defineMetadata(WORKLOAD_SERVICE_KEY, {
56
+ ...(workload === undefined ? {} : { workload }),
57
+ path,
58
+ ...(options.subscriptions ? { subscriptions: [...options.subscriptions] } : {}),
59
+ }, target);
60
+ return target;
61
+ };
62
+ }
63
+ export function getWorkload(target) {
64
+ return getOwnMetadata(WORKLOAD_KEY, target);
65
+ }
66
+ export function getWorkloadComponent(target) {
67
+ return getOwnMetadata(WORKLOAD_COMPONENT_KEY, target);
68
+ }
69
+ export function getWorkloadService(target) {
70
+ return getOwnMetadata(WORKLOAD_SERVICE_KEY, target);
71
+ }
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@di-framework/bindings",
3
- "version": "0.0.1",
4
- "description": "Placeholder for di-framework platform app bindings.",
3
+ "version": "6.0.1",
4
+ "description": "Native platform WIT service bindings for DI Framework applications.",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "module": "./dist/index.js",
5
8
  "type": "module",
6
9
  "license": "(MIT OR Apache-2.0)",
7
10
  "author": "@di-framework Contributors",
@@ -10,13 +13,42 @@
10
13
  "url": "https://github.com/di-framework/platform",
11
14
  "directory": "platform/bindings"
12
15
  },
13
- "main": "./src/index.ts",
14
- "types": "./src/index.ts",
16
+ "bugs": {
17
+ "url": "https://github.com/di-framework/platform/issues"
18
+ },
19
+ "homepage": "https://github.com/di-framework/platform",
20
+ "keywords": [
21
+ "wasmcloud",
22
+ "wasi",
23
+ "wit",
24
+ "webassembly",
25
+ "dependency-injection",
26
+ "decorators"
27
+ ],
28
+ "scripts": {
29
+ "test": "bun test",
30
+ "build": "rm -rf dist && bun x tsc -p tsconfig.build.json && bun ../../scripts/fix-esm-imports.ts dist"
31
+ },
15
32
  "exports": {
16
- ".": "./src/index.ts"
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "bun": "./dist/index.js",
36
+ "import": "./dist/index.js",
37
+ "default": "./dist/index.js"
38
+ },
39
+ "./catalog.json": "./catalog.json"
40
+ },
41
+ "peerDependencies": {
42
+ "@di-framework/core": "^5",
43
+ "typescript": "^5"
17
44
  },
18
- "files": ["src"],
19
- "publishConfig": {
20
- "access": "public"
45
+ "files": [
46
+ "dist",
47
+ "catalog.json",
48
+ "README.md"
49
+ ],
50
+ "devDependencies": {
51
+ "@di-framework/core": "^5",
52
+ "@types/bun": "latest"
21
53
  }
22
54
  }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- /** Placeholder. Implementation ships from di-framework/platform. */
2
- export const name = "@di-framework/bindings" as const;