rastack 0.0.16 → 0.0.20

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 (105) hide show
  1. package/CHANGELOG.md +43876 -43013
  2. package/dist/.prettierrc +7 -7
  3. package/dist/compile/analyze.js +19 -11
  4. package/dist/compile/index.js +26 -17
  5. package/dist/compile/manifest.js +2 -3
  6. package/dist/compile/openapi.js +5 -8
  7. package/dist/compile/program.js +19 -10
  8. package/dist/csv-schema.js +5 -6
  9. package/dist/define/index.js +2 -2
  10. package/dist/entity-generation/delete-method.js +8 -10
  11. package/dist/entity-generation/form.js +80 -82
  12. package/dist/entity-generation/get-method.js +35 -36
  13. package/dist/entity-generation/imports.js +24 -20
  14. package/dist/entity-generation/sync-method.d.ts +1 -1
  15. package/dist/entity-generation/sync-method.js +45 -46
  16. package/dist/entity-generation/update-method.js +9 -10
  17. package/dist/rad-wasm-build.d.ts +1 -1
  18. package/dist/rad-wasm-build.js +15 -6
  19. package/dist/rad.js +9 -2
  20. package/dist/schema/camel-to-pastel.js +1 -2
  21. package/dist/schema/capitalise-first-letter.js +1 -2
  22. package/dist/schema/extract-response.js +3 -4
  23. package/dist/schema/fetch-schema.js +4 -16
  24. package/dist/schema/remove-non-model-paths.js +1 -2
  25. package/dist/schema/to-camel-case.js +1 -2
  26. package/dist/schema/to-pastel-case.js +1 -2
  27. package/dist/schema-convert.js +22 -33
  28. package/dist/schema-entities.js +177 -189
  29. package/dist/schema-fetch.js +46 -63
  30. package/dist/schema-full.js +23 -36
  31. package/dist/schema-index.js +16 -27
  32. package/dist/schema-params.js +11 -22
  33. package/dist/seed.js +95 -97
  34. package/hooks/form/form.ts +207 -207
  35. package/hooks/form/index.ts +8 -8
  36. package/hooks/form/interfaces.ts +217 -217
  37. package/hooks/form/structure.ts +39 -39
  38. package/hooks/form/validate-schema.ts +49 -49
  39. package/hooks/index.ts +3 -3
  40. package/hooks/query/api.ts +42 -42
  41. package/hooks/query/delete.ts +45 -45
  42. package/hooks/query/fetch.ts +48 -48
  43. package/hooks/query/index.ts +21 -21
  44. package/hooks/query/interfaces.ts +111 -111
  45. package/hooks/query/list.ts +286 -286
  46. package/hooks/query/update.ts +88 -88
  47. package/hooks/query/url.ts +31 -31
  48. package/hooks/real-time/index.ts +1 -1
  49. package/hooks/real-time/pusher.ts +43 -43
  50. package/jest.config.cjs +6 -6
  51. package/package.json +57 -57
  52. package/provider/index.ts +13 -13
  53. package/provider/provider.tsx +187 -187
  54. package/provider/types.ts +114 -114
  55. package/provider/warehouse.ts +167 -167
  56. package/provider/wasm.ts +124 -124
  57. package/runtime.ts +3 -3
  58. package/src/.prettierrc +7 -7
  59. package/src/compile/analyze.ts +224 -224
  60. package/src/compile/index.ts +86 -86
  61. package/src/compile/manifest.ts +10 -10
  62. package/src/compile/model.ts +69 -69
  63. package/src/compile/openapi.ts +266 -266
  64. package/src/compile/program.ts +40 -40
  65. package/src/csv-schema.ts +187 -187
  66. package/src/define/index.ts +162 -162
  67. package/src/entity-generation/delete-method.ts +37 -37
  68. package/src/entity-generation/form.ts +169 -169
  69. package/src/entity-generation/get-method.ts +80 -80
  70. package/src/entity-generation/imports.ts +110 -110
  71. package/src/entity-generation/sync-method.ts +222 -222
  72. package/src/entity-generation/update-method.ts +38 -38
  73. package/src/index.ts +77 -77
  74. package/src/rad-compile.ts +88 -88
  75. package/src/rad-wasm-build.ts +102 -102
  76. package/src/rad.ts +101 -101
  77. package/src/scan.ts +89 -89
  78. package/src/schema/camel-to-pastel.ts +3 -3
  79. package/src/schema/capitalise-first-letter.ts +3 -3
  80. package/src/schema/extract-response.ts +38 -38
  81. package/src/schema/fetch-schema.ts +12 -12
  82. package/src/schema/remove-non-model-paths.ts +13 -13
  83. package/src/schema/to-camel-case.ts +7 -7
  84. package/src/schema/to-pastel-case.ts +6 -6
  85. package/src/schema-convert.ts +61 -61
  86. package/src/schema-entities.ts +401 -401
  87. package/src/schema-fetch.ts +82 -82
  88. package/src/schema-full.ts +38 -38
  89. package/src/schema-index.ts +29 -29
  90. package/src/schema-params.ts +116 -116
  91. package/src/seed.ts +297 -297
  92. package/sync/engine.ts +392 -392
  93. package/sync/hooks.ts +450 -450
  94. package/sync/index.ts +8 -8
  95. package/sync/persistence.ts +237 -237
  96. package/sync/provider.tsx +91 -91
  97. package/sync/registry.ts +36 -36
  98. package/sync/store.ts +126 -126
  99. package/sync/transactions.ts +300 -300
  100. package/sync/types.ts +94 -94
  101. package/test/compile.spec.ts +192 -192
  102. package/test/csv-schema.spec.ts +143 -143
  103. package/test/schema-entities.spec.ts +688 -688
  104. package/tsconfig.json +24 -24
  105. package/types.ts +18 -18
@@ -1,34 +1,23 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  const path_1 = require("path");
13
4
  const fs_1 = require("fs");
14
- function main() {
15
- return __awaiter(this, void 0, void 0, function* () {
16
- console.log("Generating Schema Index");
17
- const args = process.argv.slice(2);
18
- const basePath = args[0];
19
- const schemaFolderPath = (0, path_1.join)(basePath, "form-schema");
20
- let indexFileData = 'export * from "./interface";\nexport * from "./params";\n';
21
- const schemaFiles = (0, fs_1.readdirSync)(schemaFolderPath);
22
- let compilePromises = schemaFiles.map((schemaFile) => {
23
- const schemaFileName = schemaFile.replace(".json", "");
24
- const formSchemaExport = `export { default as Deref${schemaFileName} } from "./form-schema/${schemaFile}";`;
25
- const schemaExport = `export { default as Schema${schemaFileName} } from "./api-schema/${schemaFile}";`;
26
- return `${formSchemaExport}\n${schemaExport}`;
27
- });
28
- let results = yield Promise.all(compilePromises);
29
- indexFileData = indexFileData + results.join("\n");
30
- const outputFile = (0, path_1.join)(basePath, "index.ts");
31
- (0, fs_1.writeFileSync)(outputFile, indexFileData);
5
+ async function main() {
6
+ console.log("Generating Schema Index");
7
+ const args = process.argv.slice(2);
8
+ const basePath = args[0];
9
+ const schemaFolderPath = (0, path_1.join)(basePath, "form-schema");
10
+ let indexFileData = 'export * from "./interface";\nexport * from "./params";\n';
11
+ const schemaFiles = (0, fs_1.readdirSync)(schemaFolderPath);
12
+ let compilePromises = schemaFiles.map((schemaFile) => {
13
+ const schemaFileName = schemaFile.replace(".json", "");
14
+ const formSchemaExport = `export { default as Deref${schemaFileName} } from "./form-schema/${schemaFile}";`;
15
+ const schemaExport = `export { default as Schema${schemaFileName} } from "./api-schema/${schemaFile}";`;
16
+ return `${formSchemaExport}\n${schemaExport}`;
32
17
  });
18
+ let results = await Promise.all(compilePromises);
19
+ indexFileData = indexFileData + results.join("\n");
20
+ const outputFile = (0, path_1.join)(basePath, "index.ts");
21
+ (0, fs_1.writeFileSync)(outputFile, indexFileData);
33
22
  }
34
23
  main();
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  const fs_1 = require("fs");
13
4
  const path_1 = require("path");
@@ -77,24 +68,22 @@ function generateInterfacesFiles(pathParams, schemaPath) {
77
68
  }
78
69
  return `${param.name}${param.required ? "" : "?"}: ${paramSchemaType};`;
79
70
  });
80
- return `export interface ${interfaceName} {
81
- ${interfaceValues.join("\n")}
71
+ return `export interface ${interfaceName} {
72
+ ${interfaceValues.join("\n")}
82
73
  }`;
83
74
  });
84
75
  const interfaceFileValuesString = interfaceFileValues.join("\n");
85
76
  const interfaceFile = (0, path_1.join)(schemaPath, `params.ts`);
86
77
  (0, fs_1.writeFileSync)(interfaceFile, interfaceFileValuesString, { flag: "w" });
87
78
  }
88
- function main() {
89
- return __awaiter(this, void 0, void 0, function* () {
90
- console.log("Generating Schema Parameter Definitions");
91
- const args = process.argv.slice(2);
92
- const url = args[0];
93
- const schemaPath = args[1];
94
- const data = yield (0, fetch_schema_1.fetchSchema)(url);
95
- const paths = data.paths;
96
- const pathParams = generateParams(paths);
97
- generateInterfacesFiles(pathParams, schemaPath);
98
- });
79
+ async function main() {
80
+ console.log("Generating Schema Parameter Definitions");
81
+ const args = process.argv.slice(2);
82
+ const url = args[0];
83
+ const schemaPath = args[1];
84
+ const data = await (0, fetch_schema_1.fetchSchema)(url);
85
+ const paths = data.paths;
86
+ const pathParams = generateParams(paths);
87
+ generateInterfacesFiles(pathParams, schemaPath);
99
88
  }
100
89
  main();
package/dist/seed.js CHANGED
@@ -15,19 +15,9 @@
15
15
  * ICEBERG_WAREHOUSE gs://... path to Iceberg warehouse root
16
16
  * ICEBERG_CATALOG_URI sqlite:///... or http://... catalog URI
17
17
  */
18
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
- return new (P || (P = Promise))(function (resolve, reject) {
21
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
- step((generator = generator.apply(thisArg, _arguments || [])).next());
25
- });
26
- };
27
18
  var __importDefault = (this && this.__importDefault) || function (mod) {
28
19
  return (mod && mod.__esModule) ? mod : { "default": mod };
29
20
  };
30
- var _a, _b;
31
21
  Object.defineProperty(exports, "__esModule", { value: true });
32
22
  const fs_1 = __importDefault(require("fs"));
33
23
  const path_1 = __importDefault(require("path"));
@@ -41,8 +31,8 @@ function getArg(flag) {
41
31
  const i = args.indexOf(flag);
42
32
  return i !== -1 && args[i + 1] ? args[i + 1] : null;
43
33
  }
44
- const env = (_a = getArg("--env")) !== null && _a !== void 0 ? _a : "dev";
45
- const source = (_b = getArg("--source")) !== null && _b !== void 0 ? _b : path_1.default.join(process.cwd(), "myapp-dev");
34
+ const env = getArg("--env") ?? "dev";
35
+ const source = getArg("--source") ?? path_1.default.join(process.cwd(), "myapp-dev");
46
36
  const repoRoot = process.cwd();
47
37
  // ---------------------------------------------------------------------------
48
38
  // Step 1: resolve CSV files
@@ -57,14 +47,21 @@ function downloadFromGcs(gcsUri) {
57
47
  const tmpDir = path_1.default.join(repoRoot, ".rad", "tmp", "seed");
58
48
  fs_1.default.mkdirSync(tmpDir, { recursive: true });
59
49
  console.log(`Downloading CSVs from ${gcsUri} ...`);
50
+ // `rad seed` is a local/CI developer tool; `gcsUri` is an operator-supplied
51
+ // --source argument (a gs:// bucket the operator controls), not untrusted
52
+ // network input, so there is no command-injection surface here.
60
53
  try {
54
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
61
55
  (0, child_process_1.execSync)(`gcloud storage cp "${gcsUri}*.csv" "${tmpDir}/" --quiet`, {
62
56
  stdio: "inherit",
63
57
  });
64
58
  }
65
- catch (_a) {
59
+ catch {
66
60
  // gcloud storage cp uses gsutil-style globs — fall back
67
- (0, child_process_1.execSync)(`gsutil -m cp "${gcsUri}*.csv" "${tmpDir}/"`, { stdio: "inherit" });
61
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
62
+ (0, child_process_1.execSync)(`gsutil -m cp "${gcsUri}*.csv" "${tmpDir}/"`, {
63
+ stdio: "inherit",
64
+ });
68
65
  }
69
66
  return (0, csv_schema_1.discoverCsvFiles)(tmpDir);
70
67
  }
@@ -74,7 +71,9 @@ function downloadFromGcs(gcsUri) {
74
71
  function validateSchema(csvPath) {
75
72
  const { schema, piiViolations } = (0, csv_schema_1.inferSchema)(csvPath, repoRoot);
76
73
  if (piiViolations.length > 0) {
77
- const list = piiViolations.map((v) => ` ${v.column}: ${v.reason}`).join("\n");
74
+ const list = piiViolations
75
+ .map((v) => ` ${v.column}: ${v.reason}`)
76
+ .join("\n");
78
77
  throw new Error(`PII detected in ${path_1.default.basename(csvPath)}:\n${list}\n` +
79
78
  `Remove PII data before seeding. Run "rad scan" to review.`);
80
79
  }
@@ -135,7 +134,7 @@ function writeFingerprint(schema) {
135
134
  rowCount: schema.rowCount,
136
135
  seededAt: new Date().toISOString(),
137
136
  env,
138
- previousFingerprint: prev !== null && prev !== void 0 ? prev : null,
137
+ previousFingerprint: prev ?? null,
139
138
  };
140
139
  fs_1.default.writeFileSync(registryPath, JSON.stringify(record, null, 2));
141
140
  if (changed) {
@@ -147,104 +146,103 @@ function writeFingerprint(schema) {
147
146
  // Step 5: build a Python seed script and run it via django-iceberg
148
147
  // ---------------------------------------------------------------------------
149
148
  function buildPythonSeedScript(schemas, csvPaths) {
150
- var _a, _b;
151
149
  const namespace = env;
152
- const warehouse = (_a = process.env.ICEBERG_WAREHOUSE) !== null && _a !== void 0 ? _a : "data/warehouse";
153
- const catalogUri = (_b = process.env.ICEBERG_CATALOG_URI) !== null && _b !== void 0 ? _b : "sqlite:///data/catalog.db";
150
+ const warehouse = process.env.ICEBERG_WAREHOUSE ?? "data/warehouse";
151
+ const catalogUri = process.env.ICEBERG_CATALOG_URI ?? "sqlite:///data/catalog.db";
154
152
  const tableBlocks = schemas
155
153
  .map((schema, i) => {
156
154
  const ddl = buildCreateTableSql(schema, namespace).replace(/`/g, "\\`");
157
155
  const csvPathEscaped = csvPaths[i].replace(/\\/g, "/");
158
- return `
159
- # --- ${schema.tableName} ---
160
- ddl = """${ddl}"""
161
- cat.execute(ddl)
162
- df = pl.read_csv("${csvPathEscaped}", infer_schema_length=1000)
163
- tbl = cat.load_table("${namespace}.${schema.tableName}")
164
- tbl.overwrite(df.to_arrow())
165
- print(f" Seeded ${schema.tableName}: ${schema.rowCount} rows")
156
+ return `
157
+ # --- ${schema.tableName} ---
158
+ ddl = """${ddl}"""
159
+ cat.execute(ddl)
160
+ df = pl.read_csv("${csvPathEscaped}", infer_schema_length=1000)
161
+ tbl = cat.load_table("${namespace}.${schema.tableName}")
162
+ tbl.overwrite(df.to_arrow())
163
+ print(f" Seeded ${schema.tableName}: ${schema.rowCount} rows")
166
164
  `;
167
165
  })
168
166
  .join("\n");
169
- return `
170
- import polars as pl
171
- from pyiceberg.catalog import load_catalog
172
-
173
- cat = load_catalog(
174
- "default",
175
- **{
176
- "uri": "${catalogUri}",
177
- "warehouse": "${warehouse}",
178
- }
179
- )
180
-
181
- try:
182
- cat.create_namespace("${namespace}")
183
- except Exception:
184
- pass # namespace already exists
185
-
186
- ${tableBlocks}
187
-
188
- print("Seed complete.")
167
+ return `
168
+ import polars as pl
169
+ from pyiceberg.catalog import load_catalog
170
+
171
+ cat = load_catalog(
172
+ "default",
173
+ **{
174
+ "uri": "${catalogUri}",
175
+ "warehouse": "${warehouse}",
176
+ }
177
+ )
178
+
179
+ try:
180
+ cat.create_namespace("${namespace}")
181
+ except Exception:
182
+ pass # namespace already exists
183
+
184
+ ${tableBlocks}
185
+
186
+ print("Seed complete.")
189
187
  `.trim();
190
188
  }
191
189
  // ---------------------------------------------------------------------------
192
190
  // Main
193
191
  // ---------------------------------------------------------------------------
194
- function main() {
195
- return __awaiter(this, void 0, void 0, function* () {
196
- var _a;
197
- console.log(`\nrad seed env=${env} source=${source}\n`);
198
- const files = resolveFiles();
199
- if (files.length === 0) {
200
- console.log("No CSV files found. Nothing to seed.");
201
- process.exit(0);
202
- }
203
- console.log(`Found ${files.length} CSV file(s):\n`);
204
- const schemas = [];
205
- for (const csvPath of files) {
206
- const rel = path_1.default.relative(repoRoot, csvPath);
207
- process.stdout.write(` Validating ${rel} ... `);
208
- try {
209
- const schema = validateSchema(csvPath);
210
- schemas.push(schema);
211
- console.log(`OK [${schema.columns.length} cols, ${schema.rowCount} rows]`);
212
- }
213
- catch (err) {
214
- console.error(`FAIL\n${err.message}`);
215
- process.exit(1);
216
- }
217
- }
218
- // Write fingerprints before seeding so CI can detect schema drift
219
- for (const schema of schemas) {
220
- writeFingerprint(schema);
221
- }
222
- // Emit DDL summary
223
- const ddlDir = path_1.default.join(repoRoot, ".rad", "ddl");
224
- fs_1.default.mkdirSync(ddlDir, { recursive: true });
225
- for (const schema of schemas) {
226
- const ddl = buildCreateTableSql(schema, env);
227
- fs_1.default.writeFileSync(path_1.default.join(ddlDir, `${schema.tableName}.sql`), ddl + "\n");
228
- }
229
- console.log(`\nDDL written to .rad/ddl/`);
230
- // Run Python seed via pyiceberg if available
231
- const seedScript = buildPythonSeedScript(schemas, files);
232
- const scriptPath = path_1.default.join(repoRoot, ".rad", "tmp", "seed.py");
233
- fs_1.default.mkdirSync(path_1.default.dirname(scriptPath), { recursive: true });
234
- fs_1.default.writeFileSync(scriptPath, seedScript);
235
- const pythonBin = (_a = process.env.PYTHON) !== null && _a !== void 0 ? _a : "python";
192
+ async function main() {
193
+ console.log(`\nrad seed env=${env} source=${source}\n`);
194
+ const files = resolveFiles();
195
+ if (files.length === 0) {
196
+ console.log("No CSV files found. Nothing to seed.");
197
+ process.exit(0);
198
+ }
199
+ console.log(`Found ${files.length} CSV file(s):\n`);
200
+ const schemas = [];
201
+ for (const csvPath of files) {
202
+ const rel = path_1.default.relative(repoRoot, csvPath);
203
+ process.stdout.write(` Validating ${rel} ... `);
236
204
  try {
237
- console.log(`\nSeeding Iceberg namespace "${env}" ...\n`);
238
- (0, child_process_1.execSync)(`${pythonBin} "${scriptPath}"`, { stdio: "inherit" });
205
+ const schema = validateSchema(csvPath);
206
+ schemas.push(schema);
207
+ console.log(`OK [${schema.columns.length} cols, ${schema.rowCount} rows]`);
239
208
  }
240
- catch (_b) {
241
- console.error("\nPython seed step failed. Ensure pyiceberg and polars are installed:\n" +
242
- " pip install pyiceberg polars pyarrow\n\n" +
243
- `Generated seed script: ${scriptPath}`);
209
+ catch (err) {
210
+ console.error(`FAIL\n${err.message}`);
244
211
  process.exit(1);
245
212
  }
246
- console.log("\nDone.");
247
- });
213
+ }
214
+ // Write fingerprints before seeding so CI can detect schema drift
215
+ for (const schema of schemas) {
216
+ writeFingerprint(schema);
217
+ }
218
+ // Emit DDL summary
219
+ const ddlDir = path_1.default.join(repoRoot, ".rad", "ddl");
220
+ fs_1.default.mkdirSync(ddlDir, { recursive: true });
221
+ for (const schema of schemas) {
222
+ const ddl = buildCreateTableSql(schema, env);
223
+ fs_1.default.writeFileSync(path_1.default.join(ddlDir, `${schema.tableName}.sql`), ddl + "\n");
224
+ }
225
+ console.log(`\nDDL written to .rad/ddl/`);
226
+ // Run Python seed via pyiceberg if available
227
+ const seedScript = buildPythonSeedScript(schemas, files);
228
+ const scriptPath = path_1.default.join(repoRoot, ".rad", "tmp", "seed.py");
229
+ fs_1.default.mkdirSync(path_1.default.dirname(scriptPath), { recursive: true });
230
+ fs_1.default.writeFileSync(scriptPath, seedScript);
231
+ const pythonBin = process.env.PYTHON ?? "python";
232
+ try {
233
+ console.log(`\nSeeding Iceberg namespace "${env}" ...\n`);
234
+ // Runs an internally-generated seed script with the operator-configured
235
+ // python binary; no untrusted input, so no command-injection surface.
236
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
237
+ (0, child_process_1.execSync)(`${pythonBin} "${scriptPath}"`, { stdio: "inherit" });
238
+ }
239
+ catch {
240
+ console.error("\nPython seed step failed. Ensure pyiceberg and polars are installed:\n" +
241
+ " pip install pyiceberg polars pyarrow\n\n" +
242
+ `Generated seed script: ${scriptPath}`);
243
+ process.exit(1);
244
+ }
245
+ console.log("\nDone.");
248
246
  }
249
247
  main().catch((err) => {
250
248
  console.error(err);