diff --git a/marklogic.d.ts b/marklogic.d.ts index 0de2cfde..0c606ef9 100644 --- a/marklogic.d.ts +++ b/marklogic.d.ts @@ -128,6 +128,29 @@ declare module 'marklogic' { systemTime?: string; } + /** + * A timestamp object representing a point in time on the server. + * Used for point-in-time queries and operations. + * @since 2.1.1 + */ + export interface Timestamp { + /** The timestamp value as a string */ + value: string | null; + } + + /** + * Result value from eval, xqueryEval, or invoke operations. + * Each returned value includes format, datatype, and the actual value. + */ + export interface EvalResult { + /** Format of the value: 'json', 'xml', 'text', or 'binary' */ + format: 'json' | 'xml' | 'text' | 'binary'; + /** Datatype of the value (e.g., 'node()', 'string', 'boolean', 'integer') */ + datatype: string; + /** The actual value (type depends on format and datatype) */ + value: any; + } + /** * Result from a removeAll operation. */ @@ -390,6 +413,114 @@ declare module 'marklogic' { */ checkConnection(): ResultProvider; + /** + * Creates one or more JSON documents for a collection. + * The server assigns URI identifiers to the documents. + * This is a simplified convenience method - use documents.write() for more control. + * @since 1.0 + * @param collection - The collection name for the documents + * @param content - The JSON content object(s) for the documents + * @returns A result provider that resolves to an array of assigned URIs + */ + createCollection(collection: string, ...content: any[]): ResultProvider; + + /** + * Probes whether a document exists. + * This is a simplified convenience method - use documents.probe() for more information. + * @since 1.0 + * @param uri - The URI of the document to check + * @returns A result provider that resolves to a boolean + */ + probe(uri: string): ResultProvider; + + /** + * Queries documents in a collection. + * This is a simplified convenience method - use documents.query() for more control. + * @since 1.0 + * @param collection - The collection name + * @param query - Optional query built by queryBuilder + * @returns A result provider that resolves to an array of document content + */ + queryCollection(collection: string, query?: any): ResultProvider; + + /** + * Reads one or more documents, returning just the content. + * This is a simplified convenience method - use documents.read() for metadata too. + * @since 1.0 + * @param uris - One or more document URIs + * @returns A result provider that resolves to an array of document content + */ + read(...uris: string[]): ResultProvider; + + /** + * Removes one or more documents. + * This is a simplified convenience method - use documents.remove() for more control. + * @since 1.0 + * @param uris - One or more document URIs to remove + * @returns A result provider that resolves to an array of removed URIs + */ + remove(...uris: string[]): ResultProvider; + + /** + * Removes all documents in a collection. + * This is a simplified convenience method - use documents.removeAll() for more options. + * @since 1.0 + * @param collection - The collection whose documents should be deleted + * @returns A result provider that resolves to the collection name + */ + removeCollection(collection: string): ResultProvider; + + /** + * Writes documents to a collection using a URI-to-content mapping. + * This is a simplified convenience method - use documents.write() for more control. + * @since 1.0 + * @param collection - The collection name for the documents + * @param documents - An object mapping URIs to document content + * @returns A result provider that resolves to an array of written URIs + */ + writeCollection(collection: string, documents: Record): ResultProvider; + + /** + * Creates a timestamp object for point-in-time operations. + * @since 2.1.1 + * @param value - Optional timestamp value as a string + * @returns A Timestamp object + */ + createTimestamp(value?: string): Timestamp; + + /** + * Evaluates JavaScript code on the server. + * The user must have permission to evaluate code and execute the actions performed. + * @since 1.0 + * @param source - The JavaScript source code to evaluate + * @param variables - Optional object with variable name-value pairs + * @param txid - Optional transaction ID or Transaction object + * @returns A result provider that resolves to an array of EvalResult objects + */ + eval(source: string, variables?: Record, txid?: string | object): ResultProvider; + + /** + * Evaluates XQuery code on the server. + * The user must have permission to evaluate code and execute the actions performed. + * @since 1.0 + * @param source - The XQuery source code to evaluate + * @param variables - Optional object with variable name-value pairs (keys may use Clark notation) + * @param txid - Optional transaction ID or Transaction object + * @returns A result provider that resolves to an array of EvalResult objects + */ + xqueryEval(source: string, variables?: Record, txid?: string | object): ResultProvider; + + /** + * Invokes a JavaScript or XQuery module on the server. + * The module must have been installed previously (typically with config.extlibs.write()). + * @since 1.0 + * @param path - The path of the module in the modules database + * @param variables - Optional object with variable name-value pairs + * @param txid - Optional transaction ID or Transaction object + * @returns A result provider that resolves to an array of EvalResult objects + */ + invoke(path: string, variables?: Record, txid?: string | object): ResultProvider; + /** * Releases the client and destroys the agent. * Call this method when you're done with the client to free up resources. diff --git a/test-app/src/main/ml-modules/root/hello.xqy b/test-app/src/main/ml-modules/root/hello.xqy new file mode 100644 index 00000000..fc95be76 --- /dev/null +++ b/test-app/src/main/ml-modules/root/hello.xqy @@ -0,0 +1 @@ +world diff --git a/test-typescript/dbclient-convenience-runtime.test.ts b/test-typescript/dbclient-convenience-runtime.test.ts new file mode 100644 index 00000000..3aff4d06 --- /dev/null +++ b/test-typescript/dbclient-convenience-runtime.test.ts @@ -0,0 +1,161 @@ +/* +* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. +*/ + +/// + +/** + * Runtime validation tests for DatabaseClient convenience methods. + * + * These tests verify that the simplified convenience methods on DatabaseClient + * have correct TypeScript types and work as expected. + * + * Run with: npm run test:compile && npx mocha test-typescript/*.js + */ + +const should = require('should'); +import type { DatabaseClient } from 'marklogic'; + +const testConfig = require('../etc/test-config.js'); +const marklogic = require('../lib/marklogic.js'); + +describe('DatabaseClient convenience methods runtime validation', function() { + let client: DatabaseClient; + const testUri = '/test-typescript/convenience-test.json'; + const testContent = { message: 'Convenience method test', timestamp: Date.now() }; + + before(function() { + client = marklogic.createDatabaseClient(testConfig.restWriterConnection); + }); + + after(function() { + client.release(); + }); + + it('should create collection with createCollection()', async function() { + const uris = await client.createCollection('typescript-convenience-test', testContent).result(); + + uris.should.be.an.Array(); + uris.length.should.equal(1); + uris[0].should.be.a.String(); + }); + + it('should probe document with probe()', async function() { + // Write a test document first + await client.documents.write({ + uri: testUri, + content: testContent, + collections: ['typescript-convenience-test'] + }).result(); + + const exists = await client.probe(testUri).result(); + + exists.should.be.a.Boolean(); + exists.should.equal(true); + }); + + it('should read document with read()', async function() { + const contents = await client.read(testUri).result(); + + contents.should.be.an.Array(); + contents.length.should.equal(1); + contents[0].should.have.property('message'); + }); + + it('should query collection with queryCollection()', async function() { + const results = await client.queryCollection('typescript-convenience-test').result(); + + results.should.be.an.Array(); + results.length.should.be.greaterThan(0); + }); + + it('should write collection with writeCollection()', async function() { + const uriMap = { + '/test-typescript/conv-1.json': { id: 1, name: 'Test 1' }, + '/test-typescript/conv-2.json': { id: 2, name: 'Test 2' } + }; + + const uris = await client.writeCollection('typescript-convenience-test', uriMap).result(); + + uris.should.be.an.Array(); + uris.length.should.equal(2); + }); + + it('should remove document with remove()', async function() { + const uris = await client.remove('/test-typescript/conv-1.json', '/test-typescript/conv-2.json').result(); + + uris.should.be.an.Array(); + uris.length.should.equal(2); + }); + + it('should remove collection with removeCollection()', async function() { + const result = await client.removeCollection('typescript-convenience-test').result(); + + result.should.be.a.String(); + result.should.equal('typescript-convenience-test'); + }); + + it('should create timestamp with createTimestamp()', function() { + const ts1 = client.createTimestamp(); + ts1.should.have.property('value'); + + const ts2 = client.createTimestamp('2025-11-25T12:00:00Z'); + ts2.should.have.property('value'); + if (ts2.value !== null) { + ts2.value.should.equal('2025-11-25T12:00:00Z'); + } + }); + + it('should evaluate JavaScript with eval()', async function() { + const results = await client.eval('xdmp.toJSON({message: "Hello from eval"})').result(); + + results.should.be.an.Array(); + results.length.should.equal(1); + results[0].should.have.property('format'); + results[0].should.have.property('datatype'); + results[0].should.have.property('value'); + results[0].format.should.equal('json'); + results[0].value.should.have.property('message'); + results[0].value.message.should.equal('Hello from eval'); + }); + + it('should evaluate JavaScript with variables', async function() { + const results = await client.eval( + 'var x; xdmp.toJSON({result: x * 2})', + { x: 21 } + ).result(); + + results.should.be.an.Array(); + results[0].value.result.should.equal(42); + }); + + it('should evaluate XQuery with xqueryEval()', async function() { + const results = await client.xqueryEval('"Hello from XQuery"').result(); + + results.should.be.an.Array(); + results.length.should.equal(1); + results[0].format.should.equal('text'); + results[0].datatype.should.equal('string'); + results[0].value.should.equal('Hello from XQuery'); + }); + + it('should evaluate XQuery with variables', async function() { + const results = await client.xqueryEval( + 'declare variable $x as xs:integer external; $x * 3', + { x: 14 } + ).result(); + + results.should.be.an.Array(); + results[0].value.should.equal(42); + }); + + it('should invoke a module with invoke()', async function() { + const results = await client.invoke('/hello.xqy').result(); + + results.should.be.an.Array(); + results.length.should.equal(1); + results[0].should.have.property('format'); + results[0].should.have.property('value'); + results[0].value.should.be.a.String(); + }); +}); diff --git a/typescript-test-project/test.ts b/typescript-test-project/test.ts index 53667f01..aa508e67 100644 --- a/typescript-test-project/test.ts +++ b/typescript-test-project/test.ts @@ -56,6 +56,17 @@ async function run() { const wipeResult = await client.documents.wipe({uri: temporalUri, temporalCollection: temporalCollection}).result(); console.log('wipe', wipeResult); + + // Try out eval / invoke + const evalResult = await client.eval('fn.currentDateTime()').result(); + console.log('eval', evalResult); + + const xqueryEvalResult = await client.xqueryEval('fn:current-dateTime()').result(); + console.log('xqueryEval', xqueryEvalResult); + + const invokeResult = await client.invoke('/hello.xqy').result(); + console.log('invoke', invokeResult); + } else { console.error(`❌ Connection failed: ${result.httpStatusCode} - ${result.httpStatusMessage}`); process.exit(1);