@orkestrel/database 0.0.5 → 0.0.6

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.
@@ -1,28 +1,10 @@
1
+ import { DatabaseError, MemoryDriver, applyCriteria, computeAggregate, extractKey, filterRows, isDriverMeta, matchesCriteria } from "../core/index.js";
1
2
  import { isBoolean, isFiniteNumber, isRecord, isString } from "@orkestrel/contract";
2
3
  import { randomUUID } from "node:crypto";
3
- import { DatabaseError, MemoryDriver, applyCriteria, computeAggregate, extractKey, filterRows, isDriverMeta, matchesCriteria } from "../core/index.js";
4
4
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
5
5
  import { dirname } from "node:path";
6
6
  import { createSQLiteDatabase, isSQLiteError } from "@orkestrel/sqlite";
7
- //#region src/server/helpers.ts
8
- /**
9
- * Generate a fresh unique key — a v4 UUID string, backed by `node:crypto`.
10
- *
11
- * @remarks
12
- * Supply this as {@link import('@orkestrel/database').DatabaseOptions.key} so a table mints
13
- * a key when a written row lacks its primary-key value. Strings work as keys on
14
- * every backend; supply your own key values directly to use numeric keys instead.
15
- *
16
- * @returns A new UUID string
17
- *
18
- * @example
19
- * ```ts
20
- * const db = createDatabase({ driver, tables, key: generateKey })
21
- * ```
22
- */
23
- function generateKey() {
24
- return randomUUID();
25
- }
7
+ //#region src/server/constants.ts
26
8
  /**
27
9
  * The declared {@link ColumnType}s whose SQL EQUALITY comparisons (`equals` /
28
10
  * `not` / `any` / `none`) and `starts` / `ends` compiles are provably
@@ -45,12 +27,12 @@ function generateKey() {
45
27
  * values are BMP-only, or a custom SQLite collation matching `compareValues`
46
28
  * is registered) could restore native text ranges/ordering.
47
29
  */
48
- var EXACT_COLUMN_TYPES = [
30
+ var EXACT_COLUMN_TYPES = Object.freeze([
49
31
  "text",
50
32
  "integer",
51
33
  "real",
52
34
  "boolean"
53
- ];
35
+ ]);
54
36
  /**
55
37
  * The declared {@link ColumnType}s whose SQL RANGE comparisons
56
38
  * (`above` / `below` / `from` / `to` / `between`) and `ORDER BY` compiles are
@@ -59,11 +41,41 @@ var EXACT_COLUMN_TYPES = [
59
41
  * (code-point) vs. JS `<` (code-unit) divergence on supplementary-plane
60
42
  * characters.
61
43
  */
62
- var EXACT_RANGE_COLUMN_TYPES = [
44
+ var EXACT_RANGE_COLUMN_TYPES = Object.freeze([
63
45
  "integer",
64
46
  "real",
65
47
  "boolean"
66
- ];
48
+ ]);
49
+ /**
50
+ * The reserved metadata table the {@link SQLiteDriver} creates on `open` to
51
+ * persist its stamped `DriverMeta` (`version` + declared schema JSON) — the
52
+ * SQLite realization of the `meta` / `stamp` driver hooks.
53
+ *
54
+ * @remarks
55
+ * A single-row table (`id = 1`). A user table named `_meta` collides with the
56
+ * reservation — the caller's concern to avoid, documented on the driver class.
57
+ */
58
+ var META_TABLE = "_meta";
59
+ //#endregion
60
+ //#region src/server/helpers.ts
61
+ /**
62
+ * Generate a fresh unique key — a v4 UUID string, backed by `node:crypto`.
63
+ *
64
+ * @remarks
65
+ * Supply this as {@link import('@orkestrel/database').DatabaseOptions.key} so a table mints
66
+ * a key when a written row lacks its primary-key value. Strings work as keys on
67
+ * every backend; supply your own key values directly to use numeric keys instead.
68
+ *
69
+ * @returns A new UUID string
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const db = createDatabase({ driver, tables, key: generateKey })
74
+ * ```
75
+ */
76
+ function generateKey() {
77
+ return randomUUID();
78
+ }
67
79
  /**
68
80
  * Whether a value's runtime type matches a column's declared exact type —
69
81
  * the operand side of the declared-type-trust proof.
@@ -253,8 +265,10 @@ function quote(identifier) {
253
265
  */
254
266
  function fieldColumn(path) {
255
267
  if (isString(path)) return quote(path);
256
- const rest = path.slice(1).map((key) => "." + key.replaceAll("'", "''")).join("");
257
- return "json_extract(" + quote(path[0]) + ", '$" + rest + "')";
268
+ const [column, ...nested] = path;
269
+ if (column === void 0) throw new DatabaseError("VALIDATION", "A field path must contain at least one column");
270
+ const rest = nested.map((key) => "." + key.replaceAll("'", "''")).join("");
271
+ return "json_extract(" + quote(column) + ", '$" + rest + "')";
258
272
  }
259
273
  /**
260
274
  * Compile an {@link AggregateFunction} over a {@link FieldPath} to its SQL
@@ -372,6 +386,37 @@ function encodeRow(row, schema) {
372
386
  return result;
373
387
  }
374
388
  /**
389
+ * Extract a stored row's values in a declared positional order.
390
+ *
391
+ * @remarks
392
+ * SQLite statements bind arrays positionally. Every requested column must be
393
+ * present in `row`; an incomplete backend row is a typed `DRIVER` fault carrying
394
+ * the table and missing column in its context.
395
+ *
396
+ * @param row - The stored SQLite row
397
+ * @param names - The column names in binding order
398
+ * @param table - The owning table name for fault context
399
+ * @returns The row values in the same order as `names`
400
+ * @throws A `DRIVER` {@link DatabaseError} when a requested column is missing
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * extractValues({ id: 'u1', age: 36 }, ['age', 'id'], 'users') // [36, 'u1']
405
+ * ```
406
+ */
407
+ function extractValues(row, names, table) {
408
+ const values = [];
409
+ for (const name of names) {
410
+ const value = row[name];
411
+ if (value === void 0) throw new DatabaseError("DRIVER", "SQLite row is missing a declared column", {
412
+ table,
413
+ column: name
414
+ });
415
+ values.push(value);
416
+ }
417
+ return values;
418
+ }
419
+ /**
375
420
  * Decode a stored {@link SQLiteRow} back to a {@link Row} by its table's schema.
376
421
  *
377
422
  * @remarks
@@ -394,7 +439,9 @@ function encodeRow(row, schema) {
394
439
  function decodeRow(row, schema) {
395
440
  const result = {};
396
441
  for (const column of schema.columns) {
397
- const decoded = decodeValue(row[column.name], column.type);
442
+ const value = row[column.name];
443
+ if (value === void 0) continue;
444
+ const decoded = decodeValue(value, column.type);
398
445
  if (decoded !== void 0) result[column.name] = decoded;
399
446
  }
400
447
  return result;
@@ -567,8 +614,10 @@ function stepToSchema(schema, step) {
567
614
  * ```
568
615
  */
569
616
  function jsonTypeColumn(path) {
570
- const rest = path.slice(1).map((key) => "." + key.replaceAll("'", "''")).join("");
571
- return "json_type(" + quote(path[0]) + ", '$" + rest + "')";
617
+ const [column, ...nested] = path;
618
+ if (column === void 0) throw new DatabaseError("VALIDATION", "A field path must contain at least one column");
619
+ const rest = nested.map((key) => "." + key.replaceAll("'", "''")).join("");
620
+ return "json_type(" + quote(column) + ", '$" + rest + "')";
572
621
  }
573
622
  /**
574
623
  * Escape `\`, `%`, and `_` (each with a leading `\`) so a `starts` / `ends`
@@ -703,21 +752,21 @@ function fragment(condition, schema) {
703
752
  const column = fieldColumn(condition.column);
704
753
  const nested = !isString(condition.column);
705
754
  const declared = isString(condition.column) ? declaredType(condition.column, schema) : void 0;
706
- const encode = (value) => encodeValue(value, nested ? valueType(value) : declared ?? "json");
707
755
  const first = condition.values[0];
708
756
  const second = condition.values[1];
709
757
  const nullOperand = first === null || first === void 0;
710
758
  const jsonType = !isString(condition.column) ? jsonTypeColumn(condition.column) : "";
759
+ let sql;
760
+ let values;
711
761
  switch (condition.operator) {
712
762
  case "equals":
713
763
  if (nullOperand && nested) return {
714
764
  sql: jsonType + " = 'null'",
715
765
  params: []
716
766
  };
717
- return {
718
- sql: column + " = ?",
719
- params: [encode(first)]
720
- };
767
+ sql = column + " = ?";
768
+ values = [first];
769
+ break;
721
770
  case "not":
722
771
  if (nullOperand) {
723
772
  if (nested) return {
@@ -729,38 +778,37 @@ function fragment(condition, schema) {
729
778
  params: []
730
779
  };
731
780
  }
732
- return {
733
- sql: "(" + column + " != ? OR " + column + " IS NULL)",
734
- params: [encode(first)]
735
- };
736
- case "above": return {
737
- sql: column + " > ?",
738
- params: [encode(first)]
739
- };
740
- case "below": return {
741
- sql: "(" + column + " < ? OR " + column + " IS NULL)",
742
- params: [encode(first)]
743
- };
744
- case "from": return {
745
- sql: column + " >= ?",
746
- params: [encode(first)]
747
- };
748
- case "to": return {
749
- sql: "(" + column + " <= ? OR " + column + " IS NULL)",
750
- params: [encode(first)]
751
- };
752
- case "between": return {
753
- sql: column + " BETWEEN ? AND ?",
754
- params: [encode(first), encode(second)]
755
- };
756
- case "like": return {
757
- sql: column + " LIKE ?",
758
- params: [encode(first)]
759
- };
760
- case "glob": return {
761
- sql: column + " GLOB ?",
762
- params: [encode(first)]
763
- };
781
+ sql = "(" + column + " != ? OR " + column + " IS NULL)";
782
+ values = [first];
783
+ break;
784
+ case "above":
785
+ sql = column + " > ?";
786
+ values = [first];
787
+ break;
788
+ case "below":
789
+ sql = "(" + column + " < ? OR " + column + " IS NULL)";
790
+ values = [first];
791
+ break;
792
+ case "from":
793
+ sql = column + " >= ?";
794
+ values = [first];
795
+ break;
796
+ case "to":
797
+ sql = "(" + column + " <= ? OR " + column + " IS NULL)";
798
+ values = [first];
799
+ break;
800
+ case "between":
801
+ sql = column + " BETWEEN ? AND ?";
802
+ values = [first, second];
803
+ break;
804
+ case "like":
805
+ sql = column + " LIKE ?";
806
+ values = [first];
807
+ break;
808
+ case "glob":
809
+ sql = column + " GLOB ?";
810
+ values = [first];
811
+ break;
764
812
  case "starts": {
765
813
  const text = isString(first) ? first : "";
766
814
  if (text === "") return {
@@ -768,10 +816,9 @@ function fragment(condition, schema) {
768
816
  params: []
769
817
  };
770
818
  const length = Array.from(text).length;
771
- return {
772
- sql: "(typeof(" + column + ") = 'text' AND substr(" + column + ", 1, " + length + ") = ?)",
773
- params: [encode(first)]
774
- };
819
+ sql = "(typeof(" + column + ") = 'text' AND substr(" + column + ", 1, " + length + ") = ?)";
820
+ values = [first];
821
+ break;
775
822
  }
776
823
  case "ends": {
777
824
  const text = isString(first) ? first : "";
@@ -780,29 +827,26 @@ function fragment(condition, schema) {
780
827
  params: []
781
828
  };
782
829
  const length = Array.from(text).length;
783
- return {
784
- sql: "(typeof(" + column + ") = 'text' AND substr(" + column + ", -" + length + ") = ?)",
785
- params: [encode(first)]
786
- };
830
+ sql = "(typeof(" + column + ") = 'text' AND substr(" + column + ", -" + length + ") = ?)";
831
+ values = [first];
832
+ break;
787
833
  }
788
834
  case "any":
789
835
  if (condition.values.length === 0) return {
790
836
  sql: "0",
791
837
  params: []
792
838
  };
793
- return {
794
- sql: column + " IN (" + condition.values.map(() => "?").join(", ") + ")",
795
- params: condition.values.map(encode)
796
- };
839
+ sql = column + " IN (" + condition.values.map(() => "?").join(", ") + ")";
840
+ values = condition.values;
841
+ break;
797
842
  case "none":
798
843
  if (condition.values.length === 0) return {
799
844
  sql: "1",
800
845
  params: []
801
846
  };
802
- return {
803
- sql: "(" + column + " NOT IN (" + condition.values.map(() => "?").join(", ") + ") OR " + column + " IS NULL)",
804
- params: condition.values.map(encode)
805
- };
847
+ sql = "(" + column + " NOT IN (" + condition.values.map(() => "?").join(", ") + ") OR " + column + " IS NULL)";
848
+ values = condition.values;
849
+ break;
806
850
  case "absent": return {
807
851
  sql: column + " IS NULL",
808
852
  params: []
@@ -812,6 +856,10 @@ function fragment(condition, schema) {
812
856
  params: []
813
857
  };
814
858
  }
859
+ return {
860
+ sql,
861
+ params: values.map((value) => encodeValue(value, nested ? valueType(value) : declared ?? "json"))
862
+ };
815
863
  }
816
864
  /**
817
865
  * Fold the conditions into one WHERE clause, parenthesizing progressively
@@ -835,16 +883,17 @@ function fragment(condition, schema) {
835
883
  * ```
836
884
  */
837
885
  function compileWhere(conditions, schema) {
838
- if (conditions.length === 0) return {
886
+ const [first, ...remaining] = conditions;
887
+ if (first === void 0) return {
839
888
  sql: "",
840
889
  params: []
841
890
  };
842
- const head = fragment(conditions[0], schema);
891
+ const head = fragment(first, schema);
843
892
  let clause = head.sql;
844
893
  const params = [...head.params];
845
- for (let index = 1; index < conditions.length; index += 1) {
846
- const next = fragment(conditions[index], schema);
847
- const operator = conditions[index].connector === "or" ? "OR" : "AND";
894
+ for (const condition of remaining) {
895
+ const next = fragment(condition, schema);
896
+ const operator = condition.connector === "or" ? "OR" : "AND";
848
897
  clause = "(" + clause + " " + operator + " " + next.sql + ")";
849
898
  params.push(...next.params);
850
899
  }
@@ -961,18 +1010,6 @@ function compileCriteria(criteria, schema) {
961
1010
  };
962
1011
  }
963
1012
  //#endregion
964
- //#region src/server/constants.ts
965
- /**
966
- * The reserved metadata table the {@link SQLiteDriver} creates on `open` to
967
- * persist its stamped `DriverMeta` (`version` + declared schema JSON) — the
968
- * SQLite realization of the `meta` / `stamp` driver hooks.
969
- *
970
- * @remarks
971
- * A single-row table (`id = 1`). A user table named `_meta` collides with the
972
- * reservation — the caller's concern to avoid, documented on the driver class.
973
- */
974
- var META_TABLE = "_meta";
975
- //#endregion
976
1013
  //#region src/server/drivers/JSONDriver.ts
977
1014
  /**
978
1015
  * A persistent {@link DriverInterface} backed by a single JSON file — the
@@ -1012,6 +1049,7 @@ var JSONDriver = class {
1012
1049
  #flushCount = 0;
1013
1050
  #chain = Promise.resolve();
1014
1051
  #deferring = false;
1052
+ #transaction;
1015
1053
  constructor(path) {
1016
1054
  this.#path = path;
1017
1055
  }
@@ -1080,22 +1118,12 @@ var JSONDriver = class {
1080
1118
  async transaction() {
1081
1119
  if (this.#deferring) throw new DatabaseError("CONFLICT", "A transaction is already active on this driver", {});
1082
1120
  const rollback = await this.#memory.snapshot();
1121
+ const token = {};
1083
1122
  this.#deferring = true;
1084
- let settled = false;
1123
+ this.#transaction = token;
1085
1124
  return {
1086
- commit: async () => {
1087
- if (settled) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1088
- settled = true;
1089
- this.#deferring = false;
1090
- await this.#flush();
1091
- },
1092
- rollback: async () => {
1093
- if (settled) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1094
- settled = true;
1095
- await rollback();
1096
- this.#deferring = false;
1097
- await this.#flush();
1098
- }
1125
+ commit: this.#commit.bind(this, token),
1126
+ rollback: this.#rollback.bind(this, token, rollback)
1099
1127
  };
1100
1128
  }
1101
1129
  async snapshot(tables) {
@@ -1148,6 +1176,19 @@ var JSONDriver = class {
1148
1176
  this.#schema = schema;
1149
1177
  await this.#flush();
1150
1178
  }
1179
+ async #commit(token) {
1180
+ if (this.#transaction !== token) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1181
+ this.#transaction = void 0;
1182
+ this.#deferring = false;
1183
+ await this.#flush();
1184
+ }
1185
+ async #rollback(token, rollback) {
1186
+ if (this.#transaction !== token) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1187
+ this.#transaction = void 0;
1188
+ await rollback();
1189
+ this.#deferring = false;
1190
+ await this.#flush();
1191
+ }
1151
1192
  async #load() {
1152
1193
  let raw;
1153
1194
  try {
@@ -1260,7 +1301,7 @@ var SQLiteDriver = class {
1260
1301
  #options;
1261
1302
  #database;
1262
1303
  #schema = /* @__PURE__ */ new Map();
1263
- #transacting = false;
1304
+ #transaction;
1264
1305
  constructor(path, options) {
1265
1306
  this.#path = path;
1266
1307
  this.#options = options ?? {};
@@ -1271,9 +1312,9 @@ var SQLiteDriver = class {
1271
1312
  this.#database?.close();
1272
1313
  const database = createSQLiteDatabase({
1273
1314
  path: this.#path,
1274
- readonly: this.#options.readonly,
1275
- timeout: this.#options.timeout,
1276
- foreignKeys: this.#options.foreignKeys
1315
+ ...this.#options.readonly !== void 0 ? { readonly: this.#options.readonly } : {},
1316
+ ...this.#options.timeout !== void 0 ? { timeout: this.#options.timeout } : {},
1317
+ ...this.#options.foreignKeys !== void 0 ? { foreignKeys: this.#options.foreignKeys } : {}
1277
1318
  });
1278
1319
  database.connect();
1279
1320
  for (const [name, value] of Object.entries(this.#options.pragmas ?? {})) database.pragma(name, value);
@@ -1307,7 +1348,7 @@ var SQLiteDriver = class {
1307
1348
  [schema.primary]: key
1308
1349
  }, schema);
1309
1350
  const names = schema.columns.map((column) => column.name);
1310
- const values = names.map((name) => encoded[name]);
1351
+ const values = extractValues(encoded, names, table);
1311
1352
  this.#require().prepare("INSERT OR REPLACE INTO " + quote(table) + " (" + names.map(quote).join(", ") + ") VALUES (" + names.map(() => "?").join(", ") + ")").run(values);
1312
1353
  });
1313
1354
  }
@@ -1387,8 +1428,8 @@ var SQLiteDriver = class {
1387
1428
  if (conditions.every((condition) => isExactCondition(condition, schema))) {
1388
1429
  const compiled = compileCriteria({
1389
1430
  conditions,
1390
- limit: criteria.limit,
1391
- offset: criteria.offset
1431
+ ...criteria.limit !== void 0 ? { limit: criteria.limit } : {},
1432
+ ...criteria.offset !== void 0 ? { offset: criteria.offset } : {}
1392
1433
  }, schema);
1393
1434
  for (const row of this.#require().prepare("SELECT * FROM " + quote(table) + (compiled.sql === "" ? "" : " " + compiled.sql)).iterate(compiled.params)) yield decodeRow(row, schema);
1394
1435
  return;
@@ -1420,21 +1461,11 @@ var SQLiteDriver = class {
1420
1461
  async transaction() {
1421
1462
  const database = this.#require();
1422
1463
  this.#guard(() => database.exec("BEGIN"));
1423
- let settled = false;
1424
- this.#transacting = true;
1464
+ const token = {};
1465
+ this.#transaction = token;
1425
1466
  return {
1426
- commit: async () => {
1427
- if (settled) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1428
- settled = true;
1429
- this.#transacting = false;
1430
- this.#guard(() => database.exec("COMMIT"));
1431
- },
1432
- rollback: async () => {
1433
- if (settled) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1434
- settled = true;
1435
- this.#transacting = false;
1436
- this.#guard(() => database.exec("ROLLBACK"));
1437
- }
1467
+ commit: this.#commit.bind(this, token, database),
1468
+ rollback: this.#rollback.bind(this, token, database)
1438
1469
  };
1439
1470
  }
1440
1471
  /**
@@ -1464,7 +1495,7 @@ var SQLiteDriver = class {
1464
1495
  const database = this.#require();
1465
1496
  const schema = new Map(this.#schema);
1466
1497
  this.#guard(() => {
1467
- if (this.#transacting) this.#applyPlan(database, plan, schema);
1498
+ if (this.#transaction !== void 0) this.#applyPlan(database, plan, schema);
1468
1499
  else database.transaction(() => this.#applyPlan(database, plan, schema));
1469
1500
  });
1470
1501
  this.#schema = schema;
@@ -1524,11 +1555,21 @@ var SQLiteDriver = class {
1524
1555
  for (const [name, snapshot] of captured) {
1525
1556
  current.exec("DELETE FROM " + quote(name));
1526
1557
  const statement = current.prepare("INSERT OR REPLACE INTO " + quote(name) + " (" + snapshot.names.map(quote).join(", ") + ") VALUES (" + snapshot.names.map(() => "?").join(", ") + ")");
1527
- for (const row of snapshot.rows) statement.run(snapshot.names.map((column) => row[column]));
1558
+ for (const row of snapshot.rows) statement.run(extractValues(row, snapshot.names, name));
1528
1559
  }
1529
1560
  });
1530
1561
  };
1531
1562
  }
1563
+ async #commit(token, database) {
1564
+ if (this.#transaction !== token) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1565
+ this.#transaction = void 0;
1566
+ this.#guard(() => database.exec("COMMIT"));
1567
+ }
1568
+ async #rollback(token, database) {
1569
+ if (this.#transaction !== token) throw new DatabaseError("CONFLICT", "Transaction already settled", {});
1570
+ this.#transaction = void 0;
1571
+ this.#guard(() => database.exec("ROLLBACK"));
1572
+ }
1532
1573
  #guard(run) {
1533
1574
  try {
1534
1575
  return run();
@@ -1658,6 +1699,6 @@ function createSQLiteDriver(options = ":memory:") {
1658
1699
  return new SQLiteDriver(resolved.path ?? ":memory:", resolved);
1659
1700
  }
1660
1701
  //#endregion
1661
- export { EXACT_COLUMN_TYPES, EXACT_RANGE_COLUMN_TYPES, JSONDriver, META_TABLE, SQLiteDriver, aggregateSQL, columnSQL, compileCriteria, compileOrder, compilePage, compileWhere, createJSONDriver, createSQLiteDriver, declaredType, decodeRow, decodeValue, encodeRow, encodeValue, escapeLike, fieldColumn, fragment, generateKey, indexName, isExactCondition, isExactCriteria, isExactOrder, jsonTypeColumn, matchesDeclaredType, quote, schemaToIndexes, schemaToTable, stepToSQL, stepToSchema, valueType };
1702
+ export { EXACT_COLUMN_TYPES, EXACT_RANGE_COLUMN_TYPES, JSONDriver, META_TABLE, SQLiteDriver, aggregateSQL, columnSQL, compileCriteria, compileOrder, compilePage, compileWhere, createJSONDriver, createSQLiteDriver, declaredType, decodeRow, decodeValue, encodeRow, encodeValue, escapeLike, extractValues, fieldColumn, fragment, generateKey, indexName, isExactCondition, isExactCriteria, isExactOrder, jsonTypeColumn, matchesDeclaredType, quote, schemaToIndexes, schemaToTable, stepToSQL, stepToSchema, valueType };
1662
1703
 
1663
1704
  //# sourceMappingURL=index.js.map