taladb 0.9.4 → 0.10.0
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/README.md +2 -2
- package/dist/index.browser.mjs +218 -24
- package/dist/index.d.mts +163 -5
- package/dist/index.d.ts +163 -5
- package/dist/index.js +218 -24
- package/dist/index.mjs +218 -24
- package/dist/index.react-native.mjs +218 -24
- package/package.json +9 -4
package/dist/index.mjs
CHANGED
|
@@ -716,18 +716,52 @@ function deepEqual(a, b) {
|
|
|
716
716
|
);
|
|
717
717
|
}
|
|
718
718
|
function applySchema(col, options) {
|
|
719
|
-
const {
|
|
719
|
+
const {
|
|
720
|
+
schema,
|
|
721
|
+
validateOnRead = false,
|
|
722
|
+
migrateDocument,
|
|
723
|
+
downgradeDocument,
|
|
724
|
+
syncSchema,
|
|
725
|
+
persistMigrations = false,
|
|
726
|
+
allowFieldRemoval = false,
|
|
727
|
+
retiredFields = []
|
|
728
|
+
} = options;
|
|
720
729
|
const targetVersion = syncSchema?.version ?? 0;
|
|
721
730
|
if (migrateDocument && targetVersion < 1) {
|
|
722
731
|
throw new Error("CollectionOptions.migrateDocument requires syncSchema.version (the migration target)");
|
|
723
732
|
}
|
|
733
|
+
if (downgradeDocument && targetVersion < 1) {
|
|
734
|
+
throw new Error("CollectionOptions.downgradeDocument requires syncSchema.version (the shape this build reads)");
|
|
735
|
+
}
|
|
724
736
|
if (syncSchema && targetVersion < 1 && (syncSchema.renames || syncSchema.defaults)) {
|
|
725
737
|
throw new Error(
|
|
726
738
|
"CollectionOptions.syncSchema.renames/defaults require syncSchema.version >= 1 \u2014 without a version the import migration step never runs and documents missing the renamed/defaulted fields are quarantined instead of upgraded"
|
|
727
739
|
);
|
|
728
740
|
}
|
|
729
741
|
const stampVersion = targetVersion > 0;
|
|
730
|
-
if (!schema && !migrateDocument && !stampVersion) return col;
|
|
742
|
+
if (!schema && !migrateDocument && !downgradeDocument && !stampVersion) return col;
|
|
743
|
+
const retired = new Set(retiredFields);
|
|
744
|
+
const engineOwned = /* @__PURE__ */ new Set([
|
|
745
|
+
"_id",
|
|
746
|
+
"_v",
|
|
747
|
+
"_changed_at",
|
|
748
|
+
"_remote",
|
|
749
|
+
"_remote_rev",
|
|
750
|
+
"_replica_scope"
|
|
751
|
+
]);
|
|
752
|
+
const downcastViews = /* @__PURE__ */ new WeakSet();
|
|
753
|
+
function preserveFields(original, next, preserveUnknown, preserveVersion = true) {
|
|
754
|
+
let out = null;
|
|
755
|
+
for (const k of Object.keys(original)) {
|
|
756
|
+
const ownedField = engineOwned.has(k) && (k !== "_v" || preserveVersion);
|
|
757
|
+
const mustRestore = ownedField || preserveUnknown && !retired.has(k) && !(k in next);
|
|
758
|
+
if (!mustRestore) continue;
|
|
759
|
+
if (!ownedField && k in next) continue;
|
|
760
|
+
out ?? (out = { ...next });
|
|
761
|
+
out[k] = original[k];
|
|
762
|
+
}
|
|
763
|
+
return out ?? next;
|
|
764
|
+
}
|
|
731
765
|
function parseWrite(doc, label) {
|
|
732
766
|
try {
|
|
733
767
|
return schema.parse(doc);
|
|
@@ -735,6 +769,43 @@ function applySchema(col, options) {
|
|
|
735
769
|
throw new TalaDbValidationError(err, label);
|
|
736
770
|
}
|
|
737
771
|
}
|
|
772
|
+
function assertWritableDocument(doc, label) {
|
|
773
|
+
if (doc && typeof doc === "object" && downcastViews.has(doc)) {
|
|
774
|
+
throw new Error(`${label}: a downgradeDocument result is a read-only compatibility view`);
|
|
775
|
+
}
|
|
776
|
+
const version = doc?._v;
|
|
777
|
+
if (targetVersion > 0 && typeof version === "number" && version > targetVersion) {
|
|
778
|
+
throw new Error(
|
|
779
|
+
`${label}: this client supports schema v${targetVersion}, but the document is v${version}`
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function writableFilter(filter) {
|
|
784
|
+
if (targetVersion < 1) return filter;
|
|
785
|
+
return {
|
|
786
|
+
$and: [
|
|
787
|
+
filter,
|
|
788
|
+
{
|
|
789
|
+
$or: [
|
|
790
|
+
{ _v: { $exists: false } },
|
|
791
|
+
{ _v: { $lte: targetVersion } }
|
|
792
|
+
]
|
|
793
|
+
}
|
|
794
|
+
]
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function assertSafeUpdate(update) {
|
|
798
|
+
const record = update;
|
|
799
|
+
for (const op of ["$set", "$unset", "$inc", "$push", "$pull"]) {
|
|
800
|
+
const fields = record[op];
|
|
801
|
+
if (!fields) continue;
|
|
802
|
+
for (const field of Object.keys(fields)) {
|
|
803
|
+
if (engineOwned.has(field)) {
|
|
804
|
+
throw new Error(`update cannot modify engine-owned field '${field}'`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
738
809
|
function stamp(doc) {
|
|
739
810
|
if (!stampVersion || doc._v !== void 0) return doc;
|
|
740
811
|
return { ...doc, _v: targetVersion };
|
|
@@ -750,50 +821,86 @@ function applySchema(col, options) {
|
|
|
750
821
|
if (k === "_id") continue;
|
|
751
822
|
if (!deepEqual(migrated[k], original[k])) $set[k] = migrated[k];
|
|
752
823
|
}
|
|
753
|
-
|
|
754
|
-
|
|
824
|
+
if (allowFieldRemoval || retired.size > 0) {
|
|
825
|
+
for (const k of Object.keys(original)) {
|
|
826
|
+
if (k !== "_id" && !(k in migrated) && (allowFieldRemoval || retired.has(k))) $unset[k] = true;
|
|
827
|
+
}
|
|
755
828
|
}
|
|
756
829
|
const update = {};
|
|
757
830
|
if (Object.keys($set).length) update.$set = $set;
|
|
758
831
|
if (Object.keys($unset).length) update.$unset = $unset;
|
|
759
832
|
return Object.keys(update).length ? update : null;
|
|
760
833
|
}
|
|
761
|
-
function
|
|
762
|
-
if (!migrateDocument) return doc;
|
|
834
|
+
function normalizeRead(doc) {
|
|
763
835
|
const fromVersion = typeof doc._v === "number" ? doc._v : 0;
|
|
764
|
-
if (fromVersion
|
|
765
|
-
|
|
836
|
+
if (migrateDocument && fromVersion < targetVersion) {
|
|
837
|
+
const up = { ...migrateDocument(doc, fromVersion), _v: targetVersion };
|
|
838
|
+
return {
|
|
839
|
+
// The migration owns the version transition, while every other
|
|
840
|
+
// engine-owned field continues to come from the stored document.
|
|
841
|
+
value: allowFieldRemoval ? preserveFields(doc, up, false, false) : preserveFields(doc, up, true, false),
|
|
842
|
+
persistable: true
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
if (downgradeDocument && fromVersion > targetVersion) {
|
|
846
|
+
const projected = {
|
|
847
|
+
...downgradeDocument(doc, fromVersion),
|
|
848
|
+
...doc._id !== void 0 ? { _id: doc._id } : {},
|
|
849
|
+
_v: fromVersion
|
|
850
|
+
};
|
|
851
|
+
downcastViews.add(projected);
|
|
852
|
+
return { value: projected, persistable: false };
|
|
853
|
+
}
|
|
854
|
+
return { value: doc, persistable: true };
|
|
766
855
|
}
|
|
767
856
|
function validateRead(doc) {
|
|
768
857
|
if (!validateOnRead || !schema) return doc;
|
|
769
858
|
try {
|
|
770
|
-
|
|
859
|
+
const parsed = schema.parse(doc);
|
|
860
|
+
const fromVersion = typeof doc._v === "number" ? doc._v : 0;
|
|
861
|
+
return preserveFields(doc, parsed, targetVersion > 0 && fromVersion > targetVersion);
|
|
771
862
|
} catch (err) {
|
|
772
863
|
throw new TalaDbValidationError(err, "read");
|
|
773
864
|
}
|
|
774
865
|
}
|
|
775
|
-
async function persistAll(originals,
|
|
866
|
+
async function persistAll(originals, normalized) {
|
|
776
867
|
if (!persistMigrations) return;
|
|
777
868
|
for (let i = 0; i < originals.length; i++) {
|
|
778
869
|
const original = originals[i];
|
|
779
|
-
if (
|
|
780
|
-
const
|
|
870
|
+
if (!normalized[i].persistable) continue;
|
|
871
|
+
const migrated = normalized[i].value;
|
|
872
|
+
if (migrated === original || typeof original._id !== "string") continue;
|
|
873
|
+
const update = diffUpdate(original, migrated);
|
|
781
874
|
if (!update) continue;
|
|
782
875
|
try {
|
|
783
|
-
|
|
876
|
+
const guards = [{ _id: original._id }];
|
|
877
|
+
if (original._v === void 0) guards.push({ _v: { $exists: false } });
|
|
878
|
+
else guards.push({ _v: original._v });
|
|
879
|
+
if (original._changed_at !== void 0) {
|
|
880
|
+
guards.push({ _changed_at: original._changed_at });
|
|
881
|
+
}
|
|
882
|
+
await col.updateOne({ $and: guards }, update);
|
|
784
883
|
} catch {
|
|
785
884
|
}
|
|
786
885
|
}
|
|
787
886
|
}
|
|
788
|
-
const wrapReads = Boolean(migrateDocument) || validateOnRead && Boolean(schema);
|
|
887
|
+
const wrapReads = Boolean(migrateDocument) || Boolean(downgradeDocument) || validateOnRead && Boolean(schema);
|
|
789
888
|
const wrapWrites = Boolean(schema) || stampVersion;
|
|
889
|
+
function pipelinePreservesDocuments(pipeline) {
|
|
890
|
+
return pipeline.every((stage) => !("$group" in stage) && !("$project" in stage));
|
|
891
|
+
}
|
|
892
|
+
function normalizeViewRows(docs) {
|
|
893
|
+
return docs.map((doc) => validateRead(normalizeRead(doc).value));
|
|
894
|
+
}
|
|
790
895
|
return {
|
|
791
896
|
...col,
|
|
792
897
|
insert: wrapWrites ? async (doc) => {
|
|
898
|
+
assertWritableDocument(doc, "insert");
|
|
793
899
|
if (schema) parseWrite(doc, "insert");
|
|
794
900
|
return col.insert(stamp(doc));
|
|
795
901
|
} : col.insert.bind(col),
|
|
796
902
|
insertMany: wrapWrites ? async (docs) => {
|
|
903
|
+
docs.forEach((doc, i) => assertWritableDocument(doc, `insertMany[${i}]`));
|
|
797
904
|
if (schema) docs.forEach((doc, i) => parseWrite(doc, `insertMany[${i}]`));
|
|
798
905
|
return col.insertMany(docs.map(stamp));
|
|
799
906
|
} : col.insertMany.bind(col),
|
|
@@ -802,6 +909,14 @@ function applySchema(col, options) {
|
|
|
802
909
|
// runtime schema check have to be the same seam, or a malformed server
|
|
803
910
|
// response walks straight into a typed collection.
|
|
804
911
|
replaceManyWithIds: wrapWrites ? async (docs, origin) => {
|
|
912
|
+
if (origin !== "remote") {
|
|
913
|
+
docs.forEach((doc, i) => assertWritableDocument(doc, `replaceManyWithIds[${i}]`));
|
|
914
|
+
if (targetVersion > 0) {
|
|
915
|
+
throw new Error(
|
|
916
|
+
"local replaceManyWithIds is disabled on versioned collections; use updateOne/updateMany so schema-version guards are atomic"
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
805
920
|
if (schema) docs.forEach((doc, i) => {
|
|
806
921
|
const { _replica_scope, _remote_rev, ...schemaDoc } = doc;
|
|
807
922
|
void _replica_scope;
|
|
@@ -810,38 +925,75 @@ function applySchema(col, options) {
|
|
|
810
925
|
});
|
|
811
926
|
return col.replaceManyWithIds(docs.map((d) => stampDoc(d)), origin);
|
|
812
927
|
} : col.replaceManyWithIds.bind(col),
|
|
928
|
+
deleteManyWithIds: stampVersion ? async (ids, origin) => {
|
|
929
|
+
if (origin !== "remote") {
|
|
930
|
+
throw new Error(
|
|
931
|
+
"local deleteManyWithIds is disabled on versioned collections; use deleteOne/deleteMany so schema-version guards are atomic"
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
return col.deleteManyWithIds(ids, origin);
|
|
935
|
+
} : col.deleteManyWithIds.bind(col),
|
|
936
|
+
updateOne: wrapWrites ? async (filter, update) => {
|
|
937
|
+
assertSafeUpdate(update);
|
|
938
|
+
return col.updateOne(writableFilter(filter), update);
|
|
939
|
+
} : col.updateOne.bind(col),
|
|
940
|
+
updateMany: wrapWrites ? async (filter, update) => {
|
|
941
|
+
assertSafeUpdate(update);
|
|
942
|
+
return col.updateMany(writableFilter(filter), update);
|
|
943
|
+
} : col.updateMany.bind(col),
|
|
944
|
+
deleteOne: stampVersion ? (filter) => col.deleteOne(writableFilter(filter)) : col.deleteOne.bind(col),
|
|
945
|
+
deleteMany: stampVersion ? (filter) => col.deleteMany(writableFilter(filter)) : col.deleteMany.bind(col),
|
|
813
946
|
find: wrapReads ? async (filter) => {
|
|
814
947
|
const docs = await col.find(filter);
|
|
815
|
-
const
|
|
816
|
-
await persistAll(docs,
|
|
817
|
-
return
|
|
948
|
+
const normalized = docs.map(normalizeRead);
|
|
949
|
+
await persistAll(docs, normalized);
|
|
950
|
+
return normalized.map((n) => validateRead(n.value));
|
|
818
951
|
} : col.find.bind(col),
|
|
819
952
|
findOne: wrapReads ? async (filter) => {
|
|
820
953
|
const doc = await col.findOne(filter);
|
|
821
954
|
if (doc === null) return null;
|
|
822
|
-
const
|
|
823
|
-
await persistAll([doc], [
|
|
824
|
-
return validateRead(
|
|
955
|
+
const normalized = normalizeRead(doc);
|
|
956
|
+
await persistAll([doc], [normalized]);
|
|
957
|
+
return validateRead(normalized.value);
|
|
825
958
|
} : col.findOne.bind(col),
|
|
959
|
+
aggregate: wrapReads ? async (pipeline) => {
|
|
960
|
+
const docs = await col.aggregate(pipeline);
|
|
961
|
+
return pipelinePreservesDocuments(pipeline) ? normalizeViewRows(docs) : docs;
|
|
962
|
+
} : col.aggregate.bind(col),
|
|
826
963
|
// Live queries feed every @taladb/react hook (useFind, useFindOne,
|
|
827
964
|
// useQueries). Leaving them unwrapped meant React components received the
|
|
828
965
|
// un-migrated shape while a direct find() returned the migrated one.
|
|
829
966
|
subscribe: wrapReads ? (filter, callback, onError) => col.subscribe(
|
|
830
967
|
filter,
|
|
831
968
|
(docs) => {
|
|
832
|
-
const
|
|
969
|
+
const normalized = docs.map(normalizeRead);
|
|
833
970
|
let out;
|
|
834
971
|
try {
|
|
835
|
-
out =
|
|
972
|
+
out = normalized.map((n) => validateRead(n.value));
|
|
836
973
|
} catch (err) {
|
|
837
974
|
onError?.(err);
|
|
838
975
|
return;
|
|
839
976
|
}
|
|
840
977
|
callback(out);
|
|
841
|
-
void persistAll(docs,
|
|
978
|
+
void persistAll(docs, normalized);
|
|
842
979
|
},
|
|
843
980
|
onError
|
|
844
|
-
) : col.subscribe.bind(col)
|
|
981
|
+
) : col.subscribe.bind(col),
|
|
982
|
+
subscribeAggregate: wrapReads ? (pipeline, callback, onError) => col.subscribeAggregate(
|
|
983
|
+
pipeline,
|
|
984
|
+
(docs) => {
|
|
985
|
+
if (!pipelinePreservesDocuments(pipeline)) {
|
|
986
|
+
callback(docs);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
try {
|
|
990
|
+
callback(normalizeViewRows(docs));
|
|
991
|
+
} catch (error) {
|
|
992
|
+
onError?.(error);
|
|
993
|
+
}
|
|
994
|
+
},
|
|
995
|
+
onError
|
|
996
|
+
) : col.subscribeAggregate.bind(col)
|
|
845
997
|
};
|
|
846
998
|
}
|
|
847
999
|
function detectPlatform() {
|
|
@@ -1051,6 +1203,30 @@ async function createBrowserDB(dbName, config, passphrase, migrations) {
|
|
|
1051
1203
|
});
|
|
1052
1204
|
return JSON.parse(json);
|
|
1053
1205
|
},
|
|
1206
|
+
searchText: async (field, query, topK, filter, options) => {
|
|
1207
|
+
const json = await proxy.send("searchText", {
|
|
1208
|
+
collection: name,
|
|
1209
|
+
field,
|
|
1210
|
+
query,
|
|
1211
|
+
topK,
|
|
1212
|
+
filterJson: filter ? JSON.stringify(filter) : "null",
|
|
1213
|
+
optionsJson: options ? JSON.stringify(options) : "null"
|
|
1214
|
+
});
|
|
1215
|
+
return JSON.parse(json);
|
|
1216
|
+
},
|
|
1217
|
+
hybridSearch: async (text, vector, topK, filter, options) => {
|
|
1218
|
+
const json = await proxy.send("hybridSearch", {
|
|
1219
|
+
collection: name,
|
|
1220
|
+
textField: text.textField,
|
|
1221
|
+
text: text.text,
|
|
1222
|
+
vectorField: vector.vectorField,
|
|
1223
|
+
vectorJson: JSON.stringify(vector.vector),
|
|
1224
|
+
topK,
|
|
1225
|
+
filterJson: filter ? JSON.stringify(filter) : "null",
|
|
1226
|
+
optionsJson: options ? JSON.stringify(options) : "null"
|
|
1227
|
+
});
|
|
1228
|
+
return JSON.parse(json);
|
|
1229
|
+
},
|
|
1054
1230
|
subscribe: (filter, callback, onError) => nudgedPoller(
|
|
1055
1231
|
() => proxy.send("find", {
|
|
1056
1232
|
collection: name,
|
|
@@ -1195,6 +1371,12 @@ async function createNodeDB(dbName, config, passphrase, migrations) {
|
|
|
1195
1371
|
const raw = await col.findNearest(field, vector, topK, filter ?? null);
|
|
1196
1372
|
return raw;
|
|
1197
1373
|
},
|
|
1374
|
+
searchText: async (field, query, topK, filter, options) => {
|
|
1375
|
+
return col.searchText(field, query, topK, filter ?? null, options ?? null);
|
|
1376
|
+
},
|
|
1377
|
+
hybridSearch: async (text, vector, topK, filter, options) => {
|
|
1378
|
+
return col.hybridSearch(text.textField, text.text, vector.vectorField, vector.vector, topK, filter ?? null, options ?? null);
|
|
1379
|
+
},
|
|
1198
1380
|
subscribe: (filter, callback, onError) => makePoller(async () => col.find(filter ?? null), callback, onError),
|
|
1199
1381
|
subscribeAggregate: (pipeline, callback, onError) => makePoller(async () => wrapped.aggregate(pipeline), callback, onError)
|
|
1200
1382
|
};
|
|
@@ -1277,6 +1459,18 @@ async function createNativeDB(_dbName, migrations) {
|
|
|
1277
1459
|
const raw = native.findNearest(name, field, vector, topK, filter ?? null);
|
|
1278
1460
|
return raw;
|
|
1279
1461
|
},
|
|
1462
|
+
searchText: async (field, query, topK, filter, options) => {
|
|
1463
|
+
if (!native.searchText) {
|
|
1464
|
+
throw new Error("searchText requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
|
|
1465
|
+
}
|
|
1466
|
+
return native.searchText(name, field, query, topK, filter ?? null, options ?? null);
|
|
1467
|
+
},
|
|
1468
|
+
hybridSearch: async (text, vector, topK, filter, options) => {
|
|
1469
|
+
if (!native.hybridSearch) {
|
|
1470
|
+
throw new Error("hybridSearch requires @taladb/react-native \u2265 0.10 \u2014 rebuild the native module");
|
|
1471
|
+
}
|
|
1472
|
+
return native.hybridSearch(name, text.textField, text.text, vector.vectorField, vector.vector, topK, filter ?? null, options ?? null);
|
|
1473
|
+
},
|
|
1280
1474
|
subscribe: (filter, callback, onError) => makePoller(async () => native.find(name, filter ?? {}), callback, onError),
|
|
1281
1475
|
subscribeAggregate: (pipeline, callback, onError) => makePoller(async () => native.aggregate(name, pipeline), callback, onError)
|
|
1282
1476
|
};
|