diff --git a/.gitignore b/.gitignore index 30834652..e8606379 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ package-lock.json .vscode demo.db demo*.db* -coverage \ No newline at end of file +coverage +.env \ No newline at end of file diff --git a/package.json b/package.json index 41fe390c..7b5826ea 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,8 @@ "node-cls": "^1.0.5", "promise": "^8.0.3", "rfdc": "^1.2.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "wrangler": "^3.86.0" }, "peerDependencies": { "msnodesqlv8": "^4.1.0", @@ -102,6 +103,8 @@ } }, "devDependencies": { + "@cloudflare/workers-types": "^4.20241106.0", + "@miniflare/d1": "^2.14.4", "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-node-resolve": "^13.0.0", "@typescript-eslint/eslint-plugin": "^6.x", diff --git a/src/client/clientMap.js b/src/client/clientMap.js index 880e0d01..2a957225 100644 --- a/src/client/clientMap.js +++ b/src/client/clientMap.js @@ -39,6 +39,7 @@ function map(index, _fn) { dbMap.sap = throwDb; dbMap.oracle = throwDb; dbMap.sqlite = throwDb; + dbMap.d1 = throwDb; function throwDb() { throw new Error('Cannot create pool for database outside node'); @@ -65,6 +66,7 @@ function map(index, _fn) { onFinal.sap = () => index({ db: throwDb, providers: dbMap }); onFinal.oracle = () => index({ db: throwDb, providers: dbMap }); onFinal.sqlite = () => index({ db: throwDb, providers: dbMap }); + onFinal.d1 = () => index({ db: throwDb, providers: dbMap }); return new Proxy(onFinal, handler); } diff --git a/src/client/createProviders.js b/src/client/createProviders.js index 780d29c0..83419278 100644 --- a/src/client/createProviders.js +++ b/src/client/createProviders.js @@ -48,6 +48,11 @@ function createProviders(index) { return createPool.bind(null, 'sqlite'); } }); + Object.defineProperty(dbMap, 'd1', { + get: function() { + return createPool.bind(null, 'd1'); + } + }); Object.defineProperty(dbMap, 'http', { get: function() { return createPool.bind(null, 'http'); @@ -97,12 +102,19 @@ function negotiateCachedPool(fn, providers) { get sqlite() { return createPool.bind(null, 'sqlite'); }, + get d1() { + return createPool.bind(null, 'd1'); + }, get http() { return createPool.bind(null, 'http'); } }; function createPool(providerName, ...args) { + //todo + if (providerName === 'd1') { + return providers[providerName].apply(null, args); + } const key = JSON.stringify(args); if (!cache[providerName]) cache[providerName] = {}; diff --git a/src/client/index.js b/src/client/index.js index 48330ed9..5b21e30d 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -57,6 +57,7 @@ function rdbClient(options = {}) { client.mssqlNative = onProvider.bind(null, 'mssqlNative'); client.pg = onProvider.bind(null, 'pg'); client.postgres = onProvider.bind(null, 'postgres'); + client.d1 = onProvider.bind(null, 'd1'); client.sqlite = onProvider.bind(null, 'sqlite'); client.sap = onProvider.bind(null, 'sap'); client.oracle = onProvider.bind(null, 'oracle'); @@ -128,7 +129,8 @@ function rdbClient(options = {}) { } async function query() { - return netAdapter(baseUrl, undefined, { tableOptions: { db: baseUrl, transaction } }).query.apply(null, arguments); + const adapter = netAdapter(baseUrl, undefined, { tableOptions: { db: baseUrl, transaction } }); + return adapter.query.apply(null, arguments); } function express(arg) { diff --git a/src/client/index.mjs b/src/client/index.mjs index 2ce71224..a1a2986a 100644 --- a/src/client/index.mjs +++ b/src/client/index.mjs @@ -2311,6 +2311,36 @@ const isAsyncFn = kindOfTest('AsyncFunction'); const isThenable = (thing) => thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch); +// original code +// https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34 + +const _setImmediate = ((setImmediateSupported, postMessageSupported) => { + if (setImmediateSupported) { + return setImmediate; + } + + return postMessageSupported ? ((token, callbacks) => { + _global.addEventListener("message", ({source, data}) => { + if (source === _global && data === token) { + callbacks.length && callbacks.shift()(); + } + }, false); + + return (cb) => { + callbacks.push(cb); + _global.postMessage(token, "*"); + } + })(`axios@${Math.random()}`, []) : (cb) => setTimeout(cb); +})( + typeof setImmediate === 'function', + isFunction(_global.postMessage) +); + +const asap = typeof queueMicrotask !== 'undefined' ? + queueMicrotask.bind(_global) : ( typeof process !== 'undefined' && process.nextTick || _setImmediate); + +// ********************* + var utils$1 = { isArray, isArrayBuffer, @@ -2366,7 +2396,9 @@ var utils$1 = { isSpecCompliantForm, toJSONObject, isAsyncFn, - isThenable + isThenable, + setImmediate: _setImmediate, + asap }; /** @@ -2394,7 +2426,10 @@ function AxiosError(message, code, config, request, response) { code && (this.code = code); config && (this.config = config); request && (this.request = request); - response && (this.response = response); + if (response) { + this.response = response; + this.status = response.status ? response.status : null; + } } utils$1.inherits(AxiosError, Error, { @@ -2414,7 +2449,7 @@ utils$1.inherits(AxiosError, Error, { // Axios config: utils$1.toJSONObject(this.config), code: this.code, - status: this.response && this.response.status ? this.response.status : null + status: this.status }; } }); @@ -2882,6 +2917,8 @@ var platform$1 = { const hasBrowserEnv = typeof window !== 'undefined' && typeof document !== 'undefined'; +const _navigator = typeof navigator === 'object' && navigator || undefined; + /** * Determine if we're running in a standard browser environment * @@ -2899,10 +2936,8 @@ const hasBrowserEnv = typeof window !== 'undefined' && typeof document !== 'unde * * @returns {boolean} */ -const hasStandardBrowserEnv = ( - (product) => { - return hasBrowserEnv && ['ReactNative', 'NativeScript', 'NS'].indexOf(product) < 0 - })(typeof navigator !== 'undefined' && navigator.product); +const hasStandardBrowserEnv = hasBrowserEnv && + (!_navigator || ['ReactNative', 'NativeScript', 'NS'].indexOf(_navigator.product) < 0); /** * Determine if we're running in a standard browser webWorker environment @@ -2929,6 +2964,7 @@ var utils = /*#__PURE__*/Object.freeze({ hasBrowserEnv: hasBrowserEnv, hasStandardBrowserWebWorkerEnv: hasStandardBrowserWebWorkerEnv, hasStandardBrowserEnv: hasStandardBrowserEnv, + navigator: _navigator, origin: origin }); @@ -3677,31 +3713,42 @@ function speedometer(samplesCount, min) { */ function throttle(fn, freq) { let timestamp = 0; - const threshold = 1000 / freq; - let timer = null; - return function throttled() { - const force = this === true; + let threshold = 1000 / freq; + let lastArgs; + let timer; + + const invoke = (args, now = Date.now()) => { + timestamp = now; + lastArgs = null; + if (timer) { + clearTimeout(timer); + timer = null; + } + fn.apply(null, args); + }; + const throttled = (...args) => { const now = Date.now(); - if (force || now - timestamp > threshold) { - if (timer) { - clearTimeout(timer); - timer = null; + const passed = now - timestamp; + if ( passed >= threshold) { + invoke(args, now); + } else { + lastArgs = args; + if (!timer) { + timer = setTimeout(() => { + timer = null; + invoke(lastArgs); + }, threshold - passed); } - timestamp = now; - return fn.apply(null, arguments); - } - if (!timer) { - timer = setTimeout(() => { - timer = null; - timestamp = Date.now(); - return fn.apply(null, arguments); - }, threshold - (now - timestamp)); } }; + + const flush = () => lastArgs && invoke(lastArgs); + + return [throttled, flush]; } -var progressEventReducer = (listener, isDownloadStream, freq = 3) => { +const progressEventReducer = (listener, isDownloadStream, freq = 3) => { let bytesNotified = 0; const _speedometer = speedometer(50, 250); @@ -3722,21 +3769,32 @@ var progressEventReducer = (listener, isDownloadStream, freq = 3) => { rate: rate ? rate : undefined, estimated: rate && total && inRange ? (total - loaded) / rate : undefined, event: e, - lengthComputable: total != null + lengthComputable: total != null, + [isDownloadStream ? 'download' : 'upload']: true }; - data[isDownloadStream ? 'download' : 'upload'] = true; - listener(data); }, freq); }; +const progressEventDecorator = (total, throttled) => { + const lengthComputable = total != null; + + return [(loaded) => throttled[0]({ + lengthComputable, + total, + loaded + }), throttled[1]]; +}; + +const asyncDecorator = (fn) => (...args) => utils$1.asap(() => fn(...args)); + var isURLSameOrigin = platform.hasStandardBrowserEnv ? // Standard browser envs have full support of the APIs needed to test // whether the request URL is of the same origin as current location. (function standardBrowserEnv() { - const msie = /(msie|trident)/i.test(navigator.userAgent); + const msie = platform.navigator && /(msie|trident)/i.test(platform.navigator.userAgent); const urlParsingNode = document.createElement('a'); let originURL; @@ -4035,16 +4093,18 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { const _config = resolveConfig(config); let requestData = _config.data; const requestHeaders = AxiosHeaders$1.from(_config.headers).normalize(); - let {responseType} = _config; + let {responseType, onUploadProgress, onDownloadProgress} = _config; let onCanceled; + let uploadThrottled, downloadThrottled; + let flushUpload, flushDownload; + function done() { - if (_config.cancelToken) { - _config.cancelToken.unsubscribe(onCanceled); - } + flushUpload && flushUpload(); // flush events + flushDownload && flushDownload(); // flush events - if (_config.signal) { - _config.signal.removeEventListener('abort', onCanceled); - } + _config.cancelToken && _config.cancelToken.unsubscribe(onCanceled); + + _config.signal && _config.signal.removeEventListener('abort', onCanceled); } let request = new XMLHttpRequest(); @@ -4114,7 +4174,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { return; } - reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, _config, request)); + reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); // Clean up request request = null; @@ -4124,7 +4184,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { request.onerror = function handleError() { // Real errors are hidden from us by the browser // onerror should only fire if it's a network error - reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, _config, request)); + reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request)); // Clean up request request = null; @@ -4140,7 +4200,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { reject(new AxiosError( timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, - _config, + config, request)); // Clean up request @@ -4168,13 +4228,18 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { } // Handle progress if needed - if (typeof _config.onDownloadProgress === 'function') { - request.addEventListener('progress', progressEventReducer(_config.onDownloadProgress, true)); + if (onDownloadProgress) { + ([downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true)); + request.addEventListener('progress', downloadThrottled); } // Not all browsers support upload events - if (typeof _config.onUploadProgress === 'function' && request.upload) { - request.upload.addEventListener('progress', progressEventReducer(_config.onUploadProgress)); + if (onUploadProgress && request.upload) { + ([uploadThrottled, flushUpload] = progressEventReducer(onUploadProgress)); + + request.upload.addEventListener('progress', uploadThrottled); + + request.upload.addEventListener('loadend', flushUpload); } if (_config.cancelToken || _config.signal) { @@ -4209,45 +4274,46 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { }; const composeSignals = (signals, timeout) => { - let controller = new AbortController(); + const {length} = (signals = signals ? signals.filter(Boolean) : []); - let aborted; + if (timeout || length) { + let controller = new AbortController(); - const onabort = function (cancel) { - if (!aborted) { - aborted = true; - unsubscribe(); - const err = cancel instanceof Error ? cancel : this.reason; - controller.abort(err instanceof AxiosError ? err : new CanceledError(err instanceof Error ? err.message : err)); - } - }; + let aborted; - let timer = timeout && setTimeout(() => { - onabort(new AxiosError(`timeout ${timeout} of ms exceeded`, AxiosError.ETIMEDOUT)); - }, timeout); + const onabort = function (reason) { + if (!aborted) { + aborted = true; + unsubscribe(); + const err = reason instanceof Error ? reason : this.reason; + controller.abort(err instanceof AxiosError ? err : new CanceledError(err instanceof Error ? err.message : err)); + } + }; - const unsubscribe = () => { - if (signals) { - timer && clearTimeout(timer); + let timer = timeout && setTimeout(() => { timer = null; - signals.forEach(signal => { - signal && - (signal.removeEventListener ? signal.removeEventListener('abort', onabort) : signal.unsubscribe(onabort)); - }); - signals = null; - } - }; + onabort(new AxiosError(`timeout ${timeout} of ms exceeded`, AxiosError.ETIMEDOUT)); + }, timeout); - signals.forEach((signal) => signal && signal.addEventListener && signal.addEventListener('abort', onabort)); + const unsubscribe = () => { + if (signals) { + timer && clearTimeout(timer); + timer = null; + signals.forEach(signal => { + signal.unsubscribe ? signal.unsubscribe(onabort) : signal.removeEventListener('abort', onabort); + }); + signals = null; + } + }; - const {signal} = controller; + signals.forEach((signal) => signal.addEventListener('abort', onabort)); - signal.unsubscribe = unsubscribe; + const {signal} = controller; - return [signal, () => { - timer && clearTimeout(timer); - timer = null; - }]; + signal.unsubscribe = () => utils$1.asap(unsubscribe); + + return signal; + } }; var composeSignals$1 = composeSignals; @@ -4270,35 +4336,68 @@ const streamChunk = function* (chunk, chunkSize) { } }; -const readBytes = async function* (iterable, chunkSize, encode) { - for await (const chunk of iterable) { - yield* streamChunk(ArrayBuffer.isView(chunk) ? chunk : (await encode(String(chunk))), chunkSize); +const readBytes = async function* (iterable, chunkSize) { + for await (const chunk of readStream(iterable)) { + yield* streamChunk(chunk, chunkSize); } }; -const trackStream = (stream, chunkSize, onProgress, onFinish, encode) => { - const iterator = readBytes(stream, chunkSize, encode); +const readStream = async function* (stream) { + if (stream[Symbol.asyncIterator]) { + yield* stream; + return; + } + + const reader = stream.getReader(); + try { + for (;;) { + const {done, value} = await reader.read(); + if (done) { + break; + } + yield value; + } + } finally { + await reader.cancel(); + } +}; + +const trackStream = (stream, chunkSize, onProgress, onFinish) => { + const iterator = readBytes(stream, chunkSize); let bytes = 0; + let done; + let _onFinish = (e) => { + if (!done) { + done = true; + onFinish && onFinish(e); + } + }; return new ReadableStream({ - type: 'bytes', - async pull(controller) { - const {done, value} = await iterator.next(); + try { + const {done, value} = await iterator.next(); - if (done) { - controller.close(); - onFinish(); - return; - } + if (done) { + _onFinish(); + controller.close(); + return; + } - let len = value.byteLength; - onProgress && onProgress(bytes += len); - controller.enqueue(new Uint8Array(value)); + let len = value.byteLength; + if (onProgress) { + let loadedBytes = bytes += len; + onProgress(loadedBytes); + } + controller.enqueue(new Uint8Array(value)); + } catch (err) { + _onFinish(err); + throw err; + } }, cancel(reason) { - onFinish(reason); + _onFinish(reason); return iterator.return(); } }, { @@ -4306,15 +4405,6 @@ const trackStream = (stream, chunkSize, onProgress, onFinish, encode) => { }) }; -const fetchProgressDecorator = (total, fn) => { - const lengthComputable = total != null; - return (loaded) => setTimeout(() => fn({ - lengthComputable, - total, - loaded - })); -}; - const isFetchSupported = typeof fetch === 'function' && typeof Request === 'function' && typeof Response === 'function'; const isReadableStreamSupported = isFetchSupported && typeof ReadableStream === 'function'; @@ -4324,7 +4414,15 @@ const encodeText = isFetchSupported && (typeof TextEncoder === 'function' ? async (str) => new Uint8Array(await new Response(str).arrayBuffer()) ); -const supportsRequestStream = isReadableStreamSupported && (() => { +const test = (fn, ...args) => { + try { + return !!fn(...args); + } catch (e) { + return false + } +}; + +const supportsRequestStream = isReadableStreamSupported && test(() => { let duplexAccessed = false; const hasContentType = new Request(platform.origin, { @@ -4337,17 +4435,13 @@ const supportsRequestStream = isReadableStreamSupported && (() => { }).headers.has('Content-Type'); return duplexAccessed && !hasContentType; -})(); +}); const DEFAULT_CHUNK_SIZE = 64 * 1024; -const supportsResponseStream = isReadableStreamSupported && !!(()=> { - try { - return utils$1.isReadableStream(new Response('').body); - } catch(err) { - // return undefined - } -})(); +const supportsResponseStream = isReadableStreamSupported && + test(() => utils$1.isReadableStream(new Response('').body)); + const resolvers = { stream: supportsResponseStream && ((res) => res.body) @@ -4372,10 +4466,14 @@ const getBodyLength = async (body) => { } if(utils$1.isSpecCompliantForm(body)) { - return (await new Request(body).arrayBuffer()).byteLength; + const _request = new Request(platform.origin, { + method: 'POST', + body, + }); + return (await _request.arrayBuffer()).byteLength; } - if(utils$1.isArrayBufferView(body)) { + if(utils$1.isArrayBufferView(body) || utils$1.isArrayBuffer(body)) { return body.byteLength; } @@ -4412,18 +4510,13 @@ var fetchAdapter = isFetchSupported && (async (config) => { responseType = responseType ? (responseType + '').toLowerCase() : 'text'; - let [composedSignal, stopTimeout] = (signal || cancelToken || timeout) ? - composeSignals$1([signal, cancelToken], timeout) : []; + let composedSignal = composeSignals$1([signal, cancelToken && cancelToken.toAbortSignal()], timeout); - let finished, request; + let request; - const onFinish = () => { - !finished && setTimeout(() => { - composedSignal && composedSignal.unsubscribe(); - }); - - finished = true; - }; + const unsubscribe = composedSignal && composedSignal.unsubscribe && (() => { + composedSignal.unsubscribe(); + }); let requestContentLength; @@ -4445,17 +4538,22 @@ var fetchAdapter = isFetchSupported && (async (config) => { } if (_request.body) { - data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, fetchProgressDecorator( + const [onProgress, flush] = progressEventDecorator( requestContentLength, - progressEventReducer(onUploadProgress) - ), null, encodeText); + progressEventReducer(asyncDecorator(onUploadProgress)) + ); + + data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush); } } if (!utils$1.isString(withCredentials)) { - withCredentials = withCredentials ? 'cors' : 'omit'; + withCredentials = withCredentials ? 'include' : 'omit'; } + // Cloudflare Workers throws when credentials are defined + // see https://github.com/cloudflare/workerd/issues/902 + const isCredentialsSupported = "credentials" in Request.prototype; request = new Request(url, { ...fetchOptions, signal: composedSignal, @@ -4463,14 +4561,14 @@ var fetchAdapter = isFetchSupported && (async (config) => { headers: headers.normalize().toJSON(), body: data, duplex: "half", - withCredentials + credentials: isCredentialsSupported ? withCredentials : undefined }); let response = await fetch(request); const isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response'); - if (supportsResponseStream && (onDownloadProgress || isStreamResponse)) { + if (supportsResponseStream && (onDownloadProgress || (isStreamResponse && unsubscribe))) { const options = {}; ['status', 'statusText', 'headers'].forEach(prop => { @@ -4479,11 +4577,16 @@ var fetchAdapter = isFetchSupported && (async (config) => { const responseContentLength = utils$1.toFiniteNumber(response.headers.get('content-length')); + const [onProgress, flush] = onDownloadProgress && progressEventDecorator( + responseContentLength, + progressEventReducer(asyncDecorator(onDownloadProgress), true) + ) || []; + response = new Response( - trackStream(response.body, DEFAULT_CHUNK_SIZE, onDownloadProgress && fetchProgressDecorator( - responseContentLength, - progressEventReducer(onDownloadProgress, true) - ), isStreamResponse && onFinish, encodeText), + trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => { + flush && flush(); + unsubscribe && unsubscribe(); + }), options ); } @@ -4492,9 +4595,7 @@ var fetchAdapter = isFetchSupported && (async (config) => { let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || 'text'](response, config); - !isStreamResponse && onFinish(); - - stopTimeout && stopTimeout(); + !isStreamResponse && unsubscribe && unsubscribe(); return await new Promise((resolve, reject) => { settle(resolve, reject, { @@ -4507,7 +4608,7 @@ var fetchAdapter = isFetchSupported && (async (config) => { }); }) } catch (err) { - onFinish(); + unsubscribe && unsubscribe(); if (err && err.name === 'TypeError' && /fetch/i.test(err.message)) { throw Object.assign( @@ -4669,7 +4770,7 @@ function dispatchRequest(config) { }); } -const VERSION = "1.7.2"; +const VERSION = "1.7.7"; const validators$1 = {}; @@ -5076,6 +5177,20 @@ class CancelToken { } } + toAbortSignal() { + const controller = new AbortController(); + + const abort = (err) => { + controller.abort(err); + }; + + this.subscribe(abort); + + controller.signal.unsubscribe = () => this.unsubscribe(abort); + + return controller.signal; + } + /** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. @@ -5479,6 +5594,7 @@ function map$1(index, _fn) { dbMap.sap = throwDb; dbMap.oracle = throwDb; dbMap.sqlite = throwDb; + dbMap.d1 = throwDb; function throwDb() { throw new Error('Cannot create pool for database outside node'); @@ -5505,6 +5621,7 @@ function map$1(index, _fn) { onFinal.sap = () => index({ db: throwDb, providers: dbMap }); onFinal.oracle = () => index({ db: throwDb, providers: dbMap }); onFinal.sqlite = () => index({ db: throwDb, providers: dbMap }); + onFinal.d1 = () => index({ db: throwDb, providers: dbMap }); return new Proxy(onFinal, handler); } @@ -5523,32 +5640,20 @@ function copyBuffer (cur) { function rfdc (opts) { opts = opts || {}; - if (opts.circles) return rfdcCircles(opts) - - const constructorHandlers = new Map(); - constructorHandlers.set(Date, (o) => new Date(o)); - constructorHandlers.set(Map, (o, fn) => new Map(cloneArray(Array.from(o), fn))); - constructorHandlers.set(Set, (o, fn) => new Set(cloneArray(Array.from(o), fn))); - if (opts.constructorHandlers) { - for (const handler of opts.constructorHandlers) { - constructorHandlers.set(handler[0], handler[1]); - } - } - - let handler = null; + if (opts.circles) return rfdcCircles(opts) return opts.proto ? cloneProto : clone function cloneArray (a, fn) { - const keys = Object.keys(a); - const a2 = new Array(keys.length); - for (let i = 0; i < keys.length; i++) { - const k = keys[i]; - const cur = a[k]; + var keys = Object.keys(a); + var a2 = new Array(keys.length); + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + var cur = a[k]; if (typeof cur !== 'object' || cur === null) { a2[k] = cur; - } else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) { - a2[k] = handler(cur, fn); + } else if (cur instanceof Date) { + a2[k] = new Date(cur); } else if (ArrayBuffer.isView(cur)) { a2[k] = copyBuffer(cur); } else { @@ -5560,18 +5665,22 @@ function rfdc (opts) { function clone (o) { if (typeof o !== 'object' || o === null) return o + if (o instanceof Date) return new Date(o) if (Array.isArray(o)) return cloneArray(o, clone) - if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) { - return handler(o, clone) - } - const o2 = {}; - for (const k in o) { + if (o instanceof Map) return new Map(cloneArray(Array.from(o), clone)) + if (o instanceof Set) return new Set(cloneArray(Array.from(o), clone)) + var o2 = {}; + for (var k in o) { if (Object.hasOwnProperty.call(o, k) === false) continue - const cur = o[k]; + var cur = o[k]; if (typeof cur !== 'object' || cur === null) { o2[k] = cur; - } else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) { - o2[k] = handler(cur, clone); + } else if (cur instanceof Date) { + o2[k] = new Date(cur); + } else if (cur instanceof Map) { + o2[k] = new Map(cloneArray(Array.from(cur), clone)); + } else if (cur instanceof Set) { + o2[k] = new Set(cloneArray(Array.from(cur), clone)); } else if (ArrayBuffer.isView(cur)) { o2[k] = copyBuffer(cur); } else { @@ -5583,17 +5692,21 @@ function rfdc (opts) { function cloneProto (o) { if (typeof o !== 'object' || o === null) return o + if (o instanceof Date) return new Date(o) if (Array.isArray(o)) return cloneArray(o, cloneProto) - if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) { - return handler(o, cloneProto) - } - const o2 = {}; - for (const k in o) { - const cur = o[k]; + if (o instanceof Map) return new Map(cloneArray(Array.from(o), cloneProto)) + if (o instanceof Set) return new Set(cloneArray(Array.from(o), cloneProto)) + var o2 = {}; + for (var k in o) { + var cur = o[k]; if (typeof cur !== 'object' || cur === null) { o2[k] = cur; - } else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) { - o2[k] = handler(cur, cloneProto); + } else if (cur instanceof Date) { + o2[k] = new Date(cur); + } else if (cur instanceof Map) { + o2[k] = new Map(cloneArray(Array.from(cur), cloneProto)); + } else if (cur instanceof Set) { + o2[k] = new Set(cloneArray(Array.from(cur), cloneProto)); } else if (ArrayBuffer.isView(cur)) { o2[k] = copyBuffer(cur); } else { @@ -5605,36 +5718,25 @@ function rfdc (opts) { } function rfdcCircles (opts) { - const refs = []; - const refsNew = []; - - const constructorHandlers = new Map(); - constructorHandlers.set(Date, (o) => new Date(o)); - constructorHandlers.set(Map, (o, fn) => new Map(cloneArray(Array.from(o), fn))); - constructorHandlers.set(Set, (o, fn) => new Set(cloneArray(Array.from(o), fn))); - if (opts.constructorHandlers) { - for (const handler of opts.constructorHandlers) { - constructorHandlers.set(handler[0], handler[1]); - } - } + var refs = []; + var refsNew = []; - let handler = null; return opts.proto ? cloneProto : clone function cloneArray (a, fn) { - const keys = Object.keys(a); - const a2 = new Array(keys.length); - for (let i = 0; i < keys.length; i++) { - const k = keys[i]; - const cur = a[k]; + var keys = Object.keys(a); + var a2 = new Array(keys.length); + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + var cur = a[k]; if (typeof cur !== 'object' || cur === null) { a2[k] = cur; - } else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) { - a2[k] = handler(cur, fn); + } else if (cur instanceof Date) { + a2[k] = new Date(cur); } else if (ArrayBuffer.isView(cur)) { a2[k] = copyBuffer(cur); } else { - const index = refs.indexOf(cur); + var index = refs.indexOf(cur); if (index !== -1) { a2[k] = refsNew[index]; } else { @@ -5647,24 +5749,28 @@ function rfdcCircles (opts) { function clone (o) { if (typeof o !== 'object' || o === null) return o + if (o instanceof Date) return new Date(o) if (Array.isArray(o)) return cloneArray(o, clone) - if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) { - return handler(o, clone) - } - const o2 = {}; + if (o instanceof Map) return new Map(cloneArray(Array.from(o), clone)) + if (o instanceof Set) return new Set(cloneArray(Array.from(o), clone)) + var o2 = {}; refs.push(o); refsNew.push(o2); - for (const k in o) { + for (var k in o) { if (Object.hasOwnProperty.call(o, k) === false) continue - const cur = o[k]; + var cur = o[k]; if (typeof cur !== 'object' || cur === null) { o2[k] = cur; - } else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) { - o2[k] = handler(cur, clone); + } else if (cur instanceof Date) { + o2[k] = new Date(cur); + } else if (cur instanceof Map) { + o2[k] = new Map(cloneArray(Array.from(cur), clone)); + } else if (cur instanceof Set) { + o2[k] = new Set(cloneArray(Array.from(cur), clone)); } else if (ArrayBuffer.isView(cur)) { o2[k] = copyBuffer(cur); } else { - const i = refs.indexOf(cur); + var i = refs.indexOf(cur); if (i !== -1) { o2[k] = refsNew[i]; } else { @@ -5679,23 +5785,27 @@ function rfdcCircles (opts) { function cloneProto (o) { if (typeof o !== 'object' || o === null) return o + if (o instanceof Date) return new Date(o) if (Array.isArray(o)) return cloneArray(o, cloneProto) - if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) { - return handler(o, cloneProto) - } - const o2 = {}; + if (o instanceof Map) return new Map(cloneArray(Array.from(o), cloneProto)) + if (o instanceof Set) return new Set(cloneArray(Array.from(o), cloneProto)) + var o2 = {}; refs.push(o); refsNew.push(o2); - for (const k in o) { - const cur = o[k]; + for (var k in o) { + var cur = o[k]; if (typeof cur !== 'object' || cur === null) { o2[k] = cur; - } else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) { - o2[k] = handler(cur, cloneProto); + } else if (cur instanceof Date) { + o2[k] = new Date(cur); + } else if (cur instanceof Map) { + o2[k] = new Map(cloneArray(Array.from(cur), cloneProto)); + } else if (cur instanceof Set) { + o2[k] = new Set(cloneArray(Array.from(cur), cloneProto)); } else if (ArrayBuffer.isView(cur)) { o2[k] = copyBuffer(cur); } else { - const i = refs.indexOf(cur); + var i = refs.indexOf(cur); if (i !== -1) { o2[k] = refsNew[i]; } else { @@ -5815,6 +5925,7 @@ function rdbClient(options = {}) { client.mssqlNative = onProvider.bind(null, 'mssqlNative'); client.pg = onProvider.bind(null, 'pg'); client.postgres = onProvider.bind(null, 'postgres'); + client.d1 = onProvider.bind(null, 'd1'); client.sqlite = onProvider.bind(null, 'sqlite'); client.sap = onProvider.bind(null, 'sap'); client.oracle = onProvider.bind(null, 'oracle'); @@ -5886,7 +5997,8 @@ function rdbClient(options = {}) { } async function query() { - return netAdapter(baseUrl, undefined, { tableOptions: { db: baseUrl, transaction } }).query.apply(null, arguments); + const adapter = netAdapter(baseUrl, undefined, { tableOptions: { db: baseUrl, transaction } }); + return adapter.query.apply(null, arguments); } function express(arg) { diff --git a/src/client/map.js b/src/client/map.js index c7b59d43..f5d2b18e 100644 --- a/src/client/map.js +++ b/src/client/map.js @@ -48,6 +48,7 @@ function map(index, context, providers, fn) { context.sap = connect.bind(null, 'sap'); context.oracle = connect.bind(null, 'oracle'); context.sqlite = connect.bind(null, 'sqlite'); + context.d1 = connect.bind(null, 'd1'); context.http = function(url) { return index({ db: url, providers}); }; diff --git a/src/d1/newDatabase.js b/src/d1/newDatabase.js new file mode 100644 index 00000000..ddbd3185 --- /dev/null +++ b/src/d1/newDatabase.js @@ -0,0 +1,133 @@ +let createDomain = require('../createDomain'); +let newTransaction = require('./newTransaction'); +let _begin = require('../table/begin'); +let commit = require('../table/commit'); +let rollback = require('../table/rollback'); +let newPool = require('./newPool'); +let useHook = require('../useHook'); +let promise = require('promise/domains'); +let versionArray = process.version.replace('v', '').split('.'); +let major = parseInt(versionArray[0]); +let express = require('../hostExpress'); +let hostLocal = require('../hostLocal'); +let doQuery = require('../query'); +let releaseDbClient = require('../table/releaseDbClient'); +let setSessionSingleton = require('../table/setSessionSingleton'); + +function newDatabase(d1Database, poolOptions) { + if (!d1Database) + throw new Error('Missing d1Database'); + var pool; + if (!poolOptions) + pool = newPool.bind(null,d1Database, poolOptions); + else + pool = newPool(d1Database, poolOptions); + + let c = {poolFactory: pool, hostLocal, express}; + + c.transaction = function(options, fn) { + if ((arguments.length === 1) && (typeof options === 'function')) { + fn = options; + options = undefined; + } + let domain = createDomain(); + + if (fn) + return domain.run(runInTransaction); + else if ((major >= 12) && useHook()) { + domain.exitContext = true; + return domain.start().then(run); + } + else + return domain.run(run); + + function begin() { + const transactionLess = true; + return _begin(transactionLess); + } + + async function runInTransaction() { + let result; + let transaction = newTransaction(domain, pool, options); + await new Promise(transaction) + .then(begin) + .then(fn) + .then((res) => result = res) + .then(c.commit) + .then(null, c.rollback); + return result; + } + + function run() { + let p; + let transaction = newTransaction(domain, pool, options); + if (useHook()) + p = new Promise(transaction); + else + p = new promise(transaction); + + return p.then(begin); + } + + }; + + c.createTransaction = function() { + let domain = createDomain(); + let transaction = newTransaction(domain, pool); + let p = domain.run(() => new Promise(transaction).then(_begin)); + + function run(fn) { + return p.then(domain.run.bind(domain, fn)); + } + return run; + }; + + c.bindTransaction = function() { + // @ts-ignore + var domain = process.domain; + let p = domain.run(() => true); + + function run(fn) { + return p.then(domain.run.bind(domain, fn)); + } + return run; + }; + + c.query = function(query) { + let domain = createDomain(); + let transaction = newTransaction(domain, pool); + let p = domain.run(() => new Promise(transaction) + .then(() => setSessionSingleton('changes', [])) + .then(() => doQuery(query).then(onResult, onError))); + return p; + + function onResult(result) { + releaseDbClient(); + return result; + } + + function onError(e) { + releaseDbClient(); + throw e; + } + }; + + + c.rollback = rollback; + c.commit = commit; + + c.end = function() { + if (poolOptions) + return pool.end(); + else + return Promise.resolve(); + }; + + c.accept = function(caller) { + caller.visitSqlite(); + }; + + return c; +} + +module.exports = newDatabase; diff --git a/src/d1/newPool.js b/src/d1/newPool.js new file mode 100644 index 00000000..15420172 --- /dev/null +++ b/src/d1/newPool.js @@ -0,0 +1,19 @@ +var pools = require('../pools'); +var promise = require('../table/promise'); +var end = require('./pool/end'); +var newGenericPool = require('./pool/newGenericPool'); +var newId = require('../newId'); + +function newPool(d1Database, poolOptions) { + var pool = newGenericPool(d1Database, poolOptions); + var id = newId(); + var boundEnd = end.bind(null, pool, id); + var c = {}; + + c.connect = pool.connect; + c.end = promise.denodeify(boundEnd); + pools[id] = c; + return c; +} + +module.exports = newPool; \ No newline at end of file diff --git a/src/d1/newTransaction.js b/src/d1/newTransaction.js new file mode 100644 index 00000000..bda17833 --- /dev/null +++ b/src/d1/newTransaction.js @@ -0,0 +1,82 @@ +const wrapQuery = require('./wrapQuery'); +const encodeBoolean = require('../sqlite/encodeBoolean'); +const deleteFromSql = require('../sqlite/deleteFromSql'); +const selectForUpdateSql = require('../sqlite/selectForUpdateSql'); +const lastInsertedSql = require('../sqlite/lastInsertedSql'); +const limitAndOffset = require('../sqlite/limitAndOffset'); +const insertSql = require('../sqlite/insertSql'); +const insert = require('../sqlite/insert'); + +function newResolveTransaction(domain, pool, { readonly = false } = {}) { + var rdb = {poolFactory: pool}; + if (!pool.connect) { + pool = pool(); + rdb.pool = pool; + } + rdb.engine = 'sqlite'; + rdb.encodeBoolean = encodeBoolean; + rdb.decodeJSON = decodeJSON; + rdb.encodeJSON = JSON.stringify; + rdb.deleteFromSql = deleteFromSql; + rdb.selectForUpdateSql = selectForUpdateSql; + rdb.lastInsertedSql = lastInsertedSql; + rdb.insertSql = insertSql; + rdb.insert = insert; + rdb.lastInsertedIsSeparate = true; + rdb.multipleStatements = false; + rdb.limitAndOffset = limitAndOffset; + rdb.accept = function(caller) { + caller.visitSqlite(); + }; + rdb.aggregateCount = 0; + rdb.quote = (name) => `"${name}"`; + + if (readonly) { + rdb.dbClient = { + executeQuery: function(query, callback) { + pool.connect((err, client, done) => { + if (err) { + return callback(err); + } + try { + wrapQuery(client)(query, (err, res) => { + done(); + callback(err, res); + }); + } catch (e) { + done(); + callback(e); + } + }); + } + }; + domain.rdb = rdb; + return (onSuccess) => onSuccess(); + } + + return function(onSuccess, onError) { + pool.connect(onConnected); + + function onConnected(err, client, done) { + try { + if (err) { + onError(err); + return; + } + client.executeQuery = wrapQuery(client); + rdb.dbClient = client; + rdb.dbClientDone = done; + domain.rdb = rdb; + onSuccess(); + } catch (e) { + onError(e); + } + } + }; +} + +function decodeJSON(value) { + return JSON.parse(value); +} + +module.exports = newResolveTransaction; \ No newline at end of file diff --git a/src/d1/pool/defaults.js b/src/d1/pool/defaults.js new file mode 100644 index 00000000..f21f834a --- /dev/null +++ b/src/d1/pool/defaults.js @@ -0,0 +1,45 @@ +module.exports = { + // database host defaults to localhost + host: 'localhost', + + //database user's name + user: process.platform === 'win32' ? process.env.USERNAME : process.env.USER, + + //name of database to connect + database: process.platform === 'win32' ? process.env.USERNAME : process.env.USER, + + //database user's password + password: null, + + //database port + port: 5432, + + //number of rows to return at a time from a prepared statement's + //portal. 0 will return all rows at once + rows: 0, + + // binary result mode + binary: false, + + //Connection pool options - see https://github.com/coopernurse/node-pool + //number of connections to use in connection pool + //0 will disable connection pooling + poolSize: 0, + + //max milliseconds a client can go unused before it is removed + //from the pool and destroyed + poolIdleTimeout: 30000, + + //frequeny to check for idle clients within the client pool + reapIntervalMillis: 1000, + + //pool log function / boolean + poolLog: false, + + client_encoding: '', + + ssl: false, + + application_name : undefined, + fallback_application_name: undefined +}; \ No newline at end of file diff --git a/src/d1/pool/end.js b/src/d1/pool/end.js new file mode 100644 index 00000000..7b43fa3e --- /dev/null +++ b/src/d1/pool/end.js @@ -0,0 +1,13 @@ +var pools = require('../../pools'); + +function endPool(genericPool, id, done) { + genericPool.drain(onDrained); + + function onDrained() { + genericPool.destroyAllNow(); + delete pools[id]; + done(); + } +} + +module.exports = endPool; diff --git a/src/d1/pool/newGenericPool.js b/src/d1/pool/newGenericPool.js new file mode 100644 index 00000000..a508a509 --- /dev/null +++ b/src/d1/pool/newGenericPool.js @@ -0,0 +1,52 @@ +/* eslint-disable no-prototype-builtins */ +var EventEmitter = require('events').EventEmitter; + +var defaults = require('./defaults'); +var genericPool = require('../../generic-pool'); + +function newGenericPool(d1Database, poolOptions) { + poolOptions = poolOptions || {}; + // @ts-ignore + var pool = genericPool.Pool({ + max: 1, + idleTimeoutMillis: poolOptions.idleTimeout || defaults.poolIdleTimeout, + reapIntervalMillis: poolOptions.reapIntervalMillis || defaults.reapIntervalMillis, + log: poolOptions.log || defaults.poolLog, + create: function(cb) { + var client = {d1: d1Database, poolCount: 0}; + + return cb(null, client); + }, + + destroy: function() { + } + }); + //mixin EventEmitter to pool + EventEmitter.call(pool); + for(var key in EventEmitter.prototype) { + if(EventEmitter.prototype.hasOwnProperty(key)) { + pool[key] = EventEmitter.prototype[key]; + } + } + //monkey-patch with connect method + pool.connect = function(cb) { + var domain = process.domain; + pool.acquire(function(err, client) { + if(domain) { + cb = domain.bind(cb); + } + if(err) return cb(err, null, function() {/*NOOP*/}); + client.poolCount++; + cb(null, client, function(err) { + if(err) { + pool.destroy(client); + } else { + pool.release(client); + } + }); + }); + }; + return pool; +} + +module.exports = newGenericPool; \ No newline at end of file diff --git a/src/d1/wrapQuery.js b/src/d1/wrapQuery.js new file mode 100644 index 00000000..9f8197ca --- /dev/null +++ b/src/d1/wrapQuery.js @@ -0,0 +1,22 @@ +var log = require('../table/log'); + +function wrapQuery(client) { + + return runQuery; + + function runQuery(query, onCompleted) { + + var params = query.parameters; + var sql = query.sql(); + log.emitQuery({sql, parameters: params}); + client.d1.prepare(sql, params).bind(...params).all().then(onInnerCompleted, onCompleted); + + function onInnerCompleted(response) { + onCompleted(null, response.results); + } + + } + +} + +module.exports = wrapQuery; \ No newline at end of file diff --git a/src/d1test.js b/src/d1test.js new file mode 100644 index 00000000..fa22aa42 --- /dev/null +++ b/src/d1test.js @@ -0,0 +1,35 @@ +import { connect } from '@cloudflare/d1'; + +const databaseId = process.env.D1_DATABASE_ID; +const accountId = process.env.D1_ACCOUNT_ID; +const apiToken = process.env.D1_API_TOKEN; + +export class Database { + constructor() { + this.db = connect({ + databaseId, + accountId, + apiToken, + }); + } + + async createUser(name, email) { + return await this.db + .prepare('INSERT INTO users (name, email) VALUES (?, ?)') + .bind(name, email) + .run(); + } + + async getUsers() { + return await this.db + .prepare('SELECT * FROM users') + .all(); + } + + async getUserById(id) { + return await this.db + .prepare('SELECT * FROM users WHERE id = ?') + .bind(id) + .first(); + } +} diff --git a/src/index.d.ts b/src/index.d.ts index 3ec64ab2..80b316eb 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,5 +1,6 @@ import type { Options } from './ajv'; import type { RequestHandler } from 'express'; +import type { D1Database } from '@cloudflare/workers-types'; import type { ConnectionConfiguration } from 'tedious'; import type { PoolAttributes } from 'oracledb'; import type { AllowedDbMap, DbMapper, MappedDbDef } from './map'; @@ -10,6 +11,7 @@ declare namespace r { function table(name: string): Table; function end(): Promise; + function d1(database: D1Database, options?: PoolOptions): Pool; function postgres(connectionString: string, options?: PoolOptions): Pool; function sqlite(connectionString: string, options?: PoolOptions): Pool; function sap(connectionString: string, options?: PoolOptions): Pool; diff --git a/src/index.js b/src/index.js index 3d12d0f6..cfec177d 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ var hostExpress = require('./hostExpress'); var client = require('./client/index.js'); var _mySql; var _pg; +var _d1; var _sqlite; var _mssqlNative; var _sap; @@ -76,6 +77,14 @@ Object.defineProperty(connectViaPool, 'sqlite', { } }); +Object.defineProperty(connectViaPool, 'd1', { + get: function() { + if (!_d1) + _d1 = require('./d1/newDatabase'); + return _d1; + } +}); + Object.defineProperty(connectViaPool, 'mssqlNative', { get: function() { if (!_mssqlNative) diff --git a/src/map.d.ts b/src/map.d.ts index 1aa7db0e..cf1b6c46 100644 --- a/src/map.d.ts +++ b/src/map.d.ts @@ -1,5 +1,6 @@ import type { Options } from './ajv'; import type { ConnectionConfiguration } from 'tedious'; +import type { D1Database } from '@cloudflare/workers-types'; import type { PoolAttributes } from 'oracledb'; import type { AxiosInterceptorManager, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; @@ -29,6 +30,7 @@ type MappedDb = { type DbConnectable = { http(url: string): MappedDbInstance; + d1(database: D1Database): MappedDbInstance; postgres(connectionString: string, options?: PoolOptions): MappedDbInstance; sqlite(connectionString: string, options?: PoolOptions): MappedDbInstance; sap(connectionString: string, options?: PoolOptions): MappedDbInstance; @@ -59,6 +61,7 @@ type DbOptions = { interface Connectors { http(url: string): Pool; + d1(database: D1Database): Pool; postgres(connectionString: string, options?: PoolOptions): Pool; sqlite(connectionString: string, options?: PoolOptions): Pool; sap(connectionString: string, options?: PoolOptions): Pool; diff --git a/src/mssql/newTransaction.js b/src/mssql/newTransaction.js index eb225955..d2d1a39d 100644 --- a/src/mssql/newTransaction.js +++ b/src/mssql/newTransaction.js @@ -8,7 +8,7 @@ const formatDateOut = require('../tedious/formatDateOut'); const insertSql = require('../tedious/insertSql'); const insert = require('../tedious/insert'); -function newResolveTransaction(domain, pool, { readonly } = {}) { +function newResolveTransaction(domain, pool, { readonly = false } = {}) { var rdb = {poolFactory: pool}; if (!pool.connect) { pool = pool(); diff --git a/src/mySql/newTransaction.js b/src/mySql/newTransaction.js index 851e26f1..7ccae8e4 100644 --- a/src/mySql/newTransaction.js +++ b/src/mySql/newTransaction.js @@ -7,7 +7,7 @@ const limitAndOffset = require('./limitAndOffset'); const insertSql = require('./insertSql'); const insert = require('./insert'); -function newResolveTransaction(domain, pool, { readonly } = {}) { +function newResolveTransaction(domain, pool, { readonly = false } = {}) { var rdb = {poolFactory: pool}; if (!pool.connect) { pool = pool(); diff --git a/src/oracle/newTransaction.js b/src/oracle/newTransaction.js index e0b699e3..afcdf125 100644 --- a/src/oracle/newTransaction.js +++ b/src/oracle/newTransaction.js @@ -9,7 +9,7 @@ const insert = require('./insert'); const formatDateOut = require('./formatDateOut'); const formatDateIn = require('./formatDateIn'); -function newResolveTransaction(domain, pool, { readonly } = {}) { +function newResolveTransaction(domain, pool, { readonly = false } = {}) { var rdb = {poolFactory: pool}; if (!pool.connect) { pool = pool(); diff --git a/src/pg/newTransaction.js b/src/pg/newTransaction.js index ab7ae463..f0d0116f 100644 --- a/src/pg/newTransaction.js +++ b/src/pg/newTransaction.js @@ -9,7 +9,7 @@ var encodeJSON = require('./encodeJSON'); var insertSql = require('./insertSql'); var insert = require('./insert'); -function newResolveTransaction(domain, pool, { readonly } = {}) { +function newResolveTransaction(domain, pool, { readonly = false } = {}) { var rdb = { poolFactory: pool }; if (!pool.connect) { pool = pool(); diff --git a/src/sap/newTransaction.js b/src/sap/newTransaction.js index ae1ef85c..161c3d1f 100644 --- a/src/sap/newTransaction.js +++ b/src/sap/newTransaction.js @@ -8,7 +8,7 @@ const insertSql = require('./insertSql'); const insert = require('./insert'); const limitAndOffset = require('./limitAndOffset'); -function newResolveTransaction(domain, pool, { readonly } = {}) { +function newResolveTransaction(domain, pool, { readonly = false } = {}) { var rdb = {poolFactory: pool}; if (!pool.connect) { pool = pool(); diff --git a/src/sqlite/newTransaction.js b/src/sqlite/newTransaction.js index 831610c6..718cdee1 100644 --- a/src/sqlite/newTransaction.js +++ b/src/sqlite/newTransaction.js @@ -7,7 +7,7 @@ const limitAndOffset = require('./limitAndOffset'); const insertSql = require('./insertSql'); const insert = require('./insert'); -function newResolveTransaction(domain, pool, { readonly } = {}) { +function newResolveTransaction(domain, pool, { readonly = false } = {}) { var rdb = {poolFactory: pool}; if (!pool.connect) { pool = pool(); diff --git a/src/table/begin.js b/src/table/begin.js index c1ead460..81961c4a 100644 --- a/src/table/begin.js +++ b/src/table/begin.js @@ -5,7 +5,7 @@ let setSessionSingleton = require('./setSessionSingleton'); function begin(readonly) { setSessionSingleton('changes', []); if (readonly) { - setSessionSingleton('readonly', true); + setSessionSingleton('transactionLess', true); return Promise.resolve(); } return executeQuery(beginCommand()); diff --git a/src/table/commit.js b/src/table/commit.js index ef84b69f..6372bc90 100644 --- a/src/table/commit.js +++ b/src/table/commit.js @@ -20,7 +20,7 @@ function commit(result) { await executeChanges(changes); changes = popChanges(); } - if (!getSessionSingleton('readonly')) + if (!getSessionSingleton('transactionLess')) pushCommand(commitCommand); return executeChanges(popChanges()); } diff --git a/src/tedious/newTransaction.js b/src/tedious/newTransaction.js index fa3eb0b9..102ec908 100644 --- a/src/tedious/newTransaction.js +++ b/src/tedious/newTransaction.js @@ -10,7 +10,7 @@ const formatJSONOut = require('./formatJSONOut'); const insertSql = require('./insertSql'); const insert = require('./insert'); -function newResolveTransaction(domain, pool, { readonly } = {}) { +function newResolveTransaction(domain, pool, { readonly = false } = {}) { var rdb = {poolFactory: pool}; if (!pool.connect) { pool = pool(); diff --git a/tests/aggregate.test.js b/tests/aggregate.test.js index 0711183e..1563f2da 100644 --- a/tests/aggregate.test.js +++ b/tests/aggregate.test.js @@ -1,5 +1,6 @@ import { describe, test, beforeAll, afterAll, expect } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const express = require('express'); import { json } from 'body-parser'; import cors from 'cors'; @@ -13,10 +14,12 @@ const initSap = require('./initSap'); const versionArray = process.version.replace('v', '').split('.'); const major = parseInt(versionArray[0]); const port = 3010; - let server; +let d1; +let miniflare; afterAll(async () => { + await miniflare.dispose(); return new Promise((res) => { if (server) server.close(res); @@ -26,6 +29,7 @@ afterAll(async () => { }); beforeAll(async () => { + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await createMs('mssql'); await insertData('pg'); await insertData('oracle'); @@ -34,6 +38,7 @@ beforeAll(async () => { await insertData('mssqlNative'); await insertData('mysql'); await insertData('sqlite'); + await insertData('d1'); await insertData('sqlite2'); await insertData('sap'); hostExpress(); @@ -132,6 +137,7 @@ describe('count', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -151,6 +157,7 @@ describe('count filter', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -203,6 +210,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -219,7 +230,7 @@ const connections = { password: 'P@assword123', connectString: 'oracle/XE', privilege: 2 - }, {size: 1} + }, { size: 1 } ) }), @@ -244,6 +255,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/conflicts.test.js b/tests/conflicts.test.js index 44c0c283..6ac7d1d5 100644 --- a/tests/conflicts.test.js +++ b/tests/conflicts.test.js @@ -1,6 +1,7 @@ import { fileURLToPath } from 'url'; const map = require('./db'); import { describe, test, beforeAll, afterAll, expect } from 'vitest'; +import setupD1 from './setupD1'; const express = require('express'); import { json } from 'body-parser'; import cors from 'cors'; @@ -14,8 +15,11 @@ const versionArray = process.version.replace('v', '').split('.'); const major = parseInt(versionArray[0]); const port = 3007; let server; +let d1; +let miniflare; beforeAll(async () => { + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await createMs('mssql'); hostExpress(); @@ -40,6 +44,7 @@ beforeAll(async () => { }, 20000); afterAll(async () => { + await miniflare.dispose(); return new Promise((res) => { if (server) server.close(res); @@ -56,6 +61,7 @@ describe('optimistic fail', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -101,6 +107,7 @@ describe('insert skipOnConflict with overwrite column', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -156,6 +163,7 @@ describe('savechanges overload overwrite', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -212,6 +220,7 @@ describe('savechanges overload optimistic', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -263,6 +272,7 @@ describe('insert empty skipOnConflict', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -302,6 +312,7 @@ describe('columnDiscriminator insert skipOnConflict with overwrite column', () = test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -365,6 +376,7 @@ describe('insert overwrite with skipOnConflict column', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -420,6 +432,7 @@ describe('insert overwrite with optimistic column changed', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -475,6 +488,7 @@ describe('insert overwrite with optimistic column unchanged', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -562,6 +576,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -578,7 +596,7 @@ const connections = { password: 'P@assword123', connectString: 'oracle/XE', privilege: 2 - }, {size: 1} + }, { size: 1 } ) }), @@ -603,6 +621,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/d1.test.js b/tests/d1.test.js new file mode 100644 index 00000000..9428b290 --- /dev/null +++ b/tests/d1.test.js @@ -0,0 +1,66 @@ +import { describe, beforeAll, afterAll, test, expect } from 'vitest'; +import { Miniflare } from 'miniflare'; +import { fileURLToPath } from 'url'; +import path from 'path'; + +// Determine the SQLite filename dynamically +const pathSegments = fileURLToPath(import.meta.url).split('/'); +const lastSegment = pathSegments[pathSegments.length - 1]; +const fileNameWithoutExtension = lastSegment.split('.')[0]; + +let miniflare; +let d1; + +async function setupD1() { + const sqliteName = path.join(__dirname, `demo.d1.${fileNameWithoutExtension}.db`); + miniflare = new Miniflare({ + modules: true, // Enable module mode explicitly for ES module support + script: 'export default { fetch() {} };', // Minimal worker script as a module + d1Databases: { + DB: sqliteName, // D1 binding + }, + envPath: true, // Load environment variables from .env if needed + }); + + await miniflare.getBindings(); + d1 = await miniflare.getD1Database('DB'); +} + +describe('Cloudflare D1 Database Tests', () => { + + beforeAll(async () => { + await setupD1(); + // Create the table if it doesn’t exist + await d1.prepare( + `CREATE TABLE IF NOT EXISTS my_table ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + value INTEGER + );` + ).run(); + }); + + afterAll(async () => { + await miniflare.dispose(); + }); + + test('query database without worker', async () => { + const result = await d1.prepare('SELECT * FROM my_table').all(); + expect(result).toBeDefined(); + expect(result.results.length).toBe(0); + }); + + test('insert and retrieve data', async () => { + await d1.prepare('INSERT INTO my_table (name, value) VALUES (?, ?)') + .bind('test', 123) + .run(); + + const result = await d1.prepare('SELECT * FROM my_table WHERE name = ?') + .bind('test') + .all(); + console.dir(result); + + expect(result.results[0].name).toBe('test'); + expect(result.results[0].value).toBe(123); + }); +}); diff --git a/tests/dataTypes.test.js b/tests/dataTypes.test.js index 03c1558b..95076d0e 100644 --- a/tests/dataTypes.test.js +++ b/tests/dataTypes.test.js @@ -1,5 +1,6 @@ import { describe, test, beforeAll, afterAll, expect } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const express = require('express'); const map = require('./db'); import { json } from 'body-parser'; @@ -15,8 +16,11 @@ const versionArray = process.version.replace('v', '').split('.'); const major = parseInt(versionArray[0]); const port = 3005; let server; +let d1; +let miniflare; afterAll(async () => { + await miniflare.dispose(); return new Promise((res) => { if (server) server.close(res); @@ -26,7 +30,7 @@ afterAll(async () => { }); beforeAll(async () => { - + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await createMs('mssql'); await insertData('pg'); await insertData('oracle'); @@ -35,6 +39,7 @@ beforeAll(async () => { await insertData('mssqlNative'); await insertData('mysql'); await insertData('sqlite'); + await insertData('d1'); await insertData('sqlite2'); await insertData('sap'); hostExpress(); @@ -142,6 +147,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -158,7 +167,7 @@ const connections = { password: 'P@assword123', connectString: 'oracle/XE', privilege: 2 - }, {size: 1} + }, { size: 1 } ) }), @@ -183,6 +192,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/dateformat.test.js b/tests/dateformat.test.js index bd840e5a..844ab55f 100644 --- a/tests/dateformat.test.js +++ b/tests/dateformat.test.js @@ -1,7 +1,7 @@ -import { describe, test, beforeAll, expect } from 'vitest'; +import { describe, test, beforeAll, expect, afterAll } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const map = require('./db'); - const initPg = require('./initPg'); const initOracle = require('./initOracle'); const initMs = require('./initMs'); @@ -10,15 +10,18 @@ const initSqlite = require('./initSqlite'); const initSap = require('./initSap'); const port = 3002; +let d1; +let miniflare; beforeAll(async () => { - + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await insertData('pg'); await insertData('oracle'); await insertData('mssql'); await insertData('mysql'); await insertData('sap'); await insertData('sqlite'); + await insertData('d1'); async function insertData(dbName) { const { db, init } = getDb(dbName); @@ -72,6 +75,11 @@ beforeAll(async () => { } }); +afterAll(async () => { + await miniflare.dispose(); +}); + + // describe('dateformat raw', () => { // test('pg', async () => { @@ -186,6 +194,18 @@ describe('dateformat get', () => { expect(result).toEqual({ id: 1, date: '2023-08-05T12:00:00', datetime: '2023-08-05T12:00:00', datetime_tz: '2023-08-05T12:00:00-03:00' }); }); + test('d1', async () => { + const { db } = getDb('d1'); + const result = await db.datetestWithTz.getOne(); + expect(result).toEqual({ id: 1, date: '2023-07-14T12:00:00', datetime: '2023-07-14T12:00:00', datetime_tz: '2023-07-14T12:00:00-08:00' }); + result.date = newValue; + result.datetime = newValue; + result.datetime_tz = newValue; + await result.saveChanges(); + await result.refresh(); + expect(result).toEqual({ id: 1, date: '2023-08-05T12:00:00', datetime: '2023-08-05T12:00:00', datetime_tz: '2023-08-05T12:00:00-03:00' }); + }); + }); @@ -195,7 +215,6 @@ const fileNameWithoutExtension = lastSegment.split('.')[0]; const sqliteName = `demo.${fileNameWithoutExtension}.db`; const sqliteName2 = `demo.${fileNameWithoutExtension}2.db`; - const connections = { mssql: { db: @@ -230,6 +249,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -246,7 +269,7 @@ const connections = { password: 'P@assword123', connectString: 'oracle/XE', privilege: 2 - }, {size: 1} + }, { size: 1 } ) }), @@ -271,6 +294,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/getMany.test.js b/tests/getMany.test.js index 0ad4ed90..b18dd002 100644 --- a/tests/getMany.test.js +++ b/tests/getMany.test.js @@ -1,5 +1,6 @@ import { describe, test, beforeAll, afterAll, expect } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const express = require('express'); import { json } from 'body-parser'; import cors from 'cors'; @@ -15,8 +16,11 @@ const versionArray = process.version.replace('v', '').split('.'); const major = parseInt(versionArray[0]); const port = 3000; let server; +let d1; +let miniflare; afterAll(async () => { + await miniflare.dispose(); return new Promise((res) => { if (server) server.close(res); @@ -27,6 +31,7 @@ afterAll(async () => { beforeAll(async () => { + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await createMs('mssql'); await insertData('pg'); await insertData('mssql'); @@ -34,6 +39,7 @@ beforeAll(async () => { await insertData('mssqlNative'); await insertData('mysql'); await insertData('sqlite'); + await insertData('d1'); await insertData('sqlite2'); await insertData('sap'); await insertData('oracle'); @@ -136,6 +142,7 @@ describe('offset', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -169,6 +176,7 @@ describe('boolean filter', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -191,6 +199,7 @@ describe('empty array-filter', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -212,6 +221,7 @@ describe('AND empty-array', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -231,6 +241,7 @@ describe('AND one in array', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -250,6 +261,7 @@ describe('boolean true filter', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -271,6 +283,7 @@ describe('any-subFilter filter nested', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -306,6 +319,7 @@ describe('any-subFilter filter nested where', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -342,6 +356,7 @@ describe('getMany hasOne sub filter', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -379,6 +394,7 @@ describe('getMany none sub filter', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -414,6 +430,7 @@ describe('getMany', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -447,6 +464,7 @@ describe('getAll orderBy array', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -481,6 +499,7 @@ describe('getMany with column strategy', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -510,6 +529,7 @@ describe('aggregate', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -555,6 +575,7 @@ describe('aggregate each row', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -653,6 +674,7 @@ describe('getMany with relations', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -730,6 +752,7 @@ describe('getMany with filtered relations', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -804,6 +827,7 @@ describe('getMany composite', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); async function verify(dbName) { @@ -853,6 +877,7 @@ describe('getMany raw filter', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); async function verify(dbName) { @@ -887,6 +912,7 @@ describe('getMany raw filter where', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); async function verify(dbName) { @@ -1050,6 +1076,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -1091,6 +1121,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/insert.test.js b/tests/insert.test.js index 1b14ff0d..b380cbed 100644 --- a/tests/insert.test.js +++ b/tests/insert.test.js @@ -1,5 +1,7 @@ +import rdb from '../src/index.js'; import { describe, test, beforeAll, expect, afterAll } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const express = require('express'); import { json } from 'body-parser'; import cors from 'cors'; @@ -15,9 +17,14 @@ const versionArray = process.version.replace('v', '').split('.'); const major = parseInt(versionArray[0]); const port = 3010; let server; +let d1; +let miniflare; + +rdb.on('query', (query) => { console.dir(query); }); beforeAll(async () => { + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await createMs('mssql'); hostExpress(); @@ -42,6 +49,7 @@ beforeAll(async () => { }); afterAll(async () => { + await miniflare.dispose(); return new Promise((res) => { if (server) server.close(res); @@ -93,6 +101,7 @@ describe('validate', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -132,6 +141,7 @@ describe('validate chained', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -168,6 +178,7 @@ describe('validate JSONSchema', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -205,6 +216,7 @@ describe('validate notNull', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -238,6 +250,7 @@ describe('validate notNullExceptInsert', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -281,6 +294,7 @@ describe('insert autoincremental', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -321,6 +335,7 @@ describe('insert default', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -358,6 +373,7 @@ describe('insert default override', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -394,6 +410,7 @@ describe('insert dbNull', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -445,6 +462,7 @@ describe('insert autoincremental with relations', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -596,6 +614,7 @@ describe('insert autoincremental with relations and strategy', () => { test('mssqlNative', async () => await verify('mssqlNative')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -758,6 +777,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -774,7 +797,7 @@ const connections = { password: 'P@assword123', connectString: 'oracle/XE', privilege: 2 - }, {size: 1} + }, { size: 1 } ) }), @@ -799,6 +822,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/readonly.test.js b/tests/readonly.test.js index 8c306f90..90c9f961 100644 --- a/tests/readonly.test.js +++ b/tests/readonly.test.js @@ -1,5 +1,6 @@ import { describe, test, beforeAll, afterAll, expect } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const map = require('./db2'); import express from 'express'; import cors from 'cors'; @@ -12,14 +13,18 @@ const initSqlite = require('./initSqlite'); const initSap = require('./initSap'); const port = 3009; let server; +let d1; +let miniflare; beforeAll(async () => { + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await insertData('pg'); await insertData('oracle'); await insertData('mssql'); await insertData('mysql'); await insertData('sap'); await insertData('sqlite'); + await insertData('d1'); await insertData('sqlite2'); async function insertData(dbName) { @@ -74,6 +79,11 @@ beforeAll(async () => { } }); +afterAll(async () => { + await miniflare.dispose(); +}); + + describe('readonly everything', () => { const options = { readonly: true }; @@ -90,6 +100,7 @@ describe('readonly everything', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -221,6 +232,7 @@ describe('readonly table', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -274,6 +286,7 @@ describe('readonly column', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -329,6 +342,7 @@ describe('readonly table delete', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -367,6 +381,7 @@ describe('readonly nested table delete', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -404,6 +419,7 @@ describe('readonly on grandChildren table delete', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -442,6 +458,7 @@ describe('readonly nested table delete override', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -475,6 +492,7 @@ describe('readonly column no change', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -521,6 +539,7 @@ describe('readonly nested column', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -564,6 +583,7 @@ describe('readonly nested table', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -602,6 +622,7 @@ describe('readonly table with column override', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -649,6 +670,7 @@ describe('readonly column delete', () => { test('mssql', async () => await verify('mssql')); test('mysql', async () => await verify('mysql')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('sap', async () => await verify('sap')); test('http', async () => await verify('http')); @@ -714,6 +736,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -730,7 +756,7 @@ const connections = { password: 'P@assword123', connectString: 'oracle/XE', privilege: 2 - }, {size: 1} + }, { size: 1 } ) }), @@ -755,6 +781,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/setupD1.js b/tests/setupD1.js new file mode 100644 index 00000000..f555bc23 --- /dev/null +++ b/tests/setupD1.js @@ -0,0 +1,25 @@ +import { Miniflare } from 'miniflare'; + +export default async function setupD1(path) { + const pathSegments = path.split('/'); + const lastSegment = pathSegments[pathSegments.length - 1]; + const fileNameWithoutExtension = lastSegment.split('.')[0]; + let miniflare; + let d1; + const sqliteName = `demo.d1.${fileNameWithoutExtension}.db`; + + miniflare = new Miniflare({ + modules: true, // Enable module mode explicitly for ES module support + script: 'export default { fetch() {} };', // Minimal worker script as a module + d1Databases: { + DB: sqliteName, // D1 binding + }, + envPath: true, // Load environment variables from .env if needed + }); + + await miniflare.getBindings(); + d1 = await miniflare.getD1Database('DB'); + return { miniflare, d1 }; +} + + diff --git a/tests/update.test.js b/tests/update.test.js index 0f7093aa..805c561c 100644 --- a/tests/update.test.js +++ b/tests/update.test.js @@ -1,5 +1,6 @@ import { describe, test, beforeAll, afterAll, expect } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const map = require('./db'); import express from 'express'; import cors from 'cors'; @@ -19,8 +20,11 @@ const date1 = new Date(2022, 0, 11, 9, 24, 47); const date2 = new Date(2021, 0, 11, 12, 22, 45); let server = null; const port = 3002; +let d1; +let miniflare; afterAll(async () => { + await miniflare.dispose(); return new Promise((res) => { if (server) server.close(res); @@ -31,6 +35,7 @@ afterAll(async () => { beforeAll(async () => { + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await insertData('pg'); await insertData('oracle'); await insertData('mssql'); @@ -39,6 +44,7 @@ beforeAll(async () => { await insertData('mysql'); await insertData('sap'); await insertData('sqlite'); + await insertData('d1'); await insertData('sqlite2'); hostExpress(); @@ -113,6 +119,7 @@ describe('update date in array', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -137,6 +144,7 @@ describe('update multiple in array', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -218,6 +226,7 @@ describe('delete row', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -241,6 +250,7 @@ describe('update boolean', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -263,6 +273,7 @@ describe('update date', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -287,6 +298,7 @@ describe('add hasOne', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -365,6 +377,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -381,7 +397,7 @@ const connections = { password: 'P@assword123', connectString: 'oracle/XE', privilege: 2 - }, {size: 1} + }, { size: 1 } ) }), @@ -406,6 +422,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap') diff --git a/tests/updateJSON.test.js b/tests/updateJSON.test.js index 7d0752c2..324ec3d0 100644 --- a/tests/updateJSON.test.js +++ b/tests/updateJSON.test.js @@ -1,5 +1,6 @@ import { describe, test, beforeAll, afterAll, expect } from 'vitest'; import { fileURLToPath } from 'url'; +import setupD1 from './setupD1'; const map = require('./db'); import express from 'express'; import cors from 'cors'; @@ -18,8 +19,11 @@ const date1 = new Date(2022, 0, 11, 9, 24, 47); const date2 = new Date(2021, 0, 11, 12, 22, 45); let server = null; const port = 3002; +let d1; +let miniflare; async function globalSetup() { + ({ d1, miniflare } = await setupD1(fileURLToPath(import.meta.url))); await insertData('pg'); await insertData('oracle'); await insertData('mssql'); @@ -28,6 +32,7 @@ async function globalSetup() { await insertData('mysql'); await insertData('sap'); await insertData('sqlite'); + await insertData('d1'); await insertData('sqlite2'); hostExpress(); } @@ -90,7 +95,8 @@ function hostExpress() { server = app.listen(port, () => console.log(`Example app listening on port ${port}!`)); } -function globalTeardown() { +async function globalTeardown() { + await miniflare.dispose(); return new Promise((res) => { if (server) server.close(res); @@ -116,6 +122,7 @@ describe('updateChanges', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -176,6 +183,7 @@ describe('replace then return rows', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -246,6 +254,7 @@ describe('replace', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -319,6 +328,7 @@ describe('update with JSON', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -392,6 +402,7 @@ describe('update with JSON then return rows', () => { test('mysql', async () => await verify('mysql')); test('sap', async () => await verify('sap')); test('sqlite', async () => await verify('sqlite')); + test('d1', async () => await verify('d1')); test('http', async () => await verify('http')); async function verify(dbName) { @@ -484,6 +495,10 @@ const connections = { db: map({ db: (con) => con.sqlite(sqliteName, { size: 1 }) }), init: initSqlite }, + d1: { + db: map({ db: (con) => con.d1(d1, { size: 1 }) }), + init: initSqlite + }, sqlite2: { db: map({ db: (con) => con.sqlite(sqliteName2, { size: 1 }) }), init: initSqlite @@ -525,6 +540,8 @@ function getDb(name) { return connections.pg; else if (name === 'sqlite') return connections.sqlite; + else if (name === 'd1') + return connections.d1; else if (name === 'sqlite2') return connections.sqlite2; else if (name === 'sap')