@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 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", // enable .find({ user: <USERID> })
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
- haikuUsers: new index_1.default({ key: "haikuuser" }),
18
- userHaikus: new index_1.default({ key: "userhaiku" }),
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
- const [users, haikuUsers, userHaikus] = await Promise.all([
23
- store.users.ids({ scan: "*", count: 999999 }),
24
- store.haikuUsers.ids({ scan: "*", count: 999999 }),
25
- store.userHaikus.ids({ scan: "*", count: 999999 }),
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
- console.log("counts", { users: users.size, haikuUsers: haikuUsers.size, userHaikus: userHaikus.size });
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
- const min = query.offset || 0;
125
- const max = min + (query.count || 0) - 1;
126
- delete query.offset;
135
+ let count = query.count;
127
136
  delete query.count;
128
- const queryEntries = query && Object.entries(query);
129
- // TODO: support more than one
130
- if ((queryEntries === null || queryEntries === void 0 ? void 0 : queryEntries.length) > 1) {
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 ids = [];
134
- const queryEntry = queryEntries && queryEntries[0];
135
- const [queryKey, queryVal] = queryEntry || [];
136
- if (queryKey == "id" && Array.isArray(queryVal)) {
137
- this.debug && console.log(`RedisStore<${this.key}>.ids special case: query is for IDs`, { ids: queryVal });
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
- else {
141
- if (queryKey) {
142
- /* NOT SUPPORTED FOR NOW
143
- if (queryVal == "*") {
144
- // lookup keys via the foos:bars lookup set
145
- keys = (await this.kv.zrange(`${this.setKey}:${queryKey}s`, 0, -1))
146
- // @ts-ignore
147
- .map((key: string) => `${this.key}:${key}`);
148
- } else */ if (queryVal) {
149
- // lookup keys via the foos:bar:123 lookup set
150
- // @ts-ignore
151
- ids = await this.redis.zrange(`${this.setKey}:${queryKey}:${queryVal}`, min, max, { rev: true });
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
- // get all keys via the index set
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
- return new Set(ids);
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
- ? Array.from(await this.ids(query))
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
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desmat/redis-store",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {