sliftutils 1.4.0 → 1.4.2
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.
- package/index.d.ts +31 -2
- package/package.json +1 -1
- package/storage/BulkDatabase2/BulkDatabase2.d.ts +19 -2
- package/storage/BulkDatabase2/BulkDatabase2.ts +11 -4
- package/storage/BulkDatabase2/BulkDatabaseBase.d.ts +12 -0
- package/storage/BulkDatabase2/BulkDatabaseBase.ts +67 -30
- package/storage/FileFolderAPI.tsx +4 -1
package/index.d.ts
CHANGED
|
@@ -813,20 +813,37 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabase2" {
|
|
|
813
813
|
getKeys(): Promise<string[]>;
|
|
814
814
|
/** One field's value for a key, or undefined if the key/column isn't set or the key is deleted. */
|
|
815
815
|
getSingleField<Column extends keyof T>(key: string, column: Column): Promise<T[Column] | undefined>;
|
|
816
|
-
/**
|
|
816
|
+
/**
|
|
817
|
+
* Like getSingleField but returns { key, value, time } (the same shape a getColumn entry has), where
|
|
818
|
+
* time is roughly when the value last changed. undefined only when the key isn't present/live.
|
|
819
|
+
*/
|
|
820
|
+
getSingleFieldObj<Column extends keyof T>(key: string, column: Column): Promise<{
|
|
821
|
+
key: string;
|
|
822
|
+
value: T[Column];
|
|
823
|
+
time: number;
|
|
824
|
+
} | undefined>;
|
|
825
|
+
/** A whole column as { key, value, time } for every live key (time ≈ when each value last changed). */
|
|
817
826
|
getColumn<Column extends keyof T>(column: Column): Promise<{
|
|
818
827
|
key: string;
|
|
819
828
|
value: T[Column];
|
|
829
|
+
time: number;
|
|
820
830
|
}[]>;
|
|
821
831
|
/**
|
|
822
832
|
* Synchronous, reactive read of one field. Returns undefined while the base value is still loading
|
|
823
833
|
* (and re-renders once it arrives, under a mobx observer); reflects pending writes immediately.
|
|
824
834
|
*/
|
|
825
835
|
getSingleFieldSync<Column extends keyof T>(key: string, column: Column): T[Column] | undefined;
|
|
826
|
-
/**
|
|
836
|
+
/** Sync, reactive counterpart of getSingleFieldObj: { key, value, time } once loaded, else undefined. */
|
|
837
|
+
getSingleFieldObjSync<Column extends keyof T>(key: string, column: Column): {
|
|
838
|
+
key: string;
|
|
839
|
+
value: T[Column];
|
|
840
|
+
time: number;
|
|
841
|
+
} | undefined;
|
|
842
|
+
/** Synchronous, reactive read of a whole column ({ key, value, time }). undefined while still loading. */
|
|
827
843
|
getColumnSync<Column extends keyof T>(column: Column): {
|
|
828
844
|
key: string;
|
|
829
845
|
value: T[Column];
|
|
846
|
+
time: number;
|
|
830
847
|
}[] | undefined;
|
|
831
848
|
/** The columns present on disk and their byte sizes (no row data read). */
|
|
832
849
|
getColumnInfo(): Promise<BulkColumnInfo[]>;
|
|
@@ -931,9 +948,15 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
|
|
|
931
948
|
private formatInfo;
|
|
932
949
|
private patchColumn;
|
|
933
950
|
getSingleField<Column extends keyof T>(key: string, column: Column): Promise<T[Column] | undefined>;
|
|
951
|
+
getSingleFieldObj<Column extends keyof T>(key: string, column: Column): Promise<{
|
|
952
|
+
key: string;
|
|
953
|
+
value: T[Column];
|
|
954
|
+
time: number;
|
|
955
|
+
} | undefined>;
|
|
934
956
|
getColumn<Column extends keyof T>(column: Column): Promise<{
|
|
935
957
|
key: string;
|
|
936
958
|
value: T[Column];
|
|
959
|
+
time: number;
|
|
937
960
|
}[]>;
|
|
938
961
|
getKeys(): Promise<string[]>;
|
|
939
962
|
private baseColumns;
|
|
@@ -943,9 +966,15 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" {
|
|
|
943
966
|
private ensureBaseColumn;
|
|
944
967
|
private ensureBaseField;
|
|
945
968
|
getSingleFieldSync<Column extends keyof T>(key: string, column: Column): T[Column] | undefined;
|
|
969
|
+
getSingleFieldObjSync<Column extends keyof T>(key: string, column: Column): {
|
|
970
|
+
key: string;
|
|
971
|
+
value: T[Column];
|
|
972
|
+
time: number;
|
|
973
|
+
} | undefined;
|
|
946
974
|
getColumnSync<Column extends keyof T>(column: Column): {
|
|
947
975
|
key: string;
|
|
948
976
|
value: T[Column];
|
|
977
|
+
time: number;
|
|
949
978
|
}[] | undefined;
|
|
950
979
|
getColumnInfo(): Promise<{
|
|
951
980
|
column: string;
|
package/package.json
CHANGED
|
@@ -48,20 +48,37 @@ export interface IBulkDatabase2<T extends {
|
|
|
48
48
|
getKeys(): Promise<string[]>;
|
|
49
49
|
/** One field's value for a key, or undefined if the key/column isn't set or the key is deleted. */
|
|
50
50
|
getSingleField<Column extends keyof T>(key: string, column: Column): Promise<T[Column] | undefined>;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Like getSingleField but returns { key, value, time } (the same shape a getColumn entry has), where
|
|
53
|
+
* time is roughly when the value last changed. undefined only when the key isn't present/live.
|
|
54
|
+
*/
|
|
55
|
+
getSingleFieldObj<Column extends keyof T>(key: string, column: Column): Promise<{
|
|
56
|
+
key: string;
|
|
57
|
+
value: T[Column];
|
|
58
|
+
time: number;
|
|
59
|
+
} | undefined>;
|
|
60
|
+
/** A whole column as { key, value, time } for every live key (time ≈ when each value last changed). */
|
|
52
61
|
getColumn<Column extends keyof T>(column: Column): Promise<{
|
|
53
62
|
key: string;
|
|
54
63
|
value: T[Column];
|
|
64
|
+
time: number;
|
|
55
65
|
}[]>;
|
|
56
66
|
/**
|
|
57
67
|
* Synchronous, reactive read of one field. Returns undefined while the base value is still loading
|
|
58
68
|
* (and re-renders once it arrives, under a mobx observer); reflects pending writes immediately.
|
|
59
69
|
*/
|
|
60
70
|
getSingleFieldSync<Column extends keyof T>(key: string, column: Column): T[Column] | undefined;
|
|
61
|
-
/**
|
|
71
|
+
/** Sync, reactive counterpart of getSingleFieldObj: { key, value, time } once loaded, else undefined. */
|
|
72
|
+
getSingleFieldObjSync<Column extends keyof T>(key: string, column: Column): {
|
|
73
|
+
key: string;
|
|
74
|
+
value: T[Column];
|
|
75
|
+
time: number;
|
|
76
|
+
} | undefined;
|
|
77
|
+
/** Synchronous, reactive read of a whole column ({ key, value, time }). undefined while still loading. */
|
|
62
78
|
getColumnSync<Column extends keyof T>(column: Column): {
|
|
63
79
|
key: string;
|
|
64
80
|
value: T[Column];
|
|
81
|
+
time: number;
|
|
65
82
|
}[] | undefined;
|
|
66
83
|
/** The columns present on disk and their byte sizes (no row data read). */
|
|
67
84
|
getColumnInfo(): Promise<BulkColumnInfo[]>;
|
|
@@ -47,16 +47,23 @@ export interface IBulkDatabase2<T extends { key: string }> {
|
|
|
47
47
|
getKeys(): Promise<string[]>;
|
|
48
48
|
/** One field's value for a key, or undefined if the key/column isn't set or the key is deleted. */
|
|
49
49
|
getSingleField<Column extends keyof T>(key: string, column: Column): Promise<T[Column] | undefined>;
|
|
50
|
-
/**
|
|
51
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Like getSingleField but returns { key, value, time } (the same shape a getColumn entry has), where
|
|
52
|
+
* time is roughly when the value last changed. undefined only when the key isn't present/live.
|
|
53
|
+
*/
|
|
54
|
+
getSingleFieldObj<Column extends keyof T>(key: string, column: Column): Promise<{ key: string; value: T[Column]; time: number } | undefined>;
|
|
55
|
+
/** A whole column as { key, value, time } for every live key (time ≈ when each value last changed). */
|
|
56
|
+
getColumn<Column extends keyof T>(column: Column): Promise<{ key: string; value: T[Column]; time: number }[]>;
|
|
52
57
|
|
|
53
58
|
/**
|
|
54
59
|
* Synchronous, reactive read of one field. Returns undefined while the base value is still loading
|
|
55
60
|
* (and re-renders once it arrives, under a mobx observer); reflects pending writes immediately.
|
|
56
61
|
*/
|
|
57
62
|
getSingleFieldSync<Column extends keyof T>(key: string, column: Column): T[Column] | undefined;
|
|
58
|
-
/**
|
|
59
|
-
|
|
63
|
+
/** Sync, reactive counterpart of getSingleFieldObj: { key, value, time } once loaded, else undefined. */
|
|
64
|
+
getSingleFieldObjSync<Column extends keyof T>(key: string, column: Column): { key: string; value: T[Column]; time: number } | undefined;
|
|
65
|
+
/** Synchronous, reactive read of a whole column ({ key, value, time }). undefined while still loading. */
|
|
66
|
+
getColumnSync<Column extends keyof T>(column: Column): { key: string; value: T[Column]; time: number }[] | undefined;
|
|
60
67
|
|
|
61
68
|
/** The columns present on disk and their byte sizes (no row data read). */
|
|
62
69
|
getColumnInfo(): Promise<BulkColumnInfo[]>;
|
|
@@ -72,9 +72,15 @@ export declare class BulkDatabaseBase<T extends {
|
|
|
72
72
|
private formatInfo;
|
|
73
73
|
private patchColumn;
|
|
74
74
|
getSingleField<Column extends keyof T>(key: string, column: Column): Promise<T[Column] | undefined>;
|
|
75
|
+
getSingleFieldObj<Column extends keyof T>(key: string, column: Column): Promise<{
|
|
76
|
+
key: string;
|
|
77
|
+
value: T[Column];
|
|
78
|
+
time: number;
|
|
79
|
+
} | undefined>;
|
|
75
80
|
getColumn<Column extends keyof T>(column: Column): Promise<{
|
|
76
81
|
key: string;
|
|
77
82
|
value: T[Column];
|
|
83
|
+
time: number;
|
|
78
84
|
}[]>;
|
|
79
85
|
getKeys(): Promise<string[]>;
|
|
80
86
|
private baseColumns;
|
|
@@ -84,9 +90,15 @@ export declare class BulkDatabaseBase<T extends {
|
|
|
84
90
|
private ensureBaseColumn;
|
|
85
91
|
private ensureBaseField;
|
|
86
92
|
getSingleFieldSync<Column extends keyof T>(key: string, column: Column): T[Column] | undefined;
|
|
93
|
+
getSingleFieldObjSync<Column extends keyof T>(key: string, column: Column): {
|
|
94
|
+
key: string;
|
|
95
|
+
value: T[Column];
|
|
96
|
+
time: number;
|
|
97
|
+
} | undefined;
|
|
87
98
|
getColumnSync<Column extends keyof T>(column: Column): {
|
|
88
99
|
key: string;
|
|
89
100
|
value: T[Column];
|
|
101
|
+
time: number;
|
|
90
102
|
}[] | undefined;
|
|
91
103
|
getColumnInfo(): Promise<{
|
|
92
104
|
column: string;
|
|
@@ -883,45 +883,60 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
883
883
|
}
|
|
884
884
|
|
|
885
885
|
// Applies the overlay (pending writes/deletes) on top of a base column. No-op when empty. An
|
|
886
|
-
// overlay entry that doesn't include this column leaves the base (disk) value in place — a
|
|
887
|
-
// write/update only overrides the columns it set; everything else falls through.
|
|
888
|
-
|
|
886
|
+
// overlay entry that doesn't include this column leaves the base (disk) value+time in place — a
|
|
887
|
+
// partial write/update only overrides the columns it set; everything else falls through. An overlay
|
|
888
|
+
// override carries the overlay write's time (when that pending write happened).
|
|
889
|
+
private patchColumn(base: { key: string; value: unknown; time: number }[], column: string): { key: string; value: unknown; time: number }[] {
|
|
889
890
|
if (this.overlay.size === 0) return base;
|
|
890
|
-
const map = new Map(base.map(e => [e.key, e.value]));
|
|
891
|
+
const map = new Map(base.map(e => [e.key, { value: e.value, time: e.time }]));
|
|
891
892
|
for (const [key, entry] of this.overlay) {
|
|
892
893
|
if (entry.value === DELETED) { map.delete(key); continue; }
|
|
893
|
-
if (column in entry.value) map.set(key, entry.value[column]);
|
|
894
|
-
else if (!map.has(key)) map.set(key, undefined);
|
|
894
|
+
if (column in entry.value) map.set(key, { value: entry.value[column], time: entry.time });
|
|
895
|
+
else if (!map.has(key)) map.set(key, { value: undefined, time: entry.time });
|
|
895
896
|
}
|
|
896
|
-
return [...map].map(([key,
|
|
897
|
+
return [...map].map(([key, v]) => ({ key, value: v.value, time: v.time }));
|
|
897
898
|
}
|
|
898
899
|
|
|
899
900
|
// ---- async reads (overlay-aware) ----
|
|
900
901
|
|
|
901
902
|
public async getSingleField<Column extends keyof T>(key: string, column: Column): Promise<T[Column] | undefined> {
|
|
903
|
+
return (await this.getSingleFieldObj(key, column))?.value;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Like getSingleField, but returns the same shape a getColumn entry has: { key, value, time }, where
|
|
907
|
+
// time is roughly when that value last changed (the resolved write-time; for a row-merged value it's
|
|
908
|
+
// the newest contributing write). Returns undefined only when the key isn't present/live.
|
|
909
|
+
public async getSingleFieldObj<Column extends keyof T>(key: string, column: Column): Promise<{ key: string; value: T[Column]; time: number } | undefined> {
|
|
902
910
|
void this.syncSetup();
|
|
911
|
+
const col = String(column);
|
|
903
912
|
const entry = this.overlay.get(key);
|
|
904
913
|
if (entry !== undefined) {
|
|
905
914
|
if (entry.value === DELETED) return undefined;
|
|
906
|
-
if (
|
|
907
|
-
// column not set in the overlay entry — fall through to disk
|
|
915
|
+
if (col in entry.value) return { key, value: entry.value[col] as T[Column], time: entry.time };
|
|
916
|
+
// column not set in the overlay entry — fall through to disk for this column
|
|
908
917
|
}
|
|
909
918
|
let time = Date.now();
|
|
910
919
|
let reader = await this.reader();
|
|
911
|
-
|
|
920
|
+
const r = await reader.getSingleField(key, col);
|
|
912
921
|
time = Date.now() - time;
|
|
913
922
|
if (time > 50) {
|
|
914
|
-
console.log(`${blue(`${this.name}.
|
|
923
|
+
console.log(`${blue(`${this.name}.getSingleFieldObj(${JSON.stringify(key)}, ${JSON.stringify(column)})`)} took ${red(formatTime(time))} ${this.formatInfo(reader)}`);
|
|
915
924
|
}
|
|
916
|
-
|
|
925
|
+
if (r === undefined) {
|
|
926
|
+
// Not live on disk; but if the overlay holds the key (a partial write of a not-yet-on-disk
|
|
927
|
+
// key) it's live with this column unset.
|
|
928
|
+
if (entry !== undefined && entry.value !== DELETED) return { key, value: undefined as T[Column], time: entry.time };
|
|
929
|
+
return undefined;
|
|
930
|
+
}
|
|
931
|
+
return { key, value: r.value as T[Column], time: r.time };
|
|
917
932
|
}
|
|
918
933
|
|
|
919
|
-
public async getColumn<Column extends keyof T>(column: Column): Promise<{ key: string; value: T[Column] }[]> {
|
|
934
|
+
public async getColumn<Column extends keyof T>(column: Column): Promise<{ key: string; value: T[Column]; time: number }[]> {
|
|
920
935
|
void this.syncSetup();
|
|
921
936
|
let time = Date.now();
|
|
922
937
|
let reader = await this.reader();
|
|
923
|
-
let base = await reader.getColumn(String(column))
|
|
924
|
-
let result = this.patchColumn(base, String(column)) as { key: string; value: T[Column] }[];
|
|
938
|
+
let base = await reader.getColumn(String(column));
|
|
939
|
+
let result = this.patchColumn(base, String(column)) as { key: string; value: T[Column]; time: number }[];
|
|
925
940
|
time = Date.now() - time;
|
|
926
941
|
if (time > 50) {
|
|
927
942
|
console.log(`${blue(`${this.name}.getColumn(${JSON.stringify(column)})`)} took ${red(formatTime(time))} ${this.formatInfo(reader)}`);
|
|
@@ -947,9 +962,11 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
947
962
|
// loaded once and cached; the overlay is layered on top (we can't async-cache the combined result
|
|
948
963
|
// because the overlay mutates).
|
|
949
964
|
|
|
950
|
-
private baseColumns = new Map<string, { key: string; value: unknown }[]>();
|
|
965
|
+
private baseColumns = new Map<string, { key: string; value: unknown; time: number }[]>();
|
|
951
966
|
private baseColumnsLoading = new Set<string>();
|
|
952
|
-
|
|
967
|
+
// The disk-resolved field: { value, time } when the key is live on disk, or undefined when it isn't
|
|
968
|
+
// (Map.has distinguishes "loaded" from "not loaded yet").
|
|
969
|
+
private baseFields = new Map<string, { value: unknown; time: number } | undefined>();
|
|
953
970
|
private baseFieldsLoading = new Set<string>();
|
|
954
971
|
|
|
955
972
|
private ensureBaseColumn(column: string) {
|
|
@@ -972,9 +989,9 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
972
989
|
this.baseFieldsLoading.add(cacheKey);
|
|
973
990
|
void (async () => {
|
|
974
991
|
let reader = await this.reader();
|
|
975
|
-
let
|
|
992
|
+
let resolved = await reader.getSingleField(key, column);
|
|
976
993
|
this.deps.batch(() => {
|
|
977
|
-
this.baseFields.set(cacheKey,
|
|
994
|
+
this.baseFields.set(cacheKey, resolved);
|
|
978
995
|
this.baseFieldsLoading.delete(cacheKey);
|
|
979
996
|
this.deps.invalidate(LOAD_SIGNAL);
|
|
980
997
|
});
|
|
@@ -982,6 +999,12 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
982
999
|
}
|
|
983
1000
|
|
|
984
1001
|
public getSingleFieldSync<Column extends keyof T>(key: string, column: Column): T[Column] | undefined {
|
|
1002
|
+
return this.getSingleFieldObjSync(key, column)?.value;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Sync (reactive) counterpart of getSingleFieldObj: { key, value, time } once loaded, undefined while
|
|
1006
|
+
// loading or when the key isn't present/live. time is roughly when the value last changed.
|
|
1007
|
+
public getSingleFieldObjSync<Column extends keyof T>(key: string, column: Column): { key: string; value: T[Column]; time: number } | undefined {
|
|
985
1008
|
void this.syncSetup();
|
|
986
1009
|
this.deps.observe(LOAD_SIGNAL);
|
|
987
1010
|
this.deps.observe(key);
|
|
@@ -989,18 +1012,24 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
989
1012
|
let entry = this.overlay.get(key);
|
|
990
1013
|
if (entry !== undefined) {
|
|
991
1014
|
if (entry.value === DELETED) return undefined;
|
|
992
|
-
if (col in entry.value) return entry.value[col] as T[Column];
|
|
993
|
-
// column not set in the overlay entry — fall through to the base field cache
|
|
1015
|
+
if (col in entry.value) return { key, value: entry.value[col] as T[Column], time: entry.time };
|
|
1016
|
+
// column not set in the overlay entry — fall through to the base field cache for this column
|
|
994
1017
|
}
|
|
995
1018
|
let cacheKey = nullJoin(col, key);
|
|
996
1019
|
if (!this.baseFields.has(cacheKey)) {
|
|
997
1020
|
this.ensureBaseField(key, col);
|
|
998
1021
|
return undefined;
|
|
999
1022
|
}
|
|
1000
|
-
|
|
1023
|
+
const base = this.baseFields.get(cacheKey);
|
|
1024
|
+
if (base === undefined) {
|
|
1025
|
+
// Not live on disk; but an overlay entry for the key (partial write) makes it live, column unset.
|
|
1026
|
+
if (entry !== undefined && entry.value !== DELETED) return { key, value: undefined as T[Column], time: entry.time };
|
|
1027
|
+
return undefined;
|
|
1028
|
+
}
|
|
1029
|
+
return { key, value: base.value as T[Column], time: base.time };
|
|
1001
1030
|
}
|
|
1002
1031
|
|
|
1003
|
-
public getColumnSync<Column extends keyof T>(column: Column): { key: string; value: T[Column] }[] | undefined {
|
|
1032
|
+
public getColumnSync<Column extends keyof T>(column: Column): { key: string; value: T[Column]; time: number }[] | undefined {
|
|
1004
1033
|
void this.syncSetup();
|
|
1005
1034
|
this.deps.observe(LOAD_SIGNAL);
|
|
1006
1035
|
// Observe the overlay-wide signal so we recompute once the base arrives or the overlay changes.
|
|
@@ -1011,7 +1040,7 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
1011
1040
|
this.ensureBaseColumn(col);
|
|
1012
1041
|
return undefined;
|
|
1013
1042
|
}
|
|
1014
|
-
return this.patchColumn(base, col) as { key: string; value: T[Column] }[];
|
|
1043
|
+
return this.patchColumn(base, col) as { key: string; value: T[Column]; time: number }[];
|
|
1015
1044
|
}
|
|
1016
1045
|
|
|
1017
1046
|
public async getColumnInfo() {
|
|
@@ -1031,15 +1060,17 @@ export class BulkDatabaseBase<T extends { key: string }> {
|
|
|
1031
1060
|
}
|
|
1032
1061
|
}
|
|
1033
1062
|
|
|
1034
|
-
// The merged, time-resolved view over all readers. getColumn/getSingleField return
|
|
1035
|
-
//
|
|
1063
|
+
// The merged, time-resolved view over all readers. getColumn/getSingleField return the resolved value
|
|
1064
|
+
// AND its write-time (so reads can expose roughly when a value last changed); the base layers the
|
|
1065
|
+
// overlay on top of these. getSingleField returns undefined only when the key isn't live (deleted or
|
|
1066
|
+
// absent) — a live key whose column is merely unset returns { value: undefined, time: rowTime }.
|
|
1036
1067
|
type ResolvedReader = {
|
|
1037
1068
|
rowCount: number;
|
|
1038
1069
|
totalBytes: number;
|
|
1039
1070
|
keys: string[];
|
|
1040
1071
|
columns: { column: string; byteSize: number }[];
|
|
1041
|
-
getColumn: (column: string) => Promise<{ key: string; value: unknown }[]>;
|
|
1042
|
-
getSingleField: (key: string, column: string) => Promise<unknown | undefined>;
|
|
1072
|
+
getColumn: (column: string) => Promise<{ key: string; value: unknown; time: number }[]>;
|
|
1073
|
+
getSingleField: (key: string, column: string) => Promise<{ value: unknown; time: number } | undefined>;
|
|
1043
1074
|
};
|
|
1044
1075
|
|
|
1045
1076
|
// Resolve every read by ACTUAL write-time across all readers (stream + bulk), per key and per column:
|
|
@@ -1097,10 +1128,15 @@ async function joinBulkDatabases(databases: BaseBulkDatabaseReader[]): Promise<R
|
|
|
1097
1128
|
if (!cell || cell.value === ABSENT) continue;
|
|
1098
1129
|
if (cell.time > bestTime) { bestTime = cell.time; bestVal = cell.value; found = true; }
|
|
1099
1130
|
}
|
|
1100
|
-
|
|
1131
|
+
// time = the column value's write-time when this column has one, else the row's last
|
|
1132
|
+
// write-time (the key is live but never set this column).
|
|
1133
|
+
const live = found && bestTime > delOf(key);
|
|
1134
|
+
return { key, value: live ? bestVal : undefined, time: live ? bestTime : (keyTime.get(key) ?? 0) };
|
|
1101
1135
|
});
|
|
1102
1136
|
},
|
|
1103
1137
|
async getSingleField(key, column) {
|
|
1138
|
+
const kt = keyTime.get(key);
|
|
1139
|
+
if (kt === undefined || kt <= delOf(key)) return undefined; // key not live
|
|
1104
1140
|
let bestTime = -Infinity;
|
|
1105
1141
|
let bestVal: unknown;
|
|
1106
1142
|
let found = false;
|
|
@@ -1110,7 +1146,8 @@ async function joinBulkDatabases(databases: BaseBulkDatabaseReader[]): Promise<R
|
|
|
1110
1146
|
if (r === ABSENT) continue;
|
|
1111
1147
|
if (r.time > bestTime) { bestTime = r.time; bestVal = r.value; found = true; }
|
|
1112
1148
|
}
|
|
1113
|
-
|
|
1149
|
+
const live = found && bestTime > delOf(key);
|
|
1150
|
+
return { value: live ? bestVal : undefined, time: live ? bestTime : kt };
|
|
1114
1151
|
},
|
|
1115
1152
|
};
|
|
1116
1153
|
}
|
|
@@ -396,12 +396,15 @@ export const getFileStorageNested2 = cache(async function getFileStorage(pathStr
|
|
|
396
396
|
} else {
|
|
397
397
|
base = await getDirectoryHandle();
|
|
398
398
|
let dirs: string[] = [];
|
|
399
|
+
let alls: string[] = [];
|
|
399
400
|
for await (const [name, entry] of base) {
|
|
400
401
|
if (entry.kind === "directory") {
|
|
401
402
|
dirs.push(name);
|
|
402
403
|
}
|
|
404
|
+
alls.push(name);
|
|
403
405
|
}
|
|
404
|
-
|
|
406
|
+
// HACK: If there are enough files, it's almost certainly not a directory that contains collections. It's probably the user's data instead
|
|
407
|
+
if (dirs.includes(".git") || dirs.includes("data") || alls.length > 100) {
|
|
405
408
|
base = await base.getDirectoryHandle("data", { create: true });
|
|
406
409
|
}
|
|
407
410
|
}
|