mythix-orm 1.11.7 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,6 @@
3
3
  const { DateTime } = require('luxon');
4
4
  const EventEmitter = require('events');
5
5
  const Nife = require('nife');
6
- const SqlString = require('sqlstring');
7
6
  const { QueryEngine } = require('../query-engine');
8
7
  const Utils = require('../utils');
9
8
  const { Model: ModelBase } = require('../model');
@@ -230,6 +229,8 @@ class ConnectionBase extends EventEmitter {
230
229
 
231
230
  if (!options.queryGenerator)
232
231
  options.queryGenerator = this.createQueryGenerator(options);
232
+ else
233
+ options.queryGenerator.setConnection(this);
233
234
 
234
235
  Object.defineProperties(this, {
235
236
  'dialect': {
@@ -751,7 +752,7 @@ class ConnectionBase extends EventEmitter {
751
752
  /// it was created.
752
753
  ///
753
754
  /// Arguments:
754
- /// Model: Array<class <see>Model</see>> | { [key: string]: class <see>Model</see> }
755
+ /// models: Array<class <see>Model</see>> | { [key: string]: class <see>Model</see> }
755
756
  /// The model classes to register with this connection. If no models are bound, then
756
757
  /// they will simply exist in the model pool for this connection. If bound, then
757
758
  /// this connection will bind itself to every model being registered.
@@ -761,13 +762,14 @@ class ConnectionBase extends EventEmitter {
761
762
  /// provided to the connection when it was created. If you specify either of these
762
763
  /// options they simply override the connection's default.
763
764
  ///
764
- /// Return: class <see>Model</see>
765
+ /// Return: { [key: string]: class <see>Model</see> }
765
766
  /// The registered model classes, **which may have changed during registration**.
766
767
  /// It is not uncommon for the connection driver itself to modify the model
767
768
  /// classes, or to return a new model classes that inherit from your model classes.
768
769
  /// The classes that are returned should be the classes that you use for this connection,
769
770
  /// and will be the same classes returned by a call to <see>Connection.getModel</see>,
770
- /// or <see>Connection.getModels</see>.
771
+ /// or <see>Connection.getModels</see>. An object is returned, where each key is
772
+ /// a model name, and each value is a model class.
771
773
  registerModels(models, options) {
772
774
  if (!models)
773
775
  return;
@@ -1054,7 +1056,14 @@ class ConnectionBase extends EventEmitter {
1054
1056
  /// Return the <see>QueryGenerator</see> for this connection,
1055
1057
  /// or return `null` if none is defined for this connection.
1056
1058
  getQueryGenerator() {
1057
- return this.queryGenerator;
1059
+ let queryGenerator = this.queryGenerator;
1060
+ if (queryGenerator) {
1061
+ let connection = queryGenerator.getConnection();
1062
+ if (!connection)
1063
+ queryGenerator.setConnection(this);
1064
+ }
1065
+
1066
+ return queryGenerator;
1058
1067
  }
1059
1068
 
1060
1069
  /// Set the <see>QueryGenerator</see> instance for this
@@ -1078,15 +1087,10 @@ class ConnectionBase extends EventEmitter {
1078
1087
  }
1079
1088
 
1080
1089
  /// The low-level DB interface for escaping a
1081
- /// value. By default this function uses the
1082
- /// [sqlstring](https://www.npmjs.com/package/sqlstring)
1083
- /// module to escape values. However, the `escape`
1084
- /// method for whatever database the connection is
1085
- /// using should be used instead of this. This is
1086
- /// a "default implementation" that is meant as a
1087
- /// fallback when a connection doesn't provide its
1088
- /// own, but each connection should provide its own
1089
- /// when it is able.
1090
+ /// value. By default this function simply returns
1091
+ /// the value it is provided. It is up to the
1092
+ /// database driver itself to provide a proper
1093
+ /// implementation of this method.
1090
1094
  ///
1091
1095
  /// Note:
1092
1096
  /// This method escapes "values" that are given in
@@ -1095,8 +1099,7 @@ class ConnectionBase extends EventEmitter {
1095
1099
  /// instead.
1096
1100
  ///
1097
1101
  /// Return: string
1098
- /// The value provided, escaped for the specific
1099
- /// underlying database driver.
1102
+ /// The value provided.
1100
1103
  ///
1101
1104
  /// Arguments:
1102
1105
  /// value: any
@@ -1104,10 +1107,7 @@ class ConnectionBase extends EventEmitter {
1104
1107
  /// a string, or anything else that can be provided to your
1105
1108
  /// specific database.
1106
1109
  _escape(value) {
1107
- if (Nife.instanceOf(value, 'string'))
1108
- return `'${value.replace(/'/g, '\'\'')}'`;
1109
-
1110
- return SqlString.escape(value);
1110
+ return value;
1111
1111
  }
1112
1112
 
1113
1113
  /// Unlike <see>ConnectionBase._escape</see> --which is
@@ -1198,22 +1198,18 @@ class ConnectionBase extends EventEmitter {
1198
1198
  /// provides as a "fallback" to database drivers that don't
1199
1199
  /// supply their own.
1200
1200
  ///
1201
- /// It works by first stripping all quotes (single `'`, double `"`, and backtick `` ` ``)
1202
- /// from the provided `value`. After this, it will split on the period (dot) character
1203
- /// `.`, and then will map each resulting part through [sqlstring](https://www.npmjs.com/package/sqlstring)
1204
- /// `escapeId` method, finally re-joining the parts with a period `.` character.
1205
- ///
1206
- /// The extra processing is to allow for already escaped identifiers to not be double-escaped.
1201
+ /// This method simply returns the value provided. It is
1202
+ /// expected that each database driver will properly overload
1203
+ /// this method to provide the correct escaping for identifiers.
1207
1204
  ///
1208
1205
  /// Return: string
1209
- /// The provided identifier, escaped for the underlying database.
1206
+ /// The provided identifier.
1210
1207
  ///
1211
1208
  /// Arguments:
1212
1209
  /// value: string
1213
1210
  /// The identifier to escape.
1214
1211
  _escapeID(value) {
1215
- let parts = value.replace(/['"`]/g, '').split(/\.+/g);
1216
- return parts.map((part) => SqlString.escapeId(part).replace(/^`/, '"').replace(/`$/, '"')).join('.');
1212
+ return value;
1217
1213
  }
1218
1214
 
1219
1215
  /// This method is very similar to <see>ConnectionBase._escapeID</see>,
@@ -2789,7 +2785,7 @@ class ConnectionBase extends EventEmitter {
2789
2785
  /// Create a table/bucket using the provided model class.
2790
2786
  ///
2791
2787
  /// The provided `options` are database specific,
2792
- /// but might contain things like `ifExists`, for
2788
+ /// but might contain things like `ifNotExists`, for
2793
2789
  /// example.
2794
2790
  ///
2795
2791
  /// Return: any
@@ -2,8 +2,49 @@
2
2
 
3
3
  /// The base query generator class.
4
4
  ///
5
+ /// A "query generator" is an interface that will take
6
+ /// parameters (usually a <see>QueryEngine</see> or a
7
+ /// <see>Model</see>) and generate database query statements
8
+ /// from the input. For SQL type databases this would mean
9
+ /// generating `SELECT`, `INSERT`, `UPDATE`, and `DELETE`
10
+ /// statements, as well as generators for creating and altering
11
+ /// tables, among other things.
12
+ ///
13
+ /// The methods of this class are generally many, with the
14
+ /// design pattern for most generators being that nearly
15
+ /// all methods are split apart and added to the class, allowing
16
+ /// by deliberate design much finer control when using overloaded
17
+ /// methods, to modify or replace any parts of the generator.
18
+ ///
19
+ /// Any connection can be provided a custom query generator
20
+ /// interface via the `queryGenerator` option that can be
21
+ /// used when instantiating a connection. Most connections
22
+ /// will supply their own by default.
23
+ ///
24
+ /// A connection should always be bound to a query generator
25
+ /// instance. If not when first created, then at least when
26
+ /// provided to a connection. The connection is a required
27
+ /// part of the generator interface, and will do things, such
28
+ /// as for example, escaping values and ids using the connection
29
+ /// itself. The connection may be used for other operations as
30
+ /// well.
31
+ ///
32
+ /// Note:
33
+ /// Some database drivers may not have a generator at all. Though
34
+ /// database drivers commonly do have a database statement generator,
35
+ /// a connection isn't required to have one.
36
+ ///
5
37
  /// Alias: QueryGenerator
6
38
  class QueryGeneratorBase {
39
+ /// Construct a new query generator
40
+ ///
41
+ /// Arguments:
42
+ /// connection?: <see>Connection</see>
43
+ /// The connection that this interface is for. Sometimes the connection
44
+ /// isn't yet available when creating the query generator, so this argument
45
+ /// is optional. When provided to a <see>Connection</see>, the connection
46
+ /// will call <see>QueryGenerator.setConnection</see> with itself to set
47
+ /// the connection for the query generator.
7
48
  constructor(connection) {
8
49
  Object.defineProperties(this, {
9
50
  'connection': {
@@ -15,57 +56,289 @@ class QueryGeneratorBase {
15
56
  });
16
57
  }
17
58
 
59
+ /// Get the <see>Connection</see> bound to this
60
+ /// query generator.
61
+ ///
62
+ /// Return: <see>Connection</see>
63
+ /// The connection bound to this query generator. A connection
64
+ /// should always be bound before any generating methods of
65
+ /// the class are called.
66
+ getConnection() {
67
+ return this.connection;
68
+ }
69
+
70
+ /// Set the <see>Connection</see> bound to this
71
+ /// query generator.
72
+ ///
73
+ /// Arguments:
74
+ /// connection: <see>Connection</see>
75
+ /// The connection to bind to this query generator.
76
+ ///
77
+ /// Return: <see>QueryGenerator</see>
78
+ /// Return `this` to allow for chaining.
79
+ setConnection(connection) {
80
+ this.connection = connection;
81
+ return this;
82
+ }
83
+
84
+ /// This call proxies to <see>Connection.stackAssign</see>.
85
+ /// Refer to the documentation of that method for more information.
86
+ ///
87
+ /// See: Connection.stackAssign
18
88
  stackAssign(obj, ...args) {
19
89
  return this.connection.stackAssign(obj, ...args);
20
90
  }
21
91
 
92
+ /// This call proxies to <see>Connection.escape</see>.
93
+ /// Refer to the documentation of that method for more information.
94
+ ///
95
+ /// See: Connection.escape
22
96
  escape(...args) {
23
97
  return this.connection.escape(...args);
24
98
  }
25
99
 
100
+ /// This call proxies to <see>Connection.escapeID</see>.
101
+ /// Refer to the documentation of that method for more information.
102
+ ///
103
+ /// See: Connection.escapeID
26
104
  escapeID(...args) {
27
105
  return this.connection.escapeID(...args);
28
106
  }
29
107
 
108
+ /// Convert an <see>AverageLiteral</see> into a
109
+ /// string representation for the underlying database.
110
+ ///
111
+ /// It is expected that each database driver will implement
112
+ /// this method. By default it will simply throw an
113
+ /// "unsupported" error.
114
+ ///
115
+ /// Refer to the specific documentation for your database
116
+ /// driver for more information.
117
+ ///
118
+ /// Arguments:
119
+ /// literal: <see>AverageLiteral</see>
120
+ /// The literal to stringify for the underlying database.
121
+ /// options?: object
122
+ /// Options for the stringify process. These are often database
123
+ /// driver specific. However, one common option is the `as`
124
+ /// option, which will allow you to give your literal an alias.
125
+ ///
126
+ /// Return: string
127
+ /// The literal, converted into the proper string for the underlying
128
+ /// database.
30
129
  // eslint-disable-next-line no-unused-vars
31
130
  _averageLiteralToString(literal, options) {
32
131
  throw new Error(`${this.constructor.name}::_averageLiteralToString: This operation is not supported for this connection type.`);
33
132
  }
34
133
 
134
+ /// Convert an <see>CountLiteral</see> into a
135
+ /// string representation for the underlying database.
136
+ ///
137
+ /// It is expected that each database driver will implement
138
+ /// this method. By default it will simply throw an
139
+ /// "unsupported" error.
140
+ ///
141
+ /// Refer to the specific documentation for your database
142
+ /// driver for more information.
143
+ ///
144
+ /// Arguments:
145
+ /// literal: <see>CountLiteral</see>
146
+ /// The literal to stringify for the underlying database.
147
+ /// options?: object
148
+ /// Options for the stringify process. These are often database
149
+ /// driver specific. However, one common option is the `as`
150
+ /// option, which will allow you to give your literal an alias.
151
+ ///
152
+ /// Return: string
153
+ /// The literal, converted into the proper string for the underlying
154
+ /// database.
35
155
  // eslint-disable-next-line no-unused-vars
36
156
  _countLiteralToString(literal, options) {
37
157
  throw new Error(`${this.constructor.name}::_countLiteralToString: This operation is not supported for this connection type.`);
38
158
  }
39
159
 
160
+ /// Convert an <see>DistinctLiteral</see> into a
161
+ /// string representation for the underlying database.
162
+ ///
163
+ /// It is expected that each database driver will implement
164
+ /// this method. By default it will simply throw an
165
+ /// "unsupported" error.
166
+ ///
167
+ /// Refer to the specific documentation for your database
168
+ /// driver for more information.
169
+ ///
170
+ /// Arguments:
171
+ /// literal: <see>DistinctLiteral</see>
172
+ /// The literal to stringify for the underlying database.
173
+ /// options?: object
174
+ /// Options for the stringify process. These are often database
175
+ /// driver specific. However, one common option is the `as`
176
+ /// option, which will allow you to give your literal an alias.
177
+ ///
178
+ /// Return: string
179
+ /// The literal, converted into the proper string for the underlying
180
+ /// database.
40
181
  // eslint-disable-next-line no-unused-vars
41
182
  _distinctLiteralToString(literal, options) {
42
183
  throw new Error(`${this.constructor.name}::_distinctLiteralToString: This operation is not supported for this connection type.`);
43
184
  }
44
185
 
186
+ /// Convert an <see>FieldLiteral</see> into a
187
+ /// string representation for the underlying database.
188
+ ///
189
+ /// It is expected that each database driver will implement
190
+ /// this method. By default it will simply throw an
191
+ /// "unsupported" error.
192
+ ///
193
+ /// Refer to the specific documentation for your database
194
+ /// driver for more information.
195
+ ///
196
+ /// Arguments:
197
+ /// literal: <see>FieldLiteral</see>
198
+ /// The literal to stringify for the underlying database.
199
+ /// options?: object
200
+ /// Options for the stringify process. These are often database
201
+ /// driver specific. However, one common option is the `as`
202
+ /// option, which will allow you to give your literal an alias.
203
+ ///
204
+ /// Return: string
205
+ /// The literal, converted into the proper string for the underlying
206
+ /// database.
45
207
  // eslint-disable-next-line no-unused-vars
46
208
  _fieldLiteralToString(literal, options) {
47
209
  throw new Error(`${this.constructor.name}::_fieldLiteralToString: This operation is not supported for this connection type.`);
48
210
  }
49
211
 
212
+ /// Convert an <see>MaxLiteral</see> into a
213
+ /// string representation for the underlying database.
214
+ ///
215
+ /// It is expected that each database driver will implement
216
+ /// this method. By default it will simply throw an
217
+ /// "unsupported" error.
218
+ ///
219
+ /// Refer to the specific documentation for your database
220
+ /// driver for more information.
221
+ ///
222
+ /// Arguments:
223
+ /// literal: <see>MaxLiteral</see>
224
+ /// The literal to stringify for the underlying database.
225
+ /// options?: object
226
+ /// Options for the stringify process. These are often database
227
+ /// driver specific. However, one common option is the `as`
228
+ /// option, which will allow you to give your literal an alias.
229
+ ///
230
+ /// Return: string
231
+ /// The literal, converted into the proper string for the underlying
232
+ /// database.
50
233
  // eslint-disable-next-line no-unused-vars
51
234
  _maxLiteralToString(literal, options) {
52
235
  throw new Error(`${this.constructor.name}::_maxLiteralToString: This operation is not supported for this connection type.`);
53
236
  }
54
237
 
238
+ /// Convert an <see>MinLiteral</see> into a
239
+ /// string representation for the underlying database.
240
+ ///
241
+ /// It is expected that each database driver will implement
242
+ /// this method. By default it will simply throw an
243
+ /// "unsupported" error.
244
+ ///
245
+ /// Refer to the specific documentation for your database
246
+ /// driver for more information.
247
+ ///
248
+ /// Arguments:
249
+ /// literal: <see>MinLiteral</see>
250
+ /// The literal to stringify for the underlying database.
251
+ /// options?: object
252
+ /// Options for the stringify process. These are often database
253
+ /// driver specific. However, one common option is the `as`
254
+ /// option, which will allow you to give your literal an alias.
255
+ ///
256
+ /// Return: string
257
+ /// The literal, converted into the proper string for the underlying
258
+ /// database.
55
259
  // eslint-disable-next-line no-unused-vars
56
260
  _minLiteralToString(literal, options) {
57
261
  throw new Error(`${this.constructor.name}::_minLiteralToString: This operation is not supported for this connection type.`);
58
262
  }
59
263
 
264
+ /// Convert an <see>SumLiteral</see> into a
265
+ /// string representation for the underlying database.
266
+ ///
267
+ /// It is expected that each database driver will implement
268
+ /// this method. By default it will simply throw an
269
+ /// "unsupported" error.
270
+ ///
271
+ /// Refer to the specific documentation for your database
272
+ /// driver for more information.
273
+ ///
274
+ /// Arguments:
275
+ /// literal: <see>SumLiteral</see>
276
+ /// The literal to stringify for the underlying database.
277
+ /// options?: object
278
+ /// Options for the stringify process. These are often database
279
+ /// driver specific. However, one common option is the `as`
280
+ /// option, which will allow you to give your literal an alias.
281
+ ///
282
+ /// Return: string
283
+ /// The literal, converted into the proper string for the underlying
284
+ /// database.
60
285
  // eslint-disable-next-line no-unused-vars
61
286
  _sumLiteralToString(literal, options) {
62
287
  throw new Error(`${this.constructor.name}::_sumLiteralToString: This operation is not supported for this connection type.`);
63
288
  }
64
289
 
290
+ /// Take a <see>QueryEngine</see> instance and
291
+ /// convert it into a query. For SQL type databases
292
+ /// this would turn a <see>QueryEngine</see> into a
293
+ /// `SELECT` statement. For other types of databases,
294
+ /// this should return a "fetch" query--or string representation
295
+ /// of such a query--in the database's native query language.
296
+ ///
297
+ /// Arguments:
298
+ /// queryEngine: <see>QueryEngine</see>
299
+ /// The query engine instance to stringify.
300
+ /// options?: object
301
+ /// Connection and operation specific options. These
302
+ /// generally aren't needed, but are provided in case
303
+ /// the underlying connection needs them.
304
+ ///
305
+ /// Return: string
306
+ /// A "fetch" query in the databases native query language,
307
+ /// generated from the provided `queryEngine`.
65
308
  // eslint-disable-next-line no-unused-vars
66
309
  toConnectionString(queryEngine, options) {
67
310
  return '<not supported by connection>';
68
311
  }
312
+
313
+ /// Get the "default value" for the given field
314
+ /// for the underlying database. This is used
315
+ /// primarily for "CREATE TABLE" statements.
316
+ ///
317
+ /// By default, the implementation of this method
318
+ /// is empty. It is expected that each database driver
319
+ /// will implement their own version of this method.
320
+ ///
321
+ /// Arguments:
322
+ /// field: <see>Field</see>
323
+ /// The field instance we are getting a "default value"
324
+ /// from.
325
+ /// fieldName: string
326
+ /// The name of the field that we are getting the "default value"
327
+ /// from. This should always be the same as `field.fieldName`.
328
+ /// options?: object
329
+ /// Options for the operation. These will likely be connection
330
+ /// specific. Please refer to the documentation of your specific
331
+ /// connection for more details.
332
+ ///
333
+ /// Return: any
334
+ /// Though in most cases this method will return a string for
335
+ /// most database drivers in most situations, it may return other
336
+ /// types as well, such as literals, or other raw values.
337
+ /// Please refer to the documentation of your specific
338
+ /// connection for more details.
339
+ // eslint-disable-next-line no-unused-vars
340
+ getFieldDefaultValue(field, fieldName, _options) {
341
+ }
69
342
  }
70
343
 
71
344
  module.exports = QueryGeneratorBase;
@@ -4,27 +4,27 @@
4
4
 
5
5
  'use strict';
6
6
 
7
- const APPLY = Symbol.for('@_apply');
8
- const CALLABLE = Symbol.for('@_callable');
9
- const CONSTRUCT = Symbol.for('@_construct');
10
- const DEFINE_PROPERTY = Symbol.for('@_defineProperty');
11
- const DELETE_PROPERTY = Symbol.for('@_deleteProperty');
12
- const GET = Symbol.for('@_get');
13
- const GET_OWN_PROPERTY_DESCRIPTOR = Symbol.for('@_getOwnPropertyDescriptor');
14
- const GET_PROTOTYPEOF = Symbol.for('@_getPrototypeOf');
15
- const HAS = Symbol.for('@_has');
16
- const IS_EXTENSIBLE = Symbol.for('@_isExtensible');
17
- const MISSING = Symbol.for('@_missing');
18
- const OWN_KEYS = Symbol.for('@_ownKeys');
19
- const PREVENT_EXTENSIONS = Symbol.for('@_preventExtensions');
20
- const SET = Symbol.for('@_set');
21
- const SET_PROTOTYPEOF = Symbol.for('@_setPrototypeOf');
22
- const PROXY = Symbol.for('@__proxy');
23
- const TARGET = Symbol.for('@__target');
24
- const SELF = Symbol.for('@__rootInstance');
25
- const AUTO_CALL_CALLER = Symbol.for('@__autoCallCaller');
26
- const AUTO_CALL_CALLED = Symbol.for('@__autoCallCalled');
27
- const AUTO_CALL = Symbol.for('@__autoCall');
7
+ const APPLY = Symbol.for('@_mythix/orm/ProxyClass/apply');
8
+ const CALLABLE = Symbol.for('@_mythix/orm/ProxyClass/callable');
9
+ const CONSTRUCT = Symbol.for('@_mythix/orm/ProxyClass/construct');
10
+ const DEFINE_PROPERTY = Symbol.for('@_mythix/orm/ProxyClass/defineProperty');
11
+ const DELETE_PROPERTY = Symbol.for('@_mythix/orm/ProxyClass/deleteProperty');
12
+ const GET = Symbol.for('@_mythix/orm/ProxyClass/get');
13
+ const GET_OWN_PROPERTY_DESCRIPTOR = Symbol.for('@_mythix/orm/ProxyClass/getOwnPropertyDescriptor');
14
+ const GET_PROTOTYPEOF = Symbol.for('@_mythix/orm/ProxyClass/getPrototypeOf');
15
+ const HAS = Symbol.for('@_mythix/orm/ProxyClass/has');
16
+ const IS_EXTENSIBLE = Symbol.for('@_mythix/orm/ProxyClass/isExtensible');
17
+ const MISSING = Symbol.for('@_mythix/orm/ProxyClass/missing');
18
+ const OWN_KEYS = Symbol.for('@_mythix/orm/ProxyClass/ownKeys');
19
+ const PREVENT_EXTENSIONS = Symbol.for('@_mythix/orm/ProxyClass/preventExtensions');
20
+ const SET = Symbol.for('@_mythix/orm/ProxyClass/set');
21
+ const SET_PROTOTYPEOF = Symbol.for('@_mythix/orm/ProxyClass/setPrototypeOf');
22
+ const PROXY = Symbol.for('@__mythix/orm/ProxyClass/proxy');
23
+ const TARGET = Symbol.for('@__mythix/orm/ProxyClass/target');
24
+ const SELF = Symbol.for('@__mythix/orm/ProxyClass/rootInstance');
25
+ const AUTO_CALL_CALLER = Symbol.for('@__mythix/orm/ProxyClass/autoCallCaller');
26
+ const AUTO_CALL_CALLED = Symbol.for('@__mythix/orm/ProxyClass/autoCallCalled');
27
+ const AUTO_CALL = Symbol.for('@__mythix/orm/ProxyClass/autoCall');
28
28
 
29
29
  function shouldSkipProxy(prop) {
30
30
  if (prop === 'bind' || prop === 'call' || prop === 'apply')
@@ -39,6 +39,26 @@ function shouldSkipProxy(prop) {
39
39
  return false;
40
40
  }
41
41
 
42
+ /// This is essentially a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
43
+ /// converted into class form. What that means is that instead of defining a
44
+ /// proxy by passing it a "handlers" object to it, this instead *is* the handler
45
+ /// for all classes that inherit from it. Just like a `Proxy`, inheriting from
46
+ /// this class will allow the child-class to intercept property gets and sets,
47
+ /// intercept method calls, property deletion, etc...
48
+ ///
49
+ /// It works by returning `this` inside the `constructor` wrapped in a
50
+ /// `Proxy`. The `Proxy` it creates is then managed by the class instance itself.
51
+ /// For example, during key access, if a key the user is requesting is not found,
52
+ /// the proxy will call the instance method `MISSING` on the class. This allows
53
+ /// the child class to provide a method for `MISSING`, and then respond to key
54
+ /// access for keys that don't actually exist on the instance.
55
+ ///
56
+ /// That is just one example of many. This class provides full `Proxy` support,
57
+ /// and so has methods (or stubs) for every feature available natively to a `Proxy`.
58
+ /// Instance methods are keyed by symbols. This is to try and reduce the chance
59
+ /// of a name collision... keeping this class useful for many scenarios. For example,
60
+ /// the `MISSING` method above is actually `Symbol.for('@_mythix/orm/ProxyClass/missing')`,
61
+ /// that is assigned to the constant <see>ProxyClass.MISSING</see>.
42
62
  class ProxyClass {
43
63
  static APPLY = APPLY;
44
64
  static CALLABLE = CALLABLE;
@@ -175,6 +195,8 @@ class ProxyClass {
175
195
  return proxy;
176
196
  }
177
197
 
198
+ /// Construct the class instance, with
199
+ /// `this` returned wrapped in a `Proxy`.
178
200
  constructor() {
179
201
  Object.defineProperties(this, {
180
202
  [AUTO_CALL_CALLER]: {
@@ -195,6 +217,65 @@ class ProxyClass {
195
217
  return proxy;
196
218
  }
197
219
 
220
+ /// Any method of the instance wrapped in an
221
+ /// `__autoCall` factory will be automatically
222
+ /// called by the engine if not called by the user.
223
+ ///
224
+ /// This works by the `ProxyClass` pushing the auto-call
225
+ /// into a queue when the method key is accessed. If another
226
+ /// key is accessed (any other key), then the `ProxyClass` will
227
+ /// check if the auto-call method has been called yet. If it
228
+ /// hasn't, then the `ProxyClass` will call it, providing no
229
+ /// arguments, and using the return value of the call for the
230
+ /// pending key access. If the auto-call method is simply called,
231
+ /// then the queue is cleared, and the return value simply returned
232
+ /// to the user.
233
+ ///
234
+ /// Example:
235
+ /// class Greeter extends ProxyClass {
236
+ /// greet = this.__autoCall((name) => {
237
+ /// if (arguments.length === 0) {
238
+ /// // An auto-call, or the user didn't
239
+ /// // provide any arguments.
240
+ /// console.log('Hello whoever you are!');
241
+ /// } else {
242
+ /// // Was definitely called by the user
243
+ /// console.log(`Hello ${name}!`);
244
+ /// }
245
+ /// });
246
+ ///
247
+ /// finish() {
248
+ /// // finish operation
249
+ /// }
250
+ /// }
251
+ ///
252
+ /// // Example 1
253
+ /// let greeter = new Greeter();
254
+ /// greeter.greet.finish();
255
+ /// // ^---- Auto call happens here
256
+ /// // output: Hello whoever you are!
257
+ ///
258
+ /// // Example 2
259
+ /// greeter.greet('Wyatt Greenway').finish();
260
+ /// // No auto-call happens... this is a manual call.
261
+ /// // output: Hello Wyatt Greenway!
262
+ ///
263
+ /// Note:
264
+ /// For an auto-call to work, a key access attempt must happen
265
+ /// after the auto-call method is accessed. This is almost always
266
+ /// the case, because in interacting with the object you are almost
267
+ /// guaranteed to access a key again, i.e. `.toString` if converting
268
+ /// to a string, `.toJSON` if converting to JSON, iterator access,
269
+ /// or even debugging the object.
270
+ ///
271
+ /// Arguments:
272
+ /// caller: Function
273
+ /// The method implementation for the class. This method will
274
+ /// be used by the factory to create an auto-call method for
275
+ /// the class.
276
+ ///
277
+ /// Return: Function
278
+ /// The `caller` method provided, wrapped into an auto-call factory method.
198
279
  __autoCall(caller) {
199
280
  this[AUTO_CALL_CALLER] = caller;
200
281
  this[AUTO_CALL_CALLED] = false;
@@ -202,6 +283,60 @@ class ProxyClass {
202
283
  return this;
203
284
  }
204
285
 
286
+ /// This is a factory much like <see>ProxyClass.__autoCall</see>
287
+ /// for creating instance methods. It differs however in that
288
+ /// the method returned by this factory isn't auto-called, but
289
+ /// instead an *optional* call.
290
+ ///
291
+ /// The way it works is that the method provided is returned,
292
+ /// itself wrapped in a `Proxy`. If it is called, then the
293
+ /// `Proxy` will pass the call through to the method, and return
294
+ /// the result. Being a `Proxy`, it passes all key access back
295
+ /// to the original class instance, allowing the method itself
296
+ /// to mimic the class instance. This allows for instance methods
297
+ /// that can *optionally* be called, but if they aren't called,
298
+ /// will act as though you are still interacting with the instance
299
+ /// of the class itself.
300
+ ///
301
+ /// Example:
302
+ /// class Greeter extends ProxyClass {
303
+ /// constructor() {
304
+ /// super();
305
+ ///
306
+ /// this.greetName = undefined;
307
+ /// }
308
+ ///
309
+ /// name = this.__call((name) => {
310
+ /// this.greetName = name;
311
+ /// });
312
+ ///
313
+ /// greet() {
314
+ /// if (this.greetName) {
315
+ /// console.log(`Hello ${this.greetName}!`);
316
+ /// } else {
317
+ /// console.log('Hello whoever you are!');
318
+ /// }
319
+ /// }
320
+ /// }
321
+ ///
322
+ /// // Example 1
323
+ /// let greeter = new Greeter();
324
+ /// greeter.name.greet();
325
+ /// // ^---- optional call here
326
+ /// // output: Hello whoever you are!
327
+ ///
328
+ /// // Example 2
329
+ /// greeter.name('Wyatt Greenway').greet();
330
+ /// // output: Hello Wyatt Greenway!
331
+ ///
332
+ /// Arguments:
333
+ /// caller: Function
334
+ /// The method implementation for the class. This method will
335
+ /// be used by the factory to create an optional call method for
336
+ /// the class.
337
+ ///
338
+ /// Return: Function
339
+ /// The `caller` method provided, wrapped into an optional call factory method.
205
340
  __call(caller) {
206
341
  return ProxyClass.createProxy.call(this, caller.bind(this[PROXY]));
207
342
  }
@@ -1,3 +1,29 @@
1
+ ///! import `var { Utils: { AsyncStore } } = require('mythix-orm');`
2
+ ///!
3
+ ///! AsyncStore utilities provide the only global
4
+ ///! used in mythix-orm. The global used here is an
5
+ ///! [AsyncLocalStorage](https://nodejs.org/docs/latest-v18.x/api/async_context.html#class-asynclocalstorage) instance used to track
6
+ ///! connections (and transactions) through asynchronous
7
+ ///! calls in the engine.
8
+ ///!
9
+ ///! The `node:async_hooks` module is imported inside a
10
+ ///! `try/catch` block, so if your Javascript engine doesn't
11
+ ///! support `AsyncLocalStorage`, this will fail, and silently
12
+ ///! fallback to running the engine with no `AsyncLocalStorage`
13
+ ///! support... which simply means that connection instances need
14
+ ///! to be manually passed around everywhere.
15
+ ///!
16
+ ///! **!WARNING!: Never set the `'connection'` key, or a string key
17
+ ///! that matches one of your model names to this `AsyncLocalStorage` context
18
+ ///! unless you know exactly what you are doing. These keys are reserved
19
+ ///! by Mythix ORM to pass connections and transactions through calls.** Any
20
+ ///! and all other custom keys are available for use, though it would be
21
+ ///! wise for you to prefix your key names, so as to avoid future name collisions
22
+ ///! that might occur due to newer versions of Mythix ORM, or name collisions with
23
+ ///! other 3rd party plugins or code that might set keys on the context as well.
24
+ ///!
25
+ ///! DocScope: AsyncStore
26
+
1
27
  'use strict';
2
28
 
3
29
  let globalAsyncStore = global._mythixGlobalAsyncLocalStore;
@@ -14,10 +40,33 @@ if (!globalAsyncStore) {
14
40
  }
15
41
  }
16
42
 
43
+ /// Fetch the AsyncLocalStorage store.
44
+ /// This calls `.getStore()` on the global
45
+ /// `AsyncLocalStorage` instance.
46
+ ///
47
+ /// Return: any
48
+ /// The value from a `.getStore()` call on the global `AsyncLocalStorage` instance.
49
+ /// This will be `undefined` if no `AsyncLocalStorage` context is in scope.
17
50
  function getContextStore() {
18
51
  return globalAsyncStore.getStore();
19
52
  }
20
53
 
54
+ /// Get a specific value from the global `AsyncLocalStorage` context
55
+ /// by name.
56
+ ///
57
+ /// Arguments:
58
+ /// key: any
59
+ /// The name of the property to return. The `AsyncLocalStorage`
60
+ /// context internally uses a `Map` instance, so the `key` provided
61
+ /// can be of any type.
62
+ /// defaultValue: any
63
+ /// The default value to return if the key specified is not found.
64
+ ///
65
+ /// Return:
66
+ /// Return the property named by `key` if one is found, otherwise
67
+ /// return the `defaultValue` that was provided. If the global `AsyncLocalStorage` context is not
68
+ /// in scope when this is called, then the `defaultValue` will always be
69
+ /// returned.
21
70
  function getContextValue(key, defaultValue) {
22
71
  let store = globalAsyncStore.getStore();
23
72
  while (store) {
@@ -33,19 +82,47 @@ function getContextValue(key, defaultValue) {
33
82
  return defaultValue;
34
83
  }
35
84
 
85
+ /// Set a specific value on the global `AsyncLocalStorage` context
86
+ /// by name. A `Map` instance is used internally, so the `key` can
87
+ /// be of any type.
88
+ ///
89
+ /// Note:
90
+ /// The global `AsyncLocalStorage` context must be in scope for this method to work.
91
+ ///
92
+ /// Arguments:
93
+ /// key: any
94
+ /// The key you wish to set on the global `AsyncLocalStorage` context.
95
+ /// value: any
96
+ /// The value you wish to set on the global `AsyncLocalStorage` context.
97
+ ///
98
+ /// Return: undefined
99
+ /// This method returns nothing.
36
100
  function setContextValue(key, value) {
37
101
  let store = globalAsyncStore.getStore();
38
102
  if (!store || !store.context)
39
103
  return;
40
104
 
41
- return store.context.set(key, value);
105
+ store.context.set(key, value);
42
106
  }
43
107
 
108
+ /// Run an asynchronous method in the global `AsyncLocalStorage` context.
109
+ ///
110
+ /// Running a method this way will provide the method, and all calls within
111
+ /// its scope the global `AsyncLocalStorage` context.
112
+ ///
113
+ /// Arguments:
114
+ /// context: Map | null
115
+ /// The context `Map` to use for the operation.
116
+ /// callback: Function
117
+ /// The asynchronous method to call and provide the context to.
118
+ ///
119
+ /// Return: any
120
+ /// The return value from the callback.
44
121
  function runInContext(context, callback) {
45
122
  return globalAsyncStore.run(
46
123
  {
47
- parent: globalAsyncStore.getStore(),
48
- context,
124
+ parent: globalAsyncStore.getStore(),
125
+ context: context || new Map(),
49
126
  },
50
127
  callback,
51
128
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix-orm",
3
- "version": "1.11.7",
3
+ "version": "1.12.0",
4
4
  "description": "ORM for Mythix framework",
5
5
  "main": "lib/index",
6
6
  "type": "commonjs",
@@ -47,7 +47,6 @@
47
47
  "inflection": "^2.0.0",
48
48
  "luxon": "^3.1.0",
49
49
  "nife": "^1.12.1",
50
- "sqlstring": "^2.3.3",
51
50
  "uuid": "^9.0.0",
52
51
  "xid-js": "^1.0.1"
53
52
  },