rxdb
Version:
A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/
307 lines (304 loc) • 12 kB
JavaScript
import { getFromMapOrCreate } from "../utils/index.js";
import { REPLICATION_STATE_BY_COLLECTION } from "../replication/index.js";
import { Subject, firstValueFrom } from 'rxjs';
import { newRxError } from "../../rx-error.js";
import { getChangedDocumentsSince } from "../../rx-storage-helper.js";
import { NOSQL_QUERY_JSON_SCHEMA } from "./nosql-query-schema.js";
export function registerWebMCPDatabase(options) {
var database = this;
var collections = this.collections;
var error$ = new Subject();
var log$ = new Subject();
var registerCollection = collection => {
var res = collection.registerWebMCP(options);
res.error$.subscribe(error$);
res.log$.subscribe(log$);
};
// Register existing collections
for (var [name, collection] of Object.entries(collections)) {
registerCollection(collection);
}
// Store options and subjects on the database instance so the hook can pick them up dynamically
database._webmcpOptions = options || {};
database._webmcpError$ = error$;
database._webmcpLog$ = log$;
// We should probably tear this down if the database is destroyed... For now it's okay.
return {
error$,
log$
};
}
export function registerWebMCPCollection(options) {
if (typeof navigator === 'undefined' || !navigator.modelContext) {
// Return dummy subjects if WebMCP is not available
return {
error$: new Subject(),
log$: new Subject()
};
}
var collection = this;
var modelContext = navigator.modelContext;
var errorSubject = new Subject();
var logSubject = new Subject();
var withMiddleware = (toolName, fn) => {
return async (args, context) => {
try {
var result = await fn(args, context);
logSubject.next({
collectionName: collection.name,
databaseName: collection.database.name,
toolName,
args,
result
});
return result;
} catch (err) {
errorSubject.next(err);
logSubject.next({
collectionName: collection.name,
databaseName: collection.database.name,
toolName,
args,
error: err
});
throw err;
}
};
};
var awaitSyncIfRequired = async () => {
var shouldAwait = options?.awaitReplicationsInSync !== false;
if (shouldAwait) {
var replicationStates = getFromMapOrCreate(REPLICATION_STATE_BY_COLLECTION, collection, () => []);
await Promise.all(replicationStates.map(replicationState => {
if (!replicationState.isStopped()) {
return replicationState.awaitInSync();
}
}));
}
};
var registeredToolNames = [];
var register = tool => {
modelContext.registerTool(tool);
registeredToolNames.push(tool.name);
};
collection.onClose.push(() => {
registeredToolNames.forEach(name => {
try {
if (modelContext.unregisterTool) {
modelContext.unregisterTool(name);
}
} catch (err) {
// Ignore errors on unregister
}
});
});
var queryToolName = "rxdb_query_" + collection.database.name + "_" + collection.name + "_" + collection.schema.version;
register({
name: queryToolName,
description: "Query the RxDB collection '" + collection.name + "' of database '" + collection.database.name + "'. Allows filtering, sorting, and pagination. Returns an array of matched document objects. The collection has the following JSON schema: " + JSON.stringify(collection.schema.getJsonSchemaWithoutMeta()) + ". Note: If this tool returns an error code, you can find the decoded error message at https://rxdb.info/errors.html",
annotations: {
readOnlyHint: true
},
inputSchema: {
type: 'object',
$defs: NOSQL_QUERY_JSON_SCHEMA.$defs,
properties: {
query: Object.assign({}, NOSQL_QUERY_JSON_SCHEMA, {
$defs: undefined,
// remove nested $defs
default: {
sort: [{
[collection.schema.primaryPath]: 'asc'
}]
}
})
},
required: ['query']
},
execute: withMiddleware(queryToolName, async (args, _context) => {
await awaitSyncIfRequired();
var docs = await collection.find(args.query).exec();
return docs.map(d => d.toJSON());
})
});
var countToolName = "rxdb_count_" + collection.database.name + "_" + collection.name + "_" + collection.schema.version;
register({
name: countToolName,
description: "Counts the documents in the RxDB collection '" + collection.name + "' of database '" + collection.database.name + "' matching a given query. The collection has the following JSON schema: " + JSON.stringify(collection.schema.getJsonSchemaWithoutMeta()) + ". Note: If this tool returns an error code, you can find the decoded error message at https://rxdb.info/errors.html",
annotations: {
readOnlyHint: true
},
inputSchema: {
type: 'object',
$defs: NOSQL_QUERY_JSON_SCHEMA.$defs,
properties: {
query: Object.assign({}, NOSQL_QUERY_JSON_SCHEMA, {
$defs: undefined,
// remove nested $defs
default: {
sort: [{
[collection.schema.primaryPath]: 'asc'
}]
}
})
},
required: ['query']
},
execute: withMiddleware(countToolName, async (args, _context) => {
await awaitSyncIfRequired();
var count = await collection.count(args.query).exec();
return {
count
};
})
});
var changesToolName = "rxdb_changes_" + collection.database.name + "_" + collection.name + "_" + collection.schema.version;
register({
name: changesToolName,
description: "Returns all changes of the RxDB collection '" + collection.name + "' of database '" + collection.database.name + "' since a given checkpoint. If no checkpoint is provided, starts from the oldest change. The collection has the following JSON schema: " + JSON.stringify(collection.schema.getJsonSchemaWithoutMeta()) + ". Note: If this tool returns an error code, you can find the decoded error message at https://rxdb.info/errors.html",
annotations: {
readOnlyHint: true
},
inputSchema: {
type: 'object',
properties: {
checkpoint: {
type: 'object',
description: 'The cursor/checkpoint to start fetching changes from. Leave empty to start from the beginning.'
},
limit: {
type: 'number',
description: 'Maximum number of changes to return.',
default: 50
}
}
},
execute: withMiddleware(changesToolName, async (args, _context) => {
await awaitSyncIfRequired();
var limit = args.limit || 50;
var storageInstance = collection.storageInstance;
var changes = await getChangedDocumentsSince(storageInstance, limit, args.checkpoint);
return changes;
})
});
var waitChangesToolName = "rxdb_wait_changes_" + collection.database.name + "_" + collection.name + "_" + collection.schema.version;
register({
name: waitChangesToolName,
description: "Waits until a new write event happens to the RxDB collection '" + collection.name + "' of database '" + collection.database.name + "'. Returns a promise that resolves when a change occurs. Note: If this tool returns an error code, you can find the decoded error message at https://rxdb.info/errors.html",
annotations: {
readOnlyHint: true
},
inputSchema: {
type: 'object',
properties: {}
},
execute: withMiddleware(waitChangesToolName, async (_args, _context) => {
await firstValueFrom(collection.eventBulks$);
return {
success: true,
message: 'A write event occurred in the collection.'
};
})
});
if (options?.readOnly !== true) {
var insertToolName = "rxdb_insert_" + collection.database.name + "_" + collection.name + "_" + collection.schema.version;
register({
name: insertToolName,
description: "Insert a document into the RxDB collection '" + collection.name + "' of database '" + collection.database.name + "'. The collection has the following JSON schema: " + JSON.stringify(collection.schema.getJsonSchemaWithoutMeta()) + ". Note: If this tool returns an error code, you can find the decoded error message at https://rxdb.info/errors.html",
inputSchema: {
type: 'object',
properties: {
document: Object.assign({}, JSON.parse(JSON.stringify(collection.schema.getJsonSchemaWithoutMeta())), {
description: 'The document to insert.'
})
},
required: ['document']
},
execute: withMiddleware(insertToolName, async (args, _context) => {
await awaitSyncIfRequired();
var doc = await collection.insert(args.document);
return doc.toJSON();
})
});
var upsertToolName = "rxdb_upsert_" + collection.database.name + "_" + collection.name + "_" + collection.schema.version;
register({
name: upsertToolName,
description: "Upsert a document into the RxDB collection '" + collection.name + "' of database '" + collection.database.name + "'. If a document with the same primary key exists, it will be overwritten. The collection has the following JSON schema: " + JSON.stringify(collection.schema.getJsonSchemaWithoutMeta()) + ". Note: If this tool returns an error code, you can find the decoded error message at https://rxdb.info/errors.html",
inputSchema: {
type: 'object',
properties: {
document: Object.assign({}, JSON.parse(JSON.stringify(collection.schema.getJsonSchemaWithoutMeta())), {
description: 'The document to upsert.'
})
},
required: ['document']
},
execute: withMiddleware(upsertToolName, async (args, _context) => {
await awaitSyncIfRequired();
var doc = await collection.upsert(args.document);
return doc.toJSON();
})
});
var deleteToolName = "rxdb_delete_" + collection.database.name + "_" + collection.name + "_" + collection.schema.version;
register({
name: deleteToolName,
description: "Deletes a document by id from the RxDB collection '" + collection.name + "' of database '" + collection.database.name + "'. The collection has the following JSON schema: " + JSON.stringify(collection.schema.getJsonSchemaWithoutMeta()) + ". Note: If this tool returns an error code, you can find the decoded error message at https://rxdb.info/errors.html",
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'The primary key of the document to delete.'
}
},
required: ['id']
},
execute: withMiddleware(deleteToolName, async (args, _context) => {
await awaitSyncIfRequired();
var doc = await collection.findOne(args.id).exec();
if (!doc) {
throw newRxError('WMCP1', {
documentId: args.id
});
}
var deletedDoc = await doc.remove();
return deletedDoc.toJSON();
})
});
}
return {
error$: errorSubject,
log$: logSubject
};
}
export var RxDBWebMCPPlugin = {
name: 'webmcp',
rxdb: true,
prototypes: {
RxDatabase: proto => {
proto.registerWebMCP = registerWebMCPDatabase;
},
RxCollection: proto => {
proto.registerWebMCP = registerWebMCPCollection;
}
},
hooks: {
createRxCollection: {
after: ({
collection
}) => {
var db = collection.database;
if (db._webmcpOptions) {
var res = collection.registerWebMCP(db._webmcpOptions);
if (db._webmcpError$) {
res.error$.subscribe(db._webmcpError$);
}
if (db._webmcpLog$) {
res.log$.subscribe(db._webmcpLog$);
}
}
}
}
}
};
//# sourceMappingURL=webmcp.js.map