@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.
@@ -52,7 +52,7 @@ var DatabaseError = class extends Error {
52
52
  super(message);
53
53
  this.name = "DatabaseError";
54
54
  this.code = code;
55
- this.context = context;
55
+ if (context !== void 0) this.context = context;
56
56
  }
57
57
  };
58
58
  /**
@@ -90,16 +90,7 @@ function isDatabaseError(value) {
90
90
  * @returns `-1`, `0`, or `1`
91
91
  */
92
92
  function compareValues(left, right) {
93
- const rankOf = (value) => {
94
- if (value === void 0) return 0;
95
- if (value === null) return 1;
96
- if (typeof value === "boolean") return 2;
97
- if (typeof value === "number") return 3;
98
- if (typeof value === "string") return 4;
99
- return 5;
100
- };
101
- const leftRank = rankOf(left);
102
- const rightRank = rankOf(right);
93
+ const [leftRank = 5, rightRank = 5] = [left, right].map((value) => value === void 0 ? 0 : value === null ? 1 : typeof value === "boolean" ? 2 : typeof value === "number" ? 3 : typeof value === "string" ? 4 : 5);
103
94
  if (leftRank !== rightRank) return leftRank < rightRank ? -1 : 1;
104
95
  if (typeof left === "number" && typeof right === "number") {
105
96
  if (Number.isNaN(left) || Number.isNaN(right)) return Number.isNaN(left) ? Number.isNaN(right) ? 0 : 1 : -1;
@@ -459,19 +450,14 @@ function shapeToColumnType(shape) {
459
450
  * ```
460
451
  */
461
452
  function isDriverMeta(value) {
462
- const COLUMN_TYPES = [
453
+ return isRecord(value) && isFiniteNumber(value.version) && isArray(value.schema) && value.schema.every((table) => isRecord(table) && isString(table.name) && isString(table.primary) && isArray(table.columns) && table.columns.every((column) => isRecord(column) && isString(column.name) && isString(column.type) && [
463
454
  "text",
464
455
  "integer",
465
456
  "real",
466
457
  "boolean",
467
458
  "json",
468
459
  "blob"
469
- ];
470
- const isColumnType = (candidate) => isString(candidate) && COLUMN_TYPES.some((type) => type === candidate);
471
- const isColumnSchema = (candidate) => isRecord(candidate) && isString(candidate.name) && isColumnType(candidate.type) && isBoolean(candidate.nullable);
472
- const isIndexGroup = (candidate) => isArray(candidate) && candidate.every((entry) => isString(entry));
473
- const isTableSchema = (candidate) => isRecord(candidate) && isString(candidate.name) && isString(candidate.primary) && isArray(candidate.columns) && candidate.columns.every(isColumnSchema) && isArray(candidate.indexes) && candidate.indexes.every(isIndexGroup);
474
- return isRecord(value) && isFiniteNumber(value.version) && isArray(value.schema) && value.schema.every(isTableSchema);
460
+ ].some((type) => type === column.type) && isBoolean(column.nullable)) && isArray(table.indexes) && table.indexes.every((index) => isArray(index) && index.every((column) => isString(column))));
475
461
  }
476
462
  /**
477
463
  * Throw when an {@link ReadOptions.signal | AbortSignal} has fired — the shared
@@ -588,13 +574,12 @@ function planMigration(deployed, declared, from = 0, to = 1) {
588
574
  }
589
575
  });
590
576
  }
591
- const sameIndex = (left, right) => left.length === right.length && left.every((column, position) => column === right[position]);
592
- for (const index of before.indexes) if (!table.indexes.some((candidate) => sameIndex(candidate, index))) steps.push({
577
+ for (const index of before.indexes) if (!table.indexes.some((candidate) => deepEqual(candidate, index))) steps.push({
593
578
  operation: "index.remove",
594
579
  table: table.name,
595
580
  index
596
581
  });
597
- for (const index of table.indexes) if (!before.indexes.some((candidate) => sameIndex(candidate, index))) steps.push({
582
+ for (const index of table.indexes) if (!before.indexes.some((candidate) => deepEqual(candidate, index))) steps.push({
598
583
  operation: "index.add",
599
584
  table: table.name,
600
585
  index
@@ -734,529 +719,674 @@ async function* driverFindings(factory) {
734
719
  indexes: []
735
720
  };
736
721
  const CONFORMANCE_SCHEMA = [CONFORMANCE_USERS_SCHEMA, CONFORMANCE_POSTS_SCHEMA];
737
- const findingOf = (check, message, context) => ({
738
- check,
739
- message,
740
- context
741
- });
742
- const phases = [
743
- {
722
+ try {
723
+ const driver = factory();
724
+ await driver.open(CONFORMANCE_SCHEMA);
725
+ await driver.close();
726
+ } catch (error) {
727
+ yield {
744
728
  check: "open-close",
745
- run: async () => {
746
- const driver = factory();
747
- await driver.open(CONFORMANCE_SCHEMA);
748
- await driver.close();
729
+ message: error instanceof Error ? error.message : String(error),
730
+ context: { error }
731
+ };
732
+ }
733
+ try {
734
+ const driver = factory();
735
+ await driver.open(CONFORMANCE_SCHEMA);
736
+ const missing = await driver.read("users", "nope");
737
+ await driver.close();
738
+ if (missing !== void 0) yield {
739
+ check: "read-missing",
740
+ message: "read of a missing key must return undefined",
741
+ context: {
742
+ table: "users",
743
+ expected: void 0,
744
+ actual: missing
749
745
  }
750
- },
751
- {
746
+ };
747
+ } catch (error) {
748
+ yield {
752
749
  check: "read-missing",
753
- run: async () => {
754
- const driver = factory();
755
- await driver.open(CONFORMANCE_SCHEMA);
756
- const missing = await driver.read("users", "nope");
757
- await driver.close();
758
- if (missing !== void 0) return findingOf("read-missing", "read of a missing key must return undefined", {
750
+ message: error instanceof Error ? error.message : String(error),
751
+ context: { error }
752
+ };
753
+ }
754
+ writeRead: try {
755
+ const driver = factory();
756
+ await driver.open(CONFORMANCE_SCHEMA);
757
+ const input = {
758
+ id: "u1",
759
+ name: "Ada",
760
+ age: 30,
761
+ meta: { tags: ["a"] }
762
+ };
763
+ await driver.write("users", "u1", input);
764
+ input.name = "Mutated after write";
765
+ if (isRecord(input.meta) && Array.isArray(input.meta.tags)) input.meta.tags.push("mutated");
766
+ const stored = await driver.read("users", "u1");
767
+ const original = {
768
+ id: "u1",
769
+ name: "Ada",
770
+ age: 30,
771
+ meta: { tags: ["a"] }
772
+ };
773
+ if (stored === void 0 || !deepEqual(stored, original)) {
774
+ await driver.close();
775
+ yield {
776
+ check: "copy-in",
777
+ message: "write must deep-copy the input row (including nested fields) rather than store it by reference",
778
+ context: {
759
779
  table: "users",
760
- expected: void 0,
761
- actual: missing
762
- });
763
- }
764
- },
765
- {
766
- check: "write-read",
767
- run: async () => {
768
- const driver = factory();
769
- await driver.open(CONFORMANCE_SCHEMA);
770
- const input = {
771
- id: "u1",
772
- name: "Ada",
773
- age: 30,
774
- meta: { tags: ["a"] }
775
- };
776
- await driver.write("users", "u1", input);
777
- input.name = "Mutated after write";
778
- if (isRecord(input.meta) && Array.isArray(input.meta.tags)) input.meta.tags.push("mutated");
779
- const stored = await driver.read("users", "u1");
780
- const original = {
781
- id: "u1",
782
- name: "Ada",
783
- age: 30,
784
- meta: { tags: ["a"] }
785
- };
786
- if (stored === void 0 || !deepEqual(stored, original)) {
787
- await driver.close();
788
- return findingOf("copy-in", "write must deep-copy the input row (including nested fields) rather than store it by reference", {
789
- table: "users",
790
- expected: original,
791
- actual: stored
792
- });
780
+ expected: original,
781
+ actual: stored
793
782
  }
794
- stored.name = "Mutated after read";
795
- if (isRecord(stored.meta) && Array.isArray(stored.meta.tags)) stored.meta.tags.push("mutated");
796
- const reread = await driver.read("users", "u1");
797
- if (reread === void 0 || !deepEqual(reread, original)) {
798
- await driver.close();
799
- return findingOf("copy-out", "read must deep-copy the stored row (including nested fields) rather than return it by reference", {
800
- table: "users",
801
- expected: original,
802
- actual: reread
803
- });
783
+ };
784
+ break writeRead;
785
+ }
786
+ stored.name = "Mutated after read";
787
+ if (isRecord(stored.meta) && Array.isArray(stored.meta.tags)) stored.meta.tags.push("mutated");
788
+ const reread = await driver.read("users", "u1");
789
+ if (reread === void 0 || !deepEqual(reread, original)) {
790
+ await driver.close();
791
+ yield {
792
+ check: "copy-out",
793
+ message: "read must deep-copy the stored row (including nested fields) rather than return it by reference",
794
+ context: {
795
+ table: "users",
796
+ expected: original,
797
+ actual: reread
804
798
  }
805
- const overwrite = {
806
- id: "u1",
807
- name: "Ada Overwritten",
808
- age: 31
809
- };
810
- await driver.write("users", "u1", overwrite);
811
- const overwritten = await driver.read("users", "u1");
812
- await driver.close();
813
- if (overwritten === void 0 || !deepEqual(overwritten, overwrite)) return findingOf("upsert", "write must upsert-overwrite an existing key", {
799
+ };
800
+ break writeRead;
801
+ }
802
+ const overwrite = {
803
+ id: "u1",
804
+ name: "Ada Overwritten",
805
+ age: 31
806
+ };
807
+ await driver.write("users", "u1", overwrite);
808
+ const overwritten = await driver.read("users", "u1");
809
+ await driver.close();
810
+ if (overwritten === void 0 || !deepEqual(overwritten, overwrite)) yield {
811
+ check: "upsert",
812
+ message: "write must upsert-overwrite an existing key",
813
+ context: {
814
+ table: "users",
815
+ expected: overwrite,
816
+ actual: overwritten
817
+ }
818
+ };
819
+ } catch (error) {
820
+ yield {
821
+ check: "write-read",
822
+ message: error instanceof Error ? error.message : String(error),
823
+ context: { error }
824
+ };
825
+ }
826
+ deletePhase: try {
827
+ const driver = factory();
828
+ await driver.open(CONFORMANCE_SCHEMA);
829
+ await driver.write("users", "u1", {
830
+ id: "u1",
831
+ name: "Ada",
832
+ age: 30
833
+ });
834
+ const first = await driver.delete("users", "u1");
835
+ if (first !== true) {
836
+ await driver.close();
837
+ yield {
838
+ check: "delete-true",
839
+ message: "delete of an existing key must return true",
840
+ context: {
814
841
  table: "users",
815
- expected: overwrite,
816
- actual: overwritten
817
- });
842
+ expected: true,
843
+ actual: first
844
+ }
845
+ };
846
+ break deletePhase;
847
+ }
848
+ const second = await driver.delete("users", "u1");
849
+ await driver.close();
850
+ if (second !== false) yield {
851
+ check: "delete-false",
852
+ message: "delete of an already-removed key must return false",
853
+ context: {
854
+ table: "users",
855
+ expected: false,
856
+ actual: second
818
857
  }
819
- },
820
- {
858
+ };
859
+ } catch (error) {
860
+ yield {
821
861
  check: "delete",
822
- run: async () => {
823
- const driver = factory();
824
- await driver.open(CONFORMANCE_SCHEMA);
825
- await driver.write("users", "u1", {
826
- id: "u1",
827
- name: "Ada",
828
- age: 30
829
- });
830
- const first = await driver.delete("users", "u1");
831
- if (first !== true) {
832
- await driver.close();
833
- return findingOf("delete-true", "delete of an existing key must return true", {
834
- table: "users",
835
- expected: true,
836
- actual: first
837
- });
838
- }
839
- const second = await driver.delete("users", "u1");
840
- await driver.close();
841
- if (second !== false) return findingOf("delete-false", "delete of an already-removed key must return false", {
842
- table: "users",
843
- expected: false,
844
- actual: second
845
- });
862
+ message: error instanceof Error ? error.message : String(error),
863
+ context: { error }
864
+ };
865
+ }
866
+ orderPhase: try {
867
+ const driver = factory();
868
+ await driver.open(CONFORMANCE_SCHEMA);
869
+ for (const row of [
870
+ {
871
+ id: "c",
872
+ name: "C",
873
+ age: 3
874
+ },
875
+ {
876
+ id: "a",
877
+ name: "A",
878
+ age: 1
879
+ },
880
+ {
881
+ id: "b",
882
+ name: "B",
883
+ age: 2
846
884
  }
847
- },
848
- {
849
- check: "order",
850
- run: async () => {
851
- const driver = factory();
852
- await driver.open(CONFORMANCE_SCHEMA);
853
- for (const row of [
854
- {
855
- id: "c",
856
- name: "C",
857
- age: 3
858
- },
859
- {
860
- id: "a",
861
- name: "A",
862
- age: 1
863
- },
864
- {
865
- id: "b",
866
- name: "B",
867
- age: 2
868
- }
869
- ]) await driver.write("users", row.id, row);
870
- const expected = [
871
- "a",
872
- "b",
873
- "c"
874
- ];
875
- const keys = [...await driver.keys("users")];
876
- if (!deepEqual(keys, expected)) {
877
- await driver.close();
878
- return findingOf("keys-order", "keys must be returned in ascending key order", {
879
- table: "users",
880
- expected,
881
- actual: keys
882
- });
883
- }
884
- const scanned = [];
885
- for await (const row of driver.scan("users")) scanned.push(row);
886
- const scannedIds = scanned.map((row) => row.id);
887
- await driver.close();
888
- if (!deepEqual(scannedIds, expected)) return findingOf("scan-order", "scan must yield rows in ascending key order", {
885
+ ]) await driver.write("users", row.id, row);
886
+ const expected = [
887
+ "a",
888
+ "b",
889
+ "c"
890
+ ];
891
+ const keys = [...await driver.keys("users")];
892
+ if (!deepEqual(keys, expected)) {
893
+ await driver.close();
894
+ yield {
895
+ check: "keys-order",
896
+ message: "keys must be returned in ascending key order",
897
+ context: {
889
898
  table: "users",
890
899
  expected,
891
- actual: scannedIds
892
- });
900
+ actual: keys
901
+ }
902
+ };
903
+ break orderPhase;
904
+ }
905
+ const scanned = [];
906
+ for await (const row of driver.scan("users")) scanned.push(row);
907
+ const scannedIds = scanned.map((row) => row.id);
908
+ await driver.close();
909
+ if (!deepEqual(scannedIds, expected)) yield {
910
+ check: "scan-order",
911
+ message: "scan must yield rows in ascending key order",
912
+ context: {
913
+ table: "users",
914
+ expected,
915
+ actual: scannedIds
893
916
  }
894
- },
895
- {
896
- check: "clear",
897
- run: async () => {
898
- const driver = factory();
899
- await driver.open(CONFORMANCE_SCHEMA);
900
- await driver.write("users", "u1", {
901
- id: "u1",
902
- name: "Ada",
903
- age: 30
904
- });
905
- await driver.write("posts", "p1", {
906
- slug: "p1",
907
- title: "Post"
908
- });
909
- await driver.clear("users");
910
- const usersKeys = await driver.keys("users");
911
- const postsKeys = await driver.keys("posts");
912
- await driver.close();
913
- if (usersKeys.length !== 0) return findingOf("clear-target", "clear must empty the targeted table", {
917
+ };
918
+ } catch (error) {
919
+ yield {
920
+ check: "order",
921
+ message: error instanceof Error ? error.message : String(error),
922
+ context: { error }
923
+ };
924
+ }
925
+ clearPhase: try {
926
+ const driver = factory();
927
+ await driver.open(CONFORMANCE_SCHEMA);
928
+ await driver.write("users", "u1", {
929
+ id: "u1",
930
+ name: "Ada",
931
+ age: 30
932
+ });
933
+ await driver.write("posts", "p1", {
934
+ slug: "p1",
935
+ title: "Post"
936
+ });
937
+ await driver.clear("users");
938
+ const usersKeys = await driver.keys("users");
939
+ const postsKeys = await driver.keys("posts");
940
+ await driver.close();
941
+ if (usersKeys.length !== 0) {
942
+ yield {
943
+ check: "clear-target",
944
+ message: "clear must empty the targeted table",
945
+ context: {
914
946
  table: "users",
915
947
  expected: [],
916
948
  actual: usersKeys
917
- });
918
- if (postsKeys.length !== 1) return findingOf("clear-other", "clear must not affect other tables", {
919
- table: "posts",
920
- expected: 1,
921
- actual: postsKeys.length
922
- });
923
- }
924
- },
925
- {
926
- check: "snapshot",
927
- run: async () => {
928
- const driver = factory();
929
- await driver.open(CONFORMANCE_SCHEMA);
930
- const original = {
931
- id: "u1",
932
- name: "Ada",
933
- age: 30
934
- };
935
- await driver.write("users", "u1", original);
936
- const rollback = await driver.snapshot();
937
- await driver.write("users", "u2", {
938
- id: "u2",
939
- name: "Grace",
940
- age: 40
941
- });
942
- await driver.delete("users", "u1");
943
- await rollback();
944
- const keys = [...await driver.keys("users")];
945
- if (!deepEqual(keys, ["u1"])) {
946
- await driver.close();
947
- return findingOf("snapshot-rollback", "snapshot rollback must restore the pre-snapshot key set", {
948
- table: "users",
949
- expected: ["u1"],
950
- actual: keys
951
- });
952
949
  }
953
- const restored = await driver.read("users", "u1");
954
- await driver.close();
955
- if (restored === void 0 || !deepEqual(restored, original)) return findingOf("snapshot-rollback-value", "snapshot rollback must restore pre-snapshot row values", {
950
+ };
951
+ break clearPhase;
952
+ }
953
+ if (postsKeys.length !== 1) yield {
954
+ check: "clear-other",
955
+ message: "clear must not affect other tables",
956
+ context: {
957
+ table: "posts",
958
+ expected: 1,
959
+ actual: postsKeys.length
960
+ }
961
+ };
962
+ } catch (error) {
963
+ yield {
964
+ check: "clear",
965
+ message: error instanceof Error ? error.message : String(error),
966
+ context: { error }
967
+ };
968
+ }
969
+ snapshotPhase: try {
970
+ const driver = factory();
971
+ await driver.open(CONFORMANCE_SCHEMA);
972
+ const original = {
973
+ id: "u1",
974
+ name: "Ada",
975
+ age: 30
976
+ };
977
+ await driver.write("users", "u1", original);
978
+ const rollback = await driver.snapshot();
979
+ await driver.write("users", "u2", {
980
+ id: "u2",
981
+ name: "Grace",
982
+ age: 40
983
+ });
984
+ await driver.delete("users", "u1");
985
+ await rollback();
986
+ const keys = [...await driver.keys("users")];
987
+ if (!deepEqual(keys, ["u1"])) {
988
+ await driver.close();
989
+ yield {
990
+ check: "snapshot-rollback",
991
+ message: "snapshot rollback must restore the pre-snapshot key set",
992
+ context: {
956
993
  table: "users",
957
- expected: original,
958
- actual: restored
959
- });
994
+ expected: ["u1"],
995
+ actual: keys
996
+ }
997
+ };
998
+ break snapshotPhase;
999
+ }
1000
+ const restored = await driver.read("users", "u1");
1001
+ await driver.close();
1002
+ if (restored === void 0 || !deepEqual(restored, original)) yield {
1003
+ check: "snapshot-rollback-value",
1004
+ message: "snapshot rollback must restore pre-snapshot row values",
1005
+ context: {
1006
+ table: "users",
1007
+ expected: original,
1008
+ actual: restored
960
1009
  }
961
- },
962
- {
1010
+ };
1011
+ } catch (error) {
1012
+ yield {
1013
+ check: "snapshot",
1014
+ message: error instanceof Error ? error.message : String(error),
1015
+ context: { error }
1016
+ };
1017
+ }
1018
+ try {
1019
+ const driver = factory();
1020
+ await driver.open(CONFORMANCE_SCHEMA);
1021
+ const original = {
1022
+ id: "u3",
1023
+ name: "Nested",
1024
+ age: 20,
1025
+ meta: { tags: ["a"] }
1026
+ };
1027
+ await driver.write("users", "u3", original);
1028
+ const rollback = await driver.snapshot();
1029
+ const before = await driver.read("users", "u3");
1030
+ if (isRecord(before) && isRecord(before.meta) && Array.isArray(before.meta.tags)) before.meta.tags.push("mutated-before-restore");
1031
+ await driver.write("users", "u3", {
1032
+ id: "u3",
1033
+ name: "Nested",
1034
+ age: 20,
1035
+ meta: { tags: ["a", "mutated-after-write"] }
1036
+ });
1037
+ await rollback();
1038
+ const restored = await driver.read("users", "u3");
1039
+ await driver.close();
1040
+ if (restored === void 0 || !deepEqual(restored, original)) yield {
963
1041
  check: "snapshot-nested",
964
- run: async () => {
965
- const driver = factory();
966
- await driver.open(CONFORMANCE_SCHEMA);
967
- const original = {
968
- id: "u3",
969
- name: "Nested",
970
- age: 20,
971
- meta: { tags: ["a"] }
972
- };
973
- await driver.write("users", "u3", original);
974
- const rollback = await driver.snapshot();
975
- const before = await driver.read("users", "u3");
976
- if (isRecord(before) && isRecord(before.meta) && Array.isArray(before.meta.tags)) before.meta.tags.push("mutated-before-restore");
977
- await driver.write("users", "u3", {
978
- id: "u3",
979
- name: "Nested",
980
- age: 20,
981
- meta: { tags: ["a", "mutated-after-write"] }
982
- });
983
- await rollback();
984
- const restored = await driver.read("users", "u3");
985
- await driver.close();
986
- if (restored === void 0 || !deepEqual(restored, original)) return findingOf("snapshot-nested", "snapshot rollback must restore pre-snapshot nested field values, unaffected by a later in-place mutation of a read-back row", {
987
- table: "users",
988
- expected: original,
989
- actual: restored
990
- });
1042
+ message: "snapshot rollback must restore pre-snapshot nested field values, unaffected by a later in-place mutation of a read-back row",
1043
+ context: {
1044
+ table: "users",
1045
+ expected: original,
1046
+ actual: restored
991
1047
  }
992
- },
993
- {
1048
+ };
1049
+ } catch (error) {
1050
+ yield {
1051
+ check: "snapshot-nested",
1052
+ message: error instanceof Error ? error.message : String(error),
1053
+ context: { error }
1054
+ };
1055
+ }
1056
+ try {
1057
+ const driver = factory();
1058
+ await driver.open(CONFORMANCE_SCHEMA);
1059
+ await driver.write("posts", "hello-world", {
1060
+ slug: "hello-world",
1061
+ title: "Hello"
1062
+ });
1063
+ const post = await driver.read("posts", "hello-world");
1064
+ const key = post === void 0 ? void 0 : extractKey(post, "slug");
1065
+ await driver.close();
1066
+ if (key !== "hello-world") yield {
994
1067
  check: "non-id-primary",
995
- run: async () => {
996
- const driver = factory();
997
- await driver.open(CONFORMANCE_SCHEMA);
998
- await driver.write("posts", "hello-world", {
999
- slug: "hello-world",
1000
- title: "Hello"
1001
- });
1002
- const post = await driver.read("posts", "hello-world");
1003
- const key = post === void 0 ? void 0 : extractKey(post, "slug");
1004
- await driver.close();
1005
- if (key !== "hello-world") return findingOf("non-id-primary", "a non-id primary key column must round-trip through the store", {
1006
- table: "posts",
1007
- expected: "hello-world",
1008
- actual: key
1009
- });
1068
+ message: "a non-id primary key column must round-trip through the store",
1069
+ context: {
1070
+ table: "posts",
1071
+ expected: "hello-world",
1072
+ actual: key
1073
+ }
1074
+ };
1075
+ } catch (error) {
1076
+ yield {
1077
+ check: "non-id-primary",
1078
+ message: error instanceof Error ? error.message : String(error),
1079
+ context: { error }
1080
+ };
1081
+ }
1082
+ try {
1083
+ const driver = factory();
1084
+ await driver.open(CONFORMANCE_SCHEMA);
1085
+ const nested = {
1086
+ id: "u3",
1087
+ name: "Nested",
1088
+ age: 20,
1089
+ meta: {
1090
+ tags: ["a", "b"],
1091
+ deep: { flag: true }
1010
1092
  }
1011
- },
1012
- {
1093
+ };
1094
+ await driver.write("users", "u3", nested);
1095
+ const readBack = await driver.read("users", "u3");
1096
+ await driver.close();
1097
+ if (readBack === void 0 || !deepEqual(readBack, nested)) yield {
1013
1098
  check: "nested-roundtrip",
1014
- run: async () => {
1015
- const driver = factory();
1016
- await driver.open(CONFORMANCE_SCHEMA);
1017
- const nested = {
1018
- id: "u3",
1019
- name: "Nested",
1020
- age: 20,
1021
- meta: {
1022
- tags: ["a", "b"],
1023
- deep: { flag: true }
1024
- }
1025
- };
1026
- await driver.write("users", "u3", nested);
1027
- const readBack = await driver.read("users", "u3");
1028
- await driver.close();
1029
- if (readBack === void 0 || !deepEqual(readBack, nested)) return findingOf("nested-roundtrip", "a nested-object row must round-trip structurally", {
1099
+ message: "a nested-object row must round-trip structurally",
1100
+ context: {
1101
+ table: "users",
1102
+ expected: nested,
1103
+ actual: readBack
1104
+ }
1105
+ };
1106
+ } catch (error) {
1107
+ yield {
1108
+ check: "nested-roundtrip",
1109
+ message: error instanceof Error ? error.message : String(error),
1110
+ context: { error }
1111
+ };
1112
+ }
1113
+ migratePhase: try {
1114
+ const driver = factory();
1115
+ if (driver.migrate === void 0) break migratePhase;
1116
+ const deployedUsers = {
1117
+ ...CONFORMANCE_USERS_SCHEMA,
1118
+ columns: [...CONFORMANCE_USERS_SCHEMA.columns, {
1119
+ name: "legacy",
1120
+ type: "boolean",
1121
+ nullable: true
1122
+ }]
1123
+ };
1124
+ await driver.open([deployedUsers, CONFORMANCE_POSTS_SCHEMA]);
1125
+ await driver.write("users", "u1", {
1126
+ id: "u1",
1127
+ name: "Ada",
1128
+ age: 30,
1129
+ legacy: true
1130
+ });
1131
+ const removePlan = planMigration([deployedUsers], [CONFORMANCE_USERS_SCHEMA]);
1132
+ await driver.migrate(removePlan);
1133
+ const migrated = await driver.read("users", "u1");
1134
+ if (migrated === void 0 || "legacy" in migrated) {
1135
+ await driver.close();
1136
+ yield {
1137
+ check: "migrate-column-remove",
1138
+ message: "a column.remove migration must strip the column from stored rows",
1139
+ context: {
1030
1140
  table: "users",
1031
- expected: nested,
1032
- actual: readBack
1033
- });
1141
+ expected: void 0,
1142
+ actual: migrated === void 0 ? void 0 : migrated.legacy
1143
+ }
1144
+ };
1145
+ break migratePhase;
1146
+ }
1147
+ let caught;
1148
+ try {
1149
+ await driver.migrate({
1150
+ from: 0,
1151
+ to: 1,
1152
+ steps: [{
1153
+ operation: "table.remove",
1154
+ table: "ghost"
1155
+ }]
1156
+ });
1157
+ } catch (error) {
1158
+ caught = error;
1159
+ }
1160
+ await driver.close();
1161
+ if (!isDatabaseError(caught) || caught.code !== "MIGRATION") yield {
1162
+ check: "migrate-unknown-table",
1163
+ message: "a migration step referencing an unknown table must throw a MIGRATION DatabaseError",
1164
+ context: {
1165
+ table: "ghost",
1166
+ expected: "MIGRATION",
1167
+ actual: isDatabaseError(caught) ? caught.code : caught
1034
1168
  }
1035
- },
1036
- {
1169
+ };
1170
+ } catch (error) {
1171
+ yield {
1037
1172
  check: "migrate",
1038
- run: async () => {
1039
- const driver = factory();
1040
- if (driver.migrate === void 0) return void 0;
1041
- const deployedUsers = {
1042
- ...CONFORMANCE_USERS_SCHEMA,
1043
- columns: [...CONFORMANCE_USERS_SCHEMA.columns, {
1044
- name: "legacy",
1045
- type: "boolean",
1046
- nullable: true
1047
- }]
1048
- };
1049
- await driver.open([deployedUsers, CONFORMANCE_POSTS_SCHEMA]);
1050
- await driver.write("users", "u1", {
1051
- id: "u1",
1052
- name: "Ada",
1053
- age: 30,
1054
- legacy: true
1055
- });
1056
- const removePlan = planMigration([deployedUsers], [CONFORMANCE_USERS_SCHEMA]);
1057
- await driver.migrate(removePlan);
1058
- const migrated = await driver.read("users", "u1");
1059
- if (migrated === void 0 || "legacy" in migrated) {
1060
- await driver.close();
1061
- return findingOf("migrate-column-remove", "a column.remove migration must strip the column from stored rows", {
1062
- table: "users",
1063
- expected: void 0,
1064
- actual: migrated === void 0 ? void 0 : migrated.legacy
1065
- });
1066
- }
1067
- let caught;
1068
- try {
1069
- await driver.migrate({
1070
- from: 0,
1071
- to: 1,
1072
- steps: [{
1073
- operation: "table.remove",
1074
- table: "ghost"
1075
- }]
1076
- });
1077
- } catch (error) {
1078
- caught = error;
1173
+ message: error instanceof Error ? error.message : String(error),
1174
+ context: { error }
1175
+ };
1176
+ }
1177
+ streamPhase: try {
1178
+ const driver = factory();
1179
+ if (driver.stream === void 0) break streamPhase;
1180
+ await driver.open(CONFORMANCE_SCHEMA);
1181
+ for (const row of [
1182
+ {
1183
+ id: "a",
1184
+ name: "A",
1185
+ age: 10
1186
+ },
1187
+ {
1188
+ id: "b",
1189
+ name: "B",
1190
+ age: 20
1191
+ },
1192
+ {
1193
+ id: "c",
1194
+ name: "C",
1195
+ age: 30
1196
+ }
1197
+ ]) await driver.write("users", row.id, row);
1198
+ const criteria = { conditions: [{
1199
+ column: "age",
1200
+ operator: "above",
1201
+ values: [10],
1202
+ connector: "and"
1203
+ }] };
1204
+ const matched = [];
1205
+ for await (const row of driver.stream("users", criteria)) matched.push(row);
1206
+ const matchedIds = matched.map((row) => row.id).sort();
1207
+ if (!deepEqual(matchedIds, ["b", "c"])) {
1208
+ await driver.close();
1209
+ yield {
1210
+ check: "stream-match",
1211
+ message: "stream must yield only condition-matching rows",
1212
+ context: {
1213
+ table: "users",
1214
+ expected: ["b", "c"],
1215
+ actual: matchedIds
1079
1216
  }
1080
- await driver.close();
1081
- if (!isDatabaseError(caught) || caught.code !== "MIGRATION") return findingOf("migrate-unknown-table", "a migration step referencing an unknown table must throw a MIGRATION DatabaseError", {
1082
- table: "ghost",
1083
- expected: "MIGRATION",
1084
- actual: isDatabaseError(caught) ? caught.code : caught
1085
- });
1217
+ };
1218
+ break streamPhase;
1219
+ }
1220
+ const paged = [];
1221
+ for await (const row of driver.stream("users", {
1222
+ offset: 1,
1223
+ limit: 1
1224
+ })) paged.push(row);
1225
+ await driver.close();
1226
+ if (paged.length !== 1) yield {
1227
+ check: "stream-page",
1228
+ message: "stream must honor offset and limit",
1229
+ context: {
1230
+ table: "users",
1231
+ expected: 1,
1232
+ actual: paged.length
1086
1233
  }
1087
- },
1088
- {
1234
+ };
1235
+ } catch (error) {
1236
+ yield {
1089
1237
  check: "stream",
1090
- run: async () => {
1091
- const driver = factory();
1092
- if (driver.stream === void 0) return void 0;
1093
- await driver.open(CONFORMANCE_SCHEMA);
1094
- for (const row of [
1095
- {
1096
- id: "a",
1097
- name: "A",
1098
- age: 10
1099
- },
1100
- {
1101
- id: "b",
1102
- name: "B",
1103
- age: 20
1104
- },
1105
- {
1106
- id: "c",
1107
- name: "C",
1108
- age: 30
1109
- }
1110
- ]) await driver.write("users", row.id, row);
1111
- const criteria = { conditions: [{
1112
- column: "age",
1113
- operator: "above",
1114
- values: [10],
1115
- connector: "and"
1116
- }] };
1117
- const matched = [];
1118
- for await (const row of driver.stream("users", criteria)) matched.push(row);
1119
- const matchedIds = matched.map((row) => row.id).sort();
1120
- if (!deepEqual(matchedIds, ["b", "c"])) {
1121
- await driver.close();
1122
- return findingOf("stream-match", "stream must yield only condition-matching rows", {
1123
- table: "users",
1124
- expected: ["b", "c"],
1125
- actual: matchedIds
1126
- });
1127
- }
1128
- const paged = [];
1129
- for await (const row of driver.stream("users", {
1130
- offset: 1,
1131
- limit: 1
1132
- })) paged.push(row);
1133
- await driver.close();
1134
- if (paged.length !== 1) return findingOf("stream-page", "stream must honor offset and limit", {
1238
+ message: error instanceof Error ? error.message : String(error),
1239
+ context: { error }
1240
+ };
1241
+ }
1242
+ transactionPhase: try {
1243
+ const driver = factory();
1244
+ if (driver.transaction === void 0) break transactionPhase;
1245
+ await driver.open(CONFORMANCE_SCHEMA);
1246
+ await driver.write("users", "u1", {
1247
+ id: "u1",
1248
+ name: "Ada",
1249
+ age: 30
1250
+ });
1251
+ const committing = await driver.transaction();
1252
+ await driver.write("users", "u2", {
1253
+ id: "u2",
1254
+ name: "Grace",
1255
+ age: 40
1256
+ });
1257
+ await committing.commit();
1258
+ const afterCommit = [...await driver.keys("users")].sort();
1259
+ if (!deepEqual(afterCommit, ["u1", "u2"])) {
1260
+ await driver.close();
1261
+ yield {
1262
+ check: "transaction-commit",
1263
+ message: "transaction commit must persist writes made during the scope",
1264
+ context: {
1135
1265
  table: "users",
1136
- expected: 1,
1137
- actual: paged.length
1138
- });
1266
+ expected: ["u1", "u2"],
1267
+ actual: afterCommit
1268
+ }
1269
+ };
1270
+ break transactionPhase;
1271
+ }
1272
+ const rollingBack = await driver.transaction();
1273
+ await driver.write("users", "u3", {
1274
+ id: "u3",
1275
+ name: "Marie",
1276
+ age: 50
1277
+ });
1278
+ await rollingBack.rollback();
1279
+ const afterRollback = [...await driver.keys("users")].sort();
1280
+ await driver.close();
1281
+ if (!deepEqual(afterRollback, ["u1", "u2"])) yield {
1282
+ check: "transaction-rollback",
1283
+ message: "transaction rollback must restore pre-transaction state",
1284
+ context: {
1285
+ table: "users",
1286
+ expected: ["u1", "u2"],
1287
+ actual: afterRollback
1139
1288
  }
1140
- },
1141
- {
1289
+ };
1290
+ } catch (error) {
1291
+ yield {
1142
1292
  check: "transaction",
1143
- run: async () => {
1144
- const driver = factory();
1145
- if (driver.transaction === void 0) return void 0;
1146
- await driver.open(CONFORMANCE_SCHEMA);
1147
- await driver.write("users", "u1", {
1148
- id: "u1",
1149
- name: "Ada",
1150
- age: 30
1151
- });
1152
- const committing = await driver.transaction();
1153
- await driver.write("users", "u2", {
1154
- id: "u2",
1155
- name: "Grace",
1156
- age: 40
1157
- });
1158
- await committing.commit();
1159
- const afterCommit = [...await driver.keys("users")].sort();
1160
- if (!deepEqual(afterCommit, ["u1", "u2"])) {
1161
- await driver.close();
1162
- return findingOf("transaction-commit", "transaction commit must persist writes made during the scope", {
1163
- table: "users",
1164
- expected: ["u1", "u2"],
1165
- actual: afterCommit
1166
- });
1293
+ message: error instanceof Error ? error.message : String(error),
1294
+ context: { error }
1295
+ };
1296
+ }
1297
+ metaPhase: try {
1298
+ const driver = factory();
1299
+ if (driver.meta === void 0 || driver.stamp === void 0) break metaPhase;
1300
+ await driver.open(CONFORMANCE_SCHEMA);
1301
+ const fresh = await driver.meta();
1302
+ if (fresh !== void 0) {
1303
+ await driver.close();
1304
+ yield {
1305
+ check: "meta-fresh",
1306
+ message: "a fresh store must report undefined meta",
1307
+ context: {
1308
+ expected: void 0,
1309
+ actual: fresh
1167
1310
  }
1168
- const rollingBack = await driver.transaction();
1169
- await driver.write("users", "u3", {
1170
- id: "u3",
1171
- name: "Marie",
1172
- age: 50
1173
- });
1174
- await rollingBack.rollback();
1175
- const afterRollback = [...await driver.keys("users")].sort();
1176
- await driver.close();
1177
- if (!deepEqual(afterRollback, ["u1", "u2"])) return findingOf("transaction-rollback", "transaction rollback must restore pre-transaction state", {
1178
- table: "users",
1179
- expected: ["u1", "u2"],
1180
- actual: afterRollback
1181
- });
1182
- }
1183
- },
1184
- {
1311
+ };
1312
+ break metaPhase;
1313
+ }
1314
+ const stamped = {
1315
+ version: 1,
1316
+ schema: CONFORMANCE_SCHEMA
1317
+ };
1318
+ await driver.stamp(stamped);
1319
+ const read = await driver.meta();
1320
+ await driver.close();
1321
+ if (read === void 0 || !deepEqual(read, stamped)) yield {
1185
1322
  check: "meta-stamp",
1186
- run: async () => {
1187
- const driver = factory();
1188
- if (driver.meta === void 0 || driver.stamp === void 0) return void 0;
1189
- await driver.open(CONFORMANCE_SCHEMA);
1190
- const fresh = await driver.meta();
1191
- if (fresh !== void 0) {
1192
- await driver.close();
1193
- return findingOf("meta-fresh", "a fresh store must report undefined meta", {
1194
- expected: void 0,
1195
- actual: fresh
1196
- });
1197
- }
1198
- const stamped = {
1199
- version: 1,
1200
- schema: CONFORMANCE_SCHEMA
1201
- };
1202
- await driver.stamp(stamped);
1203
- const read = await driver.meta();
1204
- await driver.close();
1205
- if (read === void 0 || !deepEqual(read, stamped)) return findingOf("meta-stamp", "meta() must return exactly the last-stamped value", {
1206
- expected: stamped,
1207
- actual: read
1208
- });
1323
+ message: "meta() must return exactly the last-stamped value",
1324
+ context: {
1325
+ expected: stamped,
1326
+ actual: read
1209
1327
  }
1210
- },
1211
- {
1212
- check: "snapshot-scoped",
1213
- run: async () => {
1214
- const driver = factory();
1215
- await driver.open(CONFORMANCE_SCHEMA);
1216
- await driver.write("users", "u1", {
1217
- id: "u1",
1218
- name: "Ada",
1219
- age: 30
1220
- });
1221
- await driver.write("posts", "p1", {
1222
- slug: "p1",
1223
- title: "Post"
1224
- });
1225
- const rollback = await driver.snapshot(["users"]);
1226
- await driver.write("users", "u2", {
1227
- id: "u2",
1228
- name: "Grace",
1229
- age: 40
1230
- });
1231
- await driver.write("posts", "p2", {
1232
- slug: "p2",
1233
- title: "Another post"
1234
- });
1235
- await rollback();
1236
- const usersKeys = [...await driver.keys("users")];
1237
- if (!deepEqual(usersKeys, ["u1"])) {
1238
- await driver.close();
1239
- return findingOf("snapshot-scoped-users", "a scoped snapshot must roll back only the named table", {
1240
- table: "users",
1241
- expected: ["u1"],
1242
- actual: usersKeys
1243
- });
1328
+ };
1329
+ } catch (error) {
1330
+ yield {
1331
+ check: "meta-stamp",
1332
+ message: error instanceof Error ? error.message : String(error),
1333
+ context: { error }
1334
+ };
1335
+ }
1336
+ scopedPhase: try {
1337
+ const driver = factory();
1338
+ await driver.open(CONFORMANCE_SCHEMA);
1339
+ await driver.write("users", "u1", {
1340
+ id: "u1",
1341
+ name: "Ada",
1342
+ age: 30
1343
+ });
1344
+ await driver.write("posts", "p1", {
1345
+ slug: "p1",
1346
+ title: "Post"
1347
+ });
1348
+ const rollback = await driver.snapshot(["users"]);
1349
+ await driver.write("users", "u2", {
1350
+ id: "u2",
1351
+ name: "Grace",
1352
+ age: 40
1353
+ });
1354
+ await driver.write("posts", "p2", {
1355
+ slug: "p2",
1356
+ title: "Another post"
1357
+ });
1358
+ await rollback();
1359
+ const usersKeys = [...await driver.keys("users")];
1360
+ if (!deepEqual(usersKeys, ["u1"])) {
1361
+ await driver.close();
1362
+ yield {
1363
+ check: "snapshot-scoped-users",
1364
+ message: "a scoped snapshot must roll back only the named table",
1365
+ context: {
1366
+ table: "users",
1367
+ expected: ["u1"],
1368
+ actual: usersKeys
1244
1369
  }
1245
- const postsKeys = [...await driver.keys("posts")].sort();
1246
- await driver.close();
1247
- if (!deepEqual(postsKeys, ["p1", "p2"])) return findingOf("snapshot-scoped-posts", "a scoped snapshot must leave an unnamed table's mutations intact", {
1248
- table: "posts",
1249
- expected: ["p1", "p2"],
1250
- actual: postsKeys
1251
- });
1252
- }
1370
+ };
1371
+ break scopedPhase;
1253
1372
  }
1254
- ];
1255
- for (const phase of phases) try {
1256
- const finding = await phase.run();
1257
- if (finding !== void 0) yield finding;
1373
+ const postsKeys = [...await driver.keys("posts")].sort();
1374
+ await driver.close();
1375
+ if (!deepEqual(postsKeys, ["p1", "p2"])) yield {
1376
+ check: "snapshot-scoped-posts",
1377
+ message: "a scoped snapshot must leave an unnamed table's mutations intact",
1378
+ context: {
1379
+ table: "posts",
1380
+ expected: ["p1", "p2"],
1381
+ actual: postsKeys
1382
+ }
1383
+ };
1258
1384
  } catch (error) {
1259
- yield findingOf(phase.check, error instanceof Error ? error.message : String(error), { error });
1385
+ yield {
1386
+ check: "snapshot-scoped",
1387
+ message: error instanceof Error ? error.message : String(error),
1388
+ context: { error }
1389
+ };
1260
1390
  }
1261
1391
  }
1262
1392
  /**
@@ -1343,10 +1473,12 @@ async function auditDriver(factory) {
1343
1473
  * ```
1344
1474
  */
1345
1475
  function generateUUID(random = Math.random) {
1346
- const bytes = Array.from({ length: 16 }, () => Math.floor(random() * 256) & 255);
1347
- bytes[6] = bytes[6] & 15 | 64;
1348
- bytes[8] = bytes[8] & 63 | 128;
1349
- const hex = bytes.map((byte) => byte.toString(16).padStart(2, "0"));
1476
+ const hex = Array.from({ length: 16 }, (_, index) => {
1477
+ const byte = Math.floor(random() * 256) & 255;
1478
+ if (index === 6) return byte & 15 | 64;
1479
+ if (index === 8) return byte & 63 | 128;
1480
+ return byte;
1481
+ }).map((byte) => byte.toString(16).padStart(2, "0"));
1350
1482
  return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
1351
1483
  }
1352
1484
  //#endregion
@@ -1383,7 +1515,12 @@ var Cursor = class {
1383
1515
  if (this.#closed) return;
1384
1516
  this.#index += 1;
1385
1517
  while (this.#index < this.#keys.length) {
1386
- const row = await this.#table.get(this.#keys[this.#index]);
1518
+ const key = this.#keys[this.#index];
1519
+ if (key === void 0) {
1520
+ this.#index += 1;
1521
+ continue;
1522
+ }
1523
+ const row = await this.#table.get(key);
1387
1524
  if (row !== void 0) {
1388
1525
  this.#value = row;
1389
1526
  return;
@@ -1395,12 +1532,15 @@ var Cursor = class {
1395
1532
  async update(changes) {
1396
1533
  if (this.#closed || this.#value === void 0) return;
1397
1534
  const key = this.#keys[this.#index];
1535
+ if (key === void 0) return;
1398
1536
  await this.#table.update(key, changes);
1399
1537
  this.#value = await this.#table.get(key);
1400
1538
  }
1401
1539
  async remove() {
1402
1540
  if (this.#closed || this.#value === void 0) return;
1403
- await this.#table.remove(this.#keys[this.#index]);
1541
+ const key = this.#keys[this.#index];
1542
+ if (key === void 0) return;
1543
+ await this.#table.remove(key);
1404
1544
  this.#value = void 0;
1405
1545
  }
1406
1546
  close() {
@@ -1544,8 +1684,8 @@ var Query = class {
1544
1684
  if (this.#filters.length === 0) return this.#table.records({
1545
1685
  conditions: this.#conditions,
1546
1686
  order: this.#orders,
1547
- limit: this.#limit,
1548
- offset: this.#offset
1687
+ ...this.#limit !== void 0 ? { limit: this.#limit } : {},
1688
+ ...this.#offset !== void 0 ? { offset: this.#offset } : {}
1549
1689
  });
1550
1690
  const fetched = await this.#table.records({
1551
1691
  conditions: this.#conditions,
@@ -1578,8 +1718,8 @@ var Query = class {
1578
1718
  if (this.#filters.length === 0) {
1579
1719
  yield* this.#table.scan({
1580
1720
  conditions: this.#conditions,
1581
- limit: this.#limit,
1582
- offset: this.#offset
1721
+ ...this.#limit !== void 0 ? { limit: this.#limit } : {},
1722
+ ...this.#offset !== void 0 ? { offset: this.#offset } : {}
1583
1723
  }, options);
1584
1724
  return;
1585
1725
  }
@@ -1677,8 +1817,8 @@ var Table = class {
1677
1817
  this.#guard = contract.is;
1678
1818
  this.#generate = generate;
1679
1819
  this.#emitter = new Emitter({
1680
- on,
1681
- error
1820
+ ...on !== void 0 ? { on } : {},
1821
+ ...error !== void 0 ? { error } : {}
1682
1822
  });
1683
1823
  }
1684
1824
  get emitter() {
@@ -1984,8 +2124,8 @@ var Database = class Database {
1984
2124
  this.#generate = options.key;
1985
2125
  this.#version = options.version;
1986
2126
  this.#emitter = new Emitter({
1987
- on: options.on,
1988
- error: options.error
2127
+ ...options.on !== void 0 ? { on: options.on } : {},
2128
+ ...options.error !== void 0 ? { error: options.error } : {}
1989
2129
  });
1990
2130
  }
1991
2131
  get emitter() {
@@ -1999,7 +2139,8 @@ var Database = class Database {
1999
2139
  }
2000
2140
  table(name) {
2001
2141
  if (this.#status === "closed") throw new DatabaseError("CLOSED", `Database '${this.#name}' is closed`, { name: this.#name });
2002
- return this.#build(name, this.#key(name), createContract(objectShape(this.#tables[name])));
2142
+ const columns = this.#columns(name);
2143
+ return this.#build(name, this.#key(name), createContract(objectShape(columns)));
2003
2144
  }
2004
2145
  import(tables, keys) {
2005
2146
  return this.#spawn(tables, {
@@ -2010,7 +2151,7 @@ var Database = class Database {
2010
2151
  export() {
2011
2152
  const result = {};
2012
2153
  for (const name of Object.keys(this.#tables)) {
2013
- const columns = this.#tables[name];
2154
+ const columns = this.#columns(name);
2014
2155
  result[name] = {
2015
2156
  key: this.#key(name),
2016
2157
  columns,
@@ -2116,14 +2257,18 @@ var Database = class Database {
2116
2257
  #key(name) {
2117
2258
  return this.#keys[name] ?? "id";
2118
2259
  }
2260
+ #columns(name) {
2261
+ const columns = this.#tables[name];
2262
+ if (columns === void 0) throw new DatabaseError("NOT_FOUND", `Table '${name}' is not declared`, { table: name });
2263
+ return columns;
2264
+ }
2119
2265
  #schema() {
2120
2266
  return Object.keys(this.#tables).map((name) => {
2121
- const columns = this.#tables[name];
2267
+ const columns = this.#columns(name);
2122
2268
  return {
2123
2269
  name,
2124
2270
  primary: this.#key(name),
2125
- columns: Object.keys(columns).map((column) => {
2126
- const shape = columns[column];
2271
+ columns: Object.entries(columns).map(([column, shape]) => {
2127
2272
  return {
2128
2273
  name: column,
2129
2274
  type: shapeToColumnType(shape),
@@ -2379,7 +2524,14 @@ var MemoryDriver = class {
2379
2524
  const store = this.#require(step.table);
2380
2525
  const rows = [...store.entries()];
2381
2526
  const migrated = migrateRows(rows.map(([, row]) => row), [step]);
2382
- rows.forEach(([key], index) => store.set(key, migrated[index]));
2527
+ for (const [index, [key]] of rows.entries()) {
2528
+ const row = migrated[index];
2529
+ if (row === void 0) throw new DatabaseError("MIGRATION", "migrate: transformed row is missing", {
2530
+ table: step.table,
2531
+ index
2532
+ });
2533
+ store.set(key, row);
2534
+ }
2383
2535
  break;
2384
2536
  }
2385
2537
  case "index.add":