@appsemble/cli 0.20.44 → 0.21.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.
- package/README.md +145 -3
- package/commands/app/create.d.ts +2 -2
- package/commands/app/update.d.ts +2 -2
- package/commands/organization/create.d.ts +1 -1
- package/commands/organization/update.d.ts +1 -1
- package/commands/serve.d.ts +15 -0
- package/commands/serve.js +265 -0
- package/index.js +2 -0
- package/lib/app.d.ts +4 -4
- package/lib/app.js +14 -5
- package/lib/block.d.ts +2 -2
- package/lib/block.js +17 -3
- package/lib/block.test.js +6 -4
- package/lib/coercers.d.ts +2 -2
- package/lib/createServers.d.ts +19 -0
- package/lib/createServers.js +57 -0
- package/lib/getBlockConfigFromTypeScript.js +1 -0
- package/lib/getBlockConfigFromTypeScript.test.js +2 -2
- package/lib/organization.d.ts +1 -1
- package/package.json +23 -6
- package/server/controllers/action.d.ts +6 -0
- package/server/controllers/action.js +8 -0
- package/server/controllers/appMembers.d.ts +2 -0
- package/server/controllers/appMembers.js +4 -0
- package/server/controllers/appMessages.d.ts +2 -0
- package/server/controllers/appMessages.js +4 -0
- package/server/controllers/assets.d.ts +4 -0
- package/server/controllers/assets.js +6 -0
- package/server/controllers/index.d.ts +7 -0
- package/server/controllers/index.js +8 -0
- package/server/controllers/oauth2Provider.d.ts +2 -0
- package/server/controllers/oauth2Provider.js +4 -0
- package/server/controllers/resources.d.ts +7 -0
- package/server/controllers/resources.js +9 -0
- package/server/controllers/teams.d.ts +2 -0
- package/server/controllers/teams.js +4 -0
- package/server/db/index.d.ts +2 -0
- package/server/db/index.js +10 -0
- package/server/db/methods.d.ts +11 -0
- package/server/db/methods.js +232 -0
- package/server/db/methods.test.js +365 -0
- package/server/models/Resource.d.ts +14 -0
- package/server/models/Resource.js +23 -0
- package/server/models/Theme.d.ts +17 -0
- package/server/models/Theme.js +11 -0
- package/server/options/applyAppServiceSecrets.d.ts +3 -0
- package/server/options/applyAppServiceSecrets.js +2 -0
- package/server/options/checkRole.d.ts +1 -0
- package/server/options/checkRole.js +2 -0
- package/server/options/checkRole.test.d.ts +1 -0
- package/server/options/checkRole.test.js +12 -0
- package/server/options/createAppAsset.d.ts +2 -0
- package/server/options/createAppAsset.js +15 -0
- package/server/options/createAppResourcesWithAssets.d.ts +3 -0
- package/server/options/createAppResourcesWithAssets.js +18 -0
- package/server/options/createSettings.d.ts +2 -0
- package/server/options/createSettings.js +15 -0
- package/server/options/createTheme.d.ts +2 -0
- package/server/options/createTheme.js +5 -0
- package/server/options/deleteAppAsset.d.ts +2 -0
- package/server/options/deleteAppAsset.js +15 -0
- package/server/options/deleteAppResource.d.ts +2 -0
- package/server/options/deleteAppResource.js +5 -0
- package/server/options/email.d.ts +1 -0
- package/server/options/email.js +2 -0
- package/server/options/getApp.d.ts +3 -0
- package/server/options/getApp.js +5 -0
- package/server/options/getAppAssets.d.ts +2 -0
- package/server/options/getAppAssets.js +12 -0
- package/server/options/getAppBlockStyles.d.ts +2 -0
- package/server/options/getAppBlockStyles.js +26 -0
- package/server/options/getAppDetails.d.ts +2 -0
- package/server/options/getAppDetails.js +8 -0
- package/server/options/getAppIcon.d.ts +3 -0
- package/server/options/getAppIcon.js +6 -0
- package/server/options/getAppMembers.d.ts +3 -0
- package/server/options/getAppMembers.js +4 -0
- package/server/options/getAppMessages.d.ts +3 -0
- package/server/options/getAppMessages.js +4 -0
- package/server/options/getAppResource.d.ts +3 -0
- package/server/options/getAppResource.js +5 -0
- package/server/options/getAppResources.d.ts +3 -0
- package/server/options/getAppResources.js +5 -0
- package/server/options/getAppScreenshots.d.ts +2 -0
- package/server/options/getAppScreenshots.js +20 -0
- package/server/options/getAppStyles.d.ts +2 -0
- package/server/options/getAppStyles.js +7 -0
- package/server/options/getAppTeams.d.ts +2 -0
- package/server/options/getAppTeams.js +4 -0
- package/server/options/getAppUrl.d.ts +2 -0
- package/server/options/getAppUrl.js +5 -0
- package/server/options/getAppUserInfo.d.ts +3 -0
- package/server/options/getAppUserInfo.js +4 -0
- package/server/options/getBlockAsset.d.ts +2 -0
- package/server/options/getBlockAsset.js +28 -0
- package/server/options/getBlockAssetsPaths.d.ts +2 -0
- package/server/options/getBlockAssetsPaths.js +8 -0
- package/server/options/getBlockMessages.d.ts +2 -0
- package/server/options/getBlockMessages.js +4 -0
- package/server/options/getCsp.d.ts +2 -0
- package/server/options/getCsp.js +13 -0
- package/server/options/getDbUpdated.d.ts +1 -0
- package/server/options/getDbUpdated.js +2 -0
- package/server/options/getHost.d.ts +2 -0
- package/server/options/getHost.js +5 -0
- package/server/options/getTheme.d.ts +2 -0
- package/server/options/getTheme.js +8 -0
- package/server/options/index.d.ts +35 -0
- package/server/options/index.js +36 -0
- package/server/options/options.d.ts +2 -0
- package/server/options/options.js +39 -0
- package/server/options/parseQuery.d.ts +2 -0
- package/server/options/parseQuery.js +82 -0
- package/server/options/parseQuery.test.d.ts +1 -0
- package/server/options/parseQuery.test.js +43 -0
- package/server/options/reloadUser.d.ts +1 -0
- package/server/options/reloadUser.js +2 -0
- package/server/options/sendNotifications.d.ts +1 -0
- package/server/options/sendNotifications.js +2 -0
- package/server/options/updateAppResource.d.ts +3 -0
- package/server/options/updateAppResource.js +5 -0
- package/server/options/verifyResourceActionPermission.d.ts +1 -0
- package/server/options/verifyResourceActionPermission.js +2 -0
- package/server/routes/appRouter/index.d.ts +2 -0
- package/server/routes/appRouter/index.js +4 -0
- package/vitest.config.d.ts +2 -0
- package/vitest.setup.d.ts +1 -0
- package/{jest.setup.js → vitest.setup.js} +1 -1
- /package/{jest.setup.d.ts → server/db/methods.test.d.ts} +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { getDb } from './index.js';
|
|
2
|
+
const defaults = {
|
|
3
|
+
AppId: 1,
|
|
4
|
+
$created: new Date(),
|
|
5
|
+
$updated: new Date(),
|
|
6
|
+
expires: null,
|
|
7
|
+
};
|
|
8
|
+
let appName = 'app';
|
|
9
|
+
export function setAppName(name) {
|
|
10
|
+
appName = name;
|
|
11
|
+
}
|
|
12
|
+
const comparators = ['gt', 'gte', 'lt', 'lte', 'eq', 'ne'];
|
|
13
|
+
const expressions = ['and', 'or'];
|
|
14
|
+
function parseValue(value) {
|
|
15
|
+
const toNumber = Number(value);
|
|
16
|
+
if (!Number.isNaN(toNumber)) {
|
|
17
|
+
return toNumber;
|
|
18
|
+
}
|
|
19
|
+
const toDate = Date.parse(value);
|
|
20
|
+
if (!Number.isNaN(toDate)) {
|
|
21
|
+
return toDate;
|
|
22
|
+
}
|
|
23
|
+
if (value === 'false' || value === 'true') {
|
|
24
|
+
return Boolean(value);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function applyQuery(entity, key, query) {
|
|
29
|
+
if (query == null && entity[key] == null) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (query === undefined && entity[key] === undefined) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const parsedEntityValue = parseValue(entity[key]);
|
|
36
|
+
if (typeof query === 'string') {
|
|
37
|
+
return parsedEntityValue === parseValue(query);
|
|
38
|
+
}
|
|
39
|
+
if (query.or && Array.isArray(query.or)) {
|
|
40
|
+
return query.or.some((subQuery) => applyQuery(entity, key, subQuery));
|
|
41
|
+
}
|
|
42
|
+
if (query.and && Array.isArray(query.and)) {
|
|
43
|
+
return query.and.every((subQuery) => applyQuery(entity, key, subQuery));
|
|
44
|
+
}
|
|
45
|
+
if (query.gt) {
|
|
46
|
+
return parsedEntityValue > parseValue(query.gt);
|
|
47
|
+
}
|
|
48
|
+
if (query.gte) {
|
|
49
|
+
return parsedEntityValue >= parseValue(query.gte);
|
|
50
|
+
}
|
|
51
|
+
if (query.lt) {
|
|
52
|
+
return parsedEntityValue < parseValue(query.lt);
|
|
53
|
+
}
|
|
54
|
+
if (query.lte) {
|
|
55
|
+
return parsedEntityValue <= parseValue(query.lte);
|
|
56
|
+
}
|
|
57
|
+
if (query.eq || query.eq === '') {
|
|
58
|
+
return parsedEntityValue === parseValue(query.eq);
|
|
59
|
+
}
|
|
60
|
+
if (query.ne || query.ne === '') {
|
|
61
|
+
return parsedEntityValue !== parseValue(query.ne);
|
|
62
|
+
}
|
|
63
|
+
return entity[key] === query;
|
|
64
|
+
}
|
|
65
|
+
function checkOr(entity, or) {
|
|
66
|
+
return or.some((subQuery) => {
|
|
67
|
+
if (subQuery.or) {
|
|
68
|
+
return checkOr(entity, subQuery.or);
|
|
69
|
+
}
|
|
70
|
+
if (subQuery.and) {
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
72
|
+
return checkAnd(entity, subQuery.and);
|
|
73
|
+
}
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
75
|
+
return checkEntity(entity, subQuery);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function checkAnd(entity, and) {
|
|
79
|
+
return and.every((subQuery) => {
|
|
80
|
+
if (subQuery.or) {
|
|
81
|
+
return checkOr(entity, subQuery.or);
|
|
82
|
+
}
|
|
83
|
+
if (subQuery.and) {
|
|
84
|
+
return checkAnd(entity, subQuery.and);
|
|
85
|
+
}
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
87
|
+
return checkEntity(entity, subQuery);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function checkEntity(entity, query) {
|
|
91
|
+
if (query.or && Array.isArray(query.or)) {
|
|
92
|
+
return checkOr(entity, query.or);
|
|
93
|
+
}
|
|
94
|
+
if (query.and && Array.isArray(query.and)) {
|
|
95
|
+
return checkAnd(entity, query.and);
|
|
96
|
+
}
|
|
97
|
+
return Object.entries(query).every(([key, value]) => applyQuery(entity, key, value));
|
|
98
|
+
}
|
|
99
|
+
function applyAttributes(entities, attributes) {
|
|
100
|
+
return entities.map((entity) => {
|
|
101
|
+
const result = {};
|
|
102
|
+
for (const attribute of attributes) {
|
|
103
|
+
if (entity[attribute] !== undefined) {
|
|
104
|
+
result[attribute] = entity[attribute];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function applyOrder(entities, order) {
|
|
111
|
+
for (const orderItem of order) {
|
|
112
|
+
entities.sort((a, b) => {
|
|
113
|
+
const [property, direction] = orderItem;
|
|
114
|
+
if (a[property] > b[property]) {
|
|
115
|
+
return direction === 'DESC' ? -1 : 1;
|
|
116
|
+
}
|
|
117
|
+
if (a[property] < b[property]) {
|
|
118
|
+
return direction === 'DESC' ? 1 : -1;
|
|
119
|
+
}
|
|
120
|
+
return 0;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function clearEntity(entity) {
|
|
125
|
+
const { AppId, type, ...cleanPayload } = entity;
|
|
126
|
+
if (!cleanPayload.expires) {
|
|
127
|
+
delete cleanPayload.expires;
|
|
128
|
+
}
|
|
129
|
+
return cleanPayload;
|
|
130
|
+
}
|
|
131
|
+
export const Methods = {
|
|
132
|
+
async create(values, modelDir = '/') {
|
|
133
|
+
const db = await getDb(appName);
|
|
134
|
+
const existing = await this.findAll({}, modelDir);
|
|
135
|
+
const payload = {
|
|
136
|
+
...values,
|
|
137
|
+
id: existing.length + 1,
|
|
138
|
+
...defaults,
|
|
139
|
+
};
|
|
140
|
+
await db.push(modelDir, payload, true);
|
|
141
|
+
return clearEntity(payload);
|
|
142
|
+
},
|
|
143
|
+
async bulkCreate(values, modelDir = '/', override = false) {
|
|
144
|
+
const db = await getDb(appName);
|
|
145
|
+
const existing = await this.findAll({}, modelDir);
|
|
146
|
+
const payload = values.map((value, index) => ({
|
|
147
|
+
...value,
|
|
148
|
+
id: existing.length + index + 1,
|
|
149
|
+
...defaults,
|
|
150
|
+
}));
|
|
151
|
+
await db.push(modelDir, payload, existing.length === 0 ? true : override);
|
|
152
|
+
return payload.map((instance) => clearEntity(instance));
|
|
153
|
+
},
|
|
154
|
+
async findById(id, modelDir = '/') {
|
|
155
|
+
try {
|
|
156
|
+
const db = await getDb(appName);
|
|
157
|
+
const entityIndex = await db.getIndex(modelDir, id);
|
|
158
|
+
return await db.getObject(`${modelDir}[${entityIndex}]`);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
async findOne(query, modelDir = '/') {
|
|
165
|
+
try {
|
|
166
|
+
const db = await getDb(appName);
|
|
167
|
+
const entities = await db.getObject(modelDir);
|
|
168
|
+
let mapped = entities;
|
|
169
|
+
if (query.attributes && query.attributes.length > 0) {
|
|
170
|
+
mapped = applyAttributes(mapped, query.attributes);
|
|
171
|
+
}
|
|
172
|
+
let filtered = mapped;
|
|
173
|
+
if (query.where) {
|
|
174
|
+
filtered = filtered.filter((entity) => checkEntity(entity, query.where));
|
|
175
|
+
}
|
|
176
|
+
const sorted = filtered;
|
|
177
|
+
if (query.order) {
|
|
178
|
+
applyOrder(sorted, query.order);
|
|
179
|
+
}
|
|
180
|
+
return clearEntity(sorted[0]) || null;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
async findAll(query = {}, modelDir = '/') {
|
|
187
|
+
try {
|
|
188
|
+
const db = await getDb(appName);
|
|
189
|
+
const entities = await db.getObject(modelDir);
|
|
190
|
+
const sliced = entities.slice(query.offset || 0, ((query.limit ? query.limit - 1 + (query.offset || 0) : 0) || entities.length) + 1);
|
|
191
|
+
let mapped = sliced;
|
|
192
|
+
if (query.attributes && query.attributes.length > 0) {
|
|
193
|
+
mapped = applyAttributes(mapped, query.attributes);
|
|
194
|
+
}
|
|
195
|
+
let filtered = mapped;
|
|
196
|
+
if (query.where) {
|
|
197
|
+
filtered = filtered.filter((entity) => checkEntity(entity, query.where));
|
|
198
|
+
}
|
|
199
|
+
const sorted = filtered;
|
|
200
|
+
if (query.order) {
|
|
201
|
+
applyOrder(sorted, query.order);
|
|
202
|
+
}
|
|
203
|
+
return sorted.map((instance) => clearEntity(instance));
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
async updateOne(id, values, modelDir = '/') {
|
|
210
|
+
try {
|
|
211
|
+
const db = await getDb(appName);
|
|
212
|
+
const entityIndex = await db.getIndex(modelDir, id);
|
|
213
|
+
const existing = await this.findById(id, modelDir);
|
|
214
|
+
await db.push(`${modelDir}[${entityIndex}]`, { ...existing, ...values }, true);
|
|
215
|
+
return this.findOne({ where: values }, modelDir);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
async deleteOne(id, modelDir = '/') {
|
|
222
|
+
try {
|
|
223
|
+
const db = await getDb(appName);
|
|
224
|
+
const entityIndex = await db.getIndex(modelDir, id);
|
|
225
|
+
return await db.delete(`${modelDir}[${entityIndex}]`);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=methods.js.map
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import globalCacheDir from 'global-cache-dir';
|
|
4
|
+
import { Methods, setAppName } from './methods.js';
|
|
5
|
+
const appName = 'testApp';
|
|
6
|
+
setAppName(appName);
|
|
7
|
+
const cacheDir = await globalCacheDir('appsemble');
|
|
8
|
+
const dbDir = join(cacheDir, appName);
|
|
9
|
+
const dbPath = join(dbDir, 'db.json');
|
|
10
|
+
describe('methods', () => {
|
|
11
|
+
afterAll(async () => {
|
|
12
|
+
await rm(dbDir, { recursive: true, force: true });
|
|
13
|
+
});
|
|
14
|
+
describe('create', () => {
|
|
15
|
+
it('should create a new record and return it', async () => {
|
|
16
|
+
const result = await Methods.create({ name: 'testInstance' }, '/testModel');
|
|
17
|
+
expect(result).toHaveProperty('id');
|
|
18
|
+
expect(result).toHaveProperty('name', 'testInstance');
|
|
19
|
+
});
|
|
20
|
+
it('should add default values to the record', async () => {
|
|
21
|
+
const result = await Methods.create({ name: 'testInstance' }, '/testModel');
|
|
22
|
+
expect(result).toHaveProperty('$created');
|
|
23
|
+
expect(result).toHaveProperty('$updated');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('bulkCreate', () => {
|
|
27
|
+
it('should create multiple new records and return them', async () => {
|
|
28
|
+
const result = await Methods.bulkCreate([{ name: 'testInstance2' }, { name: 'testInstance3' }], '/testModel');
|
|
29
|
+
expect(result).toHaveLength(2);
|
|
30
|
+
expect(result[0]).toHaveProperty('id');
|
|
31
|
+
expect(result[0]).toHaveProperty('name', 'testInstance2');
|
|
32
|
+
expect(result[1]).toHaveProperty('id');
|
|
33
|
+
expect(result[1]).toHaveProperty('name', 'testInstance3');
|
|
34
|
+
});
|
|
35
|
+
it('should add default values to the records', async () => {
|
|
36
|
+
const result = await Methods.bulkCreate([{ name: 'testInstance2' }, { name: 'testInstance3' }], '/testModel');
|
|
37
|
+
expect(result[0]).toHaveProperty('$created');
|
|
38
|
+
expect(result[0]).toHaveProperty('$updated');
|
|
39
|
+
expect(result[1]).toHaveProperty('$created');
|
|
40
|
+
expect(result[1]).toHaveProperty('$updated');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('findById', () => {
|
|
44
|
+
it('should return a record by id', async () => {
|
|
45
|
+
await writeFile(dbPath, '{"testModel":[{"id":1,"name":"testInstance1"}]}');
|
|
46
|
+
const result = await Methods.findById(1, '/testModel');
|
|
47
|
+
expect(result).not.toBeNull();
|
|
48
|
+
expect(result).toHaveProperty('id', 1);
|
|
49
|
+
});
|
|
50
|
+
it('should return null if no record exists with the given id', async () => {
|
|
51
|
+
const result = await Methods.findById(999);
|
|
52
|
+
expect(result).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('findOne', () => {
|
|
56
|
+
it('should return a record that matches the query', async () => {
|
|
57
|
+
await writeFile(dbPath, '{"testModel":[{"id":1,"name":"testInstance1"}]}');
|
|
58
|
+
const result = await Methods.findOne({ where: { name: 'testInstance1' } }, '/testModel');
|
|
59
|
+
expect(result).not.toBeNull();
|
|
60
|
+
expect(result).toHaveProperty('name', 'testInstance1');
|
|
61
|
+
});
|
|
62
|
+
it('should return null if no record matches the query', async () => {
|
|
63
|
+
const result = await Methods.findOne({ where: { name: 'nonexistent' } }, '/testModel');
|
|
64
|
+
expect(result).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('findAll', () => {
|
|
68
|
+
it('should return an empty array when no entities exist', async () => {
|
|
69
|
+
const entities = await Methods.findAll({}, '/');
|
|
70
|
+
expect(entities).toStrictEqual([]);
|
|
71
|
+
});
|
|
72
|
+
it('should return all entities when no query is provided', async () => {
|
|
73
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
74
|
+
const entities = await Methods.findAll({}, '/person');
|
|
75
|
+
expect(entities).toStrictEqual([
|
|
76
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
77
|
+
{ id: 2, name: 'Bob', age: 25 },
|
|
78
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
it('should return all entities with only the specified attributes', async () => {
|
|
82
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
83
|
+
const entities = await Methods.findAll({ attributes: ['name', 'age'] }, '/person');
|
|
84
|
+
expect(entities).toStrictEqual([
|
|
85
|
+
{ name: 'Alice', age: 30 },
|
|
86
|
+
{ name: 'Bob', age: 25 },
|
|
87
|
+
{ name: 'Charlie', age: 35 },
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
describe('should filter entities using where clauses', () => {
|
|
91
|
+
it('that contain and', async () => {
|
|
92
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
93
|
+
const entities = await Methods.findAll({
|
|
94
|
+
where: {
|
|
95
|
+
and: [{ name: { eq: 'Alice' } }, { age: { gt: 20 } }],
|
|
96
|
+
},
|
|
97
|
+
}, '/person');
|
|
98
|
+
expect(entities).toStrictEqual([{ id: 1, name: 'Alice', age: 30 }]);
|
|
99
|
+
});
|
|
100
|
+
it('that contain or', async () => {
|
|
101
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
102
|
+
const entities = await Methods.findAll({
|
|
103
|
+
where: {
|
|
104
|
+
or: [{ name: { eq: 'Alice' } }, { age: { gt: 20 } }],
|
|
105
|
+
},
|
|
106
|
+
}, '/person');
|
|
107
|
+
expect(entities).toStrictEqual([
|
|
108
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
109
|
+
{ id: 2, name: 'Alice', age: 20 },
|
|
110
|
+
{ id: 3, name: 'Bob', age: 25 },
|
|
111
|
+
{ id: 4, name: 'Charlie', age: 35 },
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
describe('that contain a comparison', () => {
|
|
115
|
+
describe('for greater than', () => {
|
|
116
|
+
it('for numbers', async () => {
|
|
117
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
118
|
+
const entitiesGt = await Methods.findAll({
|
|
119
|
+
where: {
|
|
120
|
+
or: [{ age: { gt: 25 } }],
|
|
121
|
+
},
|
|
122
|
+
}, '/person');
|
|
123
|
+
expect(entitiesGt).toStrictEqual([
|
|
124
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
125
|
+
{ id: 4, name: 'Charlie', age: 35 },
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
it('for dates', async () => {
|
|
129
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","birthDate":"2023-07-05T13:18:26.870Z"}]}');
|
|
130
|
+
const entitiesGt = await Methods.findAll({
|
|
131
|
+
where: {
|
|
132
|
+
or: [{ birthDate: { gt: '2023-07-04T13:18:26.870Z' } }],
|
|
133
|
+
},
|
|
134
|
+
}, '/person');
|
|
135
|
+
expect(entitiesGt).toStrictEqual([
|
|
136
|
+
{ id: 1, name: 'Alice', birthDate: new Date('2023-07-05T13:18:26.870Z') },
|
|
137
|
+
]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('for greater than or equal', () => {
|
|
141
|
+
it('for numbers', async () => {
|
|
142
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
143
|
+
const entitiesGte = await Methods.findAll({
|
|
144
|
+
where: {
|
|
145
|
+
or: [{ age: { gte: 25 } }],
|
|
146
|
+
},
|
|
147
|
+
}, '/person');
|
|
148
|
+
expect(entitiesGte).toStrictEqual([
|
|
149
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
150
|
+
{ id: 3, name: 'Bob', age: 25 },
|
|
151
|
+
{ id: 4, name: 'Charlie', age: 35 },
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
it('for dates', async () => {
|
|
155
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","birthDate":"2023-07-05T13:18:26.870Z"},{"id":2,"name":"Bob","birthDate":"2023-07-04T13:18:26.870Z"}]}');
|
|
156
|
+
const entitiesGt = await Methods.findAll({
|
|
157
|
+
where: {
|
|
158
|
+
or: [{ birthDate: { gte: '2023-07-04T13:18:26.870Z' } }],
|
|
159
|
+
},
|
|
160
|
+
}, '/person');
|
|
161
|
+
expect(entitiesGt).toStrictEqual([
|
|
162
|
+
{ id: 1, name: 'Alice', birthDate: new Date('2023-07-05T13:18:26.870Z') },
|
|
163
|
+
{ id: 2, name: 'Bob', birthDate: new Date('2023-07-04T13:18:26.870Z') },
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('for less than', () => {
|
|
168
|
+
it('for numbers', async () => {
|
|
169
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
170
|
+
const entitiesLt = await Methods.findAll({
|
|
171
|
+
where: {
|
|
172
|
+
or: [{ age: { lt: 25 } }],
|
|
173
|
+
},
|
|
174
|
+
}, '/person');
|
|
175
|
+
expect(entitiesLt).toStrictEqual([{ id: 2, name: 'Alice', age: 20 }]);
|
|
176
|
+
});
|
|
177
|
+
it('for dates', async () => {
|
|
178
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","birthDate":"2023-07-05T13:18:26.870Z"}]}');
|
|
179
|
+
const entitiesGt = await Methods.findAll({
|
|
180
|
+
where: {
|
|
181
|
+
or: [{ birthDate: { lt: '2023-07-06T13:18:26.870Z' } }],
|
|
182
|
+
},
|
|
183
|
+
}, '/person');
|
|
184
|
+
expect(entitiesGt).toStrictEqual([
|
|
185
|
+
{ id: 1, name: 'Alice', birthDate: new Date('2023-07-05T13:18:26.870Z') },
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('for less than or equal', () => {
|
|
190
|
+
it('for numbers', async () => {
|
|
191
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
192
|
+
const entitiesLt = await Methods.findAll({
|
|
193
|
+
where: {
|
|
194
|
+
or: [{ age: { lte: 25 } }],
|
|
195
|
+
},
|
|
196
|
+
}, '/person');
|
|
197
|
+
expect(entitiesLt).toStrictEqual([
|
|
198
|
+
{ id: 2, name: 'Alice', age: 20 },
|
|
199
|
+
{ id: 3, name: 'Bob', age: 25 },
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
it('for dates', async () => {
|
|
203
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","birthDate":"2023-07-05T13:18:26.870Z"},{"id":2,"name":"Bob","birthDate":"2023-07-04T13:18:26.870Z"}]}');
|
|
204
|
+
const entitiesGt = await Methods.findAll({
|
|
205
|
+
where: {
|
|
206
|
+
or: [{ birthDate: { lte: '2023-07-05T13:18:26.870Z' } }],
|
|
207
|
+
},
|
|
208
|
+
}, '/person');
|
|
209
|
+
expect(entitiesGt).toStrictEqual([
|
|
210
|
+
{ id: 1, name: 'Alice', birthDate: new Date('2023-07-05T13:18:26.870Z') },
|
|
211
|
+
{ id: 2, name: 'Bob', birthDate: new Date('2023-07-04T13:18:26.870Z') },
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('for equal', () => {
|
|
216
|
+
it('for numbers', async () => {
|
|
217
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
218
|
+
const entitiesEq = await Methods.findAll({
|
|
219
|
+
where: {
|
|
220
|
+
or: [{ age: { eq: 25 } }],
|
|
221
|
+
},
|
|
222
|
+
}, '/person');
|
|
223
|
+
expect(entitiesEq).toStrictEqual([{ id: 3, name: 'Bob', age: 25 }]);
|
|
224
|
+
});
|
|
225
|
+
it('for dates', async () => {
|
|
226
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","birthDate":"2023-07-05T13:18:26.870Z"},{"id":2,"name":"Bob","birthDate":"2023-07-04T13:18:26.870Z"}]}');
|
|
227
|
+
const entitiesGt = await Methods.findAll({
|
|
228
|
+
where: {
|
|
229
|
+
or: [{ birthDate: { eq: '2023-07-04T13:18:26.870Z' } }],
|
|
230
|
+
},
|
|
231
|
+
}, '/person');
|
|
232
|
+
expect(entitiesGt).toStrictEqual([
|
|
233
|
+
{ id: 2, name: 'Bob', birthDate: new Date('2023-07-04T13:18:26.870Z') },
|
|
234
|
+
]);
|
|
235
|
+
});
|
|
236
|
+
it('for boolean values', async () => {
|
|
237
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","married":false},{"id":2,"name":"Bob","married":true}]}');
|
|
238
|
+
const entitiesEq = await Methods.findAll({
|
|
239
|
+
where: {
|
|
240
|
+
or: [{ married: { eq: true } }],
|
|
241
|
+
},
|
|
242
|
+
}, '/person');
|
|
243
|
+
expect(entitiesEq).toStrictEqual([{ id: 2, name: 'Bob', married: true }]);
|
|
244
|
+
});
|
|
245
|
+
it('for strings', async () => {
|
|
246
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]}');
|
|
247
|
+
const entitiesEq = await Methods.findAll({
|
|
248
|
+
where: {
|
|
249
|
+
or: [{ name: { eq: 'Alice' } }],
|
|
250
|
+
},
|
|
251
|
+
}, '/person');
|
|
252
|
+
expect(entitiesEq).toStrictEqual([{ id: 1, name: 'Alice' }]);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe('for not equal', () => {
|
|
256
|
+
it('for numbers', async () => {
|
|
257
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Alice","age":20},{"id":3,"name":"Bob","age":25},{"id":4,"name":"Charlie","age":35}]}');
|
|
258
|
+
const entitiesNe = await Methods.findAll({
|
|
259
|
+
where: {
|
|
260
|
+
or: [{ age: { ne: 25 } }],
|
|
261
|
+
},
|
|
262
|
+
}, '/person');
|
|
263
|
+
expect(entitiesNe).toStrictEqual([
|
|
264
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
265
|
+
{ id: 2, name: 'Alice', age: 20 },
|
|
266
|
+
{ id: 4, name: 'Charlie', age: 35 },
|
|
267
|
+
]);
|
|
268
|
+
});
|
|
269
|
+
it('for dates', async () => {
|
|
270
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","birthDate":"2023-07-05T13:18:26.870Z"},{"id":2,"name":"Bob","birthDate":"2023-07-04T13:18:26.870Z"}]}');
|
|
271
|
+
const entitiesGt = await Methods.findAll({
|
|
272
|
+
where: {
|
|
273
|
+
or: [{ birthDate: { ne: '2023-07-04T13:18:26.870Z' } }],
|
|
274
|
+
},
|
|
275
|
+
}, '/person');
|
|
276
|
+
expect(entitiesGt).toStrictEqual([
|
|
277
|
+
{ id: 1, name: 'Alice', birthDate: new Date('2023-07-05T13:18:26.870Z') },
|
|
278
|
+
]);
|
|
279
|
+
});
|
|
280
|
+
it('for boolean values', async () => {
|
|
281
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","married":false},{"id":2,"name":"Bob","married":true}]}');
|
|
282
|
+
const entitiesEq = await Methods.findAll({
|
|
283
|
+
where: {
|
|
284
|
+
or: [{ married: { ne: true } }],
|
|
285
|
+
},
|
|
286
|
+
}, '/person');
|
|
287
|
+
expect(entitiesEq).toStrictEqual([{ id: 1, name: 'Alice', married: false }]);
|
|
288
|
+
});
|
|
289
|
+
it('for strings', async () => {
|
|
290
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]}');
|
|
291
|
+
const entitiesEq = await Methods.findAll({
|
|
292
|
+
where: {
|
|
293
|
+
or: [{ name: { ne: 'Alice' } }],
|
|
294
|
+
},
|
|
295
|
+
}, '/person');
|
|
296
|
+
expect(entitiesEq).toStrictEqual([{ id: 2, name: 'Bob' }]);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
describe('should sort entities using order clauses', () => {
|
|
302
|
+
it('in ascending order', async () => {
|
|
303
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
304
|
+
const entities = await Methods.findAll({
|
|
305
|
+
order: [['age', 'ASC']],
|
|
306
|
+
}, '/person');
|
|
307
|
+
expect(entities).toStrictEqual([
|
|
308
|
+
{ id: 2, name: 'Bob', age: 25 },
|
|
309
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
310
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
311
|
+
]);
|
|
312
|
+
});
|
|
313
|
+
it('in descending order', async () => {
|
|
314
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
315
|
+
const entities = await Methods.findAll({
|
|
316
|
+
order: [['age', 'DESC']],
|
|
317
|
+
}, '/person');
|
|
318
|
+
expect(entities).toStrictEqual([
|
|
319
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
320
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
321
|
+
{ id: 2, name: 'Bob', age: 25 },
|
|
322
|
+
]);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
it('limits the number of entities returned using the limit option', async () => {
|
|
326
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
327
|
+
const entities = await Methods.findAll({ limit: 2 }, '/person');
|
|
328
|
+
expect(entities).toStrictEqual([
|
|
329
|
+
{ id: 1, name: 'Alice', age: 30 },
|
|
330
|
+
{ id: 2, name: 'Bob', age: 25 },
|
|
331
|
+
]);
|
|
332
|
+
});
|
|
333
|
+
it('skips the specified number of entities using the offset option', async () => {
|
|
334
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
335
|
+
const entities = await Methods.findAll({ offset: 1 }, '/person');
|
|
336
|
+
expect(entities).toStrictEqual([
|
|
337
|
+
{ id: 2, name: 'Bob', age: 25 },
|
|
338
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
339
|
+
]);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
describe('updateOne', () => {
|
|
343
|
+
it('should update an existing record', async () => {
|
|
344
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
345
|
+
const result = await Methods.updateOne(1, { name: 'Jane' }, '/person');
|
|
346
|
+
expect(result).toStrictEqual({
|
|
347
|
+
id: 1,
|
|
348
|
+
name: 'Jane',
|
|
349
|
+
age: 30,
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
describe('deleteOne', () => {
|
|
354
|
+
it('should delete an entity', async () => {
|
|
355
|
+
await writeFile(dbPath, '{"person":[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25},{"id":3,"name":"Charlie","age":35}]}');
|
|
356
|
+
await Methods.deleteOne(1, '/person');
|
|
357
|
+
const entities = await Methods.findAll({}, '/person');
|
|
358
|
+
expect(entities).toStrictEqual([
|
|
359
|
+
{ id: 2, name: 'Bob', age: 25 },
|
|
360
|
+
{ id: 3, name: 'Charlie', age: 35 },
|
|
361
|
+
]);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
//# sourceMappingURL=methods.test.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FindOptions } from '@appsemble/node-utils';
|
|
2
|
+
export declare class Resource {
|
|
3
|
+
id: number;
|
|
4
|
+
$clonable: boolean;
|
|
5
|
+
$created: string;
|
|
6
|
+
$updated: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
static create(values: Record<string, unknown>, type: string): Promise<Resource | null>;
|
|
9
|
+
static bulkCreate(values: Record<string, unknown>[], type: string, override?: boolean): Promise<Resource[] | []>;
|
|
10
|
+
static findById(id: number | string, type: string): Promise<Resource | null>;
|
|
11
|
+
static findAll(query: FindOptions, type: string): Promise<Resource[] | []>;
|
|
12
|
+
static updateOne(id: number | string, values: Record<string, unknown>, type: string): Promise<Resource | null>;
|
|
13
|
+
static deleteOne(id: number | string, type: string): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Methods } from '../db/methods.js';
|
|
2
|
+
const dir = '/resources';
|
|
3
|
+
export class Resource {
|
|
4
|
+
static create(values, type) {
|
|
5
|
+
return Methods.create({ ...values, type }, `${dir}/${type}[]`);
|
|
6
|
+
}
|
|
7
|
+
static bulkCreate(values, type, override = false) {
|
|
8
|
+
return Methods.bulkCreate(values.map((value) => ({ ...value, type })), `${dir}/${type}`, override);
|
|
9
|
+
}
|
|
10
|
+
static findById(id, type) {
|
|
11
|
+
return Methods.findById(id, `${dir}/${type}`);
|
|
12
|
+
}
|
|
13
|
+
static findAll(query, type) {
|
|
14
|
+
return Methods.findAll(query, `${dir}/${type}`);
|
|
15
|
+
}
|
|
16
|
+
static updateOne(id, values, type) {
|
|
17
|
+
return Methods.updateOne(id, values, `${dir}/${type}`);
|
|
18
|
+
}
|
|
19
|
+
static deleteOne(id, type) {
|
|
20
|
+
return Methods.deleteOne(id, `${dir}/${type}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=Resource.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type FindOptions } from '@appsemble/node-utils';
|
|
2
|
+
export declare class Theme {
|
|
3
|
+
bulmaVersion: string;
|
|
4
|
+
primaryColor: string;
|
|
5
|
+
linkColor: string;
|
|
6
|
+
successColor: string;
|
|
7
|
+
infoColor: string;
|
|
8
|
+
warningColor: string;
|
|
9
|
+
dangerColor: string;
|
|
10
|
+
themeColor: string;
|
|
11
|
+
splashColor: string;
|
|
12
|
+
fontFamily: string;
|
|
13
|
+
fontSource: string;
|
|
14
|
+
css: string;
|
|
15
|
+
static create(values: Record<string, unknown>): Promise<Theme | null>;
|
|
16
|
+
static findOne(query: FindOptions): Promise<Theme | null>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Methods } from '../db/methods.js';
|
|
2
|
+
const dir = '/themes';
|
|
3
|
+
export class Theme {
|
|
4
|
+
static create(values) {
|
|
5
|
+
return Methods.create(values, dir);
|
|
6
|
+
}
|
|
7
|
+
static findOne(query) {
|
|
8
|
+
return Methods.findOne(query, dir);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=Theme.js.map
|