@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/appify/build.d.ts +8 -0
  3. package/dist/appify/build.d.ts.map +1 -1
  4. package/dist/appify/build.js +68 -2
  5. package/dist/appify/build.test.js +170 -2
  6. package/dist/appify/parse.d.ts +13 -2
  7. package/dist/appify/parse.d.ts.map +1 -1
  8. package/dist/appify/parse.js +51 -2
  9. package/dist/appify/parse.test.js +149 -2
  10. package/dist/codestore.js +1 -1
  11. package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts +526 -479
  12. package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts.map +1 -1
  13. package/dist/liquidmetal/v1alpha1/catalog_pb.js +210 -1726
  14. package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.d.ts +110 -92
  15. package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.d.ts.map +1 -1
  16. package/dist/liquidmetal/v1alpha1/rainbow_auth_pb.js +38 -361
  17. package/dist/liquidmetal/v1alpha1/raindrop_pb.d.ts +14 -13
  18. package/dist/liquidmetal/v1alpha1/raindrop_pb.d.ts.map +1 -1
  19. package/dist/liquidmetal/v1alpha1/raindrop_pb.js +10 -48
  20. package/dist/liquidmetal/v1alpha1/resource_interface_pb.d.ts +146 -122
  21. package/dist/liquidmetal/v1alpha1/resource_interface_pb.d.ts.map +1 -1
  22. package/dist/liquidmetal/v1alpha1/resource_interface_pb.js +49 -428
  23. package/dist/liquidmetal/v1alpha1/search_agent_pb.d.ts +262 -142
  24. package/dist/liquidmetal/v1alpha1/search_agent_pb.d.ts.map +1 -1
  25. package/dist/liquidmetal/v1alpha1/search_agent_pb.js +68 -684
  26. package/eslint.config.mjs +3 -1
  27. package/package.json +2 -1
  28. package/src/appify/build.test.ts +197 -2
  29. package/src/appify/build.ts +71 -2
  30. package/src/appify/parse.test.ts +154 -2
  31. package/src/appify/parse.ts +55 -3
  32. package/src/codestore.ts +1 -1
  33. package/src/liquidmetal/v1alpha1/catalog_pb.ts +719 -1467
  34. package/src/liquidmetal/v1alpha1/rainbow_auth_pb.ts +142 -284
  35. package/src/liquidmetal/v1alpha1/raindrop_pb.ts +21 -35
  36. package/src/liquidmetal/v1alpha1/resource_interface_pb.ts +192 -378
  37. package/src/liquidmetal/v1alpha1/search_agent_pb.ts +310 -450
  38. package/tsconfig.json +11 -3
  39. package/tsconfig.tsbuildinfo +1 -1
  40. package/.turbo/turbo-lint.log +0 -6
  41. package/.turbo/turbo-test.log +0 -6
  42. package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts +0 -168
  43. package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts.map +0 -1
  44. package/dist/liquidmetal/v1alpha1/catalog_connect.js +0 -171
  45. package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.d.ts +0 -49
  46. package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.d.ts.map +0 -1
  47. package/dist/liquidmetal/v1alpha1/rainbow_auth_connect.js +0 -52
  48. package/dist/liquidmetal/v1alpha1/rainbow_public_connect.d.ts +0 -26
  49. package/dist/liquidmetal/v1alpha1/rainbow_public_connect.d.ts.map +0 -1
  50. package/dist/liquidmetal/v1alpha1/rainbow_public_connect.js +0 -29
  51. package/dist/liquidmetal/v1alpha1/rainbow_public_pb.d.ts +0 -202
  52. package/dist/liquidmetal/v1alpha1/rainbow_public_pb.d.ts.map +0 -1
  53. package/dist/liquidmetal/v1alpha1/rainbow_public_pb.js +0 -298
  54. package/dist/liquidmetal/v1alpha1/resource_interface_connect.d.ts +0 -68
  55. package/dist/liquidmetal/v1alpha1/resource_interface_connect.d.ts.map +0 -1
  56. package/dist/liquidmetal/v1alpha1/resource_interface_connect.js +0 -73
  57. package/dist/liquidmetal/v1alpha1/search_agent_connect.d.ts +0 -170
  58. package/dist/liquidmetal/v1alpha1/search_agent_connect.d.ts.map +0 -1
  59. package/dist/liquidmetal/v1alpha1/search_agent_connect.js +0 -173
  60. package/src/liquidmetal/v1alpha1/catalog_connect.ts +0 -174
  61. package/src/liquidmetal/v1alpha1/rainbow_auth_connect.ts +0 -55
  62. package/src/liquidmetal/v1alpha1/rainbow_public_connect.ts +0 -32
  63. package/src/liquidmetal/v1alpha1/rainbow_public_pb.ts +0 -366
  64. package/src/liquidmetal/v1alpha1/resource_interface_connect.ts +0 -77
  65. package/src/liquidmetal/v1alpha1/search_agent_connect.ts +0 -176
package/eslint.config.mjs CHANGED
@@ -1,3 +1,5 @@
1
1
  import repo_config from '@repo/eslint-config/eslint.config.mjs';
2
2
 
3
- export default repo_config.concat([]);
3
+ export default repo_config.concat([
4
+ { ignores: ['src/liquidmetal/v1alpha1/*'] },
5
+ ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquidmetal-ai/drizzle",
3
- "version": "0.2.3",
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": "*",
@@ -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("my-smart-bucket");
150
+ expect(valueOf(smartBucket.name)).toBe('my-smart-bucket');
151
151
  expect(smartBucket.locationHint).toBeDefined();
152
- expect(valueOf(smartBucket.locationHint!)).toBe("enam");
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
+ });
@@ -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
- errors.push({ message: 'unexpected assignment', ...child });
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
- errors.push({ message: 'unexpected assignment', ...child });
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;
@@ -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
- parser.parse();
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
+ });
@@ -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: AssignmentNode[];
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.parseAssignment(this.tokenizer.next(['whitespace', 'newline'])));
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: 'uint8array' });
115
+ return await zip.generateAsync({ type: 'arraybuffer' });
116
116
  }
117
117
 
118
118
  /**