@objectstack/service-datasource 7.6.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 (37) hide show
  1. package/.turbo/turbo-build.log +28 -0
  2. package/CHANGELOG.md +94 -0
  3. package/LICENSE +202 -0
  4. package/LICENSE.apache +202 -0
  5. package/README.md +50 -0
  6. package/dist/contracts/index.cjs +1 -0
  7. package/dist/contracts/index.cjs.map +1 -0
  8. package/dist/contracts/index.d.cts +178 -0
  9. package/dist/contracts/index.d.ts +178 -0
  10. package/dist/contracts/index.js +1 -0
  11. package/dist/contracts/index.js.map +1 -0
  12. package/dist/index.cjs +995 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.cts +414 -0
  15. package/dist/index.d.ts +414 -0
  16. package/dist/index.js +995 -0
  17. package/dist/index.js.map +1 -0
  18. package/package.json +61 -0
  19. package/src/__tests__/admin-routes.test.ts +106 -0
  20. package/src/__tests__/datasource-admin-plugin.test.ts +231 -0
  21. package/src/__tests__/datasource-admin-service.test.ts +288 -0
  22. package/src/__tests__/datasource-secret-binder.test.ts +101 -0
  23. package/src/__tests__/external-datasource-service.test.ts +360 -0
  24. package/src/admin-routes.ts +117 -0
  25. package/src/contracts/datasource-admin-service.ts +119 -0
  26. package/src/contracts/datasource-driver-factory.ts +77 -0
  27. package/src/contracts/index.ts +18 -0
  28. package/src/datasource-admin-plugin.ts +362 -0
  29. package/src/datasource-admin-service.ts +297 -0
  30. package/src/datasource-secret-binder.ts +144 -0
  31. package/src/default-datasource-driver-factory.ts +185 -0
  32. package/src/external-datasource-service.ts +456 -0
  33. package/src/index.ts +73 -0
  34. package/src/logger.ts +11 -0
  35. package/src/plugin.ts +119 -0
  36. package/tsconfig.json +17 -0
  37. package/tsup.config.ts +19 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * IDatasourceAdminService — runtime datasource lifecycle contract
3
+ * (ADR-0015 Addendum: Runtime UI-Created Datasources).
4
+ *
5
+ * Where {@link IExternalDatasourceService} covers *federation* (introspection,
6
+ * object drafting, schema validation) of datasources that already exist, this
7
+ * service covers their *lifecycle*: testing a connection before saving,
8
+ * creating / updating / removing a **runtime** datasource (`origin: 'runtime'`),
9
+ * and listing all datasources with their provenance + health.
10
+ *
11
+ * Code-defined datasources (`origin: 'code'`, authored as `*.datasource.ts`)
12
+ * are read-only here: `updateDatasource` / `removeDatasource` reject them, and
13
+ * a runtime datasource never shadows a code one of the same name (code wins).
14
+ *
15
+ * Credentials are never persisted in cleartext: callers pass a {@link SecretInput}
16
+ * separately from the connection `config`; the implementation encrypts it into
17
+ * the secret store (`sys_secret`) and persists only an opaque `credentialsRef`.
18
+ */
19
+ /** Provenance of a datasource definition. */
20
+ type DatasourceOrigin = 'code' | 'runtime';
21
+ /**
22
+ * A cleartext secret (password or full connection string) supplied for a
23
+ * create/update/test call. Never persisted as-is — encrypted into the secret
24
+ * store, with only the returned handle (`credentialsRef`) kept on the record.
25
+ */
26
+ interface SecretInput {
27
+ /** The cleartext value to encrypt (e.g. password or connection string). */
28
+ value: string;
29
+ /** Optional secret-store namespace (defaults to `'datasource'`). */
30
+ namespace?: string;
31
+ /** Optional secret-store key (defaults to the datasource name). */
32
+ key?: string;
33
+ }
34
+ /**
35
+ * The connection definition a caller supplies to test/create/update. A subset
36
+ * of `Datasource` — server-managed fields (`origin`) are never accepted from
37
+ * the client.
38
+ */
39
+ interface DatasourceDraft {
40
+ name: string;
41
+ label?: string;
42
+ driver: string;
43
+ schemaMode?: 'managed' | 'external' | 'validate-only';
44
+ /** Driver-specific connection config (host, port, database, …). No secrets. */
45
+ config?: Record<string, unknown>;
46
+ /** External federation settings (required when schemaMode != 'managed'). */
47
+ external?: Record<string, unknown>;
48
+ pool?: Record<string, unknown>;
49
+ active?: boolean;
50
+ }
51
+ /** Result of probing a connection (live driver connect + cheap round-trip). */
52
+ interface TestConnectionResult {
53
+ ok: boolean;
54
+ /** Round-trip latency of the probe, when the connection succeeded. */
55
+ latencyMs?: number;
56
+ /** Driver-reported server version, when available. */
57
+ serverVersion?: string;
58
+ /** Human-readable failure reason, when `ok === false`. */
59
+ error?: string;
60
+ }
61
+ /** A datasource with its provenance and current health (no secrets). */
62
+ interface DatasourceSummary {
63
+ name: string;
64
+ label?: string;
65
+ driver: string;
66
+ schemaMode: 'managed' | 'external' | 'validate-only';
67
+ origin: DatasourceOrigin;
68
+ active: boolean;
69
+ /** Validation health: `unvalidated` until the first validate/test runs. */
70
+ status: 'ok' | 'error' | 'unvalidated';
71
+ /** Package id that defines a code-origin datasource (omitted for runtime). */
72
+ definedIn?: string;
73
+ /** True when a runtime row is shadowed by a code definition of the same name. */
74
+ conflictsWithCode?: boolean;
75
+ }
76
+ /**
77
+ * Runtime datasource lifecycle service. Registered into the kernel as the
78
+ * `'datasource-admin'` service; consumed by the REST layer and Studio wizard.
79
+ */
80
+ interface IDatasourceAdminService {
81
+ /** List every datasource (code + runtime) with provenance and health. */
82
+ listDatasources(): Promise<DatasourceSummary[]>;
83
+ /**
84
+ * Probe a connection without persisting anything. Accepts an unsaved draft
85
+ * so the wizard can validate credentials before "Save".
86
+ */
87
+ testConnection(input: DatasourceDraft, secret?: SecretInput): Promise<TestConnectionResult>;
88
+ /**
89
+ * Persist a new runtime datasource (`origin: 'runtime'`, environment-scoped).
90
+ * Rejects when a code-defined datasource of the same name exists.
91
+ */
92
+ createDatasource(input: DatasourceDraft, secret?: SecretInput): Promise<DatasourceSummary>;
93
+ /**
94
+ * Patch an existing runtime datasource. Rejects for code-defined datasources.
95
+ * Passing `secret` re-wraps the stored credential.
96
+ */
97
+ updateDatasource(name: string, patch: Partial<DatasourceDraft>, secret?: SecretInput): Promise<DatasourceSummary>;
98
+ /**
99
+ * Remove a runtime datasource. Rejects for code-defined ones and while
100
+ * objects are still bound to it.
101
+ */
102
+ removeDatasource(name: string): Promise<void>;
103
+ }
104
+
105
+ /**
106
+ * IDatasourceDriverFactory — host-provided capability that builds a live driver
107
+ * from a connection spec (ADR-0015 Addendum §3.5).
108
+ *
109
+ * The framework deliberately ships no universal "driver-by-id" registry —
110
+ * concrete drivers (`SqlDriver`, `MongoDBDriver`, `TursoDriver`, …) are
111
+ * constructed by the host stack and registered as live connections. The
112
+ * runtime-datasource lifecycle (`IDatasourceAdminService`) needs to build a
113
+ * driver from an *unsaved* draft — to probe a connection before "Save", and to
114
+ * hot-register a pool after create/update — so the host exposes this factory
115
+ * as the `'datasource-driver-factory'` service.
116
+ *
117
+ * When no factory is registered, or none `supports()` a given driver id, the
118
+ * admin service degrades gracefully: `testConnection` returns
119
+ * `{ ok: false, error }` and create/update skip hot pool registration (the
120
+ * driver is picked up on the next boot instead).
121
+ *
122
+ * Security: the cleartext `secret` on {@link DatasourceConnectionSpec} is used
123
+ * only to open the live connection. Factories MUST NOT persist or log it.
124
+ */
125
+ /** Everything needed to construct one live driver connection. */
126
+ interface DatasourceConnectionSpec {
127
+ /** Datasource name, when building for an existing/named datasource. */
128
+ name?: string;
129
+ /** Driver id (e.g. `'postgres'`, `'sqlite'`, `'mongodb'`). */
130
+ driver: string;
131
+ /** Driver-specific connection config (host, port, database, …). No secrets. */
132
+ config: Record<string, unknown>;
133
+ /** Cleartext secret (password / DSN) injected for this connection only. */
134
+ secret?: string;
135
+ /** External federation settings (timeouts, allowed schemas, …). */
136
+ external?: Record<string, unknown>;
137
+ /** Connection pool settings. */
138
+ pool?: Record<string, unknown>;
139
+ }
140
+ /**
141
+ * A live (or lazily-connecting) driver handle. Intentionally structural and
142
+ * fully optional so any concrete driver satisfies it — the admin service uses
143
+ * whatever capabilities are present and skips the rest.
144
+ */
145
+ interface DatasourceDriverHandle {
146
+ /** Open the connection / pool. */
147
+ connect?(): Promise<void>;
148
+ /** Close the connection / pool. */
149
+ disconnect?(): Promise<void>;
150
+ /** Cheap liveness round-trip (preferred for probes). */
151
+ ping?(): Promise<unknown>;
152
+ /** Introspect the live schema (fallback probe when `ping` is absent). */
153
+ introspectSchema?(): Promise<unknown>;
154
+ /** Liveness check on the underlying engine driver (probe fallback). */
155
+ checkHealth?(): Promise<boolean>;
156
+ /** Driver-reported server version, when available. */
157
+ serverVersion?(): Promise<string | undefined>;
158
+ /**
159
+ * Escape hatch: the concrete engine driver to hand to
160
+ * `IDataEngine.registerDriver()` when hot-registering a pool. When present
161
+ * the admin service registers *this* (whose `.name` must equal the
162
+ * datasource name for routing) instead of the handle itself; absent ⇒ the
163
+ * handle is assumed to be the driver. Never serialized.
164
+ */
165
+ driver?: unknown;
166
+ }
167
+ /** Host-provided factory that builds drivers from connection specs. */
168
+ interface IDatasourceDriverFactory {
169
+ /** True if this factory can build a driver for the given driver id. */
170
+ supports(driverId: string): boolean;
171
+ /**
172
+ * Build a driver instance for the spec. Implementations may return a
173
+ * not-yet-connected handle; the caller calls `connect()` when needed.
174
+ */
175
+ create(spec: DatasourceConnectionSpec): Promise<DatasourceDriverHandle> | DatasourceDriverHandle;
176
+ }
177
+
178
+ export type { DatasourceConnectionSpec, DatasourceDraft, DatasourceDriverHandle, DatasourceOrigin, DatasourceSummary, IDatasourceAdminService, IDatasourceDriverFactory, SecretInput, TestConnectionResult };
@@ -0,0 +1,178 @@
1
+ /**
2
+ * IDatasourceAdminService — runtime datasource lifecycle contract
3
+ * (ADR-0015 Addendum: Runtime UI-Created Datasources).
4
+ *
5
+ * Where {@link IExternalDatasourceService} covers *federation* (introspection,
6
+ * object drafting, schema validation) of datasources that already exist, this
7
+ * service covers their *lifecycle*: testing a connection before saving,
8
+ * creating / updating / removing a **runtime** datasource (`origin: 'runtime'`),
9
+ * and listing all datasources with their provenance + health.
10
+ *
11
+ * Code-defined datasources (`origin: 'code'`, authored as `*.datasource.ts`)
12
+ * are read-only here: `updateDatasource` / `removeDatasource` reject them, and
13
+ * a runtime datasource never shadows a code one of the same name (code wins).
14
+ *
15
+ * Credentials are never persisted in cleartext: callers pass a {@link SecretInput}
16
+ * separately from the connection `config`; the implementation encrypts it into
17
+ * the secret store (`sys_secret`) and persists only an opaque `credentialsRef`.
18
+ */
19
+ /** Provenance of a datasource definition. */
20
+ type DatasourceOrigin = 'code' | 'runtime';
21
+ /**
22
+ * A cleartext secret (password or full connection string) supplied for a
23
+ * create/update/test call. Never persisted as-is — encrypted into the secret
24
+ * store, with only the returned handle (`credentialsRef`) kept on the record.
25
+ */
26
+ interface SecretInput {
27
+ /** The cleartext value to encrypt (e.g. password or connection string). */
28
+ value: string;
29
+ /** Optional secret-store namespace (defaults to `'datasource'`). */
30
+ namespace?: string;
31
+ /** Optional secret-store key (defaults to the datasource name). */
32
+ key?: string;
33
+ }
34
+ /**
35
+ * The connection definition a caller supplies to test/create/update. A subset
36
+ * of `Datasource` — server-managed fields (`origin`) are never accepted from
37
+ * the client.
38
+ */
39
+ interface DatasourceDraft {
40
+ name: string;
41
+ label?: string;
42
+ driver: string;
43
+ schemaMode?: 'managed' | 'external' | 'validate-only';
44
+ /** Driver-specific connection config (host, port, database, …). No secrets. */
45
+ config?: Record<string, unknown>;
46
+ /** External federation settings (required when schemaMode != 'managed'). */
47
+ external?: Record<string, unknown>;
48
+ pool?: Record<string, unknown>;
49
+ active?: boolean;
50
+ }
51
+ /** Result of probing a connection (live driver connect + cheap round-trip). */
52
+ interface TestConnectionResult {
53
+ ok: boolean;
54
+ /** Round-trip latency of the probe, when the connection succeeded. */
55
+ latencyMs?: number;
56
+ /** Driver-reported server version, when available. */
57
+ serverVersion?: string;
58
+ /** Human-readable failure reason, when `ok === false`. */
59
+ error?: string;
60
+ }
61
+ /** A datasource with its provenance and current health (no secrets). */
62
+ interface DatasourceSummary {
63
+ name: string;
64
+ label?: string;
65
+ driver: string;
66
+ schemaMode: 'managed' | 'external' | 'validate-only';
67
+ origin: DatasourceOrigin;
68
+ active: boolean;
69
+ /** Validation health: `unvalidated` until the first validate/test runs. */
70
+ status: 'ok' | 'error' | 'unvalidated';
71
+ /** Package id that defines a code-origin datasource (omitted for runtime). */
72
+ definedIn?: string;
73
+ /** True when a runtime row is shadowed by a code definition of the same name. */
74
+ conflictsWithCode?: boolean;
75
+ }
76
+ /**
77
+ * Runtime datasource lifecycle service. Registered into the kernel as the
78
+ * `'datasource-admin'` service; consumed by the REST layer and Studio wizard.
79
+ */
80
+ interface IDatasourceAdminService {
81
+ /** List every datasource (code + runtime) with provenance and health. */
82
+ listDatasources(): Promise<DatasourceSummary[]>;
83
+ /**
84
+ * Probe a connection without persisting anything. Accepts an unsaved draft
85
+ * so the wizard can validate credentials before "Save".
86
+ */
87
+ testConnection(input: DatasourceDraft, secret?: SecretInput): Promise<TestConnectionResult>;
88
+ /**
89
+ * Persist a new runtime datasource (`origin: 'runtime'`, environment-scoped).
90
+ * Rejects when a code-defined datasource of the same name exists.
91
+ */
92
+ createDatasource(input: DatasourceDraft, secret?: SecretInput): Promise<DatasourceSummary>;
93
+ /**
94
+ * Patch an existing runtime datasource. Rejects for code-defined datasources.
95
+ * Passing `secret` re-wraps the stored credential.
96
+ */
97
+ updateDatasource(name: string, patch: Partial<DatasourceDraft>, secret?: SecretInput): Promise<DatasourceSummary>;
98
+ /**
99
+ * Remove a runtime datasource. Rejects for code-defined ones and while
100
+ * objects are still bound to it.
101
+ */
102
+ removeDatasource(name: string): Promise<void>;
103
+ }
104
+
105
+ /**
106
+ * IDatasourceDriverFactory — host-provided capability that builds a live driver
107
+ * from a connection spec (ADR-0015 Addendum §3.5).
108
+ *
109
+ * The framework deliberately ships no universal "driver-by-id" registry —
110
+ * concrete drivers (`SqlDriver`, `MongoDBDriver`, `TursoDriver`, …) are
111
+ * constructed by the host stack and registered as live connections. The
112
+ * runtime-datasource lifecycle (`IDatasourceAdminService`) needs to build a
113
+ * driver from an *unsaved* draft — to probe a connection before "Save", and to
114
+ * hot-register a pool after create/update — so the host exposes this factory
115
+ * as the `'datasource-driver-factory'` service.
116
+ *
117
+ * When no factory is registered, or none `supports()` a given driver id, the
118
+ * admin service degrades gracefully: `testConnection` returns
119
+ * `{ ok: false, error }` and create/update skip hot pool registration (the
120
+ * driver is picked up on the next boot instead).
121
+ *
122
+ * Security: the cleartext `secret` on {@link DatasourceConnectionSpec} is used
123
+ * only to open the live connection. Factories MUST NOT persist or log it.
124
+ */
125
+ /** Everything needed to construct one live driver connection. */
126
+ interface DatasourceConnectionSpec {
127
+ /** Datasource name, when building for an existing/named datasource. */
128
+ name?: string;
129
+ /** Driver id (e.g. `'postgres'`, `'sqlite'`, `'mongodb'`). */
130
+ driver: string;
131
+ /** Driver-specific connection config (host, port, database, …). No secrets. */
132
+ config: Record<string, unknown>;
133
+ /** Cleartext secret (password / DSN) injected for this connection only. */
134
+ secret?: string;
135
+ /** External federation settings (timeouts, allowed schemas, …). */
136
+ external?: Record<string, unknown>;
137
+ /** Connection pool settings. */
138
+ pool?: Record<string, unknown>;
139
+ }
140
+ /**
141
+ * A live (or lazily-connecting) driver handle. Intentionally structural and
142
+ * fully optional so any concrete driver satisfies it — the admin service uses
143
+ * whatever capabilities are present and skips the rest.
144
+ */
145
+ interface DatasourceDriverHandle {
146
+ /** Open the connection / pool. */
147
+ connect?(): Promise<void>;
148
+ /** Close the connection / pool. */
149
+ disconnect?(): Promise<void>;
150
+ /** Cheap liveness round-trip (preferred for probes). */
151
+ ping?(): Promise<unknown>;
152
+ /** Introspect the live schema (fallback probe when `ping` is absent). */
153
+ introspectSchema?(): Promise<unknown>;
154
+ /** Liveness check on the underlying engine driver (probe fallback). */
155
+ checkHealth?(): Promise<boolean>;
156
+ /** Driver-reported server version, when available. */
157
+ serverVersion?(): Promise<string | undefined>;
158
+ /**
159
+ * Escape hatch: the concrete engine driver to hand to
160
+ * `IDataEngine.registerDriver()` when hot-registering a pool. When present
161
+ * the admin service registers *this* (whose `.name` must equal the
162
+ * datasource name for routing) instead of the handle itself; absent ⇒ the
163
+ * handle is assumed to be the driver. Never serialized.
164
+ */
165
+ driver?: unknown;
166
+ }
167
+ /** Host-provided factory that builds drivers from connection specs. */
168
+ interface IDatasourceDriverFactory {
169
+ /** True if this factory can build a driver for the given driver id. */
170
+ supports(driverId: string): boolean;
171
+ /**
172
+ * Build a driver instance for the spec. Implementations may return a
173
+ * not-yet-connected handle; the caller calls `connect()` when needed.
174
+ */
175
+ create(spec: DatasourceConnectionSpec): Promise<DatasourceDriverHandle> | DatasourceDriverHandle;
176
+ }
177
+
178
+ export type { DatasourceConnectionSpec, DatasourceDraft, DatasourceDriverHandle, DatasourceOrigin, DatasourceSummary, IDatasourceAdminService, IDatasourceDriverFactory, SecretInput, TestConnectionResult };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}