@valon-technologies/gestalt 0.0.1-alpha.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/src/cache.ts ADDED
@@ -0,0 +1,304 @@
1
+ import { connect } from "node:net";
2
+
3
+ import { createClient, type Client } from "@connectrpc/connect";
4
+ import { createGrpcTransport } from "@connectrpc/connect-node";
5
+
6
+ import { Cache as CacheService } from "../gen/v1/cache_pb.ts";
7
+ import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
8
+ import type { MaybePromise } from "./api.ts";
9
+
10
+ const ENV_CACHE_SOCKET = "GESTALT_CACHE_SOCKET";
11
+
12
+ export interface CacheEntry {
13
+ key: string;
14
+ value: Uint8Array;
15
+ }
16
+
17
+ export interface CacheSetOptions {
18
+ ttlMs?: number;
19
+ }
20
+
21
+ export interface CacheProviderOptions extends RuntimeProviderOptions {
22
+ get: (key: string) => MaybePromise<Uint8Array | null | undefined>;
23
+ set: (
24
+ key: string,
25
+ value: Uint8Array,
26
+ options?: CacheSetOptions,
27
+ ) => MaybePromise<void>;
28
+ delete: (key: string) => MaybePromise<boolean>;
29
+ touch: (key: string, ttlMs: number) => MaybePromise<boolean>;
30
+ getMany?: (keys: string[]) => MaybePromise<Record<string, Uint8Array>>;
31
+ setMany?: (
32
+ entries: CacheEntry[],
33
+ options?: CacheSetOptions,
34
+ ) => MaybePromise<void>;
35
+ deleteMany?: (keys: string[]) => MaybePromise<number | bigint>;
36
+ }
37
+
38
+ export function cacheSocketEnv(name?: string): string {
39
+ const trimmed = name?.trim() ?? "";
40
+ if (!trimmed) {
41
+ return ENV_CACHE_SOCKET;
42
+ }
43
+ return `${ENV_CACHE_SOCKET}_${trimmed.replace(/[^A-Za-z0-9]/gu, "_").toUpperCase()}`;
44
+ }
45
+
46
+ export class Cache {
47
+ private readonly client: Client<typeof CacheService>;
48
+
49
+ constructor(name?: string) {
50
+ const envName = cacheSocketEnv(name);
51
+ const socketPath = process.env[envName];
52
+ if (!socketPath) {
53
+ throw new Error(`cache: ${envName} is not set`);
54
+ }
55
+
56
+ const transport = createGrpcTransport({
57
+ baseUrl: "http://localhost",
58
+ nodeOptions: {
59
+ createConnection: () => connect(socketPath),
60
+ },
61
+ });
62
+ this.client = createClient(CacheService, transport);
63
+ }
64
+
65
+ async get(key: string): Promise<Uint8Array | undefined> {
66
+ const response = await this.client.get({
67
+ key,
68
+ });
69
+ if (!response.found) {
70
+ return undefined;
71
+ }
72
+ return cloneBytes(response.value);
73
+ }
74
+
75
+ async getMany(keys: string[]): Promise<Record<string, Uint8Array>> {
76
+ const response = await this.client.getMany({
77
+ keys: [...keys],
78
+ });
79
+ return entriesToRecord(response.entries);
80
+ }
81
+
82
+ async set(
83
+ key: string,
84
+ value: Uint8Array,
85
+ options?: CacheSetOptions,
86
+ ): Promise<void> {
87
+ const ttl = toProtoDuration(options?.ttlMs);
88
+ await this.client.set({
89
+ key,
90
+ value: cloneBytes(value),
91
+ ...(ttl ? { ttl } : {}),
92
+ });
93
+ }
94
+
95
+ async setMany(
96
+ entries: Iterable<CacheEntry>,
97
+ options?: CacheSetOptions,
98
+ ): Promise<void> {
99
+ const ttl = toProtoDuration(options?.ttlMs);
100
+ await this.client.setMany({
101
+ entries: cloneEntries(entries),
102
+ ...(ttl ? { ttl } : {}),
103
+ });
104
+ }
105
+
106
+ async delete(key: string): Promise<boolean> {
107
+ const response = await this.client.delete({
108
+ key,
109
+ });
110
+ return response.deleted;
111
+ }
112
+
113
+ async deleteMany(keys: string[]): Promise<number | bigint> {
114
+ const response = await this.client.deleteMany({
115
+ keys: [...keys],
116
+ });
117
+ return toJsInt(response.deleted);
118
+ }
119
+
120
+ async touch(key: string, ttlMs: number): Promise<boolean> {
121
+ const ttl = toProtoDuration(ttlMs);
122
+ const response = await this.client.touch({
123
+ key,
124
+ ...(ttl ? { ttl } : {}),
125
+ });
126
+ return response.touched;
127
+ }
128
+ }
129
+
130
+ export class CacheProvider extends RuntimeProvider {
131
+ readonly kind = "cache" as const;
132
+
133
+ private readonly getHandler: CacheProviderOptions["get"];
134
+ private readonly setHandler: CacheProviderOptions["set"];
135
+ private readonly deleteHandler: CacheProviderOptions["delete"];
136
+ private readonly touchHandler: CacheProviderOptions["touch"];
137
+ private readonly getManyHandler: CacheProviderOptions["getMany"];
138
+ private readonly setManyHandler: CacheProviderOptions["setMany"];
139
+ private readonly deleteManyHandler: CacheProviderOptions["deleteMany"];
140
+
141
+ constructor(options: CacheProviderOptions) {
142
+ super(options);
143
+ this.getHandler = options.get;
144
+ this.setHandler = options.set;
145
+ this.deleteHandler = options.delete;
146
+ this.touchHandler = options.touch;
147
+ this.getManyHandler = options.getMany;
148
+ this.setManyHandler = options.setMany;
149
+ this.deleteManyHandler = options.deleteMany;
150
+ }
151
+
152
+ async get(key: string): Promise<Uint8Array | undefined> {
153
+ const value = await this.getHandler(key);
154
+ if (value == null) {
155
+ return undefined;
156
+ }
157
+ return cloneBytes(value);
158
+ }
159
+
160
+ async getMany(keys: string[]): Promise<Record<string, Uint8Array>> {
161
+ if (this.getManyHandler) {
162
+ return cloneRecord(await this.getManyHandler([...keys]));
163
+ }
164
+ const values = createCacheRecord();
165
+ for (const key of keys) {
166
+ const value = await this.get(key);
167
+ if (value !== undefined) {
168
+ values[key] = cloneBytes(value);
169
+ }
170
+ }
171
+ return values;
172
+ }
173
+
174
+ async set(
175
+ key: string,
176
+ value: Uint8Array,
177
+ options?: CacheSetOptions,
178
+ ): Promise<void> {
179
+ await this.setHandler(key, cloneBytes(value), cloneSetOptions(options));
180
+ }
181
+
182
+ async setMany(
183
+ entries: Iterable<CacheEntry>,
184
+ options?: CacheSetOptions,
185
+ ): Promise<void> {
186
+ if (this.setManyHandler) {
187
+ await this.setManyHandler(cloneEntries(entries), cloneSetOptions(options));
188
+ return;
189
+ }
190
+ for (const entry of entries) {
191
+ await this.set(entry.key, entry.value, options);
192
+ }
193
+ }
194
+
195
+ async delete(key: string): Promise<boolean> {
196
+ return await this.deleteHandler(key);
197
+ }
198
+
199
+ async deleteMany(keys: string[]): Promise<number | bigint> {
200
+ if (this.deleteManyHandler) {
201
+ return await this.deleteManyHandler([...keys]);
202
+ }
203
+ let deleted = 0;
204
+ const seen = new Set<string>();
205
+ for (const key of keys) {
206
+ if (seen.has(key)) {
207
+ continue;
208
+ }
209
+ seen.add(key);
210
+ if (await this.delete(key)) {
211
+ deleted += 1;
212
+ }
213
+ }
214
+ return deleted;
215
+ }
216
+
217
+ async touch(key: string, ttlMs: number): Promise<boolean> {
218
+ return await this.touchHandler(key, ttlMs);
219
+ }
220
+ }
221
+
222
+ export function defineCacheProvider(options: CacheProviderOptions): CacheProvider {
223
+ return new CacheProvider(options);
224
+ }
225
+
226
+ export function isCacheProvider(value: unknown): value is CacheProvider {
227
+ return (
228
+ value instanceof CacheProvider ||
229
+ (typeof value === "object" &&
230
+ value !== null &&
231
+ "kind" in value &&
232
+ (value as { kind?: unknown }).kind === "cache" &&
233
+ "get" in value &&
234
+ "set" in value &&
235
+ "delete" in value &&
236
+ "touch" in value)
237
+ );
238
+ }
239
+
240
+ function cloneBytes(value: Uint8Array | ArrayBuffer): Uint8Array {
241
+ if (value instanceof Uint8Array) {
242
+ return new Uint8Array(value);
243
+ }
244
+ return new Uint8Array(value);
245
+ }
246
+
247
+ function cloneEntries(entries: Iterable<CacheEntry>): CacheEntry[] {
248
+ return [...entries].map((entry) => ({
249
+ key: entry.key,
250
+ value: cloneBytes(entry.value),
251
+ }));
252
+ }
253
+
254
+ function cloneRecord(entries: Record<string, Uint8Array>): Record<string, Uint8Array> {
255
+ const cloned = createCacheRecord();
256
+ for (const [key, value] of Object.entries(entries)) {
257
+ cloned[key] = cloneBytes(value);
258
+ }
259
+ return cloned;
260
+ }
261
+
262
+ function cloneSetOptions(options?: CacheSetOptions): CacheSetOptions | undefined {
263
+ if (!options || options.ttlMs === undefined) {
264
+ return undefined;
265
+ }
266
+ return {
267
+ ttlMs: options.ttlMs,
268
+ };
269
+ }
270
+
271
+ function entriesToRecord(
272
+ entries: ReadonlyArray<{ key: string; found: boolean; value: Uint8Array }>,
273
+ ): Record<string, Uint8Array> {
274
+ const values = createCacheRecord();
275
+ for (const entry of entries) {
276
+ if (!entry.found) {
277
+ continue;
278
+ }
279
+ values[entry.key] = cloneBytes(entry.value);
280
+ }
281
+ return values;
282
+ }
283
+
284
+ function createCacheRecord(): Record<string, Uint8Array> {
285
+ return Object.create(null) as Record<string, Uint8Array>;
286
+ }
287
+
288
+ function toProtoDuration(ttlMs: number | undefined): { seconds: bigint; nanos: number } | undefined {
289
+ if (ttlMs === undefined || !Number.isFinite(ttlMs) || ttlMs <= 0) {
290
+ return undefined;
291
+ }
292
+ const wholeMs = Math.trunc(ttlMs);
293
+ const seconds = Math.trunc(wholeMs / 1000);
294
+ const nanos = Math.trunc((wholeMs % 1000) * 1_000_000);
295
+ return {
296
+ seconds: BigInt(seconds),
297
+ nanos,
298
+ };
299
+ }
300
+
301
+ function toJsInt(value: bigint): number | bigint {
302
+ const asNumber = Number(value);
303
+ return Number.isSafeInteger(asNumber) ? asNumber : value;
304
+ }
package/src/catalog.ts ADDED
@@ -0,0 +1,188 @@
1
+ import { writeFileSync } from "node:fs";
2
+
3
+ import YAML from "yaml";
4
+
5
+ import type { Schema } from "./schema.ts";
6
+
7
+ export interface CatalogParameter {
8
+ name: string;
9
+ type: string;
10
+ description?: string;
11
+ required?: boolean;
12
+ default?: unknown;
13
+ }
14
+
15
+ export interface CatalogSchema {
16
+ type: string;
17
+ description?: string;
18
+ default?: unknown;
19
+ properties?: Record<string, CatalogSchema>;
20
+ required?: string[];
21
+ items?: CatalogSchema;
22
+ }
23
+
24
+ export interface CatalogOperation {
25
+ id: string;
26
+ method: string;
27
+ title?: string;
28
+ description?: string;
29
+ parameters?: CatalogParameter[];
30
+ inputSchema?: CatalogSchema;
31
+ outputSchema?: CatalogSchema;
32
+ tags?: string[];
33
+ readOnly?: boolean;
34
+ visible?: boolean;
35
+ allowedRoles?: string[];
36
+ }
37
+
38
+ export interface Catalog {
39
+ name?: string;
40
+ displayName?: string;
41
+ description?: string;
42
+ iconSvg?: string;
43
+ operations: CatalogOperation[];
44
+ }
45
+
46
+ export function schemaToParameters(
47
+ schema: Schema<unknown> | undefined,
48
+ ): CatalogParameter[] {
49
+ if (!schema?.fields) {
50
+ return [];
51
+ }
52
+ return Object.entries(schema.fields).map(([name, field]) => {
53
+ const parameter: CatalogParameter = {
54
+ name,
55
+ type: field.catalogType,
56
+ };
57
+ if (field.description) {
58
+ parameter.description = field.description;
59
+ }
60
+ if (field.required) {
61
+ parameter.required = true;
62
+ }
63
+ if (field.defaultValue !== undefined) {
64
+ parameter.default = field.defaultValue;
65
+ }
66
+ return parameter;
67
+ });
68
+ }
69
+
70
+ export function schemaToCatalogSchema(
71
+ schema: Schema<unknown> | undefined,
72
+ ): CatalogSchema | undefined {
73
+ if (!schema) {
74
+ return undefined;
75
+ }
76
+ const output: CatalogSchema = {
77
+ type: schema.catalogType,
78
+ };
79
+ if (schema.description) {
80
+ output.description = schema.description;
81
+ }
82
+ if (schema.defaultValue !== undefined) {
83
+ output.default = schema.defaultValue;
84
+ }
85
+ if (schema.fields) {
86
+ const properties: Record<string, CatalogSchema> = {};
87
+ const required: string[] = [];
88
+ for (const [name, field] of Object.entries(schema.fields)) {
89
+ properties[name] = schemaToCatalogSchema(field)!;
90
+ if (field.required) {
91
+ required.push(name);
92
+ }
93
+ }
94
+ output.properties = properties;
95
+ if (required.length > 0) {
96
+ output.required = required;
97
+ }
98
+ }
99
+ if (schema.item) {
100
+ output.items = schemaToCatalogSchema(schema.item)!;
101
+ }
102
+ return output;
103
+ }
104
+
105
+ export function catalogToJson(
106
+ catalog: Catalog | Record<string, unknown> | null | undefined,
107
+ ): string {
108
+ if (!catalog) {
109
+ return "";
110
+ }
111
+ return JSON.stringify(toCatalogJsonObject(catalog));
112
+ }
113
+
114
+ export function catalogToYaml(
115
+ catalog: Catalog | Record<string, unknown>,
116
+ ): string {
117
+ return YAML.stringify(toCatalogJsonObject(catalog));
118
+ }
119
+
120
+ export function writeCatalogYaml(
121
+ path: string,
122
+ catalog: Catalog | Record<string, unknown>,
123
+ ): void {
124
+ writeFileSync(path, catalogToYaml(catalog), "utf8");
125
+ }
126
+
127
+ function toCatalogJsonObject(
128
+ catalog: Catalog | Record<string, unknown>,
129
+ ): Record<string, unknown> {
130
+ if (!("operations" in catalog) || !Array.isArray(catalog.operations)) {
131
+ return {
132
+ ...catalog,
133
+ };
134
+ }
135
+
136
+ const typedCatalog = catalog as Catalog;
137
+ const output: Record<string, unknown> = {
138
+ operations: typedCatalog.operations.map((operation) => {
139
+ const serialized: Record<string, unknown> = {
140
+ id: operation.id,
141
+ method: operation.method,
142
+ };
143
+ if (operation.title) {
144
+ serialized.title = operation.title;
145
+ }
146
+ if (operation.description) {
147
+ serialized.description = operation.description;
148
+ }
149
+ if (operation.parameters && operation.parameters.length > 0) {
150
+ serialized.parameters = operation.parameters;
151
+ }
152
+ if (operation.inputSchema !== undefined) {
153
+ serialized.inputSchema = operation.inputSchema;
154
+ }
155
+ if (operation.outputSchema !== undefined) {
156
+ serialized.outputSchema = operation.outputSchema;
157
+ }
158
+ if (operation.tags && operation.tags.length > 0) {
159
+ serialized.tags = operation.tags;
160
+ }
161
+ if (operation.readOnly !== undefined) {
162
+ serialized.readOnly = operation.readOnly;
163
+ }
164
+ if (operation.visible !== undefined) {
165
+ serialized.visible = operation.visible;
166
+ }
167
+ if (operation.allowedRoles && operation.allowedRoles.length > 0) {
168
+ serialized.allowedRoles = operation.allowedRoles;
169
+ }
170
+ return serialized;
171
+ }),
172
+ };
173
+
174
+ if (typedCatalog.name) {
175
+ output.name = typedCatalog.name;
176
+ }
177
+ if (typedCatalog.displayName) {
178
+ output.displayName = typedCatalog.displayName;
179
+ }
180
+ if (typedCatalog.description) {
181
+ output.description = typedCatalog.description;
182
+ }
183
+ if (typedCatalog.iconSvg) {
184
+ output.iconSvg = typedCatalog.iconSvg;
185
+ }
186
+
187
+ return output;
188
+ }
package/src/index.ts ADDED
@@ -0,0 +1,182 @@
1
+ export {
2
+ connectionParam,
3
+ ok,
4
+ response,
5
+ responseBrand,
6
+ request,
7
+ type Access,
8
+ type MaybePromise,
9
+ type Credential,
10
+ type OperationResult,
11
+ type Request,
12
+ type Response,
13
+ type Subject,
14
+ } from "./api.ts";
15
+ export {
16
+ catalogToJson,
17
+ catalogToYaml,
18
+ schemaToCatalogSchema,
19
+ schemaToParameters,
20
+ writeCatalogYaml,
21
+ type Catalog,
22
+ type CatalogOperation,
23
+ type CatalogParameter,
24
+ type CatalogSchema,
25
+ } from "./catalog.ts";
26
+ export {
27
+ buildPluginBinary,
28
+ buildProviderBinary,
29
+ bunBuildCommand,
30
+ bunTarget,
31
+ parseBuildArgs,
32
+ } from "./build.ts";
33
+ export {
34
+ defineAuthProvider,
35
+ isAuthProvider,
36
+ type AuthenticatedUser,
37
+ type AuthProviderOptions,
38
+ type AuthSessionSettings,
39
+ type BeginLoginRequest,
40
+ type BeginLoginResponse,
41
+ type CompleteLoginRequest,
42
+ } from "./auth.ts";
43
+ export {
44
+ Cache,
45
+ CacheProvider,
46
+ cacheSocketEnv,
47
+ defineCacheProvider,
48
+ isCacheProvider,
49
+ type CacheEntry,
50
+ type CacheProviderOptions,
51
+ type CacheSetOptions,
52
+ } from "./cache.ts";
53
+ export {
54
+ defineSecretsProvider,
55
+ isSecretsProvider,
56
+ type SecretsProviderOptions,
57
+ } from "./secrets.ts";
58
+ export {
59
+ Plugin,
60
+ connectionModeToProtoValue,
61
+ connectionParamToProto,
62
+ defineIntegrationProvider,
63
+ definePlugin,
64
+ isIntegrationProvider,
65
+ operation,
66
+ type ConnectionMode,
67
+ type ConnectionParamDefinition,
68
+ type IntegrationProviderOptions,
69
+ type OperationDefinition,
70
+ type OperationOptions,
71
+ type PluginDefinitionOptions,
72
+ type SessionCatalog,
73
+ type SessionCatalogHandler,
74
+ } from "./plugin.ts";
75
+ export {
76
+ RuntimeProvider,
77
+ isRuntimeProvider,
78
+ slugName,
79
+ type CloseHandler,
80
+ type ConfigureHandler,
81
+ type HealthCheckHandler,
82
+ type ProviderKind,
83
+ type ProviderMetadata,
84
+ type RuntimeProviderOptions,
85
+ type WarningsHandler,
86
+ } from "./provider.ts";
87
+ export {
88
+ array,
89
+ boolean,
90
+ type InferSchema,
91
+ integer,
92
+ number,
93
+ object,
94
+ optional,
95
+ s,
96
+ string,
97
+ type Schema,
98
+ type SchemaOptions,
99
+ } from "./schema.ts";
100
+ export {
101
+ CURRENT_PROTOCOL_VERSION,
102
+ ENV_PROVIDER_PARENT_PID,
103
+ ENV_PROVIDER_SOCKET,
104
+ ENV_WRITE_CATALOG,
105
+ createAuthService,
106
+ createCacheService,
107
+ createSecretsService,
108
+ createProviderService,
109
+ createRuntimeService,
110
+ loadPluginFromTarget,
111
+ loadProviderFromTarget,
112
+ main as runtimeMain,
113
+ parseRuntimeArgs,
114
+ pluginCatalogYaml,
115
+ runBundledPlugin,
116
+ runBundledProvider,
117
+ runLoadedPlugin,
118
+ runLoadedProvider,
119
+ serve,
120
+ } from "./runtime.ts";
121
+ export {
122
+ defaultPluginName,
123
+ defaultProviderName,
124
+ formatModuleTarget,
125
+ formatProviderTarget,
126
+ parseModuleTarget,
127
+ parseProviderTarget,
128
+ readPackageConfig,
129
+ readPackagePluginTarget,
130
+ readPackageProviderTarget,
131
+ resolvePluginImportUrl,
132
+ resolvePluginModulePath,
133
+ resolveProviderImportUrl,
134
+ resolveProviderModulePath,
135
+ type ModuleTarget,
136
+ type PackageConfig,
137
+ type ProviderTarget,
138
+ } from "./target.ts";
139
+ export {
140
+ IndexedDB,
141
+ ObjectStore,
142
+ Index,
143
+ Cursor,
144
+ CursorDirection,
145
+ NotFoundError,
146
+ AlreadyExistsError,
147
+ ColumnType,
148
+ indexedDBSocketEnv,
149
+ type Record,
150
+ type KeyRange,
151
+ type ColumnSchema,
152
+ type IndexSchema,
153
+ type ObjectStoreSchema,
154
+ type OpenCursorOptions,
155
+ } from "./indexeddb.ts";
156
+ export {
157
+ S3,
158
+ S3Object,
159
+ S3Provider,
160
+ S3InvalidRangeError,
161
+ S3NotFoundError,
162
+ S3PreconditionFailedError,
163
+ PresignMethod,
164
+ createS3Service,
165
+ defineS3Provider,
166
+ isS3Provider,
167
+ s3SocketEnv,
168
+ type ByteRange,
169
+ type CopyOptions,
170
+ type ListOptions,
171
+ type ListPage,
172
+ type ObjectMeta,
173
+ type ObjectRef,
174
+ type PresignOptions,
175
+ type PresignResult,
176
+ type ProviderReadResult,
177
+ type ReadOptions,
178
+ type ReadResult,
179
+ type S3BodySource,
180
+ type S3ProviderOptions,
181
+ type WriteOptions,
182
+ } from "./s3.ts";