@liquidmetal-ai/drizzle 0.2.3 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/appify/build.d.ts +8 -0
- package/dist/appify/build.d.ts.map +1 -1
- package/dist/appify/build.js +68 -2
- package/dist/appify/build.test.js +170 -2
- package/dist/appify/parse.d.ts +13 -2
- package/dist/appify/parse.d.ts.map +1 -1
- package/dist/appify/parse.js +51 -2
- package/dist/appify/parse.test.js +149 -2
- package/dist/codestore.js +1 -1
- package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts +526 -479
- package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts.map +1 -1
- package/dist/liquidmetal/v1alpha1/catalog_pb.js +210 -1726
- package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.d.ts +110 -92
- package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.d.ts.map +1 -1
- package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.js +38 -361
- package/dist/liquidmetal/v1alpha1/raindrop_pb.d.ts +14 -13
- package/dist/liquidmetal/v1alpha1/raindrop_pb.d.ts.map +1 -1
- package/dist/liquidmetal/v1alpha1/raindrop_pb.js +10 -48
- package/dist/liquidmetal/v1alpha1/resource_interface_pb.d.ts +146 -122
- package/dist/liquidmetal/v1alpha1/resource_interface_pb.d.ts.map +1 -1
- package/dist/liquidmetal/v1alpha1/resource_interface_pb.js +49 -428
- package/dist/liquidmetal/v1alpha1/search_agent_pb.d.ts +262 -142
- package/dist/liquidmetal/v1alpha1/search_agent_pb.d.ts.map +1 -1
- package/dist/liquidmetal/v1alpha1/search_agent_pb.js +68 -684
- package/eslint.config.mjs +3 -1
- package/package.json +2 -1
- package/src/appify/build.test.ts +197 -2
- package/src/appify/build.ts +71 -2
- package/src/appify/parse.test.ts +154 -2
- package/src/appify/parse.ts +55 -3
- package/src/codestore.ts +1 -1
- package/src/liquidmetal/v1alpha1/catalog_pb.ts +719 -1467
- package/src/liquidmetal/v1alpha1/rainbow_auth_pb.ts +142 -284
- package/src/liquidmetal/v1alpha1/raindrop_pb.ts +21 -35
- package/src/liquidmetal/v1alpha1/resource_interface_pb.ts +192 -378
- package/src/liquidmetal/v1alpha1/search_agent_pb.ts +310 -450
- package/tsconfig.json +11 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -6
- package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts +0 -168
- package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts.map +0 -1
- package/dist/liquidmetal/v1alpha1/catalog_connect.js +0 -171
- package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.d.ts +0 -49
- package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.d.ts.map +0 -1
- package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.js +0 -52
- package/dist/liquidmetal/v1alpha1/rainbow_public_connect.d.ts +0 -26
- package/dist/liquidmetal/v1alpha1/rainbow_public_connect.d.ts.map +0 -1
- package/dist/liquidmetal/v1alpha1/rainbow_public_connect.js +0 -29
- package/dist/liquidmetal/v1alpha1/rainbow_public_pb.d.ts +0 -202
- package/dist/liquidmetal/v1alpha1/rainbow_public_pb.d.ts.map +0 -1
- package/dist/liquidmetal/v1alpha1/rainbow_public_pb.js +0 -298
- package/dist/liquidmetal/v1alpha1/resource_interface_connect.d.ts +0 -68
- package/dist/liquidmetal/v1alpha1/resource_interface_connect.d.ts.map +0 -1
- package/dist/liquidmetal/v1alpha1/resource_interface_connect.js +0 -73
- package/dist/liquidmetal/v1alpha1/search_agent_connect.d.ts +0 -170
- package/dist/liquidmetal/v1alpha1/search_agent_connect.d.ts.map +0 -1
- package/dist/liquidmetal/v1alpha1/search_agent_connect.js +0 -173
- package/src/liquidmetal/v1alpha1/catalog_connect.ts +0 -174
- package/src/liquidmetal/v1alpha1/rainbow_auth_connect.ts +0 -55
- package/src/liquidmetal/v1alpha1/rainbow_public_connect.ts +0 -32
- package/src/liquidmetal/v1alpha1/rainbow_public_pb.ts +0 -366
- package/src/liquidmetal/v1alpha1/resource_interface_connect.ts +0 -77
- package/src/liquidmetal/v1alpha1/search_agent_connect.ts +0 -176
package/eslint.config.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liquidmetal-ai/drizzle",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Raindrop core operational libraries",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"author": "Ian Sung-Schenck",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"devDependencies": {
|
|
20
|
+
"@bufbuild/protobuf": "^2.2.3",
|
|
20
21
|
"@changesets/cli": "^2.27.9",
|
|
21
22
|
"@eslint/js": "^9.11.1",
|
|
22
23
|
"@repo/eslint-config": "*",
|
package/src/appify/build.test.ts
CHANGED
|
@@ -147,9 +147,9 @@ application "my-app" {
|
|
|
147
147
|
|
|
148
148
|
// Check the smartbucket properties
|
|
149
149
|
const smartBucket = apps[0]!.smartBucket[0]!;
|
|
150
|
-
expect(valueOf(smartBucket.name)).toBe(
|
|
150
|
+
expect(valueOf(smartBucket.name)).toBe('my-smart-bucket');
|
|
151
151
|
expect(smartBucket.locationHint).toBeDefined();
|
|
152
|
-
expect(valueOf(smartBucket.locationHint!)).toBe(
|
|
152
|
+
expect(valueOf(smartBucket.locationHint!)).toBe('enam');
|
|
153
153
|
});
|
|
154
154
|
|
|
155
155
|
test('smartbucket with invalid properties', () => {
|
|
@@ -194,3 +194,198 @@ application "foo" {
|
|
|
194
194
|
},
|
|
195
195
|
]);
|
|
196
196
|
});
|
|
197
|
+
|
|
198
|
+
test('service with labels and annotations', () => {
|
|
199
|
+
const CONFIG = `
|
|
200
|
+
application "my-app" {
|
|
201
|
+
service "my-service" {
|
|
202
|
+
labels = {
|
|
203
|
+
environment = "production"
|
|
204
|
+
team = "platform"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
annotations = {
|
|
208
|
+
description = "Service for handling API requests"
|
|
209
|
+
version = "1.0.0"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
const tokenizer = new Tokenizer(CONFIG);
|
|
215
|
+
const parser = new Parser(tokenizer);
|
|
216
|
+
const ast = parser.parse();
|
|
217
|
+
expect(parser.errors).toEqual([]);
|
|
218
|
+
|
|
219
|
+
const [apps, errors] = buildManifest(ast);
|
|
220
|
+
expect(errors).toEqual([]);
|
|
221
|
+
|
|
222
|
+
// Check that we have exactly one application with one service
|
|
223
|
+
expect(apps.length).toBe(1);
|
|
224
|
+
expect(apps[0]!.service.length).toBe(1);
|
|
225
|
+
|
|
226
|
+
// Check the service's labels and annotations
|
|
227
|
+
const service = apps[0]!.service[0]!;
|
|
228
|
+
|
|
229
|
+
// Verify labels
|
|
230
|
+
expect(service.labels).toBeDefined();
|
|
231
|
+
expect(service.labels!['environment']).toBe('production');
|
|
232
|
+
expect(service.labels!['team']).toBe('platform');
|
|
233
|
+
|
|
234
|
+
// Verify annotations
|
|
235
|
+
expect(service.annotations).toBeDefined();
|
|
236
|
+
expect(service.annotations!['description']).toBe('Service for handling API requests');
|
|
237
|
+
expect(service.annotations!['version']).toBe('1.0.0');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('service with non-string labels and annotations', () => {
|
|
241
|
+
const CONFIG = `
|
|
242
|
+
application "my-app" {
|
|
243
|
+
service "my-service" {
|
|
244
|
+
labels = {
|
|
245
|
+
environment = "production"
|
|
246
|
+
priority = 1
|
|
247
|
+
isActive = true
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
annotations = {
|
|
251
|
+
description = "Service for handling API requests"
|
|
252
|
+
maxInstances = 5
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
const tokenizer = new Tokenizer(CONFIG);
|
|
258
|
+
const parser = new Parser(tokenizer);
|
|
259
|
+
const ast = parser.parse();
|
|
260
|
+
expect(parser.errors).toEqual([]);
|
|
261
|
+
|
|
262
|
+
const [_, errors] = buildManifest(ast);
|
|
263
|
+
|
|
264
|
+
// Should have errors for non-string values
|
|
265
|
+
expect(errors.length).toBe(3);
|
|
266
|
+
|
|
267
|
+
// Check error messages
|
|
268
|
+
const errorMessages = errors.map((err) => err.message);
|
|
269
|
+
expect(errorMessages).toContain('labels must have string values, found non-string value for key "priority"');
|
|
270
|
+
expect(errorMessages).toContain('labels must have string values, found non-string value for key "isActive"');
|
|
271
|
+
expect(errorMessages).toContain('annotations must have string values, found non-string value for key "maxInstances"');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('service with invalid labels type', () => {
|
|
275
|
+
const CONFIG = `
|
|
276
|
+
application "my-app" {
|
|
277
|
+
service "my-service" {
|
|
278
|
+
labels = "not-an-object"
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
`;
|
|
282
|
+
const tokenizer = new Tokenizer(CONFIG);
|
|
283
|
+
const parser = new Parser(tokenizer);
|
|
284
|
+
const ast = parser.parse();
|
|
285
|
+
expect(parser.errors).toEqual([]);
|
|
286
|
+
|
|
287
|
+
const [, errors] = buildManifest(ast);
|
|
288
|
+
|
|
289
|
+
// Should have one error for the invalid type
|
|
290
|
+
expect(errors.length).toBe(1);
|
|
291
|
+
expect(errors[0]!.message).toBe('labels must be an object');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('actor with labels and annotations', () => {
|
|
295
|
+
const CONFIG = `
|
|
296
|
+
application "my-app" {
|
|
297
|
+
actor "my-actor" {
|
|
298
|
+
labels = {
|
|
299
|
+
environment = "production"
|
|
300
|
+
team = "platform"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
annotations = {
|
|
304
|
+
description = "Actor for processing background jobs"
|
|
305
|
+
version = "1.0.0"
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
visibility = "protected"
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
const tokenizer = new Tokenizer(CONFIG);
|
|
313
|
+
const parser = new Parser(tokenizer);
|
|
314
|
+
const ast = parser.parse();
|
|
315
|
+
expect(parser.errors).toEqual([]);
|
|
316
|
+
|
|
317
|
+
const [apps, errors] = buildManifest(ast);
|
|
318
|
+
expect(errors).toEqual([]);
|
|
319
|
+
|
|
320
|
+
// Check that we have exactly one application with one actor
|
|
321
|
+
expect(apps.length).toBe(1);
|
|
322
|
+
expect(apps[0]!.actor.length).toBe(1);
|
|
323
|
+
|
|
324
|
+
// Check the actor's labels and annotations
|
|
325
|
+
const actor = apps[0]!.actor[0]!;
|
|
326
|
+
|
|
327
|
+
// Verify labels
|
|
328
|
+
expect(actor.labels).toBeDefined();
|
|
329
|
+
expect(actor.labels!['environment']).toBe('production');
|
|
330
|
+
expect(actor.labels!['team']).toBe('platform');
|
|
331
|
+
|
|
332
|
+
// Verify annotations
|
|
333
|
+
expect(actor.annotations).toBeDefined();
|
|
334
|
+
expect(actor.annotations!['description']).toBe('Actor for processing background jobs');
|
|
335
|
+
expect(actor.annotations!['version']).toBe('1.0.0');
|
|
336
|
+
|
|
337
|
+
// Verify other properties still work
|
|
338
|
+
expect(valueOf(actor.visibility!)).toBe('protected');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('observer with labels and annotations', () => {
|
|
342
|
+
const CONFIG = `
|
|
343
|
+
application "my-app" {
|
|
344
|
+
bucket "my-bucket" {}
|
|
345
|
+
|
|
346
|
+
observer "my-observer" {
|
|
347
|
+
labels = {
|
|
348
|
+
environment = "production"
|
|
349
|
+
team = "platform"
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
annotations = {
|
|
353
|
+
description = "Observer for monitoring bucket changes"
|
|
354
|
+
version = "1.0.0"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
source {
|
|
358
|
+
bucket = "my-bucket"
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
`;
|
|
363
|
+
const tokenizer = new Tokenizer(CONFIG);
|
|
364
|
+
const parser = new Parser(tokenizer);
|
|
365
|
+
const ast = parser.parse();
|
|
366
|
+
expect(parser.errors).toEqual([]);
|
|
367
|
+
|
|
368
|
+
const [apps, errors] = buildManifest(ast);
|
|
369
|
+
expect(errors).toEqual([]);
|
|
370
|
+
|
|
371
|
+
// Check that we have exactly one application with one observer
|
|
372
|
+
expect(apps.length).toBe(1);
|
|
373
|
+
expect(apps[0]!.observer.length).toBe(1);
|
|
374
|
+
|
|
375
|
+
// Check the observer's labels and annotations
|
|
376
|
+
const observer = apps[0]!.observer[0]!;
|
|
377
|
+
|
|
378
|
+
// Verify labels
|
|
379
|
+
expect(observer.labels).toBeDefined();
|
|
380
|
+
expect(observer.labels!['environment']).toBe('production');
|
|
381
|
+
expect(observer.labels!['team']).toBe('platform');
|
|
382
|
+
|
|
383
|
+
// Verify annotations
|
|
384
|
+
expect(observer.annotations).toBeDefined();
|
|
385
|
+
expect(observer.annotations!['description']).toBe('Observer for monitoring bucket changes');
|
|
386
|
+
expect(observer.annotations!['version']).toBe('1.0.0');
|
|
387
|
+
|
|
388
|
+
// Verify source still works
|
|
389
|
+
expect(observer.source.length).toBe(1);
|
|
390
|
+
expect(valueOf(observer.source[0]!.bucket!)).toBe('my-bucket');
|
|
391
|
+
});
|
package/src/appify/build.ts
CHANGED
|
@@ -136,6 +136,37 @@ function buildAssignment<T>(obj: T, field: keyof T, expected: NodeType, child: A
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// buildLabelset creates and validates a set of string-only key-value pairs,
|
|
140
|
+
// reporting errors for any non-string values.
|
|
141
|
+
function buildLabelset<T>(target: T, field: keyof T, child: AssignmentNode, errors: ConfigError[]): void {
|
|
142
|
+
if (child.value.type !== 'object') {
|
|
143
|
+
errors.push({ message: `${child.key.value} must be an object`, ...child });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Initialize the field as an empty object if it doesn't exist
|
|
148
|
+
if (!target[field]) {
|
|
149
|
+
target[field] = {} as T[keyof T];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Get the object we're populating
|
|
153
|
+
const labelset = target[field] as Record<string, string>;
|
|
154
|
+
|
|
155
|
+
// Process each property in the object
|
|
156
|
+
for (const prop of child.value.properties) {
|
|
157
|
+
// Handle indentifiers and strings as object keys.
|
|
158
|
+
const key = prop.key.type === 'string' ? valueOf(prop.key) : prop.key.value;
|
|
159
|
+
if (prop.value.type === 'string') {
|
|
160
|
+
labelset[key] = valueOf(prop.value);
|
|
161
|
+
} else {
|
|
162
|
+
errors.push({
|
|
163
|
+
message: `${child.key.value} must have string values, found non-string value for key "${prop.key.value}"`,
|
|
164
|
+
...prop.value,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
139
170
|
export function buildApplication(node: StanzaNode): [Application | undefined, ConfigError[]] {
|
|
140
171
|
const errors: ConfigError[] = [];
|
|
141
172
|
// Build out the application name.
|
|
@@ -187,7 +218,16 @@ export function buildApplication(node: StanzaNode): [Application | undefined, Co
|
|
|
187
218
|
}
|
|
188
219
|
break;
|
|
189
220
|
case 'assignment':
|
|
190
|
-
|
|
221
|
+
switch (child.key.value) {
|
|
222
|
+
case 'labels':
|
|
223
|
+
buildLabelset(app, 'labels', child, errors);
|
|
224
|
+
break;
|
|
225
|
+
case 'annotations':
|
|
226
|
+
buildLabelset(app, 'annotations', child, errors);
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
errors.push({ message: 'unexpected assignment', ...child });
|
|
230
|
+
}
|
|
191
231
|
break;
|
|
192
232
|
case 'comment':
|
|
193
233
|
break;
|
|
@@ -232,6 +272,12 @@ function buildService(stanza: StanzaNode): [Service, ConfigError[]] {
|
|
|
232
272
|
case 'visibility':
|
|
233
273
|
buildAssignment(service, 'visibility', 'string', child, errors);
|
|
234
274
|
break;
|
|
275
|
+
case 'labels':
|
|
276
|
+
buildLabelset(service, 'labels', child, errors);
|
|
277
|
+
break;
|
|
278
|
+
case 'annotations':
|
|
279
|
+
buildLabelset(service, 'annotations', child, errors);
|
|
280
|
+
break;
|
|
235
281
|
default:
|
|
236
282
|
errors.push({ message: 'unexpected assignment', ...child });
|
|
237
283
|
}
|
|
@@ -452,7 +498,16 @@ function buildObserver(stanza: StanzaNode): [Observer, ConfigError[]] {
|
|
|
452
498
|
}
|
|
453
499
|
break;
|
|
454
500
|
case 'assignment':
|
|
455
|
-
|
|
501
|
+
switch (child.key.value) {
|
|
502
|
+
case 'labels':
|
|
503
|
+
buildLabelset(observer, 'labels', child, errors);
|
|
504
|
+
break;
|
|
505
|
+
case 'annotations':
|
|
506
|
+
buildLabelset(observer, 'annotations', child, errors);
|
|
507
|
+
break;
|
|
508
|
+
default:
|
|
509
|
+
errors.push({ message: 'unexpected assignment', ...child });
|
|
510
|
+
}
|
|
456
511
|
break;
|
|
457
512
|
case 'comment':
|
|
458
513
|
case 'newline':
|
|
@@ -490,6 +545,12 @@ function buildActor(stanza: StanzaNode): [Actor, ConfigError[]] {
|
|
|
490
545
|
case 'visibility':
|
|
491
546
|
buildAssignment(actor, 'visibility', 'string', child, errors);
|
|
492
547
|
break;
|
|
548
|
+
case 'labels':
|
|
549
|
+
buildLabelset(actor, 'labels', child, errors);
|
|
550
|
+
break;
|
|
551
|
+
case 'annotations':
|
|
552
|
+
buildLabelset(actor, 'annotations', child, errors);
|
|
553
|
+
break;
|
|
493
554
|
default:
|
|
494
555
|
errors.push({ message: 'unexpected assignment', ...child });
|
|
495
556
|
}
|
|
@@ -723,6 +784,8 @@ export class Application {
|
|
|
723
784
|
vectorIndex: VectorIndex[] = [];
|
|
724
785
|
kvStore: KvStore[] = [];
|
|
725
786
|
smartBucket: SmartBucket[] = [];
|
|
787
|
+
labels: Record<string, string> = {};
|
|
788
|
+
annotations: Record<string, string> = {};
|
|
726
789
|
|
|
727
790
|
constructor(name: TokenString, obj: ConfigObject) {
|
|
728
791
|
this.name = name;
|
|
@@ -743,6 +806,8 @@ export class Service {
|
|
|
743
806
|
domains: Domain[] = [];
|
|
744
807
|
bindings: Binding[] = [];
|
|
745
808
|
env: Env[] = [];
|
|
809
|
+
labels: Record<string, string> = {};
|
|
810
|
+
annotations: Record<string, string> = {};
|
|
746
811
|
|
|
747
812
|
constructor(name: TokenString, obj: ConfigObject) {
|
|
748
813
|
this.name = name;
|
|
@@ -756,6 +821,8 @@ export class Actor {
|
|
|
756
821
|
bindings: Binding[] = [];
|
|
757
822
|
env: Env[] = [];
|
|
758
823
|
visibility?: TokenString;
|
|
824
|
+
labels: Record<string, string> = {};
|
|
825
|
+
annotations: Record<string, string> = {};
|
|
759
826
|
|
|
760
827
|
constructor(name: TokenString, obj: ConfigObject) {
|
|
761
828
|
this.name = name;
|
|
@@ -784,6 +851,8 @@ export class Observer {
|
|
|
784
851
|
source: Source[] = [];
|
|
785
852
|
bindings: Binding[] = [];
|
|
786
853
|
env: Env[] = [];
|
|
854
|
+
labels: Record<string, string> = {};
|
|
855
|
+
annotations: Record<string, string> = {};
|
|
787
856
|
|
|
788
857
|
constructor(name: TokenString, obj: ConfigObject) {
|
|
789
858
|
this.name = name;
|
package/src/appify/parse.test.ts
CHANGED
|
@@ -34,7 +34,6 @@ actions = ['PutObject', 'DeleteObject']
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
}
|
|
38
37
|
`;
|
|
39
38
|
|
|
40
39
|
const CONFIG_FORMATTED = `
|
|
@@ -272,13 +271,38 @@ application "my-app" {
|
|
|
272
271
|
}
|
|
273
272
|
foo = {
|
|
274
273
|
bar = "baz"
|
|
274
|
+
"qux" = 123
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
`);
|
|
279
279
|
const parser = new Parser(tokenizer);
|
|
280
|
-
|
|
280
|
+
const translator = new JSONTranslator();
|
|
281
|
+
const ast = parser.parse();
|
|
281
282
|
expect(parser.errors).toHaveLength(0);
|
|
283
|
+
|
|
284
|
+
const result = translator.translate(ast);
|
|
285
|
+
expect(result).toEqual({
|
|
286
|
+
application: [
|
|
287
|
+
{
|
|
288
|
+
name: 'my-app',
|
|
289
|
+
service: [
|
|
290
|
+
{
|
|
291
|
+
name: 'my-service',
|
|
292
|
+
domain: [
|
|
293
|
+
{
|
|
294
|
+
fqdn: 'testymctestface.com',
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
foo: {
|
|
298
|
+
bar: 'baz',
|
|
299
|
+
"\"qux\"": 123,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
});
|
|
282
306
|
});
|
|
283
307
|
|
|
284
308
|
test('parse object with trash', () => {
|
|
@@ -334,3 +358,131 @@ application "my-app" {
|
|
|
334
358
|
// Failed to close 3 curly braces.
|
|
335
359
|
expect(parser.errors).toHaveLength(3);
|
|
336
360
|
});
|
|
361
|
+
|
|
362
|
+
test('translate objects', () => {
|
|
363
|
+
const CONFIG = `
|
|
364
|
+
application "my-app" {
|
|
365
|
+
service "my-service" {
|
|
366
|
+
labels = {
|
|
367
|
+
environment = "production"
|
|
368
|
+
team = "platform"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
annotations = {
|
|
372
|
+
description = "Service for handling API requests"
|
|
373
|
+
version = "1.0.0"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
const tokenizer = new Tokenizer(CONFIG);
|
|
379
|
+
const parser = new Parser(tokenizer);
|
|
380
|
+
const translator = new JSONTranslator();
|
|
381
|
+
const ast = parser.parse();
|
|
382
|
+
expect(parser.errors).toEqual([]);
|
|
383
|
+
|
|
384
|
+
const result = translator.translate(ast);
|
|
385
|
+
expect(result).toEqual({
|
|
386
|
+
application: [
|
|
387
|
+
{
|
|
388
|
+
name: 'my-app',
|
|
389
|
+
service: [
|
|
390
|
+
{
|
|
391
|
+
name: 'my-service',
|
|
392
|
+
labels: {
|
|
393
|
+
environment: 'production',
|
|
394
|
+
team: 'platform',
|
|
395
|
+
},
|
|
396
|
+
annotations: {
|
|
397
|
+
description: 'Service for handling API requests',
|
|
398
|
+
version: '1.0.0',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('translate actor and observer objects', () => {
|
|
408
|
+
const CONFIG = `
|
|
409
|
+
application "my-app" {
|
|
410
|
+
bucket "my-bucket" {}
|
|
411
|
+
|
|
412
|
+
actor "my-actor" {
|
|
413
|
+
labels = {
|
|
414
|
+
environment = "production"
|
|
415
|
+
team = "platform"
|
|
416
|
+
}
|
|
417
|
+
annotations = {
|
|
418
|
+
description = "Actor for background tasks"
|
|
419
|
+
version = "1.0.0"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
observer "my-observer" {
|
|
424
|
+
labels = {
|
|
425
|
+
environment = "staging"
|
|
426
|
+
team = "platform"
|
|
427
|
+
}
|
|
428
|
+
annotations = {
|
|
429
|
+
description = "Observer for bucket events"
|
|
430
|
+
version = "1.0.0"
|
|
431
|
+
}
|
|
432
|
+
source {
|
|
433
|
+
bucket = "my-bucket"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
`;
|
|
438
|
+
const tokenizer = new Tokenizer(CONFIG);
|
|
439
|
+
const parser = new Parser(tokenizer);
|
|
440
|
+
const translator = new JSONTranslator();
|
|
441
|
+
const ast = parser.parse();
|
|
442
|
+
expect(parser.errors).toEqual([]);
|
|
443
|
+
|
|
444
|
+
const result = translator.translate(ast);
|
|
445
|
+
expect(result).toEqual({
|
|
446
|
+
application: [
|
|
447
|
+
{
|
|
448
|
+
name: 'my-app',
|
|
449
|
+
bucket: [
|
|
450
|
+
{
|
|
451
|
+
name: 'my-bucket',
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
actor: [
|
|
455
|
+
{
|
|
456
|
+
name: 'my-actor',
|
|
457
|
+
labels: {
|
|
458
|
+
environment: 'production',
|
|
459
|
+
team: 'platform',
|
|
460
|
+
},
|
|
461
|
+
annotations: {
|
|
462
|
+
description: 'Actor for background tasks',
|
|
463
|
+
version: '1.0.0',
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
observer: [
|
|
468
|
+
{
|
|
469
|
+
name: 'my-observer',
|
|
470
|
+
labels: {
|
|
471
|
+
environment: 'staging',
|
|
472
|
+
team: 'platform',
|
|
473
|
+
},
|
|
474
|
+
annotations: {
|
|
475
|
+
description: 'Observer for bucket events',
|
|
476
|
+
version: '1.0.0',
|
|
477
|
+
},
|
|
478
|
+
source: [
|
|
479
|
+
{
|
|
480
|
+
bucket: 'my-bucket',
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
});
|
|
488
|
+
});
|
package/src/appify/parse.ts
CHANGED
|
@@ -177,6 +177,7 @@ export type Node =
|
|
|
177
177
|
| EmptyNode
|
|
178
178
|
| ArrayNode
|
|
179
179
|
| ObjectNode
|
|
180
|
+
| PropertyNode
|
|
180
181
|
| Token;
|
|
181
182
|
|
|
182
183
|
type BaseNode = {
|
|
@@ -212,9 +213,16 @@ export type ArrayNode = BaseNode & {
|
|
|
212
213
|
|
|
213
214
|
export type ObjectNode = BaseNode & {
|
|
214
215
|
type: 'object';
|
|
215
|
-
properties:
|
|
216
|
+
properties: PropertyNode[];
|
|
216
217
|
};
|
|
217
218
|
|
|
219
|
+
export type PropertyNode = BaseNode & {
|
|
220
|
+
type: 'property';
|
|
221
|
+
operator: TokenOperator;
|
|
222
|
+
key: Extract<Token, { type: 'identifier' | 'string' }>;
|
|
223
|
+
value: ExpressionNode | Extract<Token, { type: 'string' | 'number' | 'boolean' }> | ArrayNode | ObjectNode;
|
|
224
|
+
}
|
|
225
|
+
|
|
218
226
|
export type ExpressionNode = BaseNode & {
|
|
219
227
|
type: 'expression';
|
|
220
228
|
operator: TokenOperator;
|
|
@@ -506,8 +514,8 @@ export class Parser {
|
|
|
506
514
|
this.tokenizer.next(['whitespace', 'newline']);
|
|
507
515
|
break;
|
|
508
516
|
}
|
|
509
|
-
if (next.type === 'identifier') {
|
|
510
|
-
properties.push(this.
|
|
517
|
+
if (next.type === 'identifier' || next.type === 'string') {
|
|
518
|
+
properties.push(this.parseProperty(this.tokenizer.next(['whitespace', 'newline'])));
|
|
511
519
|
next = this.tokenizer.peek(['whitespace', 'newline']);
|
|
512
520
|
continue;
|
|
513
521
|
}
|
|
@@ -526,6 +534,25 @@ export class Parser {
|
|
|
526
534
|
return { type: 'object', properties, line: next.line, column: next.column, start: next.start, end: next.end };
|
|
527
535
|
}
|
|
528
536
|
|
|
537
|
+
parseProperty(token: Token): PropertyNode {
|
|
538
|
+
if (token.type !== 'identifier' && token.type !== 'string') {
|
|
539
|
+
throw new Error(`Expected identifier or string but got ${token.type} at ${token.line}:${token.column}`);
|
|
540
|
+
}
|
|
541
|
+
const key = token;
|
|
542
|
+
const operator = this.expect('operator', '=');
|
|
543
|
+
const value = this.parseExpression();
|
|
544
|
+
return {
|
|
545
|
+
type: 'property',
|
|
546
|
+
operator,
|
|
547
|
+
key,
|
|
548
|
+
value,
|
|
549
|
+
line: key.line,
|
|
550
|
+
column: key.column,
|
|
551
|
+
start: key.start,
|
|
552
|
+
end: value.end,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
529
556
|
// Expect a token of a certain type, optionally with a certain
|
|
530
557
|
// value.
|
|
531
558
|
expect<T extends TokenType>(type: T, value?: string): Extract<Token, { type: T }> {
|
|
@@ -660,6 +687,30 @@ export class JSONTranslator {
|
|
|
660
687
|
}
|
|
661
688
|
return null;
|
|
662
689
|
});
|
|
690
|
+
} else if (child.value.type === 'object') {
|
|
691
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
692
|
+
const objValue: { [key: string]: any } = {};
|
|
693
|
+
for (const prop of child.value.properties) {
|
|
694
|
+
if (prop.value.type === 'string') {
|
|
695
|
+
objValue[prop.key.value] = prop.value.value.slice(1, -1);
|
|
696
|
+
} else if (prop.value.type === 'number') {
|
|
697
|
+
objValue[prop.key.value] = parseFloat(prop.value.value);
|
|
698
|
+
} else if (prop.value.type === 'boolean') {
|
|
699
|
+
objValue[prop.key.value] = prop.value.value === 'true';
|
|
700
|
+
} else if (prop.value.type === 'array') {
|
|
701
|
+
objValue[prop.key.value] = prop.value.elements.map((el) => {
|
|
702
|
+
if (el.type === 'string') {
|
|
703
|
+
return el.value.slice(1, -1);
|
|
704
|
+
} else if (el.type === 'number') {
|
|
705
|
+
return parseFloat(el.value);
|
|
706
|
+
} else if (el.type === 'boolean') {
|
|
707
|
+
return el.value === 'true';
|
|
708
|
+
}
|
|
709
|
+
return null;
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
obj[child.key.value] = objValue;
|
|
663
714
|
}
|
|
664
715
|
}
|
|
665
716
|
if (child.type === 'stanza') {
|
|
@@ -704,6 +755,7 @@ export function fmt(ast: Node | Token | null, indent: number = 0, sibling?: Node
|
|
|
704
755
|
}
|
|
705
756
|
}
|
|
706
757
|
break;
|
|
758
|
+
case 'property':
|
|
707
759
|
case 'assignment':
|
|
708
760
|
str += `${INDENT}${fmt(ast.key)} = ${fmt(ast.value)}`;
|
|
709
761
|
break;
|
package/src/codestore.ts
CHANGED
|
@@ -112,7 +112,7 @@ export async function archive(bundle: ReadableBundle): Promise<ArrayBuffer> {
|
|
|
112
112
|
for await (const file of bundle) {
|
|
113
113
|
zip.file(file.name, await file.read());
|
|
114
114
|
}
|
|
115
|
-
return await zip.generateAsync({ type: '
|
|
115
|
+
return await zip.generateAsync({ type: 'arraybuffer' });
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|