UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

307 lines (304 loc) 12 kB
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