UNPKG

8.47 kBJavaScriptView Raw
1const { EventEmitter } = require('events');
2
3const { Migrator } = require('../migrate/Migrator');
4const Seeder = require('../seed/Seeder');
5const FunctionHelper = require('../functionhelper');
6const QueryInterface = require('../query/methods');
7const { merge } = require('lodash');
8const batchInsert = require('./batchInsert');
9
10function makeKnex(client) {
11 // The object we're potentially using to kick off an initial chain.
12 function knex(tableName, options) {
13 return createQueryBuilder(knex.context, tableName, options);
14 }
15
16 redefineProperties(knex, client);
17 return knex;
18}
19
20function initContext(knexFn) {
21 const knexContext = knexFn.context || {};
22 Object.assign(knexContext, {
23 queryBuilder() {
24 return this.client.queryBuilder();
25 },
26
27 raw() {
28 return this.client.raw.apply(this.client, arguments);
29 },
30
31 batchInsert(table, batch, chunkSize = 1000) {
32 return batchInsert(this, table, batch, chunkSize);
33 },
34
35 // Creates a new transaction.
36 // If container is provided, returns a promise for when the transaction is resolved.
37 // If container is not provided, returns a promise with a transaction that is resolved
38 // when transaction is ready to be used.
39 transaction(container, config) {
40 const trx = this.client.transaction(container, config);
41 trx.userParams = this.userParams;
42
43 if (container) {
44 return trx;
45 }
46 // If no container was passed, assume user wants to get a transaction and use it directly
47 else {
48 return trx.initPromise;
49 }
50 },
51
52 transactionProvider(config) {
53 let trx;
54 return () => {
55 if (!trx) {
56 trx = this.transaction(undefined, config);
57 }
58 return trx;
59 };
60 },
61
62 // Typically never needed, initializes the pool for a knex client.
63 initialize(config) {
64 return this.client.initializePool(config);
65 },
66
67 // Convenience method for tearing down the pool.
68 destroy(callback) {
69 return this.client.destroy(callback);
70 },
71
72 ref(ref) {
73 return this.client.ref(ref);
74 },
75
76 // Do not document this as public API until naming and API is improved for general consumption
77 // This method exists to disable processing of internal queries in migrations
78 disableProcessing() {
79 if (this.userParams.isProcessingDisabled) {
80 return;
81 }
82 this.userParams.wrapIdentifier = this.client.config.wrapIdentifier;
83 this.userParams.postProcessResponse = this.client.config.postProcessResponse;
84 this.client.config.wrapIdentifier = null;
85 this.client.config.postProcessResponse = null;
86 this.userParams.isProcessingDisabled = true;
87 },
88
89 // Do not document this as public API until naming and API is improved for general consumption
90 // This method exists to enable execution of non-internal queries with consistent identifier naming in migrations
91 enableProcessing() {
92 if (!this.userParams.isProcessingDisabled) {
93 return;
94 }
95 this.client.config.wrapIdentifier = this.userParams.wrapIdentifier;
96 this.client.config.postProcessResponse = this.userParams.postProcessResponse;
97 this.userParams.isProcessingDisabled = false;
98 },
99
100 withUserParams(params) {
101 const knexClone = shallowCloneFunction(knexFn); // We need to include getters in our clone
102 if (this.client) {
103 knexClone.client = Object.create(this.client.constructor.prototype); // Clone client to avoid leaking listeners that are set on it
104 merge(knexClone.client, this.client);
105 knexClone.client.config = Object.assign({}, this.client.config); // Clone client config to make sure they can be modified independently
106 }
107
108 redefineProperties(knexClone, knexClone.client);
109 _copyEventListeners('query', knexFn, knexClone);
110 _copyEventListeners('query-error', knexFn, knexClone);
111 _copyEventListeners('query-response', knexFn, knexClone);
112 _copyEventListeners('start', knexFn, knexClone);
113 knexClone.userParams = params;
114 return knexClone;
115 },
116 });
117
118 if (!knexFn.context) {
119 knexFn.context = knexContext;
120 }
121}
122
123function _copyEventListeners(eventName, sourceKnex, targetKnex) {
124 const listeners = sourceKnex.listeners(eventName);
125 listeners.forEach((listener) => {
126 targetKnex.on(eventName, listener);
127 });
128}
129
130function redefineProperties(knex, client) {
131 // Allow chaining methods from the root object, before
132 // any other information is specified.
133 QueryInterface.forEach(function(method) {
134 knex[method] = function() {
135 const builder = knex.queryBuilder();
136 return builder[method].apply(builder, arguments);
137 };
138 });
139
140 Object.defineProperties(knex, {
141 context: {
142 get() {
143 return knex._context;
144 },
145 set(context) {
146 knex._context = context;
147
148 // Redefine public API for knex instance that would be proxying methods from correct context
149 knex.raw = context.raw;
150 knex.batchInsert = context.batchInsert;
151 knex.transaction = context.transaction;
152 knex.transactionProvider = context.transactionProvider;
153 knex.initialize = context.initialize;
154 knex.destroy = context.destroy;
155 knex.ref = context.ref;
156 knex.withUserParams = context.withUserParams;
157 knex.queryBuilder = context.queryBuilder;
158 knex.disableProcessing = context.disableProcessing;
159 knex.enableProcessing = context.enableProcessing;
160 },
161 configurable: true,
162 },
163
164 client: {
165 get() {
166 return knex.context.client;
167 },
168 set(client) {
169 knex.context.client = client;
170 },
171 configurable: true,
172 },
173
174 userParams: {
175 get() {
176 return knex.context.userParams;
177 },
178 set(userParams) {
179 knex.context.userParams = userParams;
180 },
181 configurable: true,
182 },
183
184 schema: {
185 get() {
186 return knex.client.schemaBuilder();
187 },
188 configurable: true,
189 },
190
191 migrate: {
192 get() {
193 return new Migrator(knex);
194 },
195 configurable: true,
196 },
197
198 seed: {
199 get() {
200 return new Seeder(knex);
201 },
202 configurable: true,
203 },
204
205 fn: {
206 get() {
207 return new FunctionHelper(knex.client);
208 },
209 configurable: true,
210 },
211 });
212
213 initContext(knex);
214 knex.client = client;
215 knex.client.makeKnex = makeKnex;
216 knex.userParams = {};
217
218 // Hook up the "knex" object as an EventEmitter.
219 const ee = new EventEmitter();
220 for (const key in ee) {
221 knex[key] = ee[key];
222 }
223
224 // Unfortunately, something seems to be broken in Node 6 and removing events from a clone also mutates original Knex,
225 // which is highly undesirable
226 if (knex._internalListeners) {
227 knex._internalListeners.forEach(({ eventName, listener }) => {
228 knex.client.removeListener(eventName, listener); // Remove duplicates for copies
229 });
230 }
231 knex._internalListeners = [];
232
233 // Passthrough all "start" and "query" events to the knex object.
234 _addInternalListener(knex, 'start', (obj) => {
235 knex.emit('start', obj);
236 });
237 _addInternalListener(knex, 'query', (obj) => {
238 knex.emit('query', obj);
239 });
240 _addInternalListener(knex, 'query-error', (err, obj) => {
241 knex.emit('query-error', err, obj);
242 });
243 _addInternalListener(knex, 'query-response', (response, obj, builder) => {
244 knex.emit('query-response', response, obj, builder);
245 });
246}
247
248function _addInternalListener(knex, eventName, listener) {
249 knex.client.on(eventName, listener);
250 knex._internalListeners.push({
251 eventName,
252 listener,
253 });
254}
255
256function createQueryBuilder(knexContext, tableName, options) {
257 const qb = knexContext.queryBuilder();
258 if (!tableName)
259 knexContext.client.logger.warn(
260 'calling knex without a tableName is deprecated. Use knex.queryBuilder() instead.'
261 );
262 return tableName ? qb.table(tableName, options) : qb;
263}
264
265function shallowCloneFunction(originalFunction) {
266 const fnContext = Object.create(
267 Object.getPrototypeOf(originalFunction),
268 Object.getOwnPropertyDescriptors(originalFunction)
269 );
270
271 const knexContext = {};
272 const knexFnWrapper = (tableName, options) => {
273 return createQueryBuilder(knexContext, tableName, options);
274 };
275
276 const clonedFunction = knexFnWrapper.bind(fnContext);
277 Object.assign(clonedFunction, originalFunction);
278 clonedFunction._context = knexContext;
279 return clonedFunction;
280}
281
282module.exports = makeKnex;