UNPKG

6.26 kBJavaScriptView Raw
1// Copyright IBM Corp. 2015,2019. All Rights Reserved.
2// Node module: loopback-datasource-juggler
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6'use strict';
7
8/*
9 * Describe context objects of operation hooks in comprehensive HTML table.
10 * Usage:
11 * $ node support/describe-operation-hooks.js > hooks.html
12 * $ open hooks.hml
13 *
14 */
15const Promise = global.Promise = require('bluebird');
16const DataSource = require('../').DataSource;
17const Memory = require('../lib/connectors/memory').Memory;
18
19const HOOK_NAMES = [
20 'access',
21 'before save', 'persist', 'loaded', 'after save',
22 'before delete', 'after delete',
23];
24
25const dataSources = [
26 createOptimizedDataSource(),
27 createUnoptimizedDataSource(),
28];
29
30const observedContexts = [];
31let lastId = 0;
32
33Promise.onPossiblyUnhandledRejection(function(err) {
34 console.error('POSSIBLY UNHANDLED REJECTION', err.stack);
35});
36
37/* eslint-disable camelcase */
38const operations = [
39 function find(ds) {
40 return ds.TestModel.find({where: {id: '1'}});
41 },
42
43 function count(ds) {
44 return ds.TestModel.count({id: ds.existingInstance.id});
45 },
46
47 function create(ds) {
48 return ds.TestModel.create({name: 'created'});
49 },
50
51 function findOrCreate_found(ds) {
52 return ds.TestModel.findOrCreate(
53 {where: {name: ds.existingInstance.name}},
54 {name: ds.existingInstance.name},
55 );
56 },
57
58 function findOrCreate_create(ds) {
59 return ds.TestModel.findOrCreate(
60 {where: {name: 'new-record'}},
61 {name: 'new-record'},
62 );
63 },
64
65 function updateOrCreate_create(ds) {
66 return ds.TestModel.updateOrCreate({id: 'not-found', name: 'not found'});
67 },
68
69 function updateOrCreate_update(ds) {
70 return ds.TestModel.updateOrCreate(
71 {id: ds.existingInstance.id, name: 'new name'},
72 );
73 },
74
75 function replaceOrCreate_create(ds) {
76 return ds.TestModel.replaceOrCreate({id: 'not-found', name: 'not found'});
77 },
78
79 function replaceOrCreate_update(ds) {
80 return ds.TestModel.replaceOrCreate(
81 {id: ds.existingInstance.id, name: 'new name'},
82 );
83 },
84
85 function replaceById(ds) {
86 return ds.TestModel.replaceById(
87 ds.existingInstance.id,
88 {name: 'new name'},
89 );
90 },
91
92 function updateAll(ds) {
93 return ds.TestModel.updateAll({name: 'searched'}, {name: 'updated'});
94 },
95
96 function prototypeSave(ds) {
97 ds.existingInstance.name = 'changed';
98 return ds.existingInstance.save();
99 },
100
101 function prototypeUpdateAttributes(ds) {
102 return ds.existingInstance.updateAttributes({name: 'changed'});
103 },
104
105 function prototypeDelete(ds) {
106 return ds.existingInstance.delete();
107 },
108
109 function deleteAll(ds) {
110 return ds.TestModel.deleteAll({name: ds.existingInstance.name});
111 },
112];
113/* eslint-enable camelcase */
114
115let p = setupTestModels();
116operations.forEach(function(op) {
117 p = p.then(runner(op));
118});
119
120p.then(report, function(err) { console.error(err.stack); });
121
122function createOptimizedDataSource() {
123 const ds = new DataSource({connector: Memory});
124 ds.name = 'Optimized';
125 return ds;
126}
127
128function createUnoptimizedDataSource() {
129 const ds = new DataSource({connector: Memory});
130 ds.name = 'Unoptimized';
131
132 // disable optimized methods
133 ds.connector.updateOrCreate = false;
134 ds.connector.findOrCreate = false;
135 ds.connector.replaceOrCreate = false;
136
137 return ds;
138}
139
140function setupTestModels() {
141 dataSources.forEach(function setupOnDataSource(ds) {
142 const TestModel = ds.TestModel = ds.createModel('TestModel', {
143 id: {type: String, id: true, default: uid},
144 name: {type: String, required: true},
145 extra: {type: String, required: false},
146 });
147 });
148 return Promise.resolve();
149}
150
151function uid() {
152 lastId += 1;
153 return '' + lastId;
154}
155
156function runner(fn) {
157 return function() {
158 let res = Promise.resolve();
159 dataSources.forEach(function(ds) {
160 res = res.then(function() {
161 return resetStorage(ds);
162 }).then(function() {
163 observedContexts.push({
164 operation: fn.name,
165 connector: ds.name,
166 hooks: {},
167 });
168 return fn(ds);
169 });
170 });
171 return res;
172 };
173}
174
175function resetStorage(ds) {
176 const TestModel = ds.TestModel;
177 HOOK_NAMES.forEach(function(hook) {
178 TestModel.clearObservers(hook);
179 });
180 return TestModel.deleteAll()
181 .then(function() {
182 return TestModel.create({name: 'first'});
183 })
184 .then(function(instance) {
185 // Look it up from DB so that default values are retrieved
186 return TestModel.findById(instance.id);
187 })
188 .then(function(instance) {
189 ds.existingInstance = instance;
190 return TestModel.create({name: 'second'});
191 })
192 .then(function() {
193 HOOK_NAMES.forEach(function(hook) {
194 TestModel.observe(hook, function(ctx, next) {
195 const row = observedContexts[observedContexts.length - 1];
196 row.hooks[hook] = Object.keys(ctx);
197 next();
198 });
199 });
200 });
201}
202
203function report() {
204 console.log('<style>');
205 console.log('td { font-family: "monospace": }');
206 console.log('td, th {');
207 console.log(' vertical-align: text-top;');
208 console.log(' padding: 0.5em;');
209 console.log(' border-bottom: 1px solid gray;');
210 console.log('</style>');
211
212 // merge rows where Optimized and Unoptimized produce the same context
213 observedContexts.forEach(function(row, ix) {
214 if (!ix) return;
215 const last = observedContexts[ix - 1];
216 if (row.operation != last.operation) return;
217 if (JSON.stringify(row.hooks) !== JSON.stringify(last.hooks)) return;
218 last.merge = true;
219 row.skip = true;
220 });
221
222 console.log('<!-- ==== BEGIN DATA ==== -->\n');
223 console.log('<table><thead><tr>\n <th></th>');
224 HOOK_NAMES.forEach(function(h) { console.log(' <th>' + h + '</th>'); });
225 console.log('</tr></thead><tbody>');
226
227 observedContexts.forEach(function(row) {
228 if (row.skip) return;
229 let caption = row.operation;
230 if (!row.merge) caption += ' (' + row.connector + ')';
231 console.log('<tr><th>' + caption + '</th>');
232 HOOK_NAMES.forEach(function(h) {
233 const text = row.hooks[h] ? row.hooks[h].join('<br/>') : '';
234 console.log(' <td>' + text + '</td>');
235 });
236 console.log('</tr>');
237 });
238 console.log('</table>');
239}