@desmat/redis-store 1.0.4 → 1.2.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/dist/example.js +119 -10
- package/dist/index.js +40 -36
- package/dist/vice-schema.d.ts +1 -0
- package/dist/vice-schema.js +440 -0
- package/dist/workout-schema.d.ts +68 -0
- package/dist/workout-schema.js +180 -0
- package/package.json +1 -1
package/dist/example.js
CHANGED
|
@@ -7,22 +7,131 @@ const index_1 = __importDefault(require("./index"));
|
|
|
7
7
|
require('dotenv').config();
|
|
8
8
|
const ThingOptions = {
|
|
9
9
|
lookups: {
|
|
10
|
-
user: "createdBy",
|
|
10
|
+
user: "createdBy",
|
|
11
|
+
category: "category", // enable .find({ category: <CATEGORYID> })
|
|
11
12
|
},
|
|
12
13
|
};
|
|
13
14
|
// with environment variables KV_REST_API_URL and KV_REST_API_TOKEN
|
|
14
15
|
// or `url` and `token` keys provided to RedisStore constructor
|
|
16
|
+
const debug = true;
|
|
15
17
|
const store = {
|
|
16
|
-
users: new index_1.default({ key: "user" }),
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// things: new RedisStore<Thing>({ key: "thing", options: ThingOptions }),
|
|
18
|
+
users: new index_1.default({ key: "example-user", debug }),
|
|
19
|
+
categories: new index_1.default({ key: "example-category", setKey: "example-categories", debug }),
|
|
20
|
+
things: new index_1.default({ key: "example-thing", options: ThingOptions, debug }),
|
|
20
21
|
};
|
|
22
|
+
async function cleanup() {
|
|
23
|
+
// cleanup all from previous session
|
|
24
|
+
let [users, categories, things,] = await Promise.all([
|
|
25
|
+
store.users.ids({ scan: "*" }),
|
|
26
|
+
store.categories.ids({ scan: "*" }),
|
|
27
|
+
store.things.ids({ scan: "*" }),
|
|
28
|
+
]);
|
|
29
|
+
// console.log("users to delete", { usersToDelete, postsToDelete, userPostsToDelete });
|
|
30
|
+
return Promise.all([
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
...Array.from(users).map((id) => store.users.delete(id, { hardDelete: true })),
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
...Array.from(categories).map((id) => store.categories.delete(id, { hardDelete: true })),
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
...Array.from(things).map((id) => store.things.delete(id, { hardDelete: true })),
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
21
39
|
(async function () {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
// fully cleanup previous session
|
|
41
|
+
// await cleanup();
|
|
42
|
+
// create users
|
|
43
|
+
const users = await Promise.all([
|
|
44
|
+
store.users.create({ name: "User One" }),
|
|
45
|
+
store.users.create({ name: "User Two" }),
|
|
46
|
+
]);
|
|
47
|
+
// Redis commands executed:
|
|
48
|
+
// JSON.SET user:<UUID> $ '{ "id": "<UUID>", "createdAt": <TIMEINMILLIS>, "name": "User One" }'
|
|
49
|
+
// ZADD users <TIMEINMILLIS> <UUID>
|
|
50
|
+
// ...
|
|
51
|
+
console.log("users", users);
|
|
52
|
+
// users (2 entries): [
|
|
53
|
+
// { id: '<UUID>', createdAt: <TIMEINMILLIS>, name: 'User One' },
|
|
54
|
+
// ...
|
|
55
|
+
// ]
|
|
56
|
+
// create categories and things
|
|
57
|
+
const categories = await Promise.all([
|
|
58
|
+
store.categories.create({ id: "category1", name: "Category One" }),
|
|
59
|
+
store.categories.create({ id: "category2", name: "Category Two" }),
|
|
60
|
+
]);
|
|
61
|
+
const things = await Promise.all([
|
|
62
|
+
store.things.create({
|
|
63
|
+
createdBy: users[0].id,
|
|
64
|
+
category: categories[0].id,
|
|
65
|
+
label: "A thing for user one",
|
|
66
|
+
}),
|
|
67
|
+
store.things.create({
|
|
68
|
+
createdBy: users[0].id,
|
|
69
|
+
category: categories[0].id,
|
|
70
|
+
label: "Another thing for user one",
|
|
71
|
+
}),
|
|
72
|
+
store.things.create({
|
|
73
|
+
createdBy: users[0].id,
|
|
74
|
+
category: categories[1].id,
|
|
75
|
+
label: "Yet another thing for user one",
|
|
76
|
+
}),
|
|
77
|
+
store.things.create({
|
|
78
|
+
createdBy: users[1].id,
|
|
79
|
+
category: categories[1].id,
|
|
80
|
+
label: "A thing for user two",
|
|
81
|
+
}),
|
|
82
|
+
]);
|
|
83
|
+
// Adding additional sets for lookup:
|
|
84
|
+
// JSON.SET thing:<UUID> $ '{ "id": "<UUID>", "createdAt": <TIMEINMILLIS>, "createdBy": "<USER_UUID>", "categoryId": "<CATEGORY_UUID>", label": "Another thing for user one" }'
|
|
85
|
+
// ZADD things <TIMEINMILLIS> <UUID>
|
|
86
|
+
// ZADD things:user:<USER_UUID> <TIMEINMILLIS> <UUID>
|
|
87
|
+
// ZADD things:category:<CATEGORY_UUID> <TIMEINMILLIS> <UUID>
|
|
88
|
+
// ...
|
|
89
|
+
console.log("things", things);
|
|
90
|
+
// things (4 entries): [
|
|
91
|
+
// {
|
|
92
|
+
// id: '<UUID>',
|
|
93
|
+
// createdAt: <TIMEINMILLIS>,
|
|
94
|
+
// createdBy: '<USER_UUID>',
|
|
95
|
+
// category: `<CATEGORY_UUID>',
|
|
96
|
+
// label: 'A thing for user one'
|
|
97
|
+
// },
|
|
98
|
+
// ...
|
|
99
|
+
// ]
|
|
100
|
+
// all things
|
|
101
|
+
const allThings = await store.things.find();
|
|
102
|
+
// Get thing ids from set of all things then pull their values:
|
|
103
|
+
// ZRANGE things 0 -1 REV
|
|
104
|
+
// JSON.MGET thing:<THING1_UUID> thing:<THING2_UUID> ...
|
|
105
|
+
console.log("allThings", allThings); // 4 entries
|
|
106
|
+
// latest things from first user
|
|
107
|
+
const latestUserThings = await store.things.find({ user: users[0].id });
|
|
108
|
+
// Get thing ids from set of user lookup things then pull their values:
|
|
109
|
+
// ZRANGE things:user:<USER_UUID> 0 -1 REV
|
|
110
|
+
// JSON.MGET thing:<THING1_UUID> thing:<THING2_UUID> thing:<THING3_UUID>
|
|
111
|
+
console.log("latestUserThings", latestUserThings); // 3 entries
|
|
112
|
+
// latest things from first user in first category
|
|
113
|
+
const latestUserThingsOfCategory1 = await store.things.find({
|
|
114
|
+
user: users[0].id,
|
|
115
|
+
category: categories[0].id,
|
|
116
|
+
});
|
|
117
|
+
// Get thing ids from both set of user lookup things
|
|
118
|
+
// and category lookup things, calculate intersection,
|
|
119
|
+
// then pull their values:
|
|
120
|
+
// ZRANGE things:user:<USER_UUID> 0 -1 REV
|
|
121
|
+
// ZRANGE things:category:<CATEGORY_UUID> 0 -1 REV
|
|
122
|
+
// JSON.MGET thing:<THING1_UUID> thing:<THING2_UUID> thing:<THING3_UUID>
|
|
123
|
+
console.log("latestUserThingsOfCategory1", latestUserThingsOfCategory1); // 2 entries
|
|
124
|
+
// cleanup from this session (soft delete by default)
|
|
125
|
+
await Promise.all([
|
|
126
|
+
...users.map((user) => store.users.delete(user.id)),
|
|
127
|
+
...categories.map((user) => store.categories.delete(user.id)),
|
|
128
|
+
...things.map((thing) => store.things.delete(thing.id)),
|
|
26
129
|
]);
|
|
27
|
-
|
|
130
|
+
// JSON.SET user:<UUID> $.deletedAt <TIMEINMILLIS>
|
|
131
|
+
// ZREM users <UUID>
|
|
132
|
+
// ...
|
|
133
|
+
// JSON.SET thing:<UUID> $.deletedAt <TIMEINMILLIS>
|
|
134
|
+
// ZREM things <UUID>
|
|
135
|
+
// ZREM testthings:user:<USER_UUID> <UUID>
|
|
136
|
+
// ...
|
|
28
137
|
})();
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
const moment_1 = __importDefault(require("moment"));
|
|
23
23
|
const utils_1 = require("@desmat/utils");
|
|
24
24
|
const redis_1 = require("@upstash/redis");
|
|
25
|
+
// polyfill Set.intersection
|
|
26
|
+
;
|
|
27
|
+
(function () {
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
if (!Set.prototype.intersection) {
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
Set.prototype.intersection = function (other) {
|
|
32
|
+
return new Set(Array.from(this).filter((value) => other.has(value)));
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
25
36
|
class RedisStore {
|
|
26
37
|
constructor({ url, token, key, setKey, options, debug, }) {
|
|
27
38
|
const _url = url || process.env.KV_REST_API_URL;
|
|
@@ -121,52 +132,45 @@ class RedisStore {
|
|
|
121
132
|
if (query.scan) {
|
|
122
133
|
return this.scan(query);
|
|
123
134
|
}
|
|
124
|
-
|
|
125
|
-
const max = min + (query.count || 0) - 1;
|
|
126
|
-
delete query.offset;
|
|
135
|
+
let count = query.count;
|
|
127
136
|
delete query.count;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
throw `redis.find(query) only supports a single query entry pair`;
|
|
137
|
+
if (typeof (count) != "undefined" && typeof (count) != "number") {
|
|
138
|
+
console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.count is not a numberXX`);
|
|
139
|
+
count = undefined;
|
|
132
140
|
}
|
|
133
|
-
let
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
ids = queryVal;
|
|
141
|
+
let offset = query.offset;
|
|
142
|
+
delete query.offset;
|
|
143
|
+
if (typeof (offset) != "undefined" && typeof (offset) != "number") {
|
|
144
|
+
console.warn(`RedisStore.RedisStore<${this.key}>.id WARNING: query.offset must be a numberYY`);
|
|
145
|
+
offset = undefined;
|
|
139
146
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
throw `redis.find(query) query must have key and value`;
|
|
155
|
-
}
|
|
156
|
-
this.debug && console.log(`RedisStore<${this.key}>.ids queried lookup key`, { query, ids });
|
|
147
|
+
const min = offset || 0;
|
|
148
|
+
const max = min + (count || 0) - 1;
|
|
149
|
+
const queryEntries = query && Object.entries(query);
|
|
150
|
+
// .ids() or .ids({})
|
|
151
|
+
if (!(queryEntries === null || queryEntries === void 0 ? void 0 : queryEntries.length)) {
|
|
152
|
+
// get all keys via the index set
|
|
153
|
+
return new Set(await this.redis.zrange(`${this.setKey}`, min, max, { rev: true }));
|
|
154
|
+
}
|
|
155
|
+
// .ids({ foo: "FOO", bar: "BAR", ... })
|
|
156
|
+
const setOfIds = await Promise.all(queryEntries.map(([queryKey, queryVal]) => {
|
|
157
|
+
if (queryVal) {
|
|
158
|
+
// lookup keys via the foos:bar:123 lookup set
|
|
159
|
+
return this.redis.zrange(`${this.setKey}:${queryKey}:${queryVal}`, min, max, { rev: true });
|
|
157
160
|
}
|
|
158
161
|
else {
|
|
159
|
-
|
|
160
|
-
// @ts-ignore
|
|
161
|
-
ids = await this.redis.zrange(`${this.setKey}`, min, max, { rev: true });
|
|
162
|
+
throw `redis.find(query) query must have key and value`;
|
|
162
163
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
164
|
+
}));
|
|
165
|
+
// @ts-ignore
|
|
166
|
+
const ids = setOfIds.reduce((prev, curr) => new Set(curr).intersection(prev || new Set(curr)), undefined);
|
|
167
|
+
this.debug && console.log(`RedisStore<${this.key}>.ids queried lookup key`, { query, setOfIds, ids });
|
|
168
|
+
return ids;
|
|
165
169
|
}
|
|
166
170
|
async find(query = {}) {
|
|
167
171
|
this.debug && console.log(`RedisStore<${this.key}>.find`, { query });
|
|
168
172
|
const keys = Array.isArray(query.id)
|
|
169
|
-
?
|
|
173
|
+
? query.id
|
|
170
174
|
.map((id) => id && this.valueKey(id))
|
|
171
175
|
.filter(Boolean)
|
|
172
176
|
: Array.from(await this.ids(query))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const index_1 = __importDefault(require("./index"));
|
|
7
|
+
require('dotenv').config();
|
|
8
|
+
const debug = true;
|
|
9
|
+
const store = {
|
|
10
|
+
users: new index_1.default({
|
|
11
|
+
key: "ViceUser",
|
|
12
|
+
// options: Options,
|
|
13
|
+
debug,
|
|
14
|
+
}),
|
|
15
|
+
vices: new index_1.default({
|
|
16
|
+
key: "Vice",
|
|
17
|
+
// options: Options,
|
|
18
|
+
debug,
|
|
19
|
+
}),
|
|
20
|
+
categories: new index_1.default({
|
|
21
|
+
key: "ViceCategory",
|
|
22
|
+
setKey: "ViceCategories",
|
|
23
|
+
options: {
|
|
24
|
+
lookups: {
|
|
25
|
+
vice: "vice",
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
debug,
|
|
29
|
+
}),
|
|
30
|
+
types: new index_1.default({
|
|
31
|
+
key: "ViceType",
|
|
32
|
+
options: {
|
|
33
|
+
lookups: {
|
|
34
|
+
vice: "vice",
|
|
35
|
+
category: "categoryId",
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
debug,
|
|
39
|
+
}),
|
|
40
|
+
customTypes: new index_1.default({
|
|
41
|
+
key: "ViceCustomType",
|
|
42
|
+
options: {
|
|
43
|
+
lookups: {
|
|
44
|
+
vice: "vice",
|
|
45
|
+
category: "categoryId",
|
|
46
|
+
user: "userId",
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
debug,
|
|
50
|
+
}),
|
|
51
|
+
entries: new index_1.default({
|
|
52
|
+
key: "ViceEntry",
|
|
53
|
+
setKey: "ViceEntries",
|
|
54
|
+
options: {
|
|
55
|
+
lookups: {
|
|
56
|
+
vice: "viceId",
|
|
57
|
+
type: "typeId",
|
|
58
|
+
user: "createdBy",
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
debug,
|
|
62
|
+
}),
|
|
63
|
+
hourlySummaries: new index_1.default({
|
|
64
|
+
key: "ViceHourlySummary",
|
|
65
|
+
setKey: "ViceHourlySummaries",
|
|
66
|
+
// options: Options,
|
|
67
|
+
debug,
|
|
68
|
+
}),
|
|
69
|
+
dailySummaries: new index_1.default({
|
|
70
|
+
key: "ViceDailySummary",
|
|
71
|
+
setKey: "ViceDailySummaries",
|
|
72
|
+
// options: Options,
|
|
73
|
+
debug,
|
|
74
|
+
}),
|
|
75
|
+
journalEntries: new index_1.default({
|
|
76
|
+
key: "ViceJournalEntry",
|
|
77
|
+
setKey: "ViceJournalEntries",
|
|
78
|
+
// options: JournalEntryOptions,
|
|
79
|
+
debug,
|
|
80
|
+
}),
|
|
81
|
+
insights: new index_1.default({
|
|
82
|
+
key: "ViceInsight",
|
|
83
|
+
// options: InsightOptions,
|
|
84
|
+
debug,
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
async function cleanup() {
|
|
88
|
+
// cleanup all from previous session
|
|
89
|
+
let [vices, categories, types, customTypes, entries, hourlySummaries, dailySummaries, journalEntries, insights,] = await Promise.all([
|
|
90
|
+
store.vices.ids({ scan: "*" }),
|
|
91
|
+
store.categories.ids({ scan: "*" }),
|
|
92
|
+
store.types.ids({ scan: "*" }),
|
|
93
|
+
store.customTypes.ids({ scan: "*" }),
|
|
94
|
+
store.entries.ids({ scan: "*" }),
|
|
95
|
+
store.hourlySummaries.ids({ scan: "*" }),
|
|
96
|
+
store.dailySummaries.ids({ scan: "*" }),
|
|
97
|
+
store.journalEntries.ids({ scan: "*" }),
|
|
98
|
+
store.insights.ids({ scan: "*" }),
|
|
99
|
+
]);
|
|
100
|
+
// console.log("users to delete", { usersToDelete, postsToDelete, userPostsToDelete });
|
|
101
|
+
return Promise.all([
|
|
102
|
+
// @ts-ignore
|
|
103
|
+
...Array.from(vices).map((id) => store.vices.delete(id, { hardDelete: true })),
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
...Array.from(categories).map((id) => store.categories.delete(id, { hardDelete: true })),
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
...Array.from(types).map((id) => store.types.delete(id, { hardDelete: true })),
|
|
108
|
+
// @ts-ignore
|
|
109
|
+
...Array.from(customTypes).map((id) => store.customTypes.delete(id, { hardDelete: true })),
|
|
110
|
+
// @ts-ignore
|
|
111
|
+
...Array.from(entries).map((id) => store.entries.delete(id, { hardDelete: true })),
|
|
112
|
+
// @ts-ignore
|
|
113
|
+
...Array.from(hourlySummaries).map((id) => store.hourlySummaries.delete(id, { hardDelete: true })),
|
|
114
|
+
// @ts-ignore
|
|
115
|
+
...Array.from(dailySummaries).map((id) => store.dailySummaries.delete(id, { hardDelete: true })),
|
|
116
|
+
// @ts-ignore
|
|
117
|
+
...Array.from(journalEntries).map((id) => store.journalEntries.delete(id, { hardDelete: true })),
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
...Array.from(insights).map((id) => store.insights.delete(id, { hardDelete: true })),
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
(async function () {
|
|
123
|
+
await cleanup();
|
|
124
|
+
return;
|
|
125
|
+
const userId = "user_2nnoSDL2mdJD8XiQpYKqBT3pEK5";
|
|
126
|
+
const vices = await Promise.all([
|
|
127
|
+
// store.vices.create({
|
|
128
|
+
// name: "alcohol",
|
|
129
|
+
// unit: "standard drink (14g)",
|
|
130
|
+
// }),
|
|
131
|
+
store.vices.create({
|
|
132
|
+
name: "sugar",
|
|
133
|
+
unit: "grams of net sugar",
|
|
134
|
+
}),
|
|
135
|
+
]);
|
|
136
|
+
console.log("created", { vices });
|
|
137
|
+
const categories = await Promise.all([
|
|
138
|
+
// store.categories.create({
|
|
139
|
+
// vice: vices[0].name,
|
|
140
|
+
// viceId: vices[0].id,
|
|
141
|
+
// name: "beer/cider",
|
|
142
|
+
// }),
|
|
143
|
+
// store.categories.create({
|
|
144
|
+
// vice: vices[0].name,
|
|
145
|
+
// viceId: vices[0].id,
|
|
146
|
+
// name: "wine",
|
|
147
|
+
// }),
|
|
148
|
+
// store.categories.create({
|
|
149
|
+
// vice: vices[0].name,
|
|
150
|
+
// viceId: vices[0].id,
|
|
151
|
+
// name: "spirits/cocktails",
|
|
152
|
+
// }),
|
|
153
|
+
store.categories.create({
|
|
154
|
+
vice: vices[0].name,
|
|
155
|
+
viceId: vices[0].id,
|
|
156
|
+
name: "sugar",
|
|
157
|
+
}),
|
|
158
|
+
store.categories.create({
|
|
159
|
+
vice: vices[0].name,
|
|
160
|
+
viceId: vices[0].id,
|
|
161
|
+
name: "candy",
|
|
162
|
+
}),
|
|
163
|
+
store.categories.create({
|
|
164
|
+
vice: vices[0].name,
|
|
165
|
+
viceId: vices[0].id,
|
|
166
|
+
id: "custom",
|
|
167
|
+
name: "custom",
|
|
168
|
+
}),
|
|
169
|
+
]);
|
|
170
|
+
console.log("created", { categories });
|
|
171
|
+
const types = await Promise.all([
|
|
172
|
+
// store.types.create({
|
|
173
|
+
// vice: vices[0].id,
|
|
174
|
+
// viceId: vices[0].id,
|
|
175
|
+
// category: categories[0].name,
|
|
176
|
+
// categoryId: categories[0].id,
|
|
177
|
+
// name: "beer (small)",
|
|
178
|
+
// units: 0.75,
|
|
179
|
+
// }),
|
|
180
|
+
// store.types.create({
|
|
181
|
+
// vice: vices[0].name,
|
|
182
|
+
// viceId: vices[0].id,
|
|
183
|
+
// category: categories[0].name,
|
|
184
|
+
// categoryId: categories[0].id,
|
|
185
|
+
// name: "beer",
|
|
186
|
+
// units: 1,
|
|
187
|
+
// }),
|
|
188
|
+
// store.types.create({
|
|
189
|
+
// vice: vices[0].name,
|
|
190
|
+
// viceId: vices[0].id,
|
|
191
|
+
// category: categories[0].name,
|
|
192
|
+
// categoryId: categories[0].id,
|
|
193
|
+
// name: "beer (strong)",
|
|
194
|
+
// units: 1.25,
|
|
195
|
+
// }),
|
|
196
|
+
// store.types.create({
|
|
197
|
+
// vice: vices[0].name,
|
|
198
|
+
// viceId: vices[0].id,
|
|
199
|
+
// category: categories[0].name,
|
|
200
|
+
// categoryId: categories[0].id,
|
|
201
|
+
// name: "beer (large)",
|
|
202
|
+
// units: 1.25,
|
|
203
|
+
// }),
|
|
204
|
+
// store.types.create({
|
|
205
|
+
// vice: vices[0].name,
|
|
206
|
+
// viceId: vices[0].id,
|
|
207
|
+
// category: categories[0].name,
|
|
208
|
+
// categoryId: categories[0].id,
|
|
209
|
+
// name: "beer (xlarge/xstrong/large-strong)",
|
|
210
|
+
// units: 1.5,
|
|
211
|
+
// }),
|
|
212
|
+
// store.types.create({
|
|
213
|
+
// vice: vices[0].name,
|
|
214
|
+
// viceId: vices[0].id,
|
|
215
|
+
// category: categories[1].name,
|
|
216
|
+
// categoryId: categories[1].id,
|
|
217
|
+
// name: "wine (2.5oz)",
|
|
218
|
+
// units: 0.5,
|
|
219
|
+
// }),
|
|
220
|
+
// store.types.create({
|
|
221
|
+
// vice: vices[0].name,
|
|
222
|
+
// viceId: vices[0].id,
|
|
223
|
+
// category: categories[1].name,
|
|
224
|
+
// categoryId: categories[1].id,
|
|
225
|
+
// name: "wine (3.5oz)",
|
|
226
|
+
// units: 0.75,
|
|
227
|
+
// }),
|
|
228
|
+
// store.types.create({
|
|
229
|
+
// vice: vices[0].name,
|
|
230
|
+
// viceId: vices[0].id,
|
|
231
|
+
// category: categories[1].name,
|
|
232
|
+
// categoryId: categories[1].id,
|
|
233
|
+
// name: "wine (5oz)",
|
|
234
|
+
// units: 1,
|
|
235
|
+
// }),
|
|
236
|
+
// store.types.create({
|
|
237
|
+
// vice: vices[0].name,
|
|
238
|
+
// viceId: vices[0].id,
|
|
239
|
+
// category: categories[1].name,
|
|
240
|
+
// categoryId: categories[1].id,
|
|
241
|
+
// name: "wine (6.5oz)",
|
|
242
|
+
// units: 1.25,
|
|
243
|
+
// }),
|
|
244
|
+
// store.types.create({
|
|
245
|
+
// vice: vices[0].name,
|
|
246
|
+
// viceId: vices[0].id,
|
|
247
|
+
// category: categories[1].name,
|
|
248
|
+
// categoryId: categories[1].id,
|
|
249
|
+
// name: "wine (7.5oz)",
|
|
250
|
+
// units: 1.5,
|
|
251
|
+
// }),
|
|
252
|
+
// store.types.create({
|
|
253
|
+
// vice: vices[0].name,
|
|
254
|
+
// viceId: vices[0].id,
|
|
255
|
+
// category: categories[1].name,
|
|
256
|
+
// categoryId: categories[1].id,
|
|
257
|
+
// name: "wine (9oz)",
|
|
258
|
+
// units: 1.75,
|
|
259
|
+
// }),
|
|
260
|
+
// store.types.create({
|
|
261
|
+
// vice: vices[0].name,
|
|
262
|
+
// viceId: vices[0].id,
|
|
263
|
+
// category: categories[1].name,
|
|
264
|
+
// categoryId: categories[1].id,
|
|
265
|
+
// name: "wine (500ml bottle)",
|
|
266
|
+
// units: 3.5,
|
|
267
|
+
// }),
|
|
268
|
+
// store.types.create({
|
|
269
|
+
// vice: vices[0].name,
|
|
270
|
+
// viceId: vices[0].id,
|
|
271
|
+
// category: categories[1].name,
|
|
272
|
+
// categoryId: categories[1].id,
|
|
273
|
+
// name: "wine (750ml bottle)",
|
|
274
|
+
// units: 5,
|
|
275
|
+
// }),
|
|
276
|
+
// store.types.create({
|
|
277
|
+
// vice: vices[0].name,
|
|
278
|
+
// viceId: vices[0].id,
|
|
279
|
+
// category: categories[2].name,
|
|
280
|
+
// categoryId: categories[2].id,
|
|
281
|
+
// name: "spirit (1oz)",
|
|
282
|
+
// units: 0.75,
|
|
283
|
+
// }),
|
|
284
|
+
// store.types.create({
|
|
285
|
+
// vice: vices[0].name,
|
|
286
|
+
// viceId: vices[0].id,
|
|
287
|
+
// category: categories[2].name,
|
|
288
|
+
// categoryId: categories[2].id,
|
|
289
|
+
// name: "spirit (1.5oz)",
|
|
290
|
+
// units: 1,
|
|
291
|
+
// }),
|
|
292
|
+
// store.types.create({
|
|
293
|
+
// vice: vices[0].name,
|
|
294
|
+
// viceId: vices[0].id,
|
|
295
|
+
// category: categories[2].name,
|
|
296
|
+
// categoryId: categories[2].id,
|
|
297
|
+
// name: "spirit (2oz)",
|
|
298
|
+
// units: 1.25,
|
|
299
|
+
// }),
|
|
300
|
+
// store.types.create({
|
|
301
|
+
// vice: vices[0].name,
|
|
302
|
+
// viceId: vices[0].id,
|
|
303
|
+
// category: categories[2].name,
|
|
304
|
+
// categoryId: categories[2].id,
|
|
305
|
+
// name: "cocktail",
|
|
306
|
+
// units: 1.5,
|
|
307
|
+
// }),
|
|
308
|
+
store.types.create({
|
|
309
|
+
vice: vices[0].name,
|
|
310
|
+
viceId: vices[0].id,
|
|
311
|
+
category: categories[0].name,
|
|
312
|
+
categoryId: categories[0].id,
|
|
313
|
+
name: "teaspoon/packet of sugar (4g)",
|
|
314
|
+
units: 4,
|
|
315
|
+
}),
|
|
316
|
+
store.types.create({
|
|
317
|
+
vice: vices[0].name,
|
|
318
|
+
viceId: vices[0].id,
|
|
319
|
+
category: categories[0].name,
|
|
320
|
+
categoryId: categories[0].id,
|
|
321
|
+
name: "1g of sugar",
|
|
322
|
+
units: 1,
|
|
323
|
+
}),
|
|
324
|
+
store.types.create({
|
|
325
|
+
vice: vices[0].name,
|
|
326
|
+
viceId: vices[0].id,
|
|
327
|
+
category: categories[0].name,
|
|
328
|
+
categoryId: categories[0].id,
|
|
329
|
+
name: "2g of sugar",
|
|
330
|
+
units: 2,
|
|
331
|
+
}),
|
|
332
|
+
store.types.create({
|
|
333
|
+
vice: vices[0].name,
|
|
334
|
+
viceId: vices[0].id,
|
|
335
|
+
category: categories[0].name,
|
|
336
|
+
categoryId: categories[0].id,
|
|
337
|
+
name: "5g of sugar",
|
|
338
|
+
units: 5,
|
|
339
|
+
}),
|
|
340
|
+
store.types.create({
|
|
341
|
+
vice: vices[0].name,
|
|
342
|
+
viceId: vices[0].id,
|
|
343
|
+
category: categories[0].name,
|
|
344
|
+
categoryId: categories[0].id,
|
|
345
|
+
name: "10g of sugar",
|
|
346
|
+
units: 10,
|
|
347
|
+
}),
|
|
348
|
+
store.types.create({
|
|
349
|
+
vice: vices[0].name,
|
|
350
|
+
viceId: vices[0].id,
|
|
351
|
+
category: categories[0].name,
|
|
352
|
+
categoryId: categories[0].id,
|
|
353
|
+
name: "20g of sugar",
|
|
354
|
+
units: 20,
|
|
355
|
+
}),
|
|
356
|
+
store.types.create({
|
|
357
|
+
vice: vices[0].name,
|
|
358
|
+
viceId: vices[0].id,
|
|
359
|
+
category: categories[0].name,
|
|
360
|
+
categoryId: categories[0].id,
|
|
361
|
+
name: "50g of sugar",
|
|
362
|
+
units: 50,
|
|
363
|
+
}),
|
|
364
|
+
store.types.create({
|
|
365
|
+
vice: vices[0].name,
|
|
366
|
+
viceId: vices[0].id,
|
|
367
|
+
category: categories[1].name,
|
|
368
|
+
categoryId: categories[1].id,
|
|
369
|
+
name: "typical candy bar",
|
|
370
|
+
units: 25,
|
|
371
|
+
}),
|
|
372
|
+
]);
|
|
373
|
+
// console.log("created", { types });
|
|
374
|
+
// const entries = await Promise.all([
|
|
375
|
+
// store.entries.create({
|
|
376
|
+
// ])
|
|
377
|
+
// console.log("created", { types });
|
|
378
|
+
// const entries = await Promise.all([
|
|
379
|
+
// store.entries.create({
|
|
380
|
+
// createdBy: userId,
|
|
381
|
+
// vice: vices[0].id,
|
|
382
|
+
// categoryId: categories[0].id,
|
|
383
|
+
// categoryName: categories[0].name,
|
|
384
|
+
// typeId: types[2].id,
|
|
385
|
+
// units: 1.5,
|
|
386
|
+
// date: "20241107",
|
|
387
|
+
// hour: "14",
|
|
388
|
+
// }),
|
|
389
|
+
// store.entries.create({
|
|
390
|
+
// createdBy: userId,
|
|
391
|
+
// vice: vices[0].id,
|
|
392
|
+
// categoryId: categories[0].id,
|
|
393
|
+
// categoryName: categories[0].name,
|
|
394
|
+
// typeId: types[2].id,
|
|
395
|
+
// units: ,
|
|
396
|
+
// date: "20241107",
|
|
397
|
+
// hour: "",
|
|
398
|
+
// }),
|
|
399
|
+
// store.entries.create({
|
|
400
|
+
// createdBy: userId,
|
|
401
|
+
// vice: vices[0].id,
|
|
402
|
+
// categoryId: categories[0].id,
|
|
403
|
+
// categoryName: categories[0].name,
|
|
404
|
+
// typeId: types[2].id,
|
|
405
|
+
// units: ,
|
|
406
|
+
// date: "20241107",
|
|
407
|
+
// hour: "",
|
|
408
|
+
// }),
|
|
409
|
+
// store.entries.create({
|
|
410
|
+
// createdBy: userId,
|
|
411
|
+
// vice: vices[0].id,
|
|
412
|
+
// categoryId: categories[0].id,
|
|
413
|
+
// categoryName: categories[0].name,
|
|
414
|
+
// typeId: types[3].id,
|
|
415
|
+
// units: 1.5,
|
|
416
|
+
// date: "20241107",
|
|
417
|
+
// hour: "20",
|
|
418
|
+
// }),
|
|
419
|
+
// store.entries.create({
|
|
420
|
+
// createdBy: userId,
|
|
421
|
+
// vice: vices[0].id,
|
|
422
|
+
// categoryId: categories[0].id,
|
|
423
|
+
// categoryName: categories[0].name,
|
|
424
|
+
// typeId: types[2].id,
|
|
425
|
+
// units: 1.25,
|
|
426
|
+
// date: "20241107",
|
|
427
|
+
// hour: "21",
|
|
428
|
+
// }),
|
|
429
|
+
// store.entries.create({
|
|
430
|
+
// createdBy: userId,
|
|
431
|
+
// vice: vices[0].id,
|
|
432
|
+
// categoryId: categories[0].id,
|
|
433
|
+
// categoryName: categories[0].name,
|
|
434
|
+
// typeId: types[3].id,
|
|
435
|
+
// units: 1.5,
|
|
436
|
+
// date: "20241107",
|
|
437
|
+
// hour: "22",
|
|
438
|
+
// }),
|
|
439
|
+
// ])
|
|
440
|
+
})();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export declare const SuggestedExerciseTypes: string[];
|
|
2
|
+
export type Exercise = {
|
|
3
|
+
name: string;
|
|
4
|
+
id: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
createdBy?: string;
|
|
7
|
+
deletedAt?: number;
|
|
8
|
+
deletedBy?: string;
|
|
9
|
+
updatedAt?: number;
|
|
10
|
+
updatedBy?: string;
|
|
11
|
+
prompt?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
instructions?: string[];
|
|
15
|
+
level?: string;
|
|
16
|
+
category?: string;
|
|
17
|
+
directions?: ExerciseDirections;
|
|
18
|
+
variations?: Exercise[];
|
|
19
|
+
};
|
|
20
|
+
export type ExerciseDirections = {
|
|
21
|
+
duration?: number | number[];
|
|
22
|
+
sets?: number | number[];
|
|
23
|
+
reps?: number | number[];
|
|
24
|
+
};
|
|
25
|
+
export type Workout = {
|
|
26
|
+
id: string;
|
|
27
|
+
createdAt: number;
|
|
28
|
+
createdBy?: string;
|
|
29
|
+
deletedAt?: number;
|
|
30
|
+
deletedBy?: string;
|
|
31
|
+
updatedAt?: number;
|
|
32
|
+
updatedBy?: string;
|
|
33
|
+
prompt?: string;
|
|
34
|
+
status?: string;
|
|
35
|
+
name: string;
|
|
36
|
+
exercises?: Exercise[];
|
|
37
|
+
defaultMode?: SessionMode;
|
|
38
|
+
};
|
|
39
|
+
export type WorkoutSession = {
|
|
40
|
+
id: string;
|
|
41
|
+
createdBy?: string;
|
|
42
|
+
createdAt: number;
|
|
43
|
+
deletedAt?: number;
|
|
44
|
+
updatedAt?: number;
|
|
45
|
+
status?: string;
|
|
46
|
+
workout: Workout;
|
|
47
|
+
sets: WorkoutSet[];
|
|
48
|
+
mode?: SessionMode;
|
|
49
|
+
};
|
|
50
|
+
export type WorkoutSet = {
|
|
51
|
+
id?: string;
|
|
52
|
+
createdBy?: string;
|
|
53
|
+
createdAt?: number;
|
|
54
|
+
startedAt?: number;
|
|
55
|
+
stoppedAt?: number;
|
|
56
|
+
status?: string;
|
|
57
|
+
sets?: number;
|
|
58
|
+
reps?: number;
|
|
59
|
+
duration?: number;
|
|
60
|
+
exercise: Exercise;
|
|
61
|
+
offset: number;
|
|
62
|
+
};
|
|
63
|
+
export type SessionMode = {
|
|
64
|
+
countdown: boolean;
|
|
65
|
+
shuffle: boolean;
|
|
66
|
+
repeat: boolean;
|
|
67
|
+
};
|
|
68
|
+
export declare function restore(filename: string): Promise<any>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.restore = exports.SuggestedExerciseTypes = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const moment_1 = __importDefault(require("moment"));
|
|
9
|
+
const index_1 = __importDefault(require("./index"));
|
|
10
|
+
require('dotenv').config();
|
|
11
|
+
const debug = true;
|
|
12
|
+
exports.SuggestedExerciseTypes = [
|
|
13
|
+
"Push Up",
|
|
14
|
+
"Pull Up",
|
|
15
|
+
"Sit Up",
|
|
16
|
+
];
|
|
17
|
+
const store = {
|
|
18
|
+
exercises: new index_1.default({
|
|
19
|
+
key: "workout-exercise",
|
|
20
|
+
setKey: "workout-exercises",
|
|
21
|
+
options: {
|
|
22
|
+
lookups: {
|
|
23
|
+
createdBy: "createdBy",
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
debug,
|
|
27
|
+
}),
|
|
28
|
+
workouts: new index_1.default({
|
|
29
|
+
key: "workout",
|
|
30
|
+
setKey: "workouts",
|
|
31
|
+
options: {
|
|
32
|
+
lookups: {
|
|
33
|
+
createdBy: "createdBy",
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
debug,
|
|
37
|
+
}),
|
|
38
|
+
workoutSessions: new index_1.default({
|
|
39
|
+
key: "workout-session",
|
|
40
|
+
setKey: "workout-sessions",
|
|
41
|
+
options: {
|
|
42
|
+
lookups: {
|
|
43
|
+
createdBy: "createdBy",
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
debug,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
async function cleanup() {
|
|
50
|
+
// cleanup all from previous session
|
|
51
|
+
let [exercises, workouts, workoutSessions,] = await Promise.all([
|
|
52
|
+
store.exercises.ids({ scan: "*" }),
|
|
53
|
+
store.workouts.ids({ scan: "*" }),
|
|
54
|
+
store.workoutSessions.ids({ scan: "*" }),
|
|
55
|
+
]);
|
|
56
|
+
// console.log("users to delete", { usersToDelete, postsToDelete, userPostsToDelete });
|
|
57
|
+
return Promise.all([
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
...Array.from(exercises).map((id) => store.exercises.delete(id, { hardDelete: true })),
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
...Array.from(workouts).map((id) => store.workouts.delete(id, { hardDelete: true })),
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
...Array.from(workoutSessions).map((id) => store.workoutSessions.delete(id, { hardDelete: true })),
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
async function backup() {
|
|
67
|
+
console.log('>> app.services.admin.backup', {});
|
|
68
|
+
const keys = Object.keys(store);
|
|
69
|
+
// .filter((key: string) => {
|
|
70
|
+
// return entities?.length
|
|
71
|
+
// ? entities.includes(key)
|
|
72
|
+
// : haikuIds?.length
|
|
73
|
+
// ? key == "haikus"
|
|
74
|
+
// : true
|
|
75
|
+
// });
|
|
76
|
+
if (!keys)
|
|
77
|
+
throw 'No entities to backup';
|
|
78
|
+
// TODO maybe do in chucks
|
|
79
|
+
const values = await Promise.all(
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
keys.map((key) => store[key].find({ scan: "*" })));
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
const keyValues = Object.fromEntries(await Promise.all(keys
|
|
84
|
+
.map(async (k, i) => {
|
|
85
|
+
const versionedValues = await Promise.all(values[i]
|
|
86
|
+
// .filter((v: any) => v.id == "fab762bf") // just the one for testing
|
|
87
|
+
// .filter((v: any) => v.id == "c43aa3c6") // just the one for testing
|
|
88
|
+
.map(async (currentValue) => {
|
|
89
|
+
const previousVersionIds = Array.from(Array(currentValue.version || 0).keys())
|
|
90
|
+
.map((version) => `${currentValue.id}:${version}`);
|
|
91
|
+
// console.log('>> app.services.admin.backup', { previousVersionIds });
|
|
92
|
+
const previousValues = (previousVersionIds === null || previousVersionIds === void 0 ? void 0 : previousVersionIds.length) > 0
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
? await store[k].find({ id: previousVersionIds })
|
|
95
|
+
: [];
|
|
96
|
+
// console.log('>> app.services.admin.backup', { previousValues });
|
|
97
|
+
return [currentValue, ...previousValues];
|
|
98
|
+
}));
|
|
99
|
+
// console.log('>> app.services.admin.backup', { k, v: versionedValues[i] });
|
|
100
|
+
return [k, versionedValues.flat()];
|
|
101
|
+
})));
|
|
102
|
+
// console.log('>> app.services.admin.backup', { keyValues });
|
|
103
|
+
// return keyValues;
|
|
104
|
+
const p = { name: "workout", version: "1.0.0" }; //require('/package.json');
|
|
105
|
+
const filename = `${p.name}_${p.version}_${(0, moment_1.default)().format("YYYYMMDD_kkmmss")}.json`;
|
|
106
|
+
// const buffer = Buffer.from(JSON.stringify(keyValues), 'utf8');
|
|
107
|
+
// const blob = await put(filename, buffer, {
|
|
108
|
+
// access: 'public',
|
|
109
|
+
// addRandomSuffix: false,
|
|
110
|
+
// });
|
|
111
|
+
const content = JSON.stringify(keyValues);
|
|
112
|
+
(0, fs_1.writeFileSync)(filename, content);
|
|
113
|
+
// return { filename: blob.pathname, size: formatBytes(Buffer.byteLength(buffer)), url: blob.url };
|
|
114
|
+
// console.log("BACKUP", JSON.stringify(keyValues));
|
|
115
|
+
}
|
|
116
|
+
async function restore(filename) {
|
|
117
|
+
console.log('>> app.services.admin.restore', { filename });
|
|
118
|
+
// const res = await fetch(url);
|
|
119
|
+
// // console.log('>> app.services.admin.restore', { res });
|
|
120
|
+
// if (res.status != 200) {
|
|
121
|
+
// console.error(`Error fetching '${url}': ${res.statusText} (${res.status})`)
|
|
122
|
+
// }
|
|
123
|
+
// const data = await res.json();
|
|
124
|
+
// console.log('>> app.services.admin.restore', { data });
|
|
125
|
+
const data = JSON.parse((0, fs_1.readFileSync)(filename).toString());
|
|
126
|
+
console.log('>> app.services.admin.restore', { data });
|
|
127
|
+
// return;
|
|
128
|
+
const result = {};
|
|
129
|
+
await Promise.all(Object.entries(data).map(async ([key, values]) => {
|
|
130
|
+
// console.log('>> app.services.admin.restore', { key, values });
|
|
131
|
+
if (!Array.isArray(values)) {
|
|
132
|
+
console.warn('>> app.services.admin.restore UNEXPECTED VALUES TYPE', { key, values });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// const options = key == "userHaikus" ? UserHaikuSaveOptions : {};
|
|
136
|
+
return await Promise.all(values.map(async (value) => {
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
const record = await store[key].get(value.id);
|
|
139
|
+
const options = value.deprecated || value.deprecatedAt
|
|
140
|
+
? {
|
|
141
|
+
noIndex: true,
|
|
142
|
+
noLookup: true,
|
|
143
|
+
}
|
|
144
|
+
: {};
|
|
145
|
+
if (record) {
|
|
146
|
+
// for now don't restore if already exists
|
|
147
|
+
result[`${key}_skipped`] = (result[`${key}_skipped`] || 0) + 1;
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
// await store[key].update("(system)", value, options);
|
|
150
|
+
// result[`${key}_updated`] = (result[`${key}_updated`] || 0) + 1;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// @ts-ignore
|
|
154
|
+
await store[key].create(value, options);
|
|
155
|
+
result[`${key}_created`] = (result[`${key}_created`] || 0) + 1;
|
|
156
|
+
}
|
|
157
|
+
}));
|
|
158
|
+
}));
|
|
159
|
+
console.log('>> app.services.admin.restore >>>RESULTS<<<', { result });
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
exports.restore = restore;
|
|
163
|
+
(async function () {
|
|
164
|
+
await cleanup();
|
|
165
|
+
// const ret = await backup();
|
|
166
|
+
const ret = await restore("workout_1.0.0_20241120_142135.json");
|
|
167
|
+
console.log("workout-schema", { ret });
|
|
168
|
+
// let [
|
|
169
|
+
// exercises,
|
|
170
|
+
// workouts,
|
|
171
|
+
// workoutSessions,
|
|
172
|
+
// ] = await Promise.all([
|
|
173
|
+
// store.exercises.ids({ scan: "*" }),
|
|
174
|
+
// store.workouts.ids({ scan: "*" }),
|
|
175
|
+
// store.workoutSessions.ids({ scan: "*" }),
|
|
176
|
+
// ]);
|
|
177
|
+
// console.log("exercises", { exercises });
|
|
178
|
+
// console.log("workouts", { workouts });
|
|
179
|
+
// console.log("workoutSessions", { workoutSessions });
|
|
180
|
+
})();
|