alchemymvc 1.3.3 → 1.3.5

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.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2018 Jelle De Loecker <jelle@develry.be>
1
+ Copyright (c) 2013-2023 Jelle De Loecker <jelle@elevenways.be>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <h1 align="center">
2
- <img src="https://protoblast.develry.be/media/static/alchemy-small.png" width=30 alt="Alchemy logo"/>
2
+ <img src="https://protoblast.elevenways.be/media/static/alchemy-small.png" width=30 alt="Alchemy logo"/>
3
3
  <b>Alchemy</b>
4
4
  </h1>
5
5
  <div align="center">
@@ -8,24 +8,14 @@
8
8
  <img src="https://travis-ci.org/11ways/alchemy.svg?branch=master" alt="Mac/Linux Build Status" />
9
9
  </a>
10
10
 
11
- <!-- CI - AppVeyor -->
12
- <a href="https://ci.appveyor.com/project/skerit/alchemy">
13
- <img src="https://img.shields.io/appveyor/ci/skerit/alchemy/master.svg?label=Windows" alt="Windows Build status" />
14
- </a>
15
-
16
11
  <!-- Coverage - Codecov -->
17
12
  <a href="https://codecov.io/gh/11ways/alchemy">
18
13
  <img src="https://img.shields.io/codecov/c/github/11ways/alchemy/master.svg" alt="Codecov Coverage report" />
19
14
  </a>
20
15
 
21
16
  <!-- DM - Snyk -->
22
- <a href="https://snyk.io/test/github/skerit/alchemy?targetFile=package.json">
23
- <img src="https://snyk.io/test/github/skerit/alchemy/badge.svg?targetFile=package.json" alt="Known Vulnerabilities" />
24
- </a>
25
-
26
- <!-- DM - David -->
27
- <a href="https://david-dm.org/skerit/alchemy">
28
- <img src="https://david-dm.org/skerit/alchemy/status.svg" alt="Dependency Status" />
17
+ <a href="https://snyk.io/test/github/11ways/alchemy?targetFile=package.json">
18
+ <img src="https://snyk.io/test/github/11ways/alchemy/badge.svg?targetFile=package.json" alt="Known Vulnerabilities" />
29
19
  </a>
30
20
  </div>
31
21
 
@@ -69,55 +59,8 @@ Just installing the npm package can be done like this:
69
59
 
70
60
  # Quick start guide
71
61
 
72
- Alchemy makes heavy use of the **Protoblast** utility library, which extends the native objects with many new methods.
73
- Especially the `Function.inherits()` method is interesting, as that is the basis for the Alchemy class system.
74
- You can find extensive documentation on the library here: [https://protoblast.develry.be](https://protoblast.develry.be)
75
-
76
- ## File structure
77
-
78
- All of your project code should go into an `app` directory.
79
- That directory can contain many subdirectories. Most of those files are auto-required on boot.
80
-
81
- The directories (and some of their basic files) are:
82
-
83
- * **assets**:
84
- * **images**: Static images
85
- * **scripts**: Client-side only javascript files. Accessible through `/scripts/` url.
86
- * **stylesheets**: SCSS and LESS stylesheet files. Accessible through `/stylesheets/` url.
87
- * **config**:
88
- * **bootstrap.js**: Place where all plugins should be loaded
89
- * **default.js**: Default configuration
90
- * **local.js**: Configuration file that overrides all others. Is required for the `environment` definition. Should not be checked into git.
91
- * **prefixes.js**: Place to define which prefixes (locales/languages) are used on the site
92
- * **routes.js**: Routes should be defined here
93
- * **controller**:
94
- * **app_controller.js**: Basic controller instance your other controllers should inherit from
95
- * **element**:
96
- * **app_element.js**: Basic custom element your other custom elements should inherit from.
97
- * **helper**:
98
- * **app_helper.js**: Basic helper your other helpers should inherit from.
99
- * **lib**:
100
- * Put classes here that are only needed on the server.
101
- * **model**:
102
- * **app_model.js**: Basic model your other models should inherit from.
103
- * **public**:
104
- * Files put in here will be publicly available under the `/public/` url.
105
- * **root**:
106
- * Files put in here will be publicly available under the root url (So `/`)
107
- * **view**:
108
- * Template files go in here, preferably with a `.hwk` extension, though regular `.ejs` files also work.
109
-
110
- ## Code convention
111
-
112
- ### Naming:
113
-
114
- * Class names are written using upper camel case: **MyClassName**
115
- * Method names are written using lower camel case: **myMethodName**
116
- * Property names and variables are written using snake case: **my_property_name**
117
-
118
- ### Whitespaces
119
-
120
- * Tabs are used for indentations, spaces are used for positioning.
62
+ You can find all the documentation on [https://alchemy.elevenways.be](https://alchemy.elevenways.be)
63
+
121
64
 
122
65
  ## Base class
123
66
 
@@ -25,6 +25,8 @@ body {
25
25
  .row {
26
26
  display: flex;
27
27
  margin-bottom: 2rem;
28
+ flex-flow: wrap;
29
+ gap: 2rem;
28
30
  }
29
31
 
30
32
  .col {
@@ -32,10 +34,6 @@ body {
32
34
  background: #E8F3F8;
33
35
  }
34
36
 
35
- .col:not(:last-child) {
36
- margin-right: 2rem;
37
- }
38
-
39
37
  .header-image {
40
38
  box-shadow: #000 1px 1px 3px;
41
39
  background: #0B486B;
@@ -56,6 +54,17 @@ body {
56
54
  }
57
55
  }
58
56
 
57
+ tr:nth-child(even) {
58
+ td {
59
+ background-color: rgba(100, 100, 100, 0.1);
60
+ }
61
+ }
62
+
63
+ td {
64
+ vertical-align: top;
65
+ padding: 1rem 0.2rem;
66
+ }
67
+
59
68
  .package-description {
60
69
  max-width: 350px;
61
70
  }
@@ -72,6 +81,10 @@ body {
72
81
  margin-top: 0;
73
82
  }
74
83
  }
84
+
85
+ @media screen and (max-width: 1500px) {
86
+ padding: 2rem 1rem;
87
+ }
75
88
  }
76
89
 
77
90
  .engine {
@@ -129,13 +142,18 @@ li.failed::before {
129
142
  }
130
143
 
131
144
  .settings {
132
- max-width: 50%;
133
145
 
134
146
  th {
135
147
  text-align: left;
136
148
  border-bottom: 1px solid black;
137
149
  }
138
150
 
151
+ table {
152
+ width: 100%;
153
+ }
154
+ }
155
+
156
+ .table-wrap {
139
157
  table {
140
158
  width: 100%;
141
159
  }
@@ -151,7 +151,7 @@ HttpConduit.setMethod(async function initHttp(req, res, router) {
151
151
  /**
152
152
  * Get the original url path
153
153
  *
154
- * @author Jelle De Loecker <jelle@kipdola.be>
154
+ * @author Jelle De Loecker <jelle@elevenways.be>
155
155
  * @since 1.1.0
156
156
  * @version 1.1.0
157
157
  *
@@ -166,7 +166,7 @@ HttpConduit.setProperty(function original_path() {
166
166
  /**
167
167
  * Get the original url pathname
168
168
  *
169
- * @author Jelle De Loecker <jelle@kipdola.be>
169
+ * @author Jelle De Loecker <jelle@elevenways.be>
170
170
  * @since 1.1.0
171
171
  * @version 1.1.0
172
172
  *
@@ -17,7 +17,7 @@ var Info = Function.inherits('Alchemy.Controller.App', function AlchemyInfo(cond
17
17
  *
18
18
  * @author Jelle De Loecker <jelle@elevenways.be>
19
19
  * @since 0.2.0
20
- * @version 0.2.0
20
+ * @version 1.3.5
21
21
  */
22
22
  Info.setMethod(function setInfoVariables() {
23
23
 
@@ -30,7 +30,7 @@ Info.setMethod(function setInfoVariables() {
30
30
  Object.each(Datasource.get(), function eachDatasource(datasource, key) {
31
31
  data_sources[key] = {
32
32
  type: datasource.constructor.name,
33
- error: datasource.connectionError
33
+ error: datasource.connection_error
34
34
  };
35
35
  });
36
36
 
@@ -311,16 +311,16 @@ Client.setMethod(function createStream() {
311
311
  /**
312
312
  * Make the actual connection
313
313
  *
314
- * @author Jelle De Loecker <jelle@develry.be>
314
+ * @author Jelle De Loecker <jelle@elevenways.be>
315
315
  * @since 0.2.0
316
- * @version 1.0.5
316
+ * @version 1.3.5
317
317
  *
318
318
  * @param {Function} callback
319
319
  */
320
320
  Client.setMethod(function connect(address, data, callback) {
321
321
 
322
- var that = this,
323
- ioServer,
322
+ let that = this,
323
+ io_client,
324
324
  serverstream,
325
325
  config = {},
326
326
  server;
@@ -339,9 +339,9 @@ Client.setMethod(function connect(address, data, callback) {
339
339
  }
340
340
 
341
341
  if (typeof io != 'undefined') {
342
- ioServer = io;
342
+ io_client = io;
343
343
  } else if (typeof alchemy != 'undefined') {
344
- ioServer = alchemy.use('socket.io-client');
344
+ io_client = alchemy.use('socket.io-client');
345
345
  } else {
346
346
  return callback(new Error('Could not find socket.io client library'));
347
347
  }
@@ -362,11 +362,19 @@ Client.setMethod(function connect(address, data, callback) {
362
362
  config.reconnection = false;
363
363
  }
364
364
 
365
+ if (Blast.isNode) {
366
+ let msgpack_parser = alchemy.use('socket.io-msgpack-parser');
367
+
368
+ if (msgpack_parser) {
369
+ config.parser = msgpack_parser;
370
+ }
371
+ }
372
+
365
373
  // Create the connection to the server
366
374
  if (address) {
367
- server = ioServer.connect(address, config);
375
+ server = io_client(address, config);
368
376
  } else {
369
- server = ioServer.connect(config);
377
+ server = io_client(config);
370
378
  }
371
379
 
372
380
  if (Blast.isNode) {
@@ -15,7 +15,7 @@ const Paginate = Function.inherits('Alchemy.Client.Component', 'Paginate');
15
15
  *
16
16
  * @author Jelle De Loecker <jelle@elevenways.be>
17
17
  * @since 0.0.1
18
- * @version 1.2.5
18
+ * @version 1.3.4
19
19
  *
20
20
  * @param {Model} model
21
21
  * @param {Criteria} criteria
@@ -26,13 +26,13 @@ Paginate.setMethod(function find(model, criteria) {
26
26
 
27
27
  const conduit = this.controller.conduit;
28
28
 
29
- criteria = Blast.Classes.Alchemy.Criteria.Criteria.cast(criteria);
30
-
31
29
  // Get the model if a name has been given
32
30
  if (typeof model == 'string') {
33
31
  model = this.getModel(model);
34
32
  }
35
33
 
34
+ criteria = Blast.Classes.Alchemy.Criteria.Criteria.cast(criteria, model);
35
+
36
36
  let page_size = criteria.options.page_size || 10,
37
37
  skipless = criteria.options.skipless,
38
38
  last_id,
@@ -3,15 +3,20 @@ const AUGMENTED = Symbol('AUGMENTED');
3
3
  /**
4
4
  * The Criteria class
5
5
  *
6
- * @author Jelle De Loecker <jelle@develry.be>
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
7
  * @since 1.1.0
8
- * @version 1.1.0
8
+ * @version 1.3.4
9
+ *
10
+ * @param {Model} model
9
11
  */
10
- var Criteria = Function.inherits('Alchemy.Base', 'Alchemy.Criteria', function Criteria() {
12
+ var Criteria = Function.inherits('Alchemy.Base', 'Alchemy.Criteria', function Criteria(model) {
11
13
 
12
14
  // The current active group
13
15
  this.group = null;
14
16
 
17
+ // The model
18
+ this.model = model;
19
+
15
20
  // All the created expressions
16
21
  this.all_expressions = [];
17
22
 
@@ -58,19 +63,20 @@ Criteria.setStatic(function isCriteria(instance) {
58
63
  *
59
64
  * @author Jelle De Loecker <jelle@elevenways.be>
60
65
  * @since 1.2.5
61
- * @version 1.2.5
66
+ * @version 1.3.4
62
67
  *
63
- * @param {Object} obj
68
+ * @param {Object} obj The thing that should be a criteria
69
+ * @param {Model} model The model that it probably belongs to
64
70
  *
65
71
  * @return {Criteria}
66
72
  */
67
- Criteria.setStatic(function cast(obj) {
73
+ Criteria.setStatic(function cast(obj, model) {
68
74
 
69
75
  if (Criteria.isCriteria(obj)) {
70
76
  return obj;
71
77
  }
72
78
 
73
- let instance = new Criteria();
79
+ let instance = new Criteria(model);
74
80
 
75
81
  if (obj) {
76
82
  instance.applyOldOptions(obj);
@@ -1456,9 +1462,9 @@ Select.setMethod(Blast.checksumSymbol, function toChecksum() {
1456
1462
  /**
1457
1463
  * Add an association
1458
1464
  *
1459
- * @author Jelle De Loecker <jelle@develry.be>
1465
+ * @author Jelle De Loecker <jelle@elevenways.be>
1460
1466
  * @since 1.1.0
1461
- * @version 1.2.0
1467
+ * @version 1.3.4
1462
1468
  *
1463
1469
  * @param {String} name
1464
1470
  *
@@ -1466,6 +1472,10 @@ Select.setMethod(Blast.checksumSymbol, function toChecksum() {
1466
1472
  */
1467
1473
  Select.setMethod(function addAssociation(name) {
1468
1474
 
1475
+ if (!this.criteria?.model) {
1476
+ throw new Error('Unable to select an association: this Criteria has no model info');
1477
+ }
1478
+
1469
1479
  var pieces;
1470
1480
 
1471
1481
  if (!this.associations) {
@@ -183,9 +183,9 @@ DocumentList.setMethod(function toDry() {
183
183
  /**
184
184
  * Simplify the object for Hawkejs
185
185
  *
186
- * @author Jelle De Loecker <jelle@develry.be>
186
+ * @author Jelle De Loecker <jelle@elevenways.be>
187
187
  * @since 1.0.0
188
- * @version 1.1.3
188
+ * @version 1.3.4
189
189
  *
190
190
  * @param {WeakMap} wm
191
191
  *
@@ -196,6 +196,8 @@ DocumentList.setMethod(function toHawkejs(wm) {
196
196
  let records = JSON.clone(this.records, 'toHawkejs', wm),
197
197
  result = new Blast.Classes.Alchemy.Client.DocumentList(records);
198
198
 
199
+ result.available = this.available;
200
+
199
201
  if (this.options && this.options.options) {
200
202
  let options = JSON.clone(this.options.options, 'toHawkejs', wm);
201
203
  result.options = {options: options};
@@ -599,7 +599,7 @@ Model.setMethod(function getAliasModel(alias) {
599
599
  *
600
600
  * @author Jelle De Loecker <jelle@develry.be>
601
601
  * @since 0.0.1
602
- * @version 1.3.0
602
+ * @version 1.3.4
603
603
  *
604
604
  * @param {String} type The type of find (first, all)
605
605
  * @param {Criteria} criteria The criteria object
@@ -610,9 +610,7 @@ Model.setMethod(function getAliasModel(alias) {
610
610
  Model.setMethod(function find(type, criteria, callback) {
611
611
 
612
612
  if (arguments.length == 0) {
613
- criteria = new Blast.Classes.Alchemy.Criteria();
614
- criteria.model = this;
615
- return criteria;
613
+ return new Blast.Classes.Alchemy.Criteria(this);
616
614
  }
617
615
 
618
616
  let skip_type,
@@ -645,7 +643,7 @@ Model.setMethod(function find(type, criteria, callback) {
645
643
  }
646
644
 
647
645
  try {
648
- criteria = Blast.Classes.Alchemy.Criteria.Criteria.cast(criteria);
646
+ criteria = Blast.Classes.Alchemy.Criteria.Criteria.cast(criteria, this);
649
647
  } catch (err) {
650
648
  error = err;
651
649
  }
@@ -826,7 +824,7 @@ Model.setMethod(function findById(id, options, callback) {
826
824
  *
827
825
  * @author Jelle De Loecker <jelle@develry.be>
828
826
  * @since 0.0.1
829
- * @version 1.1.0
827
+ * @version 1.3.4
830
828
  *
831
829
  * @param {String|ObjectId} pk The primary key value
832
830
  * @param {Object} options Optional options object
@@ -842,7 +840,7 @@ Model.setMethod(function findByPk(pk, options, callback) {
842
840
  return pledge;
843
841
  }
844
842
 
845
- let criteria = new Blast.Classes.Alchemy.Criteria();
843
+ let criteria = new Blast.Classes.Alchemy.Criteria(this);
846
844
 
847
845
  criteria.where(this.primary_key).equals(pk);
848
846
 
@@ -860,7 +858,7 @@ Model.setMethod(function findByPk(pk, options, callback) {
860
858
  *
861
859
  * @author Jelle De Loecker <jelle@develry.be>
862
860
  * @since 1.1.0
863
- * @version 1.1.0
861
+ * @version 1.3.4
864
862
  *
865
863
  * @param {Object} values The values to look for
866
864
  * @param {Object} options Optional options object
@@ -869,7 +867,7 @@ Model.setMethod(function findByPk(pk, options, callback) {
869
867
  */
870
868
  Model.setMethod(function findByValues(values, options) {
871
869
 
872
- var criteria = new Blast.Classes.Alchemy.Criteria(),
870
+ var criteria = new Blast.Classes.Alchemy.Criteria(this),
873
871
  key;
874
872
 
875
873
  if (options) {
@@ -888,7 +886,7 @@ Model.setMethod(function findByValues(values, options) {
888
886
  *
889
887
  * @author Jelle De Loecker <jelle@develry.be>
890
888
  * @since 1.1.0
891
- * @version 1.1.0
889
+ * @version 1.3.4
892
890
  *
893
891
  * @param {Object} values The values to look for
894
892
  * @param {Object} options Optional options object
@@ -897,7 +895,7 @@ Model.setMethod(function findByValues(values, options) {
897
895
  */
898
896
  Model.setMethod(function findAllByValues(values, options) {
899
897
 
900
- var criteria = new Blast.Classes.Alchemy.Criteria(),
898
+ var criteria = new Blast.Classes.Alchemy.Criteria(this),
901
899
  key;
902
900
 
903
901
  if (options) {
@@ -5,6 +5,8 @@
5
5
  <% set_title('Alchemy Info Page') %>
6
6
  <% this.foundation({protoblast: true}) %>
7
7
  <% style('alchemy-info') %>
8
+ <meta charset="UTF-8">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
10
  </head>
9
11
  <body>
10
12
 
@@ -64,11 +66,25 @@
64
66
  <div class="row">
65
67
  <article class="plugins col">
66
68
  <h1>Plugins</h1>
67
- <ul>
68
- {% each plugins as plugin %}
69
+ {% with plugins as plugin %}
70
+
71
+ {% none %}
72
+ <p class="none">No plugins are loaded</p>
73
+ {% /none %}
74
+
75
+ {% all %}
76
+ <ul>
77
+ {% /all %}
78
+
79
+ {% each %}
69
80
  <li>{{ plugin }}</li>
70
81
  {% /each %}
71
- </ul>
82
+
83
+ {% all %}
84
+ </ul>
85
+ {% /all %}
86
+
87
+ {% /with %}
72
88
  </article>
73
89
 
74
90
  <article class="settings col">
@@ -1505,7 +1505,7 @@ Conduit.setMethod(function redirect(status, options) {
1505
1505
  *
1506
1506
  * @author Jelle De Loecker <jelle@elevenways.be>
1507
1507
  * @since 0.2.0
1508
- * @version 1.3.1
1508
+ * @version 1.3.4
1509
1509
  *
1510
1510
  * @param {Nulber} status Response statuscode
1511
1511
  * @param {Error} message Optional error to send
@@ -1551,7 +1551,7 @@ Conduit.setMethod(function error(status, message, print_error) {
1551
1551
 
1552
1552
  if (print_dev) {
1553
1553
  let subject = 'Error found on ' + this.original_path + '';
1554
- log.error(subject + ':\n' + message, this);
1554
+ log.error(subject, message, this);
1555
1555
  } else if (print_error) {
1556
1556
  let subject = 'Error found on ' + this.original_path + '';
1557
1557
 
@@ -1646,7 +1646,7 @@ Conduit.setMethod(function notAuthorized(tried_auth) {
1646
1646
  * The current user is authenticated, but not allowed
1647
1647
  * (Default implementation, is overriden by the acl plugin)
1648
1648
  *
1649
- * @author Jelle De Loecker <jelle@kipdola.be>
1649
+ * @author Jelle De Loecker <jelle@elevenways.be>
1650
1650
  * @since 1.0.7
1651
1651
  * @version 1.1.0
1652
1652
  */
@@ -573,7 +573,7 @@ Document.setMethod(function remove(callback) {
573
573
  *
574
574
  * @author Jelle De Loecker <jelle@elevenways.be>
575
575
  * @since 1.2.3
576
- * @version 1.2.3
576
+ * @version 1.3.4
577
577
  *
578
578
  * @param {Criteria|String} criteria
579
579
  *
@@ -598,7 +598,7 @@ Document.setMethod(function preparePopulationCriteria(criteria) {
598
598
  }
599
599
 
600
600
  if (!Classes.Alchemy.Criteria.Criteria.isCriteria(criteria)) {
601
- let new_criteria = new Classes.Alchemy.Criteria();
601
+ let new_criteria = new Classes.Alchemy.Criteria(model);
602
602
  new_criteria.model = model;
603
603
  new_criteria.applyOldConditions(criteria);
604
604
  criteria = new_criteria;
@@ -780,7 +780,7 @@ Document.setMethod(async function revertTo(revision_id) {
780
780
  *
781
781
  * @author Jelle De Loecker <jelle@develry.be>
782
782
  * @since 1.0.3
783
- * @version 1.1.0
783
+ * @version 1.3.4
784
784
  *
785
785
  * @param {Number} revisions
786
786
  *
@@ -813,7 +813,7 @@ Document.setMethod(function revert(revisions) {
813
813
  record,
814
814
  i;
815
815
 
816
- criteria = new Blast.Classes.Alchemy.Criteria();
816
+ criteria = revision_model.find();
817
817
  criteria.where('record_id', that.$pk);
818
818
  criteria.where('revision').gt(target_revision).lte(that.__r);
819
819
  criteria.sort({revision: -1});
@@ -35,6 +35,24 @@ var Schema = Function.inherits(['Deck', 'Alchemy.Base'], function Schema(parent)
35
35
  this.init();
36
36
  });
37
37
 
38
+ /**
39
+ * Add a relation creator method
40
+ *
41
+ * @author Jelle De Loecker <jelle@elevenways.be>
42
+ * @since 1.3.4
43
+ * @version 1.3.4
44
+ *
45
+ * @param {String} relation_type
46
+ */
47
+ Schema.setStatic(function addRelationCreator(relation_type, relation_config) {
48
+
49
+ let method_name = relation_type[0].toLowerCase() + relation_type.slice(1);
50
+
51
+ this.setMethod(method_name, function _addAssociation(alias, model_name, options) {
52
+ this.addAssociation(relation_type, relation_config, alias, model_name, options);
53
+ });
54
+ });
55
+
38
56
  /**
39
57
  * Revive a dried schema
40
58
  *
@@ -224,129 +242,44 @@ Schema.setMethod(function getDatasource() {
224
242
  });
225
243
  });
226
244
 
227
- /**
228
- * Conform association arguments
229
- *
230
- * @author Jelle De Loecker <jelle@develry.be>
231
- * @since 0.2.0
232
- * @version 1.1.0
233
- *
234
- * @param {String} locality internal or external
235
- * @param {String} _alias
236
- * @param {String} _modelname
237
- * @param {Object} _options
238
- */
239
- Schema.setMethod(function getAssociationArguments(locality, alias, modelName, options) {
240
-
241
- if (Object.isObject(modelName)) {
242
- options = modelName;
243
- modelName = undefined;
244
- } else if (!Object.isObject(options)) {
245
- options = {};
246
- }
247
-
248
- if (typeof modelName === 'undefined') {
249
- modelName = alias;
250
- }
251
-
252
- if (options.localKey && typeof options.localKey == 'object') {
253
- throw new Error('Local key for ' + alias + ' association can not be an object');
254
- }
255
-
256
- if (options.foreignKey && typeof options.foreignKey == 'object') {
257
- throw new Error('Foreign key for ' + alias + ' association can not be an object');
258
- }
259
-
260
- if (locality == 'internal') {
261
-
262
- if (!options.localKey) {
263
- options.localKey = alias.foreign_key();
264
- }
265
-
266
- if (!options.foreignKey) {
267
- let model = this.getModel(modelName);
268
- options.foreignKey = model.primary_key || '_id';
269
- }
270
- } else {
271
-
272
- if (!options.localKey) {
273
- let model = this.getModel(modelName);
274
- options.localKey = model.primary_key || '_id';
275
- }
276
-
277
- if (!options.foreignKey) {
278
- options.foreignKey = this.name.foreign_key();
279
- }
280
- }
281
-
282
- return {alias: alias, modelName: modelName, options: options}
283
- });
284
-
285
245
  /**
286
246
  * Add an association
287
247
  *
288
248
  * @author Jelle De Loecker <jelle@develry.be>
289
249
  * @since 0.2.0
290
- * @version 1.2.4
250
+ * @version 1.3.4
291
251
  *
292
252
  * @param {String} alias
253
+ * @param {Object} relation_config
293
254
  * @param {String} modelname
294
255
  * @param {Object} options
295
256
  *
296
257
  * @return {Object}
297
258
  */
298
- Schema.setMethod(function addAssociation(type, alias, modelName, options) {
299
-
300
- var constructor,
301
- client_doc,
302
- doc_class,
303
- className,
304
- locality,
305
- singular,
306
- path,
307
- args;
259
+ Schema.setMethod(function addAssociation(type, relation_config, alias, modelName, options) {
308
260
 
309
261
  if (this.name === alias) {
310
262
  throw new Error('Can\'t add association with the same name as current model');
311
263
  }
312
264
 
313
- // Determine the locality
314
- switch (type) {
315
- case 'HasOneParent':
316
- case 'HasAndBelongsToMany':
317
- case 'BelongsTo':
318
- locality = 'internal';
319
- break;
320
-
321
- case 'HasMany':
322
- case 'HasOneChild':
323
- locality = 'external';
324
- break;
325
-
326
- default:
327
- throw new TypeError('Association type "' + type + '" does not exist');
328
- }
265
+ let is_internal = relation_config.internal === true,
266
+ is_singular = relation_config.singular === true;
329
267
 
330
- // Determine if it's a single record to be found
331
- switch (type) {
332
- case 'HasOneParent':
333
- case 'HasOneChild':
334
- case 'BelongsTo':
335
- case 'HasOne':
336
- singular = true;
337
- break;
338
-
339
- default:
340
- singular = false;
341
- }
268
+ let constructor,
269
+ client_doc,
270
+ doc_class,
271
+ className,
272
+ locality,
273
+ singular,
274
+ path;
342
275
 
343
- args = this.getAssociationArguments(locality, alias, modelName, options);
276
+ let args = this.getAssociationArguments(is_internal, alias, modelName, options);
344
277
  args.type = type;
345
278
 
346
279
  alias = args.alias;
347
280
  modelName = args.modelName;
348
281
  options = args.options;
349
- options.singular = singular;
282
+ options.singular = is_singular;
350
283
  className = this.model_name;
351
284
 
352
285
  if (this.namespace) {
@@ -366,7 +299,7 @@ Schema.setMethod(function addAssociation(type, alias, modelName, options) {
366
299
  }
367
300
 
368
301
  // If the key is part of this model, make sure the field is added
369
- if (locality == 'internal') {
302
+ if (is_internal) {
370
303
 
371
304
  if (!this.getField(options.localKey)) {
372
305
  let field_options = Object.assign({}, args);
@@ -441,81 +374,6 @@ Schema.setMethod(function setEnumValues(name, values) {
441
374
  this.enum_values[name] = values;
442
375
  });
443
376
 
444
- /**
445
- * Add a belongsTo association
446
- *
447
- * @author Jelle De Loecker <jelle@develry.be>
448
- * @since 0.2.0
449
- * @version 0.2.0
450
- *
451
- * @param {String} alias
452
- * @param {String} modelname
453
- * @param {Object} options
454
- */
455
- Schema.setMethod(function belongsTo(alias, modelName, options) {
456
- this.addAssociation('BelongsTo', alias, modelName, options);
457
- });
458
-
459
- /**
460
- * Add a hasOneParent association
461
- *
462
- * @author Jelle De Loecker <jelle@develry.be>
463
- * @since 0.2.0
464
- * @version 0.2.0
465
- *
466
- * @param {String} alias
467
- * @param {String} modelname
468
- * @param {Object} options
469
- */
470
- Schema.setMethod(function hasOneParent(alias, modelName, options) {
471
- this.addAssociation('HasOneParent', alias, modelName, options);
472
- });
473
-
474
- /**
475
- * Add a HABTM association
476
- *
477
- * @author Jelle De Loecker <jelle@develry.be>
478
- * @since 0.2.0
479
- * @version 0.2.0
480
- *
481
- * @param {String} alias
482
- * @param {String} modelname
483
- * @param {Object} options
484
- */
485
- Schema.setMethod(function hasAndBelongsToMany(alias, modelName, options) {
486
- this.addAssociation('HasAndBelongsToMany', alias, modelName, options);
487
- });
488
-
489
- /**
490
- * Add a hasMany association
491
- *
492
- * @author Jelle De Loecker <jelle@develry.be>
493
- * @since 0.2.0
494
- * @version 0.2.0
495
- *
496
- * @param {String} alias
497
- * @param {String} modelname
498
- * @param {Object} options
499
- */
500
- Schema.setMethod(function hasMany(alias, modelName, options) {
501
- this.addAssociation('HasMany', alias, modelName, options);
502
- });
503
-
504
- /**
505
- * Add a hasOneChild association
506
- *
507
- * @author Jelle De Loecker <jelle@develry.be>
508
- * @since 0.2.0
509
- * @version 0.2.0
510
- *
511
- * @param {String} alias
512
- * @param {String} modelname
513
- * @param {Object} options
514
- */
515
- Schema.setMethod(function hasOneChild(alias, modelName, options) {
516
- this.addAssociation('HasOneChild', alias, modelName, options);
517
- });
518
-
519
377
  /**
520
378
  * Clone
521
379
  *
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @param {Schema} parent
12
12
  */
13
- var Schema = Function.inherits(['Deck', 'Alchemy.Client.Base'], 'Alchemy.Client', function Schema(parent) {
13
+ const Schema = Function.inherits(['Deck', 'Alchemy.Client.Base'], 'Alchemy.Client', function Schema(parent) {
14
14
 
15
15
  Blast.Classes.Deck.call(this);
16
16
  Schema.super.call(this);
@@ -18,6 +18,24 @@ var Schema = Function.inherits(['Deck', 'Alchemy.Client.Base'], 'Alchemy.Client'
18
18
  this.init();
19
19
  });
20
20
 
21
+ /**
22
+ * Add a relation creator method
23
+ *
24
+ * @author Jelle De Loecker <jelle@elevenways.be>
25
+ * @since 1.3.4
26
+ * @version 1.3.4
27
+ *
28
+ * @param {String} relation_type
29
+ */
30
+ Schema.setStatic(function addRelationCreator(relation_type, relation_config) {
31
+
32
+ let method_name = relation_type[0].toLowerCase() + relation_type.slice(1);
33
+
34
+ this.setMethod(method_name, function _addAssociation(alias, model_name, options) {
35
+ this.addAssociation(relation_type, relation_config, alias, model_name, options);
36
+ });
37
+ });
38
+
21
39
  /**
22
40
  * Is the given variable a schema?
23
41
  *
@@ -147,6 +165,144 @@ Schema.setProperty(function has_translatable_fields() {
147
165
  return this.has_translations;
148
166
  });
149
167
 
168
+ /**
169
+ * Add the relationships
170
+ *
171
+ * @author Jelle De Loecker <jelle@elevenways.be>
172
+ * @since 0.2.0
173
+ * @version 1.3.4
174
+ */
175
+ Schema.addRelationCreator('BelongsTo', {internal: true, singular: true});
176
+ Schema.addRelationCreator('HasOneParent', {internal: true, singular: true});
177
+ Schema.addRelationCreator('HasAndBelongsToMany', {internal: true, singular: false});
178
+ Schema.addRelationCreator('HasMany', {internal: false, singular: false});
179
+ Schema.addRelationCreator('HasOneChild', {internal: false, singular: true});
180
+
181
+ /**
182
+ * Browser-side association adder
183
+ *
184
+ * @author Jelle De Loecker <jelle@elevenways.be>
185
+ * @since 1.3.4
186
+ * @version 1.3.4
187
+ *
188
+ * @param {String} alias
189
+ * @param {String} modelname
190
+ * @param {Object} options
191
+ *
192
+ * @return {Object}
193
+ */
194
+ Schema.setMethod(function addAssociation(relation_type, relation_config, alias, model_name, options) {
195
+
196
+ if (!options) {
197
+ options = {};
198
+ }
199
+
200
+ let is_internal = relation_config.internal === true,
201
+ is_singular = relation_config.singular === true;
202
+
203
+ let args = this.getAssociationArguments(is_internal, alias, model_name, options);
204
+ alias = args.alias;
205
+ model_name = args.model_name;
206
+
207
+ let class_name = this.model_name || model_name,
208
+ client_doc,
209
+ path;
210
+
211
+ if (this.namespace) {
212
+ path = this.namespace + '.' + class_name;
213
+ } else {
214
+ path = class_name;
215
+ }
216
+
217
+ client_doc = Classes.Alchemy.Client.Document.Document.getDocumentClass(class_name);
218
+
219
+ if (is_internal) {
220
+ if (!this.getField(options.local_key)) {
221
+ let field_options = {...args};
222
+
223
+ if (options && options.field_options) {
224
+ Object.assign(field_options, options.field_options);
225
+ field_options.field_options = undefined;
226
+ }
227
+
228
+ this.addField(options.local_key, relation_type, field_options);
229
+ }
230
+ }
231
+
232
+ if (client_doc) {
233
+ client_doc.setAliasGetter(alias);
234
+ }
235
+ });
236
+
237
+ /**
238
+ * Conform association arguments
239
+ *
240
+ * @author Jelle De Loecker <jelle@elevenways.be>
241
+ * @since 0.2.0
242
+ * @version 1.3.4
243
+ *
244
+ * @param {Boolean} is_internal Is this internal (A remote record's id is stored inside this record)
245
+ * @param {String} alias
246
+ * @param {String} model_name
247
+ * @param {Object} options
248
+ */
249
+ Schema.setMethod(function getAssociationArguments(is_internal, alias, model_name, options) {
250
+
251
+ if (Object.isObject(model_name)) {
252
+ options = model_name;
253
+ model_name = undefined;
254
+ } else if (!Object.isObject(options)) {
255
+ options = {};
256
+ }
257
+
258
+ if (typeof model_name === 'undefined') {
259
+ model_name = alias;
260
+ }
261
+
262
+ let local_key = options.local_key || options.localKey || false,
263
+ foreign_key = options.foreign_key || options.foreignKey || false;
264
+
265
+ if (typeof local_key == 'object') {
266
+ throw new Error('Local key for ' + alias + ' association can not be an object');
267
+ }
268
+
269
+ if (typeof foreign_key == 'object') {
270
+ throw new Error('Foreign key for ' + alias + ' association can not be an object');
271
+ }
272
+
273
+ if (is_internal) {
274
+
275
+ if (!local_key) {
276
+ local_key = alias.foreign_key();
277
+ }
278
+
279
+ if (!foreign_key) {
280
+ let model = this.getModel(model_name);
281
+ foreign_key = model.primary_key || '_id';
282
+ }
283
+ } else {
284
+
285
+ if (!local_key) {
286
+ let model = this.getModel(model_name);
287
+ local_key = model.primary_key || '_id';
288
+ }
289
+
290
+ if (!foreign_key) {
291
+ foreign_key = this.name.foreign_key();
292
+ }
293
+ }
294
+
295
+ options.local_key = options.localKey = local_key;
296
+ options.foreign_key = options.foreignKey = foreign_key;
297
+
298
+ return {
299
+ alias : alias,
300
+ model_name,
301
+ modelName : model_name,
302
+ options : options
303
+ };
304
+ });
305
+
150
306
  /**
151
307
  * Clone for JSON-Dry (JSON.clone())
152
308
  *
@@ -346,4 +346,61 @@ Sitemap.setMethod(function addRouteWithParameters(route, config, parameters, for
346
346
 
347
347
  this.addUrl(config.category, url, route_config, prefix);
348
348
  }
349
+ });
350
+
351
+ /**
352
+ * Create an XML builder
353
+ *
354
+ * @author Jelle De Loecker <jelle@elevenways.be>
355
+ * @since 1.3.4
356
+ * @version 1.3.4
357
+ *
358
+ * @return {Hawkejs.Builder}
359
+ */
360
+ Sitemap.setMethod(function getXmlBuilder() {
361
+
362
+ let xml = Classes.Hawkejs.Builder.create({xml: true}),
363
+ urlset = xml.ele('urlset');
364
+
365
+ urlset.att('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
366
+ .att('xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1')
367
+ .att('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd')
368
+ .att('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
369
+
370
+ let categories = this.getCategories();
371
+
372
+ for (let category of categories) {
373
+ for (let info of category) {
374
+ let url = urlset.ele(url);
375
+
376
+ url.ele('loc').txt(info.url);
377
+
378
+ if (info.lastmod) {
379
+ url.ele('lastmod').txt(info.lastmod);
380
+ }
381
+
382
+ if (info.changefreq) {
383
+ url.ele('changefreq').txt(info.changefreq);
384
+ }
385
+
386
+ if (info.priority) {
387
+ url.ele('priority').txt(info.priority);
388
+ }
389
+ }
390
+ }
391
+
392
+ return xml;
393
+ });
394
+
395
+ /**
396
+ * Get the XML representation
397
+ *
398
+ * @author Jelle De Loecker <jelle@elevenways.be>
399
+ * @since 1.3.4
400
+ * @version 1.3.4
401
+ *
402
+ * @return {String}
403
+ */
404
+ Sitemap.setMethod(function getXml() {
405
+ return this.getXmlBuilder().end();
349
406
  });
@@ -311,7 +311,7 @@ Alchemy.setMethod(function sourcemapMiddleware(req, res, nextMiddleware) {
311
311
  *
312
312
  * @author Jelle De Loecker <jelle@develry.be>
313
313
  * @since 0.2.0
314
- * @version 1.3.3
314
+ * @version 1.3.4
315
315
  *
316
316
  * @param {String} path
317
317
  * @param {Object} options
@@ -407,9 +407,18 @@ Alchemy.setMethod(function minifyScript(path, options, callback) {
407
407
 
408
408
  async function serveCode(data) {
409
409
 
410
- var result;
410
+ let result;
411
+ let should_minify = false;
411
412
 
412
413
  if (alchemy.settings.minify_js) {
414
+ let index = data.indexOf('@alchemy.minify.false');
415
+
416
+ if (index == -1 || index > 50) {
417
+ should_minify = true;
418
+ }
419
+ }
420
+
421
+ if (should_minify) {
413
422
 
414
423
  // Force Blast.isNode & Blast.isBrowser to be replaced later
415
424
  data = data.replaceAll('Blast.isNode', '__BLAST_IS_NODE');
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var types = alchemy.shared('Socket.types'),
3
+ let msgpack_parser,
4
4
  iostream,
5
+ types = alchemy.shared('Socket.types'),
5
6
  path = alchemy.use('path'),
6
7
  fs = alchemy.use('fs');
7
8
 
@@ -10,16 +11,12 @@ var types = alchemy.shared('Socket.types'),
10
11
  *
11
12
  * Create the socket.io listener
12
13
  *
13
- * @author Jelle De Loecker <jelle@develry.be>
14
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
15
  * @since 0.1.0
15
- * @version 1.1.3
16
+ * @version 1.3.5
16
17
  */
17
18
  alchemy.sputnik.add(function socket() {
18
19
 
19
- var clientPath,
20
- streamPath,
21
- io;
22
-
23
20
  if (!alchemy.settings.websockets) {
24
21
  log.info('Websockets have been disabled');
25
22
  return;
@@ -31,36 +28,50 @@ alchemy.sputnik.add(function socket() {
31
28
  }
32
29
  }
33
30
 
34
- io = alchemy.use('socket.io');
31
+ const socket_io = alchemy.use('socket.io');
35
32
 
36
- if (!io) {
33
+ if (!socket_io) {
37
34
  return log.error('Could not load socket.io!');
38
35
  }
39
36
 
40
37
  iostream = alchemy.use('socket.io-stream');
38
+ msgpack_parser = alchemy.use('socket.io-msgpack-parser');
39
+
40
+ let socket_io_options = {
41
+ serveClient : false,
42
+ };
43
+
44
+ if (msgpack_parser) {
45
+ socket_io_options.parser = msgpack_parser;
46
+ }
41
47
 
42
48
  // Create the Socket.io listener
43
- alchemy.io = io.listen(alchemy.server, {serveClient: false});
49
+ alchemy.io = socket_io(alchemy.server, socket_io_options);
44
50
 
45
51
  // Get the core client path
46
- clientPath = path.dirname(alchemy.findModule('socket.io-client').module_path);
47
- clientPath = path.resolve(clientPath, '..', 'dist', 'socket.io.slim.js');
52
+ let client_path = alchemy.findModule('socket.io-client').module_dir;
53
+
54
+ if (msgpack_parser) {
55
+ client_path = path.join(client_path, 'dist', 'socket.io.msgpack.min.js');
56
+ } else {
57
+ client_path = path.join(client_path, 'dist', 'socket.io.min.js');
58
+ }
48
59
 
49
60
  // Get the stream client path
50
- streamPath = path.dirname(alchemy.findModule('@11ways/socket.io-stream').module_path);
51
- streamPath = path.resolve(streamPath, 'socket.io-stream.js');
61
+ let stream_path = path.dirname(alchemy.findModule('@11ways/socket.io-stream').module_path);
62
+ stream_path = path.resolve(stream_path, 'socket.io-stream.js');
52
63
 
53
64
  // Serve the socket io core file
54
65
  Router.use('/scripts/socket.io.js', function getSocketIo(req, res, next) {
55
- alchemy.minifyScript(clientPath, function gotMinifiedPath(err, mpath) {
56
- req.conduit.serveFile(mpath || clientPath);
66
+ alchemy.minifyScript(client_path, function gotMinifiedPath(err, mpath) {
67
+ req.conduit.serveFile(mpath || client_path);
57
68
  });
58
69
  });
59
70
 
60
71
  // Serve the socket io stream file
61
72
  Router.use('/scripts/socket.io-stream.js', function getSocketStream(req, res, next) {
62
- alchemy.minifyScript(streamPath, function gotMinifiedPath(err, mpath) {
63
- req.conduit.serveFile(mpath || streamPath);
73
+ alchemy.minifyScript(stream_path, function gotMinifiedPath(err, mpath) {
74
+ req.conduit.serveFile(mpath || stream_path);
64
75
  });
65
76
  });
66
77
 
package/lib/stages.js CHANGED
@@ -381,7 +381,7 @@ alchemy.sputnik.add(function routes() {
381
381
  *
382
382
  * @author Jelle De Loecker <jelle@develry.be>
383
383
  * @since 0.0.1
384
- * @version 1.1.2
384
+ * @version 1.3.5
385
385
  */
386
386
  alchemy.sputnik.add(function start_server() {
387
387
 
@@ -429,7 +429,8 @@ alchemy.sputnik.add(function start_server() {
429
429
  // The actual `requests` listener is defined in the 'http' stage
430
430
  alchemy.server.listen(listen_target, function areListening(){
431
431
 
432
- var address = alchemy.server.address();
432
+ let address = alchemy.server.address();
433
+ let url = alchemy.settings.url;
433
434
 
434
435
  if (typeof address == 'string') {
435
436
  settings.socket = address;
@@ -439,19 +440,19 @@ alchemy.sputnik.add(function start_server() {
439
440
  if (settings.socketfile_chmod) {
440
441
  fs.chmodSync(address, settings.socketfile_chmod);
441
442
  }
442
-
443
443
  } else {
444
444
  // Get the actual server port
445
445
  settings.port = address.port;
446
446
  log.info('HTTP server listening on port', settings.port);
447
- }
448
-
449
- if (alchemy.settings.url) {
450
- let url = alchemy.settings.url;
451
447
 
452
- url = alchemy.colors.bg.getRgb(1, 0, 1) + alchemy.colors.fg.getRgb(5, 3, 0) + ' ' + url + ' ' + alchemy.colors.reset;
448
+ if (!url) {
449
+ url = 'http://localhost:' + address.port;
450
+ }
451
+ }
453
452
 
454
- log.info('Served at »»', url, '««');
453
+ if (url) {
454
+ let pretty_url = alchemy.colors.bg.getRgb(1, 0, 1) + alchemy.colors.fg.getRgb(5, 3, 0) + ' ' + url + ' ' + alchemy.colors.reset;
455
+ log.info('Served at »»', pretty_url, '««');
455
456
  }
456
457
 
457
458
  // If this process is a child, tell the parent we're ready
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.3.3",
4
+ "version": "1.3.5",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -20,9 +20,9 @@
20
20
  "body-parser" : "~1.19.2",
21
21
  "bson" : "~4.6.1",
22
22
  "chokidar" : "~3.5.3",
23
- "formidable" : "~2.0.1",
23
+ "formidable" : "~2.1.1",
24
24
  "graceful-fs" : "~4.2.9",
25
- "hawkejs" : "~2.3.3",
25
+ "hawkejs" : "~2.3.4",
26
26
  "jsondiffpatch" : "~0.4.1",
27
27
  "mime" : "~3.0.0",
28
28
  "minimist" : "~1.2.5",
@@ -33,10 +33,10 @@
33
33
  "postcss" : "~8.4.6",
34
34
  "protoblast" : "~0.8.3",
35
35
  "semver" : "~7.3.5",
36
- "socket.io" : "~2.4.0",
36
+ "socket.io" : "~4.6.0",
37
37
  "@11ways/socket.io-stream" : "~0.9.2",
38
38
  "sputnik" : "~0.1.0",
39
- "terser" : "~5.10.0",
39
+ "terser" : "~5.16.3",
40
40
  "toobusy-js" : "~0.5.1",
41
41
  "useragent" : "~2.3.0"
42
42
  },
@@ -50,12 +50,13 @@
50
50
  "sass" : "~1.53.0",
51
51
  "sass-embedded" : "~1.53.0",
52
52
  "nodent-compiler" : "~3.2.13",
53
- "socket.io-client" : "~2.4.0"
53
+ "socket.io-client" : "~4.6.0",
54
+ "socket.io-msgpack-parser": "~3.0.2"
54
55
  },
55
56
  "devDependencies": {
56
57
  "codecov" : "~3.8.1",
57
58
  "istanbul-lib-instrument" : "~4.0.3",
58
- "mocha" : "~8.3.2",
59
+ "mocha" : "~10.2.0",
59
60
  "mongo-unit" : "~3.2.0",
60
61
  "nyc" : "^15.1.0",
61
62
  "puppeteer" : "~19.0.0",