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