@liquidmetal-ai/drizzle 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.
- package/.changeset/README.md +4 -0
- package/.changeset/config.json +11 -0
- package/.changeset/odd-plums-dress.md +7 -0
- package/.turbo/turbo-build.log +6 -0
- package/dist/appify/build.d.ts +130 -0
- package/dist/appify/build.d.ts.map +1 -0
- package/dist/appify/build.js +703 -0
- package/dist/appify/build.test.d.ts +2 -0
- package/dist/appify/build.test.d.ts.map +1 -0
- package/dist/appify/build.test.js +111 -0
- package/dist/appify/index.d.ts +8 -0
- package/dist/appify/index.d.ts.map +1 -0
- package/dist/appify/index.js +28 -0
- package/dist/appify/index.test.d.ts +2 -0
- package/dist/appify/index.test.d.ts.map +1 -0
- package/dist/appify/index.test.js +40 -0
- package/dist/appify/parse.d.ts +151 -0
- package/dist/appify/parse.d.ts.map +1 -0
- package/dist/appify/parse.js +579 -0
- package/dist/appify/parse.test.d.ts +2 -0
- package/dist/appify/parse.test.d.ts.map +1 -0
- package/dist/appify/parse.test.js +319 -0
- package/dist/appify/validate.d.ts +22 -0
- package/dist/appify/validate.d.ts.map +1 -0
- package/dist/appify/validate.js +346 -0
- package/dist/appify/validate.test.d.ts +2 -0
- package/dist/appify/validate.test.d.ts.map +1 -0
- package/dist/appify/validate.test.js +327 -0
- package/dist/codestore.d.ts +34 -0
- package/dist/codestore.d.ts.map +1 -0
- package/dist/codestore.js +54 -0
- package/dist/codestore.test.d.ts +2 -0
- package/dist/codestore.test.d.ts.map +1 -0
- package/dist/codestore.test.js +84 -0
- package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts +147 -0
- package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts.map +1 -0
- package/dist/liquidmetal/v1alpha1/catalog_connect.js +150 -0
- package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts +965 -0
- package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts.map +1 -0
- package/dist/liquidmetal/v1alpha1/catalog_pb.js +1486 -0
- package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.d.ts +49 -0
- package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.d.ts.map +1 -0
- package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.js +52 -0
- package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.d.ts +271 -0
- package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.d.ts.map +1 -0
- package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.js +381 -0
- package/dist/liquidmetal/v1alpha1/raindrop_pb.d.ts +38 -0
- package/dist/liquidmetal/v1alpha1/raindrop_pb.d.ts.map +1 -0
- package/dist/liquidmetal/v1alpha1/raindrop_pb.js +52 -0
- package/dist/unsafe/codestore.d.ts +10 -0
- package/dist/unsafe/codestore.d.ts.map +1 -0
- package/dist/unsafe/codestore.js +23 -0
- package/dist/unsafe/codestore.test.d.ts +2 -0
- package/dist/unsafe/codestore.test.d.ts.map +1 -0
- package/dist/unsafe/codestore.test.js +27 -0
- package/eslint.config.mjs +4 -0
- package/package.json +45 -0
- package/src/appify/build.test.ts +116 -0
- package/src/appify/build.ts +783 -0
- package/src/appify/index.test.ts +43 -0
- package/src/appify/index.ts +33 -0
- package/src/appify/parse.test.ts +337 -0
- package/src/appify/parse.ts +744 -0
- package/src/appify/validate.test.ts +341 -0
- package/src/appify/validate.ts +435 -0
- package/src/codestore.test.ts +98 -0
- package/src/codestore.ts +93 -0
- package/src/liquidmetal/v1alpha1/catalog_connect.ts +153 -0
- package/src/liquidmetal/v1alpha1/catalog_pb.ts +1827 -0
- package/src/liquidmetal/v1alpha1/rainbow_auth_connect.ts +55 -0
- package/src/liquidmetal/v1alpha1/rainbow_auth_pb.ts +476 -0
- package/src/liquidmetal/v1alpha1/raindrop_pb.ts +63 -0
- package/src/unsafe/codestore.test.ts +34 -0
- package/src/unsafe/codestore.ts +29 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Actor,
|
|
3
|
+
Application,
|
|
4
|
+
Binding,
|
|
5
|
+
Bucket,
|
|
6
|
+
ConfigObject,
|
|
7
|
+
Domain,
|
|
8
|
+
Env,
|
|
9
|
+
Observer,
|
|
10
|
+
Queue,
|
|
11
|
+
Service,
|
|
12
|
+
SqlDatabase,
|
|
13
|
+
valueOf,
|
|
14
|
+
VectorIndex,
|
|
15
|
+
VISIBILITIES,
|
|
16
|
+
} from './build.js';
|
|
17
|
+
import { TokenString } from './parse.js';
|
|
18
|
+
|
|
19
|
+
// This is a basic linter-style validation framework that uses
|
|
20
|
+
// validators with event handlers to validate the configuration. It
|
|
21
|
+
// takes validators which can implement any event handler, and it
|
|
22
|
+
// traverses the entire manifest.
|
|
23
|
+
export type ValidationError = ConfigObject & {
|
|
24
|
+
message: string;
|
|
25
|
+
severity: 'info' | 'error' | 'warning';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type On<T> = (app: Application, obj: T) => Promise<ValidationError[]>;
|
|
29
|
+
|
|
30
|
+
export type Validator = {
|
|
31
|
+
onApplication?: On<Application>;
|
|
32
|
+
onService?: On<Service>;
|
|
33
|
+
onActor?: On<Actor>;
|
|
34
|
+
onObserver?: On<Observer>;
|
|
35
|
+
onBucket?: On<Bucket>;
|
|
36
|
+
onQueue?: On<Queue>;
|
|
37
|
+
onEnv?: On<Env>;
|
|
38
|
+
onBinding?: On<Binding>;
|
|
39
|
+
onDomain?: On<Domain>;
|
|
40
|
+
onSqlDatabase?: On<SqlDatabase>;
|
|
41
|
+
onVectorIndex?: On<VectorIndex>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
async function validateHelper<T>(
|
|
45
|
+
validatorFn: On<T> | undefined,
|
|
46
|
+
application: Application,
|
|
47
|
+
obj: T,
|
|
48
|
+
errors: ValidationError[],
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
if (validatorFn) {
|
|
51
|
+
const returnedErrors = await validatorFn(application, obj);
|
|
52
|
+
errors.push(...returnedErrors);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function validate(apps: Application[], validators: Validator[]): Promise<ValidationError[]> {
|
|
57
|
+
const errors: ValidationError[] = [];
|
|
58
|
+
for (const app of apps) {
|
|
59
|
+
for (const validator of validators) {
|
|
60
|
+
await validateHelper(validator.onApplication, app, app, errors);
|
|
61
|
+
await validateServices(app, validator, errors);
|
|
62
|
+
await validateActors(app, validator, errors);
|
|
63
|
+
await validateObservers(app, validator, errors);
|
|
64
|
+
await validateBuckets(app, validator, errors);
|
|
65
|
+
await validateQueues(app, validator, errors);
|
|
66
|
+
await validateEnvs(app, app, validator, errors);
|
|
67
|
+
await validateVectorIndexes(app, validator, errors);
|
|
68
|
+
await validateSqlDatabases(app, validator, errors);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return errors;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function validateServices(app: Application, validator: Validator, errors: ValidationError[]): Promise<void> {
|
|
75
|
+
for (const service of app.service) {
|
|
76
|
+
await validateHelper(validator.onService, app, service, errors);
|
|
77
|
+
await validateBindings(app, service, validator, errors);
|
|
78
|
+
await validateDomains(app, service, validator, errors);
|
|
79
|
+
await validateEnvs(app, service, validator, errors);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function validateActors(app: Application, validator: Validator, errors: ValidationError[]): Promise<void> {
|
|
84
|
+
for (const actor of app.actor) {
|
|
85
|
+
await validateHelper(validator.onActor, app, actor, errors);
|
|
86
|
+
await validateBindings(app, actor, validator, errors);
|
|
87
|
+
await validateEnvs(app, actor, validator, errors);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function validateObservers(app: Application, validator: Validator, errors: ValidationError[]): Promise<void> {
|
|
92
|
+
for (const observer of app.observer) {
|
|
93
|
+
await validateHelper(validator.onObserver, app, observer, errors);
|
|
94
|
+
await validateBindings(app, observer, validator, errors);
|
|
95
|
+
await validateEnvs(app, observer, validator, errors);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function validateBuckets(app: Application, validator: Validator, errors: ValidationError[]): Promise<void> {
|
|
100
|
+
for (const bucket of app.bucket) {
|
|
101
|
+
await validateHelper(validator.onBucket, app, bucket, errors);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function validateQueues(app: Application, validator: Validator, errors: ValidationError[]): Promise<void> {
|
|
106
|
+
for (const queue of app.queue) {
|
|
107
|
+
await validateHelper(validator.onQueue, app, queue, errors);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function validateVectorIndexes(app: Application, validator: Validator, errors: ValidationError[]): Promise<void> {
|
|
112
|
+
for (const vectorIndex of app.vectorIndex) {
|
|
113
|
+
await validateHelper(validator.onVectorIndex, app, vectorIndex, errors);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function validateSqlDatabases(app: Application, validator: Validator, errors: ValidationError[]): Promise<void> {
|
|
118
|
+
for (const sqlDatabase of app.sqlDatabase) {
|
|
119
|
+
await validateHelper(validator.onSqlDatabase, app, sqlDatabase, errors);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function validateEnvs(
|
|
124
|
+
app: Application,
|
|
125
|
+
obj: { env: Env[] },
|
|
126
|
+
validator: Validator,
|
|
127
|
+
errors: ValidationError[],
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
for (const env of obj.env) {
|
|
130
|
+
await validateHelper(validator.onEnv, app, env, errors);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function validateBindings(
|
|
135
|
+
app: Application,
|
|
136
|
+
obj: { bindings: Binding[] },
|
|
137
|
+
validator: Validator,
|
|
138
|
+
errors: ValidationError[],
|
|
139
|
+
): Promise<void> {
|
|
140
|
+
for (const binding of obj.bindings) {
|
|
141
|
+
await validateHelper(validator.onBinding, app, binding, errors);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function validateDomains(
|
|
146
|
+
app: Application,
|
|
147
|
+
obj: { domains: Domain[] },
|
|
148
|
+
validator: Validator,
|
|
149
|
+
errors: ValidationError[],
|
|
150
|
+
): Promise<void> {
|
|
151
|
+
for (const domain of obj.domains) {
|
|
152
|
+
await validateHelper(validator.onDomain, app, domain, errors);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Validators
|
|
157
|
+
|
|
158
|
+
const bindingNameValidator: Validator = {
|
|
159
|
+
onBinding: async (app: Application, binding: Binding): Promise<ValidationError[]> => {
|
|
160
|
+
const errors: ValidationError[] = [];
|
|
161
|
+
for (const bind of binding.bindings) {
|
|
162
|
+
if (!bind.key.value.match(/^[A-Z][A-Z_]*$/)) {
|
|
163
|
+
errors.push({
|
|
164
|
+
message: 'binding names should be UPPER_SNAKE_CASE',
|
|
165
|
+
severity: 'warning',
|
|
166
|
+
...bind.key,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return errors;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const bindingValueValidator: Validator = {
|
|
175
|
+
onBinding: async (app: Application, binding: Binding): Promise<ValidationError[]> => {
|
|
176
|
+
const errors: ValidationError[] = [];
|
|
177
|
+
for (const bind of binding.bindings) {
|
|
178
|
+
if (bind.value.type !== 'string') {
|
|
179
|
+
errors.push({
|
|
180
|
+
message: 'bindings must be to names of modules',
|
|
181
|
+
severity: 'error',
|
|
182
|
+
...bind.value,
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
const ref = valueOf(bind.value);
|
|
186
|
+
// Walk the app to see if this is a module.
|
|
187
|
+
let all: { name: TokenString }[] = [];
|
|
188
|
+
all = all.concat(app.bucket, app.env, app.observer, app.queue, app.service, app.sqlDatabase, app.vectorIndex);
|
|
189
|
+
if (!all.some((b) => valueOf(b.name) === ref)) {
|
|
190
|
+
errors.push({
|
|
191
|
+
message: `binding ${bind.value.value} not found`,
|
|
192
|
+
severity: 'error',
|
|
193
|
+
...bind.value,
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
// TODO [ian] It _is_ found, so let's make sure it's not
|
|
197
|
+
// stomping on an implicit binding and check visibility
|
|
198
|
+
// rules.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return errors;
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const domainValidator: Validator = {
|
|
207
|
+
onDomain: async (app: Application, domain: Domain): Promise<ValidationError[]> => {
|
|
208
|
+
const errors: ValidationError[] = [];
|
|
209
|
+
if (domain.fqdn === undefined) {
|
|
210
|
+
errors.push({
|
|
211
|
+
message: 'domain must have an fqdn',
|
|
212
|
+
severity: 'error',
|
|
213
|
+
...domain.obj,
|
|
214
|
+
});
|
|
215
|
+
} else if (!/^[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/.test(valueOf(domain.fqdn))) {
|
|
216
|
+
errors.push({
|
|
217
|
+
message: `domain ${domain.fqdn.value} is an invalid domain name`,
|
|
218
|
+
severity: 'warning',
|
|
219
|
+
...domain.fqdn,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return errors;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const envValidator: Validator = {
|
|
227
|
+
onEnv: async (app: Application, env: Env): Promise<ValidationError[]> => {
|
|
228
|
+
const errors: ValidationError[] = [];
|
|
229
|
+
if (!valueOf(env.name).match(/^[A-Z][A-Z_]*$/)) {
|
|
230
|
+
errors.push({
|
|
231
|
+
message: 'env names should be UPPER_SNAKE_CASE',
|
|
232
|
+
severity: 'warning',
|
|
233
|
+
...env.name,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (env.secret?.value === 'true' && env.default !== undefined) {
|
|
237
|
+
errors.push({
|
|
238
|
+
message: 'secret env vars cannot have defaults',
|
|
239
|
+
severity: 'error',
|
|
240
|
+
...env.default,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return errors;
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const observerSourceValidator: Validator = {
|
|
248
|
+
onObserver: async (app: Application, observer: Observer): Promise<ValidationError[]> => {
|
|
249
|
+
const errors: ValidationError[] = [];
|
|
250
|
+
const seen = new Set<string>();
|
|
251
|
+
for (const source of observer.source) {
|
|
252
|
+
if (source.bucket && source.queue) {
|
|
253
|
+
errors.push({
|
|
254
|
+
message: 'observer source can only have one of bucket or queue',
|
|
255
|
+
severity: 'error',
|
|
256
|
+
...source.obj,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (!source.bucket && !source.queue) {
|
|
260
|
+
errors.push({
|
|
261
|
+
message: 'observer source must have either a bucket or a queue',
|
|
262
|
+
severity: 'error',
|
|
263
|
+
...source.obj,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (
|
|
267
|
+
source.bucket !== undefined &&
|
|
268
|
+
!app.bucket.some((b) => valueOf(b.name) === (source.bucket && valueOf(source.bucket)))
|
|
269
|
+
) {
|
|
270
|
+
errors.push({
|
|
271
|
+
message: `bucket ${source.bucket.value} not found`,
|
|
272
|
+
severity: 'error',
|
|
273
|
+
...source.bucket,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (
|
|
277
|
+
source.queue !== undefined &&
|
|
278
|
+
!app.queue.some((q) => valueOf(q.name) === (source.queue && valueOf(source.queue)))
|
|
279
|
+
) {
|
|
280
|
+
errors.push({
|
|
281
|
+
message: `queue ${source.queue.value} not found`,
|
|
282
|
+
severity: 'error',
|
|
283
|
+
...source.queue,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const sourceName = (source.bucket && valueOf(source.bucket)) || (source.queue && valueOf(source.queue));
|
|
287
|
+
if (sourceName !== undefined) {
|
|
288
|
+
if (seen.has(sourceName)) {
|
|
289
|
+
errors.push({
|
|
290
|
+
message: `source ${sourceName} is duplicated`,
|
|
291
|
+
severity: 'error',
|
|
292
|
+
...source.obj,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
seen.add(sourceName);
|
|
296
|
+
}
|
|
297
|
+
if (source.rule !== undefined && source.queue !== undefined) {
|
|
298
|
+
errors.push({
|
|
299
|
+
message: 'queue sources do not have bucket notification rules',
|
|
300
|
+
severity: 'error',
|
|
301
|
+
...source.obj,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return errors;
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
async function visibilityValidatorFn(app: Application, obj: { visibility?: TokenString }): Promise<ValidationError[]> {
|
|
310
|
+
const errors: ValidationError[] = [];
|
|
311
|
+
if (obj.visibility && !(VISIBILITIES as readonly string[]).includes(valueOf(obj.visibility))) {
|
|
312
|
+
errors.push({
|
|
313
|
+
message: `visibility must be one of ${VISIBILITIES.join(', ')}`,
|
|
314
|
+
severity: 'error',
|
|
315
|
+
...(obj.visibility || obj),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return errors;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const visibilityValidator: Validator = {
|
|
322
|
+
onService: visibilityValidatorFn,
|
|
323
|
+
onActor: visibilityValidatorFn,
|
|
324
|
+
onQueue: visibilityValidatorFn,
|
|
325
|
+
onBucket: visibilityValidatorFn,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
async function nameValidatorFn(app: Application, obj: { name: TokenString }): Promise<ValidationError[]> {
|
|
329
|
+
const errors: ValidationError[] = [];
|
|
330
|
+
if (!valueOf(obj.name).match(/^[a-z][a-z0-9-]*$/)) {
|
|
331
|
+
errors.push({
|
|
332
|
+
message: `name must be lowercase and dash-separated`,
|
|
333
|
+
severity: 'error',
|
|
334
|
+
...obj.name,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return errors;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const nameValidator: Validator = {
|
|
341
|
+
onApplication: nameValidatorFn,
|
|
342
|
+
onService: nameValidatorFn,
|
|
343
|
+
onObserver: nameValidatorFn,
|
|
344
|
+
onBucket: nameValidatorFn,
|
|
345
|
+
onQueue: nameValidatorFn,
|
|
346
|
+
onActor: nameValidatorFn,
|
|
347
|
+
onSqlDatabase: nameValidatorFn,
|
|
348
|
+
onVectorIndex: nameValidatorFn,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const vectorIndexValidator: Validator = {
|
|
352
|
+
onVectorIndex: async (app: Application, vectorIndex: VectorIndex): Promise<ValidationError[]> => {
|
|
353
|
+
const errors: ValidationError[] = [];
|
|
354
|
+
if (vectorIndex.dimension === undefined) {
|
|
355
|
+
errors.push({
|
|
356
|
+
message: 'vector index must have a dimension',
|
|
357
|
+
severity: 'error',
|
|
358
|
+
...vectorIndex.obj,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
if (
|
|
362
|
+
vectorIndex.metric === undefined ||
|
|
363
|
+
(valueOf(vectorIndex.metric) !== 'cosine' && valueOf(vectorIndex.metric) !== 'euclidean')
|
|
364
|
+
) {
|
|
365
|
+
errors.push({
|
|
366
|
+
message: 'vector index metric must be either cosine or euclidean',
|
|
367
|
+
severity: 'error',
|
|
368
|
+
...(vectorIndex.metric || vectorIndex.obj),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (errors.length > 0) {
|
|
372
|
+
return errors;
|
|
373
|
+
}
|
|
374
|
+
// We have to re-check because the above is too complex for TS.
|
|
375
|
+
if (vectorIndex.dimension === undefined) {
|
|
376
|
+
return errors;
|
|
377
|
+
}
|
|
378
|
+
const dimension = valueOf(vectorIndex.dimension);
|
|
379
|
+
if (!Number.isInteger(dimension)) {
|
|
380
|
+
errors.push({
|
|
381
|
+
message: 'vector index dimension must be an integer',
|
|
382
|
+
severity: 'error',
|
|
383
|
+
...vectorIndex.dimension,
|
|
384
|
+
});
|
|
385
|
+
return errors;
|
|
386
|
+
}
|
|
387
|
+
if (dimension < 1 || dimension > 1536) {
|
|
388
|
+
errors.push({
|
|
389
|
+
message: 'vector index dimension must be between 1 and 1536',
|
|
390
|
+
severity: 'error',
|
|
391
|
+
...vectorIndex.dimension,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return errors;
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const duplicateModuleValidator: Validator = {
|
|
399
|
+
onApplication: async (app: Application, _application: Application): Promise<ValidationError[]> => {
|
|
400
|
+
const errors: ValidationError[] = [];
|
|
401
|
+
const seen = new Set<string>();
|
|
402
|
+
for (const module of [
|
|
403
|
+
...app.bucket,
|
|
404
|
+
...app.env,
|
|
405
|
+
...app.observer,
|
|
406
|
+
...app.queue,
|
|
407
|
+
...app.service,
|
|
408
|
+
...app.sqlDatabase,
|
|
409
|
+
...app.vectorIndex,
|
|
410
|
+
]) {
|
|
411
|
+
const name = valueOf(module.name);
|
|
412
|
+
if (seen.has(name)) {
|
|
413
|
+
errors.push({
|
|
414
|
+
message: `module ${name} has already been defined`,
|
|
415
|
+
severity: 'error',
|
|
416
|
+
...module.name,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
seen.add(name);
|
|
420
|
+
}
|
|
421
|
+
return errors;
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export const VALIDATORS: Validator[] = [
|
|
426
|
+
bindingNameValidator,
|
|
427
|
+
bindingValueValidator,
|
|
428
|
+
domainValidator,
|
|
429
|
+
envValidator,
|
|
430
|
+
observerSourceValidator,
|
|
431
|
+
visibilityValidator,
|
|
432
|
+
nameValidator,
|
|
433
|
+
vectorIndexValidator,
|
|
434
|
+
duplicateModuleValidator,
|
|
435
|
+
];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import { Bundle, BundleBase, archive, unarchive } from './codestore.js';
|
|
3
|
+
|
|
4
|
+
class MemoryBundle extends BundleBase implements Bundle {
|
|
5
|
+
private files: Map<string, Buffer> = new Map();
|
|
6
|
+
|
|
7
|
+
async list(): Promise<string[]> {
|
|
8
|
+
return Array.from(this.files.keys());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async read(name: string): Promise<Buffer> {
|
|
12
|
+
const buff = this.files.get(name);
|
|
13
|
+
if (buff === undefined) {
|
|
14
|
+
throw new Error(`File not found: ${name}`);
|
|
15
|
+
}
|
|
16
|
+
return buff;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async write(name: string, content: Buffer): Promise<void> {
|
|
20
|
+
this.files.set(name, content);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async delete(name: string): Promise<void> {
|
|
24
|
+
this.files.delete(name);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('archive and unarchive', async () => {
|
|
29
|
+
const bundle = new MemoryBundle();
|
|
30
|
+
await bundle.write('file1.txt', Buffer.from('hello'));
|
|
31
|
+
await bundle.write('file2.txt', Buffer.from('world'));
|
|
32
|
+
const hash1 = await bundle.hash();
|
|
33
|
+
const archiveBuffer = await archive(bundle);
|
|
34
|
+
const bundle2 = new MemoryBundle();
|
|
35
|
+
await unarchive(archiveBuffer, bundle2);
|
|
36
|
+
const hash2 = await bundle2.hash();
|
|
37
|
+
expect(hash1).toBe(hash2);
|
|
38
|
+
expect(await bundle2.list()).toEqual(['file1.txt', 'file2.txt']);
|
|
39
|
+
expect((await bundle2.read('file1.txt')).toString()).toBe('hello');
|
|
40
|
+
expect((await bundle2.read('file2.txt')).toString()).toBe('world');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('list bundle with files', async () => {
|
|
44
|
+
const bundle = new MemoryBundle();
|
|
45
|
+
await bundle.write('file1.txt', Buffer.from('hello'));
|
|
46
|
+
await bundle.write('file2.txt', Buffer.from('world'));
|
|
47
|
+
expect(await bundle.list()).toEqual(['file1.txt', 'file2.txt']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('read file from bundle', async () => {
|
|
51
|
+
const bundle = new MemoryBundle();
|
|
52
|
+
await bundle.write('file1.txt', Buffer.from('hello'));
|
|
53
|
+
expect((await bundle.read('file1.txt')).toString()).toBe('hello');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('read missing file from bundle', async () => {
|
|
57
|
+
const bundle = new MemoryBundle();
|
|
58
|
+
expect(bundle.read('file1.txt')).rejects.toThrowError('File not found: file1.txt');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('delete file from bundle', async () => {
|
|
62
|
+
const bundle = new MemoryBundle();
|
|
63
|
+
await bundle.write('file1.txt', Buffer.from('hello'));
|
|
64
|
+
await bundle.delete('file1.txt');
|
|
65
|
+
expect(bundle.read('file1.txt')).rejects.toThrowError('File not found: file1.txt');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('for await of bundle', async () => {
|
|
69
|
+
const bundle = new MemoryBundle();
|
|
70
|
+
await bundle.write('file1.txt', Buffer.from('hello'));
|
|
71
|
+
await bundle.write('file2.txt', Buffer.from('world'));
|
|
72
|
+
const files = [];
|
|
73
|
+
for await (const file of bundle) {
|
|
74
|
+
files.push(file.name);
|
|
75
|
+
}
|
|
76
|
+
expect(files).toEqual(['file1.txt', 'file2.txt']);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('stat file from bundle', async () => {
|
|
80
|
+
const bundle = new MemoryBundle();
|
|
81
|
+
await bundle.write('file1.txt', Buffer.from('hello'));
|
|
82
|
+
const stat = await bundle.stat('file1.txt');
|
|
83
|
+
expect(stat.sizeBytes).toBe(5);
|
|
84
|
+
expect(stat.sha256Hex).toBe('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('stat missing file from bundle', async () => {
|
|
88
|
+
const bundle = new MemoryBundle();
|
|
89
|
+
expect(bundle.stat('file1.txt')).rejects.toThrowError('File not found: file1.txt');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('hash bundle', async () => {
|
|
93
|
+
const bundle = new MemoryBundle();
|
|
94
|
+
await bundle.write('file1.txt', Buffer.from('hello'));
|
|
95
|
+
await bundle.write('file2.txt', Buffer.from('world'));
|
|
96
|
+
const hash = await bundle.hash();
|
|
97
|
+
expect(hash).toBe('936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af');
|
|
98
|
+
});
|
package/src/codestore.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import JSZip from 'jszip';
|
|
3
|
+
|
|
4
|
+
// CodeStore is a mapping of a handler name to a bundle of code.
|
|
5
|
+
export interface CodeStore {
|
|
6
|
+
[key: string]: Bundle;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Stat {
|
|
10
|
+
sizeBytes: number;
|
|
11
|
+
sha256Hex: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ReadableBundle {
|
|
15
|
+
list(): Promise<string[]>;
|
|
16
|
+
read(name: string): Promise<Buffer>;
|
|
17
|
+
stat(name: string): Promise<Stat>;
|
|
18
|
+
[Symbol.asyncIterator](): AsyncGenerator<File>;
|
|
19
|
+
hash(): Promise<string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WritableBundle {
|
|
23
|
+
write(name: string, content: Buffer): Promise<void>;
|
|
24
|
+
delete(name: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type Bundle = ReadableBundle & WritableBundle;
|
|
28
|
+
|
|
29
|
+
export interface File {
|
|
30
|
+
name: string;
|
|
31
|
+
read(): Promise<Buffer>;
|
|
32
|
+
stat(): Promise<Stat>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export abstract class BundleBase implements ReadableBundle {
|
|
36
|
+
abstract list(): Promise<string[]>;
|
|
37
|
+
abstract read(name: string): Promise<Buffer>;
|
|
38
|
+
|
|
39
|
+
async stat(name: string): Promise<Stat> {
|
|
40
|
+
const content = await this.read(name);
|
|
41
|
+
if (content === undefined) {
|
|
42
|
+
throw new Error(`File not found: ${name}`);
|
|
43
|
+
}
|
|
44
|
+
const hash = crypto.createHash('sha256');
|
|
45
|
+
hash.update(content);
|
|
46
|
+
const sha256Hex = hash.digest('hex');
|
|
47
|
+
return {
|
|
48
|
+
sizeBytes: content.length,
|
|
49
|
+
sha256Hex,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// This is an AsyncInterator generator function that returns "File"
|
|
54
|
+
// objects that can be read or stat'd. Use via:
|
|
55
|
+
// for await (const file of bundle) { ... }
|
|
56
|
+
async *[Symbol.asyncIterator](): AsyncGenerator<File> {
|
|
57
|
+
const list = await this.list();
|
|
58
|
+
for (const name of list) {
|
|
59
|
+
yield {
|
|
60
|
+
name,
|
|
61
|
+
read: async () => await this.read(name),
|
|
62
|
+
stat: async () => await this.stat(name),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async hash(): Promise<string> {
|
|
68
|
+
const hash = crypto.createHash('sha256', { encoding: 'hex' });
|
|
69
|
+
for await (const file of this) {
|
|
70
|
+
hash.update(await file.read());
|
|
71
|
+
}
|
|
72
|
+
return hash.digest('hex');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function archive(bundle: ReadableBundle): Promise<ArrayBuffer> {
|
|
77
|
+
const zip = new JSZip();
|
|
78
|
+
for await (const file of bundle) {
|
|
79
|
+
zip.file(file.name, await file.read());
|
|
80
|
+
}
|
|
81
|
+
return await zip.generateAsync({ type: 'arraybuffer' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function unarchive(zipBuffer: ArrayBuffer, bundle: WritableBundle): Promise<void> {
|
|
85
|
+
const zip = await JSZip.loadAsync(zipBuffer);
|
|
86
|
+
for (const [, file] of Object.entries(zip.files)) {
|
|
87
|
+
if (file.dir) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const content = await file.async('arraybuffer');
|
|
91
|
+
await bundle.write(file.name, Buffer.from(content));
|
|
92
|
+
}
|
|
93
|
+
}
|