nesoi 3.0.0 → 3.0.6

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.
Files changed (186) hide show
  1. package/README.md +10 -0
  2. package/lib/adapters/postgres/src/migrator/bucket.d.ts +20 -0
  3. package/lib/adapters/postgres/src/migrator/bucket.js +184 -0
  4. package/lib/adapters/postgres/src/migrator/csv.d.ts +7 -0
  5. package/lib/adapters/postgres/src/migrator/csv.js +72 -0
  6. package/lib/adapters/postgres/src/migrator/migration.d.ts +2 -18
  7. package/lib/adapters/postgres/src/migrator/migration.js +10 -158
  8. package/lib/adapters/postgres/src/migrator/migrator.js +8 -5
  9. package/lib/adapters/postgres/src/migrator/runner.d.ts +16 -6
  10. package/lib/adapters/postgres/src/migrator/runner.js +103 -34
  11. package/lib/adapters/postgres/src/postgres.bucket_adapter.d.ts +19 -22
  12. package/lib/adapters/postgres/src/postgres.bucket_adapter.js +116 -100
  13. package/lib/adapters/postgres/src/postgres.cli.d.ts +23 -3
  14. package/lib/adapters/postgres/src/postgres.cli.js +70 -10
  15. package/lib/adapters/postgres/src/postgres.config.d.ts +5 -0
  16. package/lib/adapters/postgres/src/postgres.config.js +2 -0
  17. package/lib/adapters/postgres/src/postgres.nql.d.ts +7 -3
  18. package/lib/adapters/postgres/src/postgres.nql.js +86 -32
  19. package/lib/adapters/postgres/src/postgres.provider.d.ts +18 -0
  20. package/lib/adapters/postgres/src/postgres.provider.js +77 -0
  21. package/lib/adapters/postgres/test/postgres.bucket_adapter.test.js +76 -39
  22. package/lib/compiler/apps/monolyth/monolyth_compiler.d.ts +3 -0
  23. package/lib/compiler/apps/monolyth/monolyth_compiler.js +24 -0
  24. package/lib/compiler/apps/monolyth/stages/2_build_typescript_stage.js +2 -1
  25. package/lib/compiler/apps/monolyth/stages/5_dump_cli_stage.js +1 -1
  26. package/lib/compiler/apps/monolyth/stages/6_dump_package_json_stage.js +1 -1
  27. package/lib/compiler/elements/bucket.element.js +26 -11
  28. package/lib/compiler/elements/constants.element.js +1 -1
  29. package/lib/compiler/elements/element.d.ts +2 -0
  30. package/lib/compiler/elements/message.element.js +4 -4
  31. package/lib/compiler/helpers/dump_helpers.js +5 -2
  32. package/lib/compiler/stages/7_dump_stage.js +2 -0
  33. package/lib/compiler/treeshake.js +9 -37
  34. package/lib/compiler/typescript/bridge/extract.js +12 -0
  35. package/lib/compiler/typescript/bridge/inject.js +3 -0
  36. package/lib/compiler/typescript/bridge/organize.js +3 -3
  37. package/lib/elements/blocks/block.builder.js +4 -2
  38. package/lib/elements/blocks/job/internal/resource_job.builder.d.ts +22 -20
  39. package/lib/elements/blocks/job/internal/resource_job.d.ts +2 -1
  40. package/lib/elements/blocks/job/internal/resource_job.js +17 -4
  41. package/lib/elements/blocks/job/job.js +3 -0
  42. package/lib/elements/blocks/job/job.types.d.ts +7 -0
  43. package/lib/elements/blocks/job/job.types.js +2 -0
  44. package/lib/elements/blocks/machine/machine.js +3 -2
  45. package/lib/elements/blocks/resource/resource.builder.js +2 -4
  46. package/lib/elements/blocks/resource/resource.d.ts +5 -3
  47. package/lib/elements/blocks/resource/resource.js +26 -17
  48. package/lib/elements/edge/controller/adapters/controller_adapter.d.ts +2 -1
  49. package/lib/elements/edge/controller/adapters/controller_adapter.js +11 -2
  50. package/lib/elements/edge/controller/controller.builder.d.ts +4 -5
  51. package/lib/elements/edge/controller/controller.builder.js +7 -7
  52. package/lib/elements/edge/controller/controller.d.ts +2 -1
  53. package/lib/elements/edge/controller/controller.js +8 -6
  54. package/lib/elements/entities/bucket/adapters/bucket_adapter.d.ts +61 -23
  55. package/lib/elements/entities/bucket/adapters/bucket_adapter.js +22 -13
  56. package/lib/elements/entities/bucket/adapters/memory.bucket_adapter.d.ts +21 -22
  57. package/lib/elements/entities/bucket/adapters/memory.bucket_adapter.js +68 -2
  58. package/lib/elements/entities/bucket/adapters/memory.nql.d.ts +10 -6
  59. package/lib/elements/entities/bucket/adapters/memory.nql.js +38 -3
  60. package/lib/elements/entities/bucket/adapters/slow_memory.bucket_adapter.d.ts +0 -20
  61. package/lib/elements/entities/bucket/adapters/slow_memory.bucket_adapter.js +46 -30
  62. package/lib/elements/entities/bucket/bucket.builder.d.ts +8 -2
  63. package/lib/elements/entities/bucket/bucket.builder.js +13 -19
  64. package/lib/elements/entities/bucket/bucket.config.d.ts +5 -1
  65. package/lib/elements/entities/bucket/bucket.d.ts +180 -19
  66. package/lib/elements/entities/bucket/bucket.js +662 -48
  67. package/lib/elements/entities/bucket/bucket.schema.d.ts +7 -1
  68. package/lib/elements/entities/bucket/bucket.schema.js +2 -1
  69. package/lib/elements/entities/bucket/bucket.types.d.ts +2 -7
  70. package/lib/elements/entities/bucket/cache/bucket_cache.d.ts +6 -2
  71. package/lib/elements/entities/bucket/cache/bucket_cache.js +12 -12
  72. package/lib/elements/entities/bucket/graph/bucket_graph.d.ts +32 -5
  73. package/lib/elements/entities/bucket/graph/bucket_graph.js +80 -111
  74. package/lib/elements/entities/bucket/graph/bucket_graph.schema.d.ts +3 -6
  75. package/lib/elements/entities/bucket/graph/bucket_graph.schema.js +1 -4
  76. package/lib/elements/entities/bucket/graph/bucket_graph_link.builder.d.ts +3 -7
  77. package/lib/elements/entities/bucket/graph/bucket_graph_link.builder.js +6 -2
  78. package/lib/elements/entities/bucket/model/bucket_model.builder.js +1 -1
  79. package/lib/elements/entities/bucket/model/bucket_model.convert.js +3 -3
  80. package/lib/elements/entities/bucket/model/bucket_model.schema.d.ts +37 -8
  81. package/lib/elements/entities/bucket/model/bucket_model.schema.js +25 -4
  82. package/lib/elements/entities/bucket/model/bucket_model_field.builder.d.ts +33 -14
  83. package/lib/elements/entities/bucket/model/bucket_model_field.builder.js +56 -13
  84. package/lib/elements/entities/bucket/query/nql.schema.d.ts +1 -0
  85. package/lib/elements/entities/bucket/query/nql_compiler.js +13 -2
  86. package/lib/elements/entities/bucket/query/nql_engine.d.ts +11 -4
  87. package/lib/elements/entities/bucket/query/nql_engine.js +20 -11
  88. package/lib/elements/entities/bucket/view/bucket_view.js +63 -35
  89. package/lib/elements/entities/bucket/view/bucket_view.schema.d.ts +5 -2
  90. package/lib/elements/entities/bucket/view/bucket_view_field.builder.d.ts +6 -2
  91. package/lib/elements/entities/bucket/view/bucket_view_field.builder.js +22 -16
  92. package/lib/elements/entities/constants/constants.schema.d.ts +1 -1
  93. package/lib/elements/entities/drive/drive_adapter.d.ts +44 -0
  94. package/lib/elements/entities/drive/drive_adapter.js +10 -0
  95. package/lib/elements/entities/drive/local.drive_adapter.d.ts +10 -0
  96. package/lib/elements/entities/drive/local.drive_adapter.js +34 -0
  97. package/lib/elements/entities/message/message.schema.d.ts +1 -0
  98. package/lib/elements/entities/message/message.schema.js +33 -0
  99. package/lib/elements/entities/message/message_parser.d.ts +5 -1
  100. package/lib/elements/entities/message/message_parser.js +56 -35
  101. package/lib/elements/entities/message/template/message_template.schema.d.ts +10 -8
  102. package/lib/elements/entities/message/template/message_template_field.builder.d.ts +16 -6
  103. package/lib/elements/entities/message/template/message_template_field.builder.js +25 -0
  104. package/lib/elements/entities/message/template/message_template_parser.js +2 -1
  105. package/lib/engine/apps/app.config.d.ts +32 -11
  106. package/lib/engine/apps/app.config.js +12 -0
  107. package/lib/engine/apps/app.d.ts +2 -0
  108. package/lib/engine/apps/app.js +3 -0
  109. package/lib/engine/apps/inline.app.d.ts +5 -3
  110. package/lib/engine/apps/inline.app.js +27 -12
  111. package/lib/engine/apps/monolyth/monolyth.app.d.ts +4 -2
  112. package/lib/engine/apps/monolyth/monolyth.app.js +22 -10
  113. package/lib/engine/auth/authn.d.ts +5 -1
  114. package/lib/engine/auth/zero.authn_provider.d.ts +4 -2
  115. package/lib/engine/auth/zero.authn_provider.js +2 -2
  116. package/lib/engine/cli/cli.d.ts +3 -1
  117. package/lib/engine/cli/cli.js +22 -3
  118. package/lib/engine/cli/cli_adapter.d.ts +2 -1
  119. package/lib/engine/cli/cli_adapter.js +2 -1
  120. package/lib/engine/cli/cli_input.d.ts +19 -0
  121. package/lib/engine/cli/cli_input.js +207 -0
  122. package/lib/engine/cli/ui.d.ts +1 -1
  123. package/lib/engine/cli/ui.js +2 -2
  124. package/lib/engine/daemon.d.ts +3 -2
  125. package/lib/engine/daemon.js +14 -2
  126. package/lib/engine/data/date.js +2 -2
  127. package/lib/engine/data/datetime.d.ts +40 -4
  128. package/lib/engine/data/datetime.js +70 -11
  129. package/lib/engine/data/decimal.d.ts +1 -1
  130. package/lib/engine/data/decimal.js +3 -3
  131. package/lib/engine/data/error.d.ts +21 -4
  132. package/lib/engine/data/error.js +23 -7
  133. package/lib/engine/data/file.d.ts +38 -0
  134. package/lib/engine/data/file.js +54 -0
  135. package/lib/engine/data/json.d.ts +6 -0
  136. package/lib/engine/data/json.js +26 -0
  137. package/lib/engine/data/obj.d.ts +1 -1
  138. package/lib/engine/data/trash.d.ts +14 -0
  139. package/lib/engine/data/trash.js +2 -0
  140. package/lib/engine/data/tree.d.ts +7 -12
  141. package/lib/engine/data/tree.js +101 -49
  142. package/lib/engine/module.d.ts +2 -1
  143. package/lib/engine/module.js +2 -5
  144. package/lib/engine/space.d.ts +1 -0
  145. package/lib/engine/space.js +6 -0
  146. package/lib/engine/transaction/nodes/bucket.trx_node.d.ts +184 -24
  147. package/lib/engine/transaction/nodes/bucket.trx_node.js +346 -451
  148. package/lib/engine/transaction/nodes/bucket_query.trx_node.d.ts +4 -2
  149. package/lib/engine/transaction/nodes/bucket_query.trx_node.js +27 -15
  150. package/lib/engine/transaction/nodes/job.trx_node.d.ts +2 -1
  151. package/lib/engine/transaction/nodes/job.trx_node.js +6 -0
  152. package/lib/engine/transaction/trx.d.ts +5 -2
  153. package/lib/engine/transaction/trx.js +2 -2
  154. package/lib/engine/transaction/trx_engine.config.d.ts +1 -3
  155. package/lib/engine/transaction/trx_engine.d.ts +2 -2
  156. package/lib/engine/transaction/trx_engine.js +14 -11
  157. package/lib/engine/transaction/trx_node.d.ts +14 -4
  158. package/lib/engine/transaction/trx_node.js +50 -8
  159. package/lib/engine/tree.d.ts +1 -1
  160. package/lib/engine/util/crypto.d.ts +50 -0
  161. package/lib/engine/util/crypto.js +89 -0
  162. package/lib/engine/util/deep.d.ts +5 -0
  163. package/lib/engine/util/deep.js +46 -0
  164. package/lib/engine/util/dotenv.d.ts +2 -8
  165. package/lib/engine/util/dotenv.js +14 -36
  166. package/lib/engine/util/hash.d.ts +3 -0
  167. package/lib/engine/util/hash.js +23 -0
  168. package/lib/engine/util/log.js +1 -1
  169. package/lib/engine/util/mime.d.ts +10 -0
  170. package/lib/engine/util/mime.js +389 -0
  171. package/lib/engine/util/parse.d.ts +6 -5
  172. package/lib/engine/util/parse.js +16 -15
  173. package/lib/engine/util/path.d.ts +3 -0
  174. package/lib/engine/util/path.js +92 -0
  175. package/lib/engine/util/rules.d.ts +4 -0
  176. package/lib/engine/util/rules.js +12 -0
  177. package/package.json +2 -2
  178. package/tools/compile.js +2 -2
  179. package/tools/dotenv.d.ts +1 -0
  180. package/tools/dotenv.js +4 -0
  181. package/tools/joaquin/job.d.ts +5 -5
  182. package/tools/joaquin/mock.d.ts +23 -2
  183. package/tools/joaquin/mock.js +127 -21
  184. package/tsconfig.build.tsbuildinfo +1 -1
  185. package/lib/adapters/postgres/test/postgres.bucket_query.test.d.ts +0 -0
  186. package/lib/adapters/postgres/test/postgres.bucket_query.test.js +0 -136
@@ -1,12 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Bucket = void 0;
3
+ exports.Bucket = exports.$id = void 0;
4
+ const trx_node_1 = require("../../../engine/transaction/trx_node");
4
5
  const error_1 = require("../../../engine/data/error");
5
6
  const bucket_view_1 = require("./view/bucket_view");
6
7
  const memory_bucket_adapter_1 = require("./adapters/memory.bucket_adapter");
7
8
  const bucket_cache_1 = require("./cache/bucket_cache");
8
9
  const log_1 = require("../../../engine/util/log");
9
10
  const bucket_graph_1 = require("./graph/bucket_graph");
11
+ const datetime_1 = require("../../../engine/data/datetime");
12
+ const tree_1 = require("../../../engine/data/tree");
13
+ const crypto_1 = require("../../../engine/util/crypto");
14
+ const bucket_model_schema_1 = require("./model/bucket_model.schema");
15
+ /**
16
+ * **This should only be used inside a `#composition` of a bucket `create`** to refer to the parent id, which doesn't exist yet.
17
+ *
18
+ * This property has no useful value outside the engine. If you try to `console.log` it, you'll find a Symbol.
19
+ * It's replaced by the bucket after creating the parent, before creating the composition.
20
+ */
21
+ exports.$id = Symbol('FUTURE ID OF CREATE');
10
22
  class Bucket {
11
23
  constructor(schema, config, providers = {}) {
12
24
  this.schema = schema;
@@ -26,100 +38,702 @@ class Bucket {
26
38
  if (this.config?.cache) {
27
39
  this.cache = new bucket_cache_1.BucketCache(this.schema.name, this.adapter, this.config.cache);
28
40
  }
41
+ // Drive
42
+ if (this.config?.drive) {
43
+ this.drive = this.config.drive(schema, providers);
44
+ }
29
45
  }
30
- // Get
31
- async readOne(trx, id) {
46
+ // Getters
47
+ getQueryMeta() {
48
+ return {
49
+ ...this.adapter.getQueryMeta(),
50
+ bucket: this.schema
51
+ };
52
+ }
53
+ /* CRUD */
54
+ /**
55
+ * Read one raw entity by `id`
56
+ *
57
+ * - Options:
58
+ * - `silent`: If not found, return `undefined` instead of throwing an exception
59
+ * - `no_tenancy`: Don't apply tenancy rules.
60
+ */
61
+ async readOne(trx, id, options) {
62
+ log_1.Log.debug('bucket', this.schema.name, `Read id=${id}`);
63
+ // Validate ID
32
64
  if (typeof id !== 'string' && typeof id !== 'number') {
33
65
  throw error_1.NesoiError.Bucket.InvalidId({ bucket: this.schema.alias, id });
34
66
  }
35
- log_1.Log.debug('bucket', this.schema.name, `Get id=${id}`);
36
- const raw = this.cache
37
- ? await this.cache.get(trx, id)
38
- : await this.adapter.get(trx, id);
39
- if (!raw)
40
- return undefined;
67
+ // Make tenancy query
68
+ const tenancy = (options?.no_tenancy)
69
+ ? undefined
70
+ : this.getTenancyQuery(trx);
71
+ let raw;
72
+ // With Tenancy
73
+ if (tenancy) {
74
+ const result = await this.adapter.query(trx, {
75
+ id,
76
+ '#and': tenancy
77
+ }, { perPage: 1 });
78
+ raw = result.data[0];
79
+ }
80
+ // Without Tenancy
81
+ else {
82
+ raw = this.cache
83
+ ? await this.cache.get(trx, id)
84
+ : await this.adapter.get(trx, id);
85
+ }
86
+ // Empty result
87
+ if (!raw) {
88
+ if (options?.silent)
89
+ return undefined;
90
+ else
91
+ throw error_1.NesoiError.Bucket.ObjNotFound({ bucket: this.schema.alias, id: id });
92
+ }
93
+ // Encryption
94
+ if (this.schema.model.hasEncryptedField) {
95
+ this.decrypt(trx, raw);
96
+ }
41
97
  return raw;
42
98
  }
43
- async readAll(trx, pagination, order) {
44
- log_1.Log.debug('bucket', this.schema.name, 'Index');
45
- const raws = this.cache
46
- ? await this.cache.index(trx)
47
- : await this.adapter.index(trx);
99
+ /**
100
+ * Read all raw entities
101
+ *
102
+ * - Options:
103
+ * - `no_tenancy`: Don't apply tenancy rules.
104
+ */
105
+ async readAll(trx, options) {
106
+ log_1.Log.debug('bucket', this.schema.name, 'Read All');
107
+ // Make tenancy query
108
+ const tenancy = (options?.no_tenancy)
109
+ ? undefined
110
+ : this.getTenancyQuery(trx);
111
+ let raws;
112
+ // With Tenancy
113
+ if (tenancy) {
114
+ const result = await this.adapter.query(trx, tenancy);
115
+ raws = result.data;
116
+ }
117
+ // Without Tenancy
118
+ else {
119
+ raws = this.cache
120
+ ? await this.cache.index(trx)
121
+ : await this.adapter.index(trx);
122
+ }
123
+ // Encryption
124
+ if (this.schema.model.hasEncryptedField) {
125
+ for (const raw of raws) {
126
+ this.decrypt(trx, raw);
127
+ }
128
+ }
48
129
  return raws;
49
130
  }
50
- async viewOne(trx, id, view) {
131
+ /**
132
+ * Read an entity's view by `id`
133
+ *
134
+ * - Options:
135
+ * - `silent`: If not found, return `undefined` instead of throwing an exception
136
+ * - `no_tenancy`: Don't apply tenancy rules.
137
+ */
138
+ async viewOne(trx, id, view, options) {
139
+ log_1.Log.debug('bucket', this.schema.name, `View id=${id}, v=${view}`);
140
+ // Read
141
+ const raw = await this.readOne(trx, id, options);
142
+ if (!raw) {
143
+ return;
144
+ }
145
+ // Build
146
+ return this.buildOne(trx, raw, view);
147
+ }
148
+ /**
149
+ * Read a view of all entities
150
+ *
151
+ * - Options:
152
+ * - `no_tenancy`: Don't apply tenancy rules.
153
+ */
154
+ async viewAll(trx, view, options) {
155
+ log_1.Log.debug('bucket', this.schema.name, `View all, v=${view}`);
156
+ // Read
157
+ const raws = await this.readAll(trx, options);
158
+ // Build
159
+ return this.buildMany(trx, raws, view);
160
+ }
161
+ // Graph
162
+ /**
163
+ * Read raw entity of a graph link
164
+ *
165
+ * - Options:
166
+ * - `silent`: If not found, return `undefined` instead of throwing an exception
167
+ * - `no_tenancy`: Don't apply tenancy rules.
168
+ */
169
+ async readLink(trx, id, link, options) {
170
+ log_1.Log.debug('bucket', this.schema.name, `Read Link, id=${id} l=${link}`);
171
+ // Validate ID
51
172
  if (typeof id !== 'string' && typeof id !== 'number') {
52
173
  throw error_1.NesoiError.Bucket.InvalidId({ bucket: this.schema.alias, id });
53
174
  }
54
- log_1.Log.debug('bucket', this.schema.name, `View id=${id}, v=${view}`);
175
+ // Read object
55
176
  const obj = await this.readOne(trx, id);
177
+ // Empty response
56
178
  if (!obj) {
57
- return;
179
+ const schema = this.schema.graph.links[link];
180
+ if (schema.many) {
181
+ return [];
182
+ }
183
+ return undefined;
58
184
  }
59
- return this.buildOne(trx, obj, view);
185
+ // Read link
186
+ const linkObj = await this.graph.readLink(trx, obj, link, options);
187
+ // Encryption
188
+ if (linkObj) {
189
+ if (this.schema.model.hasEncryptedField) {
190
+ this.decrypt(trx, linkObj);
191
+ }
192
+ }
193
+ return linkObj;
60
194
  }
61
- async viewAll(trx, view, pagination, order) {
62
- log_1.Log.debug('bucket', this.schema.name, `View all, v=${view}`);
63
- const objs = await this.readAll(trx);
64
- return this.buildAll(trx, objs, view);
195
+ /**
196
+ * Read the view of an entity of a graph link
197
+ *
198
+ * - Options:
199
+ * - `silent`: If not found, return `undefined` instead of throwing an exception
200
+ * - `no_tenancy`: Don't apply tenancy rules.
201
+ */
202
+ async viewLink(trx, id, link, view, options) {
203
+ log_1.Log.debug('bucket', this.schema.name, `View Link, id=${id} l=${link}`);
204
+ // Validate ID
205
+ if (typeof id !== 'string' && typeof id !== 'number') {
206
+ throw error_1.NesoiError.Bucket.InvalidId({ bucket: this.schema.alias, id });
207
+ }
208
+ // Read object
209
+ const obj = await this.readOne(trx, id);
210
+ // Empty response
211
+ if (!obj) {
212
+ const schema = this.schema.graph.links[link];
213
+ if (schema.many) {
214
+ return [];
215
+ }
216
+ return undefined;
217
+ }
218
+ // View link
219
+ const linkObj = await this.graph.viewLink(trx, obj, link, view, options);
220
+ // Encryption
221
+ if (linkObj && this.schema.model.hasEncryptedField) {
222
+ if (Array.isArray(linkObj)) {
223
+ for (const obj of linkObj)
224
+ this.decrypt(trx, obj);
225
+ }
226
+ else {
227
+ this.decrypt(trx, linkObj);
228
+ }
229
+ }
230
+ return linkObj;
65
231
  }
232
+ /**
233
+ * Return true if the graph link refers to at least one object
234
+ *
235
+ * - Options:
236
+ * - `no_tenancy`: Don't apply tenancy rules.
237
+ */
238
+ async hasLink(trx, id, link, options) {
239
+ log_1.Log.debug('bucket', this.schema.name, `Has Link, id=${id} l=${link}`);
240
+ // Read Object
241
+ const obj = await this.readOne(trx, id);
242
+ if (!obj) {
243
+ return undefined;
244
+ }
245
+ // Check Link
246
+ return this.graph.hasLink(trx, link, obj, options);
247
+ }
248
+ // Build
249
+ /**
250
+ * Build one object with a view
251
+ */
66
252
  async buildOne(trx, obj, view) {
67
253
  if (!(view in this.views)) {
68
254
  throw error_1.NesoiError.Bucket.ViewNotFound({ bucket: this.schema.alias, view: view });
69
255
  }
70
256
  return this.views[view].parse(trx, obj);
71
257
  }
72
- async buildAll(trx, objs, view) {
258
+ /**
259
+ * Build a list ob objects with a view
260
+ */
261
+ async buildMany(trx, objs, view) {
73
262
  return Promise.all(objs.map(obj => this.buildOne(trx, obj, view)));
74
263
  }
75
- // Put
264
+ // Create
265
+ /**
266
+ * Create an entity
267
+ */
268
+ async create(trx, obj) {
269
+ var _a, _b;
270
+ log_1.Log.debug('bucket', this.schema.name, `Create id=${obj['id'] || 'new'}`, obj);
271
+ // Separate composition
272
+ let composition = obj['#composition'];
273
+ delete obj['#composition'];
274
+ // Add meta (created_by/created_at/updated_by/updated_at)
275
+ this.addMeta(trx, obj, 'create');
276
+ // Encryption
277
+ if (this.schema.model.hasEncryptedField) {
278
+ this.encrypt(trx, obj);
279
+ }
280
+ // Drive
281
+ if (this.schema.model.hasFileField) {
282
+ await this.copyFilesToDrive(obj);
283
+ }
284
+ // Create
285
+ const input = Object.assign({}, this.schema.model.defaults, obj);
286
+ const _obj = await this.adapter.create(trx, input);
287
+ // Composition
288
+ if (composition) {
289
+ this.replaceFutureId(composition, _obj.id);
290
+ }
291
+ else {
292
+ composition = {};
293
+ }
294
+ // Create composition
295
+ for (const link of Object.values(this.schema.graph.links)) {
296
+ if (link.rel !== 'composition')
297
+ continue;
298
+ const linkObj = composition[link.name];
299
+ if (!linkObj) {
300
+ throw error_1.NesoiError.Bucket.MissingComposition({ method: 'create', bucket: this.schema.name, link: link.name });
301
+ }
302
+ if (link.many) {
303
+ if (!Array.isArray(linkObj)) {
304
+ throw error_1.NesoiError.Bucket.CompositionValueShouldBeArray({ method: 'create', bucket: this.schema.name, link: link.name });
305
+ }
306
+ _obj['#composition'] ?? (_obj['#composition'] = {});
307
+ (_a = _obj['#composition'])[_b = link.name] ?? (_a[_b] = []);
308
+ for (const linkObjItem of linkObj) {
309
+ const child = await trx.bucket(link.bucket.refName).create(linkObjItem);
310
+ _obj['#composition'][link.name].push(child);
311
+ }
312
+ }
313
+ else {
314
+ const child = await trx.bucket(link.bucket.refName).create(linkObj);
315
+ _obj['#composition'] ?? (_obj['#composition'] = {});
316
+ _obj['#composition'][link.name] = child;
317
+ }
318
+ }
319
+ return _obj;
320
+ }
321
+ /**
322
+ * Replace the `$id` symbol on an object with the proper ID value.
323
+ * This is used on composition, to access the ID of the parent.
324
+ */
325
+ replaceFutureId(composition, value) {
326
+ let poll = [composition];
327
+ while (poll.length) {
328
+ const next = [];
329
+ for (const obj of poll) {
330
+ if (Array.isArray(obj)) {
331
+ for (let i = 0; i < obj.length; i++) {
332
+ if (typeof obj[i] === 'symbol' && obj[i] == exports.$id) {
333
+ obj[i] = value;
334
+ }
335
+ else if (typeof obj[i] === 'object') {
336
+ next.push(obj[i]);
337
+ }
338
+ }
339
+ }
340
+ else {
341
+ for (const key in obj) {
342
+ if (typeof obj[key] === 'symbol' && obj[key] == exports.$id) {
343
+ obj[key] = value;
344
+ }
345
+ else if (typeof obj[key] === 'object') {
346
+ next.push(obj[key]);
347
+ }
348
+ }
349
+ }
350
+ }
351
+ poll = next;
352
+ }
353
+ }
354
+ // Update
355
+ /**
356
+ * Update an entity
357
+ *
358
+ * - Options:
359
+ * - `mode`: Type of update to perform (default: `patch`)
360
+ * - `patch`: Only modifies properties that changed
361
+ * - `replace`: Replace the whole object
362
+ * - `no_tenancy`: Don't apply tenancy rules (default: `false`)
363
+ * - `unsafe`:
364
+ * - Don't attempt to read the object before updating. This option is faster, but can throw exceptions directly from the adapter (default: `false`)
365
+ * - **WARNING** Unsafe currently avoids the tenancy check
366
+ */
367
+ async update(trx, obj, options) {
368
+ log_1.Log.debug('bucket', this.schema.name, `Update id=${obj['id']}`, obj);
369
+ // Separate composition
370
+ const composition = obj['#composition'] || {};
371
+ delete obj['#composition'];
372
+ // Tenancy
373
+ const tenancy = (options?.no_tenancy)
374
+ ? undefined
375
+ : this.getTenancyQuery(trx);
376
+ // Read old object, if safe, to check if it exists
377
+ let oldObj;
378
+ if (!options?.unsafe) {
379
+ // With Tenancy
380
+ if (tenancy) {
381
+ const result = await this.adapter.query(trx, {
382
+ id: obj.id,
383
+ '#and': tenancy
384
+ }, { perPage: 1 }, undefined, {
385
+ metadataOnly: true
386
+ });
387
+ oldObj = result.data[0];
388
+ }
389
+ // Without Tenancy
390
+ else {
391
+ oldObj = this.cache
392
+ ? await this.cache.get(trx, obj['id'])
393
+ : await this.adapter.get(trx, obj['id']);
394
+ }
395
+ // Empty response
396
+ if (!oldObj) {
397
+ throw error_1.NesoiError.Bucket.ObjNotFound({ bucket: this.schema.alias, id: obj['id'] });
398
+ }
399
+ }
400
+ // Add meta (updated_by/updated_at)
401
+ this.addMeta(trx, obj, 'update');
402
+ // Encryption
403
+ if (this.schema.model.hasEncryptedField) {
404
+ this.encrypt(trx, obj);
405
+ }
406
+ // Drive
407
+ if (this.schema.model.hasFileField) {
408
+ await this.copyFilesToDrive(obj);
409
+ }
410
+ // Patch/Replace
411
+ const mode = options?.mode || 'patch';
412
+ const _obj = await this.adapter[mode](trx, obj);
413
+ // Composition
414
+ for (const link of Object.values(this.schema.graph.links)) {
415
+ if (link.rel !== 'composition')
416
+ continue;
417
+ const linkObj = composition[link.name];
418
+ if (!linkObj) {
419
+ throw error_1.NesoiError.Bucket.MissingComposition({ method: 'patch', bucket: this.schema.name, link: link.name });
420
+ }
421
+ if (link.many) {
422
+ if (!Array.isArray(linkObj)) {
423
+ throw error_1.NesoiError.Bucket.CompositionValueShouldBeArray({ method: 'patch', bucket: this.schema.name, link: link.name });
424
+ }
425
+ for (const linkObjItem of linkObj) {
426
+ await trx.bucket(link.bucket.refName)[mode](linkObjItem);
427
+ }
428
+ }
429
+ else {
430
+ await trx.bucket(link.bucket.refName)[mode](linkObj);
431
+ }
432
+ }
433
+ return _obj;
434
+ }
435
+ /**
436
+ * Create or Replace an entity
437
+ *
438
+ * **WARNING** Tenancy not checked
439
+ */
76
440
  async put(trx, obj) {
77
- log_1.Log.debug('bucket', this.schema.name, `Put id=${obj['id'] || 'new'}`, obj);
78
- const raw = await this.adapter.put(trx, obj);
79
- return raw;
441
+ var _a, _b;
442
+ log_1.Log.debug('bucket', this.schema.name, `Put id=${obj['id']}`, obj);
443
+ // Separate composition
444
+ const composition = obj['#composition'] || {};
445
+ delete obj['#composition'];
446
+ // Add meta (updated_by/updated_at)
447
+ this.addMeta(trx, obj, 'update');
448
+ // Encryption
449
+ if (this.schema.model.hasEncryptedField) {
450
+ this.encrypt(trx, obj);
451
+ }
452
+ // Drive
453
+ if (this.schema.model.hasFileField) {
454
+ await this.copyFilesToDrive(obj);
455
+ }
456
+ // Put
457
+ const _obj = await this.adapter.put(trx, obj);
458
+ // Composition
459
+ for (const link of Object.values(this.schema.graph.links)) {
460
+ if (link.rel !== 'composition')
461
+ continue;
462
+ const linkObj = composition[link.name];
463
+ if (!linkObj) {
464
+ throw error_1.NesoiError.Bucket.MissingComposition({ method: 'replace', bucket: this.schema.name, link: link.name });
465
+ }
466
+ if (link.many) {
467
+ if (!Array.isArray(linkObj)) {
468
+ throw error_1.NesoiError.Bucket.CompositionValueShouldBeArray({ method: 'replace', bucket: this.schema.name, link: link.name });
469
+ }
470
+ _obj['#composition'] ?? (_obj['#composition'] = {});
471
+ (_a = _obj['#composition'])[_b = link.name] ?? (_a[_b] = []);
472
+ for (const linkObjItem of linkObj) {
473
+ const child = await trx.bucket(link.bucket.refName).put(linkObjItem);
474
+ _obj['#composition'][link.name].push(child);
475
+ }
476
+ }
477
+ else {
478
+ const child = await trx.bucket(link.bucket.refName).put(linkObj);
479
+ _obj['#composition'] ?? (_obj['#composition'] = {});
480
+ _obj['#composition'][link.name] = child;
481
+ }
482
+ }
483
+ return _obj;
80
484
  }
81
485
  // Delete
82
- async delete(trx, id) {
486
+ /**
487
+ * Delete an entity
488
+ *
489
+ * - Options:
490
+ * - `no_tenancy`: Don't apply tenancy rules (default: `false`)
491
+ * - `unsafe`
492
+ * - Don't attempt to read the object before updating. This option is faster, but can throw exceptions directly from the adapter (default: `false`)
493
+ * - **WARNING** Unsafe currently avoids the tenancy check
494
+ */
495
+ async delete(trx, id, options) {
496
+ log_1.Log.debug('bucket', this.schema.name, `Delete id=${id}`);
497
+ // Validate ID
83
498
  if (typeof id !== 'string' && typeof id !== 'number') {
84
499
  throw error_1.NesoiError.Bucket.InvalidId({ bucket: this.schema.alias, id });
85
500
  }
86
- log_1.Log.debug('bucket', this.schema.name, `Delete id=${id}`);
501
+ // Read object, if safe, to check if it exists
502
+ if (!options?.unsafe) {
503
+ // Tenancy
504
+ const tenancy = (options?.no_tenancy)
505
+ ? undefined
506
+ : this.getTenancyQuery(trx);
507
+ // Check if object exists
508
+ const result = await this.adapter.query(trx, {
509
+ id, '#and': tenancy
510
+ }, { perPage: 1 }, undefined, {
511
+ metadataOnly: true
512
+ });
513
+ if (!result.data.length) {
514
+ throw error_1.NesoiError.Bucket.ObjNotFound({ bucket: this.schema.alias, id });
515
+ }
516
+ }
517
+ // Delete compositions (with other key)
518
+ for (const link of Object.values(this.schema.graph.links)) {
519
+ if (link.rel !== 'composition')
520
+ continue;
521
+ if (link.keyOwner !== 'other')
522
+ continue;
523
+ const linked = await this.readLink(trx, id, link.name, {
524
+ silent: true
525
+ });
526
+ if (!linked)
527
+ continue;
528
+ if (link.many) {
529
+ for (const linkedItem of linked) {
530
+ await trx.bucket(link.bucket.refName).delete(linkedItem.id);
531
+ }
532
+ }
533
+ else {
534
+ await trx.bucket(link.bucket.refName).delete(linked.id);
535
+ }
536
+ }
537
+ // Delete the object itself
87
538
  await this.adapter.delete(trx, id);
539
+ // Composition (with self key)
540
+ for (const link of Object.values(this.schema.graph.links)) {
541
+ if (link.rel !== 'composition')
542
+ continue;
543
+ if (link.keyOwner !== 'self')
544
+ continue;
545
+ const linked = await this.readLink(trx, id, link.name, {
546
+ silent: true
547
+ });
548
+ if (!linked)
549
+ continue;
550
+ if (link.many) {
551
+ await trx.bucket(link.bucket.refName).unsafe.deleteMany(linked.map((l) => l.id));
552
+ }
553
+ else {
554
+ await trx.bucket(link.bucket.refName).delete(linked.id);
555
+ }
556
+ }
88
557
  }
89
- async deleteMany(trx, ids) {
558
+ /**
559
+ * Delete many entities
560
+ *
561
+ * - Options:
562
+ * - `no_tenancy`: Don't apply tenancy rules (default: `false`)
563
+ * - `unsafe`
564
+ * - Don't attempt to read the object before updating. This option is faster, but can throw exceptions directly from the adapter (default: `false`)
565
+ * - **WARNING** Unsafe currently avoids the tenancy check
566
+ */
567
+ async deleteMany(trx, ids, options) {
90
568
  log_1.Log.debug('bucket', this.schema.name, `Delete Many ids=${ids}`);
569
+ // Filter ids, if safe, to check if it exists
570
+ if (!options?.unsafe) {
571
+ // Tenancy
572
+ const tenancy = (options?.no_tenancy)
573
+ ? undefined
574
+ : this.getTenancyQuery(trx);
575
+ // Filter ids
576
+ const result = await this.adapter.query(trx, {
577
+ 'id in': ids,
578
+ '#and': tenancy
579
+ }, undefined, undefined, {
580
+ metadataOnly: true
581
+ });
582
+ ids = result.data.map(obj => obj.id);
583
+ }
584
+ // Composition (with other key)
585
+ for (const link of Object.values(this.schema.graph.links)) {
586
+ if (link.rel !== 'composition')
587
+ continue;
588
+ if (link.keyOwner !== 'other')
589
+ continue;
590
+ for (const id of ids) {
591
+ const linked = await this.readLink(trx, id, link.name, { silent: true });
592
+ if (!linked)
593
+ continue;
594
+ if (link.many) {
595
+ await trx.bucket(link.bucket.refName).unsafe.deleteMany(linked.map((l) => l.id));
596
+ }
597
+ else {
598
+ await trx.bucket(link.bucket.refName).unsafe.delete(linked.id);
599
+ }
600
+ }
601
+ }
91
602
  await this.adapter.deleteMany(trx, ids);
603
+ // Composition (with self key)
604
+ for (const link of Object.values(this.schema.graph.links)) {
605
+ if (link.rel !== 'composition')
606
+ continue;
607
+ if (link.keyOwner !== 'self')
608
+ continue;
609
+ for (const id of ids) {
610
+ const linked = await this.readLink(trx, id, link.name, { silent: true });
611
+ if (!linked)
612
+ continue;
613
+ if (link.many) {
614
+ await trx.bucket(link.bucket.refName).unsafe.deleteMany(linked.map((l) => l.id));
615
+ }
616
+ else {
617
+ await trx.bucket(link.bucket.refName).unsafe.delete(linked.id);
618
+ }
619
+ }
620
+ }
92
621
  }
93
622
  // Query
94
- async query(trx, query, pagination, view) {
623
+ /**
624
+ * Query entities using NQL
625
+ *
626
+ * - Options:
627
+ * - `no_tenancy`: Don't apply tenancy rules (default: `false`)
628
+ * - `params`: NQL parameters
629
+ */
630
+ async query(trx, query, pagination, view, options) {
95
631
  log_1.Log.trace('bucket', this.schema.name, 'Query', query);
96
632
  const v = (view ? this.views[view] : null) || this.views['default'];
97
633
  if (!v) {
98
634
  throw error_1.NesoiError.Bucket.Query.ViewNotFound(this.schema.name, view || 'default');
99
635
  }
100
- const raws = this.cache
101
- ? await this.cache.query(trx, v.schema, query, pagination)
102
- : await this.adapter.query(trx, query, pagination);
103
- if (!raws.length)
104
- return [];
636
+ // Tenancy
637
+ const tenancy = (options?.no_tenancy)
638
+ ? undefined
639
+ : this.getTenancyQuery(trx);
640
+ query = {
641
+ ...query,
642
+ '#and': tenancy
643
+ };
644
+ // Query
645
+ const result = await this.adapter.query(trx, query, pagination, options?.params);
646
+ if (!result.data.length)
647
+ return {
648
+ data: []
649
+ };
650
+ // Encryption
651
+ if (this.schema.model.hasEncryptedField) {
652
+ for (const obj of result.data) {
653
+ this.decrypt(trx, obj);
654
+ }
655
+ }
656
+ // Build
105
657
  if (view) {
106
- return this.buildAll(trx, raws, view);
658
+ result.data = await this.buildMany(trx, result.data, view);
107
659
  }
108
- else {
109
- return raws;
660
+ return result;
661
+ }
662
+ // Metadata
663
+ /**
664
+ * Add `created_by`, `created_at`, `updated_by` and `updated_at` fields to object
665
+ */
666
+ addMeta(trx, obj, operation) {
667
+ const match = trx_node_1.TrxNode.getFirstUserMatch(trx, this.schema.tenancy);
668
+ if (operation === 'create') {
669
+ obj[this.adapter.config.meta.created_at] = datetime_1.NesoiDatetime.now();
670
+ if (match) {
671
+ obj[this.adapter.config.meta.created_by] = match.user.id;
672
+ }
673
+ }
674
+ obj[this.adapter.config.meta.updated_at] = datetime_1.NesoiDatetime.now();
675
+ if (match) {
676
+ obj[this.adapter.config.meta.updated_by] = match.user.id;
110
677
  }
111
678
  }
112
- static getQueryMeta(bucket) {
113
- return {
114
- ...bucket.adapter.getQueryMeta(),
115
- bucket: bucket.schema
116
- };
679
+ // Tenancy
680
+ getTenancyQuery(trx) {
681
+ if (!this.schema.tenancy)
682
+ return;
683
+ const match = trx_node_1.TrxNode.getFirstUserMatch(trx, this.schema.tenancy);
684
+ return this.schema.tenancy[match.provider]?.(match.user);
117
685
  }
118
- static getQueryRunner(bucket) {
119
- return bucket.adapter.nql;
686
+ // Encryption
687
+ encrypt(trx, obj, fields = this.schema.model.fields) {
688
+ for (const key in fields) {
689
+ const field = fields[key];
690
+ if (field.crypto) {
691
+ const key = trx.value(field.crypto.key);
692
+ tree_1.Tree.set(obj, field.path, val => crypto_1.Crypto.encrypt(val, key));
693
+ }
694
+ if (field.children) {
695
+ this.encrypt(trx, obj, field.children);
696
+ }
697
+ }
120
698
  }
121
- static getAdapter(bucket) {
122
- return bucket.adapter;
699
+ decrypt(trx, obj, fields = this.schema.model.fields) {
700
+ for (const key in fields) {
701
+ const field = fields[key];
702
+ if (field.crypto) {
703
+ const key = trx.value(field.crypto.key);
704
+ tree_1.Tree.set(obj, field.path, val => crypto_1.Crypto.decrypt(val, key));
705
+ }
706
+ if (field.children) {
707
+ this.decrypt(trx, obj, field.children);
708
+ }
709
+ }
710
+ }
711
+ // Drive (Files)
712
+ /**
713
+ * Copy all files from the object to the bucket's Drive
714
+ * - Call `drive.copy` to send the files preserving the local copy
715
+ * - Replace the file on the object with a new one representing the remote
716
+ */
717
+ async copyFilesToDrive(obj) {
718
+ if (!this.drive) {
719
+ throw error_1.NesoiError.Bucket.Drive.NoAdapter({ bucket: this.schema.alias });
720
+ }
721
+ const fields = bucket_model_schema_1.$BucketModel.fieldsOfType(this.schema.model, 'file');
722
+ for (const field of fields) {
723
+ if (field.array) {
724
+ const files = tree_1.Tree.get(obj, field.path);
725
+ const remoteFiles = [];
726
+ for (const file of files) {
727
+ remoteFiles.push(await this.drive.copy(file, this.drive.dirpath));
728
+ }
729
+ tree_1.Tree.set(obj, field.path, () => remoteFiles);
730
+ }
731
+ else {
732
+ const file = tree_1.Tree.get(obj, field.path);
733
+ const remoteFile = await this.drive.copy(file, this.drive.dirpath);
734
+ tree_1.Tree.set(obj, field.path, () => remoteFile);
735
+ }
736
+ }
123
737
  }
124
738
  }
125
739
  exports.Bucket = Bucket;