@liquidmetal-ai/drizzle 0.0.4 → 0.1.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.
Files changed (56) hide show
  1. package/.changeset/slow-guests-stare.md +7 -0
  2. package/.changeset/tasty-tigers-wash.md +6 -0
  3. package/.turbo/turbo-build.log +1 -1
  4. package/.turbo/turbo-lint.log +1 -1
  5. package/.turbo/turbo-test.log +211 -0
  6. package/dist/appify/build.d.ts +1 -0
  7. package/dist/appify/build.d.ts.map +1 -1
  8. package/dist/appify/build.js +4 -3
  9. package/dist/appify/build.test.js +1 -3
  10. package/dist/appify/index.test.js +2 -2
  11. package/dist/appify/parse.test.js +0 -1
  12. package/dist/appify/validate.d.ts.map +1 -1
  13. package/dist/appify/validate.js +34 -7
  14. package/dist/appify/validate.test.js +119 -26
  15. package/dist/codestore.d.ts +40 -0
  16. package/dist/codestore.d.ts.map +1 -1
  17. package/dist/codestore.js +15 -0
  18. package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts +12 -12
  19. package/dist/liquidmetal/v1alpha1/catalog_connect.d.ts.map +1 -1
  20. package/dist/liquidmetal/v1alpha1/catalog_connect.js +12 -12
  21. package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts +119 -77
  22. package/dist/liquidmetal/v1alpha1/catalog_pb.d.ts.map +1 -1
  23. package/dist/liquidmetal/v1alpha1/catalog_pb.js +177 -107
  24. package/dist/raindrop/index.d.ts +2 -0
  25. package/dist/raindrop/index.d.ts.map +1 -0
  26. package/dist/raindrop/index.js +4 -0
  27. package/dist/raindrop/index.test.d.ts +2 -0
  28. package/dist/raindrop/index.test.d.ts.map +1 -0
  29. package/dist/raindrop/index.test.js +5 -0
  30. package/dist/unsafe/codestore.d.ts +5 -0
  31. package/dist/unsafe/codestore.d.ts.map +1 -1
  32. package/dist/unsafe/codestore.js +5 -1
  33. package/dist/unsafe/framework.d.ts +11 -0
  34. package/dist/unsafe/framework.d.ts.map +1 -0
  35. package/dist/unsafe/framework.js +96 -0
  36. package/dist/unsafe/framework.test.d.ts +2 -0
  37. package/dist/unsafe/framework.test.d.ts.map +1 -0
  38. package/dist/unsafe/framework.test.js +175 -0
  39. package/eslint.config.mjs +1 -2
  40. package/package.json +1 -1
  41. package/src/appify/build.test.ts +1 -3
  42. package/src/appify/build.ts +4 -3
  43. package/src/appify/index.test.ts +2 -2
  44. package/src/appify/parse.test.ts +0 -1
  45. package/src/appify/validate.test.ts +123 -26
  46. package/src/appify/validate.ts +39 -7
  47. package/src/codestore.ts +40 -1
  48. package/src/liquidmetal/v1alpha1/catalog_connect.ts +12 -12
  49. package/src/liquidmetal/v1alpha1/catalog_pb.ts +213 -127
  50. package/src/raindrop/index.test.ts +6 -0
  51. package/src/raindrop/index.ts +4 -0
  52. package/src/unsafe/codestore.ts +7 -3
  53. package/src/unsafe/framework.test.ts +205 -0
  54. package/src/unsafe/framework.ts +113 -0
  55. package/tsconfig.json +3 -9
  56. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,175 @@
1
+ import { exec } from 'node:child_process';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import { getPackageVersion } from './framework.js';
7
+ vi.mock('node:child_process', () => ({
8
+ exec: vi.fn(),
9
+ }));
10
+ describe('getPackageVersion', () => {
11
+ let tempDir;
12
+ beforeEach(async () => {
13
+ vi.resetAllMocks();
14
+ // Create a new temp directory for each test
15
+ tempDir = join(tmpdir(), `test-${Date.now()}`);
16
+ await mkdir(tempDir, { recursive: true });
17
+ // Mock process.cwd() to return our temp directory
18
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
19
+ });
20
+ it('should detect npm package version', async () => {
21
+ // Create package-lock.json to indicate npm
22
+ await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
23
+ // Mock npm list command output
24
+ vi.mocked(exec).mockImplementation((command, callback) => {
25
+ if (command.includes('npm list')) {
26
+ callback(null, {
27
+ stdout: JSON.stringify({
28
+ dependencies: {
29
+ 'test-package': {
30
+ version: '1.2.3',
31
+ },
32
+ },
33
+ }),
34
+ stderr: '',
35
+ });
36
+ }
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ return {};
39
+ });
40
+ const version = await getPackageVersion('test-package');
41
+ expect(version).toBe('1.2.3');
42
+ });
43
+ it('should detect yarn package version', async () => {
44
+ // Create yarn.lock to indicate yarn
45
+ await writeFile(join(tempDir, 'yarn.lock'), 'yarn lock contents');
46
+ // Mock yarn list command output
47
+ vi.mocked(exec).mockImplementation((command, callback) => {
48
+ if (command.includes('yarn list')) {
49
+ callback(null, {
50
+ stdout: JSON.stringify({
51
+ data: {
52
+ trees: [
53
+ {
54
+ name: 'test-package',
55
+ version: '2.3.4',
56
+ },
57
+ ],
58
+ },
59
+ }),
60
+ stderr: '',
61
+ });
62
+ }
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ return {};
65
+ });
66
+ const version = await getPackageVersion('test-package');
67
+ expect(version).toBe('2.3.4');
68
+ });
69
+ it('should detect pnpm package version', async () => {
70
+ // Create pnpm-lock.yaml to indicate pnpm
71
+ await writeFile(join(tempDir, 'pnpm-lock.yaml'), 'pnpm lock contents');
72
+ // Mock pnpm list command output
73
+ vi.mocked(exec).mockImplementation((command, callback) => {
74
+ if (command.includes('pnpm list')) {
75
+ callback(null, {
76
+ stdout: JSON.stringify({
77
+ dependencies: {
78
+ 'test-package': {
79
+ version: '3.4.5',
80
+ },
81
+ },
82
+ }),
83
+ stderr: '',
84
+ });
85
+ }
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ return {};
88
+ });
89
+ const version = await getPackageVersion('test-package');
90
+ expect(version).toBe('3.4.5');
91
+ });
92
+ it('should handle nested dependencies', async () => {
93
+ await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
94
+ vi.mocked(exec).mockImplementation((command, callback) => {
95
+ if (command.includes('npm list')) {
96
+ callback(null, {
97
+ stdout: JSON.stringify({
98
+ dependencies: {
99
+ 'parent-package': {
100
+ version: '1.0.0',
101
+ dependencies: {
102
+ 'test-package': {
103
+ version: '4.5.6',
104
+ },
105
+ },
106
+ },
107
+ },
108
+ }),
109
+ stderr: '',
110
+ });
111
+ }
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ return {};
114
+ });
115
+ const version = await getPackageVersion('test-package');
116
+ expect(version).toBe('4.5.6');
117
+ });
118
+ it('should return null when package is not found', async () => {
119
+ await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
120
+ vi.mocked(exec).mockImplementation((command, callback) => {
121
+ if (command.includes('npm list')) {
122
+ callback(null, {
123
+ stdout: JSON.stringify({
124
+ dependencies: {},
125
+ }),
126
+ stderr: '',
127
+ });
128
+ }
129
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
+ return {};
131
+ });
132
+ const version = await getPackageVersion('non-existent-package');
133
+ expect(version).toBeNull();
134
+ });
135
+ it('should search parent directories for package manager files', async () => {
136
+ // Create a nested directory structure
137
+ const nestedDir = join(tempDir, 'nested', 'deeper');
138
+ await mkdir(nestedDir, { recursive: true });
139
+ // Create package-lock.json in parent directory
140
+ await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
141
+ // Set cwd to nested directory
142
+ vi.spyOn(process, 'cwd').mockReturnValue(nestedDir);
143
+ vi.mocked(exec).mockImplementation((command, callback) => {
144
+ if (command.includes('npm list')) {
145
+ callback(null, {
146
+ stdout: JSON.stringify({
147
+ dependencies: {
148
+ 'test-package': {
149
+ version: '5.6.7',
150
+ },
151
+ },
152
+ }),
153
+ stderr: '',
154
+ });
155
+ }
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ return {};
158
+ });
159
+ const version = await getPackageVersion('test-package');
160
+ expect(version).toBe('5.6.7');
161
+ });
162
+ it('should handle command execution errors', async () => {
163
+ await writeFile(join(tempDir, 'package-lock.json'), JSON.stringify({ name: 'test' }));
164
+ vi.mocked(exec).mockImplementation((_command, callback) => {
165
+ callback(new Error('Command failed'), {
166
+ stdout: '',
167
+ stderr: 'failure',
168
+ });
169
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
+ return {};
171
+ });
172
+ const version = await getPackageVersion('test-package');
173
+ expect(version).toBeNull();
174
+ });
175
+ });
package/eslint.config.mjs CHANGED
@@ -1,4 +1,3 @@
1
1
  import repo_config from '@repo/eslint-config/eslint.config.mjs';
2
2
 
3
- export default repo_config.concat([
4
- ]);
3
+ export default repo_config.concat([]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquidmetal-ai/drizzle",
3
- "version": "0.0.4",
3
+ "version": "0.1.1",
4
4
  "description": "Raindrop core operational libraries",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -60,9 +60,7 @@ application "my-app" {
60
60
  visibility = 'application'
61
61
  }
62
62
 
63
- sql_database "my-database" {
64
- schema = "my-schema.sql"
65
- }
63
+ sql_database "my-database" {}
66
64
 
67
65
  vector_index "my-index" {
68
66
  dimension = 768
@@ -345,9 +345,6 @@ function buildSqlDatabase(stanza: StanzaNode): [SqlDatabase, ConfigError[]] {
345
345
  case 'visibility':
346
346
  buildAssignment(sqlDatabase, 'visibility', 'string', child, errors);
347
347
  break;
348
- case 'schema':
349
- buildAssignment(sqlDatabase, 'schema', 'string', child, errors);
350
- break;
351
348
  default:
352
349
  errors.push({ message: 'unexpected assignment', ...child });
353
350
  }
@@ -543,6 +540,9 @@ function buildDomain(stanza: StanzaNode): [Domain, ConfigError[]] {
543
540
  for (const child of stanza.block?.children ?? []) {
544
541
  if (child.type === 'assignment') {
545
542
  switch (child.key.value) {
543
+ case 'cname':
544
+ buildAssignment(domain, 'cname', 'string', child, errors);
545
+ break;
546
546
  case 'fqdn':
547
547
  buildAssignment(domain, 'fqdn', 'string', child, errors);
548
548
  break;
@@ -703,6 +703,7 @@ export class Route {
703
703
 
704
704
  export class Domain {
705
705
  obj: ConfigObject;
706
+ cname?: TokenString;
706
707
  fqdn?: TokenString;
707
708
 
708
709
  constructor(obj: ConfigObject) {
@@ -29,8 +29,8 @@ test('mustManifestFromString with warnings', async () => {
29
29
  const manifest = `
30
30
  application "app" {
31
31
  service "svc" {
32
- domain {
33
- fqdn = "bad_domain_name"
32
+ env "not_uppercase" {
33
+ default = "not_uppercase"
34
34
  }
35
35
  }
36
36
  }
@@ -244,7 +244,6 @@ application "my-app" {
244
244
  `);
245
245
  const parser = new Parser(tokenizer);
246
246
  parser.parse();
247
- console.log(parser.errors);
248
247
  expect(parser.errors).toHaveLength(1);
249
248
  });
250
249
 
@@ -55,32 +55,129 @@ application "my-app" {
55
55
  ]);
56
56
  });
57
57
 
58
- // test('domainValidator', async () => {
59
- // const manifest = `
60
- // application "my-app" {
61
- // service "my-service" {
62
- // domain {
63
- // fqdn = "not-valid.com_foo"
64
- // }
65
- // }
66
- // }
67
- // `;
68
- // const tokenizer = new Tokenizer(manifest);
69
- // const parser = new Parser(tokenizer);
70
- // const parsedManifest = parser.parse();
71
- // const [builtApps] = buildManifest(parsedManifest);
72
- // const validateErrors = await validate(builtApps, VALIDATORS);
73
- // expect(validateErrors).toMatchObject([
74
- // {
75
- // message: 'domain "not-valid.com_foo" is an invalid domain name',
76
- // line: 5,
77
- // column: 14,
78
- // start: 75,
79
- // end: 94,
80
- // severity: 'warning',
81
- // },
82
- // ]);
83
- // });
58
+ test('domainValidator ensures domain.fqdn is valid', async () => {
59
+ const manifest = `
60
+ application "my-app" {
61
+ service "my-service" {
62
+ domain {
63
+ fqdn = "not-valid.com_foo"
64
+ }
65
+ }
66
+ }
67
+ `;
68
+ const tokenizer = new Tokenizer(manifest);
69
+ const parser = new Parser(tokenizer);
70
+ const parsedManifest = parser.parse();
71
+ const [builtApps] = buildManifest(parsedManifest);
72
+ const validateErrors = await validate(builtApps, VALIDATORS);
73
+ expect(validateErrors).toMatchObject([
74
+ {
75
+ message: 'domain "not-valid.com_foo" is an invalid domain name',
76
+ line: 4,
77
+ column: 5,
78
+ start: 53,
79
+ end: 100,
80
+ severity: 'error',
81
+ },
82
+ ]);
83
+ });
84
+
85
+ test('domainValidator ensures domain.fqdn is valid but ignores variable interpolation', async () => {
86
+ const manifest = `
87
+ application "my-app" {
88
+ service "my-service" {
89
+ domain {
90
+ fqdn = "$\{MY_SERVICES_EXAMPLE_URL}"
91
+ }
92
+ }
93
+ }
94
+ `;
95
+ const tokenizer = new Tokenizer(manifest);
96
+ const parser = new Parser(tokenizer);
97
+ const parsedManifest = parser.parse();
98
+ const [builtApps] = buildManifest(parsedManifest);
99
+ const validateErrors = await validate(builtApps, VALIDATORS);
100
+ expect(validateErrors).toEqual([]);
101
+ });
102
+
103
+ test('domainValidator ensures either a cname or fqdn present', async () => {
104
+ const manifest = `
105
+ application "my-app" {
106
+ service "my-service" {
107
+ domain {}
108
+ }
109
+ }
110
+ `;
111
+ const tokenizer = new Tokenizer(manifest);
112
+ const parser = new Parser(tokenizer);
113
+ const parsedManifest = parser.parse();
114
+ const [builtApps] = buildManifest(parsedManifest);
115
+ const validateErrors = await validate(builtApps, VALIDATORS);
116
+ expect(validateErrors).toMatchObject([
117
+ {
118
+ message: 'domain must have either a cname or fqdn attribute',
119
+ line: 4,
120
+ column: 5,
121
+ start: 53,
122
+ end: 62,
123
+ severity: 'error',
124
+ },
125
+ ]);
126
+ });
127
+
128
+ test('domainValidator ensures ensures domain.cname is valid', async () => {
129
+ const manifest = `
130
+ application "my-app" {
131
+ service "my-service" {
132
+ domain {
133
+ cname = "my_cname"
134
+ }
135
+ }
136
+ }
137
+ `;
138
+ const tokenizer = new Tokenizer(manifest);
139
+ const parser = new Parser(tokenizer);
140
+ const parsedManifest = parser.parse();
141
+ const [builtApps] = buildManifest(parsedManifest);
142
+ const validateErrors = await validate(builtApps, VALIDATORS);
143
+ expect(validateErrors).toMatchObject([
144
+ {
145
+ message: 'domain "my_cname" is an invalid subdomain name',
146
+ line: 4,
147
+ column: 5,
148
+ start: 53,
149
+ end: 92,
150
+ severity: 'error',
151
+ },
152
+ ]);
153
+ });
154
+
155
+ test('domainValidator validates domain.cname does not exceed the maximum length', async () => {
156
+ const manifest = `
157
+ application "my-app" {
158
+ service "my-service" {
159
+ domain {
160
+ cname = "really-long-cname-that-is-invalid-because-it-is-too-long"
161
+ }
162
+ }
163
+ }
164
+ `;
165
+ const tokenizer = new Tokenizer(manifest);
166
+ const parser = new Parser(tokenizer);
167
+ const parsedManifest = parser.parse();
168
+ const [builtApps] = buildManifest(parsedManifest);
169
+ const validateErrors = await validate(builtApps, VALIDATORS);
170
+ expect(validateErrors).toMatchObject([
171
+ {
172
+ message: 'domain.cname undefined is too long, maximum 26 characters',
173
+ line: 4,
174
+ column: 5,
175
+ start: 53,
176
+ end: 140,
177
+ severity: 'error',
178
+ },
179
+ ]);
180
+ });
84
181
 
85
182
  test('visibilityValidator', async () => {
86
183
  const manifest = `
@@ -203,23 +203,54 @@ const bindingValueValidator: Validator = {
203
203
  },
204
204
  };
205
205
 
206
- // Disabled for now.
207
- const _domainValidator: Validator = {
206
+ const VALIDATE_DOMAIN_PATTERN = /^[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/;
207
+ const FQDN_MAX_LENGTH = 63;
208
+ const CNAME_MAX_LENGTH = 63 - `.01abcdefghijklmnopqrstuvwx.lmapp.run`.length;
209
+
210
+ const domainValidator: Validator = {
208
211
  onDomain: async (app: Application, domain: Domain): Promise<ValidationError[]> => {
209
212
  const errors: ValidationError[] = [];
210
- if (domain.fqdn === undefined) {
213
+ if (domain.cname == undefined && domain.fqdn === undefined) {
211
214
  errors.push({
212
- message: 'domain must have an fqdn',
215
+ message: 'domain must have either a cname or fqdn attribute',
213
216
  severity: 'error',
214
217
  ...domain.obj,
215
218
  });
216
- } else if (!/^[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/.test(valueOf(domain.fqdn))) {
219
+ return errors;
220
+ }
221
+ if (
222
+ domain.fqdn !== undefined &&
223
+ valueOf(domain.fqdn).indexOf('${') === -1 &&
224
+ !VALIDATE_DOMAIN_PATTERN.test(valueOf(domain.fqdn))
225
+ ) {
217
226
  errors.push({
218
227
  message: `domain ${domain.fqdn.value} is an invalid domain name`,
219
- severity: 'warning',
220
- ...domain.fqdn,
228
+ severity: 'error',
229
+ ...domain.obj,
230
+ });
231
+ }
232
+ if (domain.cname !== undefined && !VALIDATE_DOMAIN_PATTERN.test(`${valueOf(domain.cname)}.example.com`)) {
233
+ errors.push({
234
+ message: `domain ${domain.cname.value} is an invalid subdomain name`,
235
+ severity: 'error',
236
+ ...domain.obj,
237
+ });
238
+ }
239
+ if (domain.cname !== undefined && valueOf(domain.cname).length > CNAME_MAX_LENGTH) {
240
+ errors.push({
241
+ message: `domain.cname ${domain.fqdn} is too long, maximum ${CNAME_MAX_LENGTH} characters`,
242
+ severity: 'error',
243
+ ...domain.obj,
221
244
  });
222
245
  }
246
+ if (domain.fqdn !== undefined && valueOf(domain.fqdn).length > FQDN_MAX_LENGTH) {
247
+ errors.push({
248
+ message: `domain.fqdn ${domain.fqdn} is too long, maximum ${FQDN_MAX_LENGTH} characters`,
249
+ severity: 'error',
250
+ ...domain.obj,
251
+ });
252
+ }
253
+
223
254
  return errors;
224
255
  },
225
256
  };
@@ -438,6 +469,7 @@ const duplicateModuleValidator: Validator = {
438
469
  export const VALIDATORS: Validator[] = [
439
470
  bindingNameValidator,
440
471
  bindingValueValidator,
472
+ domainValidator,
441
473
  envValidator,
442
474
  observerSourceValidator,
443
475
  visibilityValidator,
package/src/codestore.ts CHANGED
@@ -1,16 +1,28 @@
1
1
  import crypto from 'crypto';
2
2
  import JSZip from 'jszip';
3
3
 
4
- // CodeStore is a mapping of a handler name to a bundle of code.
4
+ /**
5
+ * CodeStore represents a collection of code bundles mapped by handler names
6
+ * Used to organize and manage multiple code bundles in a single namespace
7
+ */
5
8
  export interface CodeStore {
6
9
  [key: string]: Bundle;
7
10
  }
8
11
 
12
+ /**
13
+ * Statistics about a file in a bundle
14
+ * Includes size and cryptographic hash information
15
+ */
9
16
  export interface Stat {
10
17
  sizeBytes: number;
11
18
  sha256Hex: string;
12
19
  }
13
20
 
21
+ /**
22
+ * Interface for read-only access to a bundle of files
23
+ * Provides methods to list, read, and get statistics about files
24
+ * Also supports async iteration over files
25
+ */
14
26
  export interface ReadableBundle {
15
27
  list(): Promise<string[]>;
16
28
  read(name: string): Promise<Buffer>;
@@ -19,19 +31,36 @@ export interface ReadableBundle {
19
31
  hash(): Promise<string>;
20
32
  }
21
33
 
34
+ /**
35
+ * Interface for write operations on a bundle of files
36
+ * Provides methods to write and delete files
37
+ */
22
38
  export interface WritableBundle {
23
39
  write(name: string, content: Buffer): Promise<void>;
24
40
  delete(name: string): Promise<void>;
25
41
  }
26
42
 
43
+ /**
44
+ * Complete interface for a bundle of files
45
+ * Combines both read and write operations
46
+ */
27
47
  export type Bundle = ReadableBundle & WritableBundle;
28
48
 
49
+ /**
50
+ * Represents a single file within a bundle
51
+ * Provides methods to read the file's contents and get its statistics
52
+ */
29
53
  export interface File {
30
54
  name: string;
31
55
  read(): Promise<Buffer>;
32
56
  stat(): Promise<Stat>;
33
57
  }
34
58
 
59
+ /**
60
+ * Base implementation of ReadableBundle
61
+ * Provides default implementations for stat(), hash() and async iteration
62
+ * Concrete implementations must provide list() and read() methods
63
+ */
35
64
  export abstract class BundleBase implements ReadableBundle {
36
65
  abstract list(): Promise<string[]>;
37
66
  abstract read(name: string): Promise<Buffer>;
@@ -73,6 +102,11 @@ export abstract class BundleBase implements ReadableBundle {
73
102
  }
74
103
  }
75
104
 
105
+ /**
106
+ * Creates a ZIP archive containing all files from a bundle
107
+ * @param bundle The bundle to archive
108
+ * @returns Promise resolving to the ZIP file contents as an ArrayBuffer
109
+ */
76
110
  export async function archive(bundle: ReadableBundle): Promise<ArrayBuffer> {
77
111
  const zip = new JSZip();
78
112
  for await (const file of bundle) {
@@ -81,6 +115,11 @@ export async function archive(bundle: ReadableBundle): Promise<ArrayBuffer> {
81
115
  return await zip.generateAsync({ type: 'arraybuffer' });
82
116
  }
83
117
 
118
+ /**
119
+ * Extracts files from a ZIP archive into a writable bundle
120
+ * @param zipBuffer The ZIP file contents as an ArrayBuffer
121
+ * @param bundle The target bundle to write the extracted files to
122
+ */
84
123
  export async function unarchive(zipBuffer: ArrayBuffer, bundle: WritableBundle): Promise<void> {
85
124
  const zip = await JSZip.loadAsync(zipBuffer);
86
125
  for (const [, file] of Object.entries(zip.files)) {
@@ -23,6 +23,18 @@ import { MethodKind } from "@bufbuild/protobuf";
23
23
  export const CatalogService = {
24
24
  typeName: "liquidmetal.v1alpha1.CatalogService",
25
25
  methods: {
26
+ /**
27
+ * Bootstrap is a special RPC that is used to bootstrap the system
28
+ * using a one-time token.
29
+ *
30
+ * @generated from rpc liquidmetal.v1alpha1.CatalogService.Bootstrap
31
+ */
32
+ bootstrap: {
33
+ name: "Bootstrap",
34
+ I: BootstrapRequest,
35
+ O: BootstrapResponse,
36
+ kind: MethodKind.Unary,
37
+ },
26
38
  /**
27
39
  * @generated from rpc liquidmetal.v1alpha1.CatalogService.Versions
28
40
  */
@@ -154,18 +166,6 @@ export const CatalogService = {
154
166
  O: QueryResourcesResponse,
155
167
  kind: MethodKind.Unary,
156
168
  },
157
- /**
158
- * Bootstrap is a special RPC that is used to bootstrap the system
159
- * using a one-time token.
160
- *
161
- * @generated from rpc liquidmetal.v1alpha1.CatalogService.Bootstrap
162
- */
163
- bootstrap: {
164
- name: "Bootstrap",
165
- I: BootstrapRequest,
166
- O: BootstrapResponse,
167
- kind: MethodKind.Unary,
168
- },
169
169
  }
170
170
  } as const;
171
171