@tanstack/db-collections
Version:
A collection for (aspirationally) every way of loading your data
148 lines (147 loc) • 4.81 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const client = require("@electric-sql/client");
const store = require("@tanstack/store");
function isUpToDateMessage(message) {
return client.isControlMessage(message) && message.headers.control === `up-to-date`;
}
function hasTxids(message) {
return `headers` in message && `txids` in message.headers && Array.isArray(message.headers.txids);
}
function electricCollectionOptions(config) {
const seenTxids = new store.Store(/* @__PURE__ */ new Set([`${Math.random()}`]));
const sync = createElectricSync(config.shapeOptions, {
seenTxids
});
const awaitTxId = async (txId, timeout = 3e4) => {
const hasTxid = seenTxids.state.has(txId);
if (hasTxid) return true;
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
unsubscribe();
reject(new Error(`Timeout waiting for txId: ${txId}`));
}, timeout);
const unsubscribe = seenTxids.subscribe(() => {
if (seenTxids.state.has(txId)) {
clearTimeout(timeoutId);
unsubscribe();
resolve(true);
}
});
});
};
const wrappedOnInsert = config.onInsert ? async (params) => {
const handlerResult = await config.onInsert(params) ?? {};
const txid = handlerResult.txid;
if (!txid) {
throw new Error(
`Electric collection onInsert handler must return a txid`
);
}
await awaitTxId(txid);
return handlerResult;
} : void 0;
const wrappedOnUpdate = config.onUpdate ? async (params) => {
const handlerResult = await config.onUpdate(params);
const txid = handlerResult.txid;
if (!txid) {
throw new Error(
`Electric collection onUpdate handler must return a txid`
);
}
await awaitTxId(txid);
return handlerResult;
} : void 0;
const wrappedOnDelete = config.onDelete ? async (params) => {
const handlerResult = await config.onDelete(params);
const txid = handlerResult.txid;
if (!txid) {
throw new Error(
`Electric collection onDelete handler must return a txid`
);
}
await awaitTxId(txid);
return handlerResult;
} : void 0;
const {
shapeOptions: _shapeOptions,
onInsert: _onInsert,
onUpdate: _onUpdate,
onDelete: _onDelete,
...restConfig
} = config;
return {
...restConfig,
sync,
onInsert: wrappedOnInsert,
onUpdate: wrappedOnUpdate,
onDelete: wrappedOnDelete,
utils: {
awaitTxId
}
};
}
function createElectricSync(shapeOptions, options) {
const { seenTxids } = options;
const relationSchema = new store.Store(void 0);
const getSyncMetadata = () => {
var _a;
const schema = relationSchema.state || `public`;
return {
relation: ((_a = shapeOptions.params) == null ? void 0 : _a.table) ? [schema, shapeOptions.params.table] : void 0
};
};
return {
sync: (params) => {
const { begin, write, commit } = params;
const stream = new client.ShapeStream(shapeOptions);
let transactionStarted = false;
let newTxids = /* @__PURE__ */ new Set();
stream.subscribe((messages) => {
let hasUpToDate = false;
for (const message of messages) {
if (hasTxids(message) && message.headers.txids) {
message.headers.txids.forEach((txid) => newTxids.add(String(txid)));
}
if (client.isChangeMessage(message) && message.headers.schema) {
if (typeof message.headers.schema === `string`) {
const schema = message.headers.schema;
relationSchema.setState(() => schema);
}
}
if (client.isChangeMessage(message)) {
if (!transactionStarted) {
begin();
transactionStarted = true;
}
const value = message.value;
const enhancedMetadata = {
...message.headers
};
write({
type: message.headers.operation,
value,
metadata: enhancedMetadata
});
} else if (isUpToDateMessage(message)) {
hasUpToDate = true;
}
}
if (hasUpToDate && transactionStarted) {
commit();
seenTxids.setState((currentTxids) => {
const clonedSeen = new Set(currentTxids);
newTxids.forEach((txid) => clonedSeen.add(String(txid)));
newTxids = /* @__PURE__ */ new Set();
return clonedSeen;
});
transactionStarted = false;
}
});
},
// Expose the getSyncMetadata function
getSyncMetadata
};
}
exports.electricCollectionOptions = electricCollectionOptions;
//# sourceMappingURL=electric.cjs.map