shuttlepro-shared 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,85 +1,258 @@
1
- const { getRedisData, setRedisData } = require("../../config/redis");
2
- const Label = require("../../models/Label");
1
+ // const { getRedisData, setRedisData } = require("../../config/redis");
2
+ // const Label = require("../../models/Label");
3
3
 
4
- const CACHE_KEY_ALL = "labels_all";
5
-
6
- const getCachedLabels = async () => {
7
- let labels = await getRedisData(CACHE_KEY_ALL);
8
- if (!labels) {
9
- labels = await Label.find().lean().exec();
10
- await setRedisData(CACHE_KEY_ALL, JSON.stringify(labels));
11
- } else {
12
- labels = JSON.parse(labels);
13
- }
14
- return labels;
15
- };
4
+ // const CACHE_KEY_ALL = "labels_all";
16
5
 
17
- const updateCachedLabels = async () => {
18
- const labels = await Label.find().lean().exec();
19
- await setRedisData(CACHE_KEY_ALL, JSON.stringify(labels));
20
- };
6
+ // const getCachedLabels = async () => {
7
+ // let labels = await getRedisData(CACHE_KEY_ALL);
8
+ // if (!labels) {
9
+ // labels = await Label.find().lean().exec();
10
+ // await setRedisData(CACHE_KEY_ALL, JSON.stringify(labels));
11
+ // } else {
12
+ // labels = JSON.parse(labels);
13
+ // }
14
+ // return labels;
15
+ // };
21
16
 
22
- const createLabel = async (data) => {
23
- const newLabel = new Label(data);
24
- const savedLabel = await newLabel.save();
25
- await updateCachedLabels();
26
- return savedLabel;
27
- };
17
+ // const updateCachedLabels = async () => {
18
+ // const labels = await Label.find().lean().exec();
19
+ // await setRedisData(CACHE_KEY_ALL, JSON.stringify(labels));
20
+ // };
28
21
 
29
- const findLabelById = async (id) => {
30
- const labels = await getCachedLabels();
31
- return labels.find((l) => l._id.toString() === id) || null;
32
- };
22
+ // const createLabel = async (data) => {
23
+ // const newLabel = new Label(data);
24
+ // const savedLabel = await newLabel.save();
25
+ // await updateCachedLabels();
26
+ // return savedLabel;
27
+ // };
28
+
29
+ // const findLabelById = async (id) => {
30
+ // const labels = await getCachedLabels();
31
+ // return labels.find((l) => l._id.toString() === id) || null;
32
+ // };
33
+
34
+ // const findLabel = async (filter) => {
35
+ // const labels = await getCachedLabels();
36
+ // return (
37
+ // labels.find((l) =>
38
+ // Object.entries(filter).every(([key, value]) => l[key] === value)
39
+ // ) || null
40
+ // );
41
+ // };
42
+
43
+ // const findAllLabels = async (filter = {}) => {
44
+ // const labels = await getCachedLabels();
45
+ // return labels.filter((l) =>
46
+ // Object.entries(filter).every(([key, value]) => l[key] === value)
47
+ // );
48
+ // };
49
+
50
+ // const updateLabelById = async (id, data) => {
51
+ // const updatedLabel = await Label.findByIdAndUpdate(id, data, {
52
+ // new: true,
53
+ // }).exec();
54
+ // if (updatedLabel) {
55
+ // await updateCachedLabels();
56
+ // }
57
+ // return updatedLabel;
58
+ // };
59
+ // const updateLabel = async (filter, data) => {
60
+ // const updatedLabel = await Label.findOneAndUpdate(filter, data, {
61
+ // new: true,
62
+ // }).exec();
63
+ // if (updatedLabel) {
64
+ // await updateCachedLabels();
65
+ // }
66
+ // return updatedLabel;
67
+ // };
68
+
69
+ // const deleteLabel = async (id) => {
70
+ // const deletedLabel = await Label.findByIdAndDelete(id).exec();
71
+ // if (deletedLabel) {
72
+ // await updateCachedLabels();
73
+ // }
74
+ // return deletedLabel;
75
+ // };
33
76
 
34
- const findLabel = async (filter) => {
35
- const labels = await getCachedLabels();
36
- return (
37
- labels.find((l) =>
38
- Object.entries(filter).every(([key, value]) => l[key] === value)
39
- ) || null
77
+ // module.exports = {
78
+ // createLabel,
79
+ // findLabelById,
80
+ // findLabel,
81
+ // findAllLabels,
82
+ // updateLabel,
83
+ // deleteLabel,
84
+ // updateLabelById,
85
+ // };
86
+
87
+ const R = require("ramda");
88
+ const Label = require("../../models/Label");
89
+ const {
90
+ setRedisData,
91
+ getRedisData,
92
+ deleteRedisData,
93
+ } = require("../../config/redis");
94
+
95
+ // Configuration as a pure function
96
+ const createConfig = (env) => ({
97
+ cache: {
98
+ keys: {
99
+ ALL_LABELS: "labels:all",
100
+ WORKSPACE_LABELS: R.memoizeWith(
101
+ R.identity,
102
+ (workspaceId) => `labels:workspace:${workspaceId}`
103
+ ),
104
+ LABEL_DETAILS: R.memoizeWith(
105
+ R.identity,
106
+ (labelId) => `labels:details:${labelId}`
107
+ ),
108
+ },
109
+ expiry: 60 * 60, // 1 hour
110
+ },
111
+ });
112
+
113
+ // Pure query construction function
114
+ const constructQuery = R.curry((baseQuery, additionalQuery) =>
115
+ R.mergeDeepRight(baseQuery, additionalQuery)
116
+ );
117
+
118
+ // Functional label repository
119
+ const createLabelRepository = (dependencies) => {
120
+ const { Label, redisConfig } = dependencies;
121
+
122
+ // Pure function for generating cache key
123
+ const generateCacheKey = R.pipe(
124
+ R.toPairs,
125
+ R.map(([key, value]) => `${key}:${JSON.stringify(value)}`),
126
+ R.join("|"),
127
+ R.concat("labels:query:")
40
128
  );
41
- };
42
129
 
43
- const findAllLabels = async (filter = {}) => {
44
- const labels = await getCachedLabels();
45
- return labels.filter((l) =>
46
- Object.entries(filter).every(([key, value]) => l[key] === value)
130
+ // Caching Higher-Order Function
131
+ const withCache = R.curry(async (cacheKey, fetchFn, forceFresh = false) => {
132
+ if (!forceFresh) {
133
+ const cachedData = await getRedisData(cacheKey);
134
+ if (cachedData) return cachedData;
135
+ }
136
+
137
+ const data = await fetchFn();
138
+
139
+ if (data && data.length) {
140
+ await setRedisData(cacheKey, data, null, redisConfig.cache.expiry);
141
+ }
142
+
143
+ return data;
144
+ });
145
+
146
+ // Pure validation function
147
+ const validateLabel = R.curry((existingLabels, newLabel) => {
148
+ const isDuplicate = R.any(
149
+ (label) =>
150
+ label.title.toLowerCase() === newLabel.title.toLowerCase() &&
151
+ R.equals(label.workspaceId, newLabel.workspaceId)
152
+ )(existingLabels);
153
+
154
+ if (isDuplicate) {
155
+ throw new Error("Label with this title already exists");
156
+ }
157
+
158
+ return newLabel;
159
+ });
160
+
161
+ // Functional CRUD Operations
162
+ const createLabel = async (labelData) => {
163
+ // Compose validation and creation
164
+ const create = R.pipe(
165
+ (label) => label.save(),
166
+ validateLabel(await Label.find()),
167
+ Label.hydrate
168
+ );
169
+
170
+ return create(labelData);
171
+ };
172
+
173
+ // Advanced finding with functional composition
174
+ const findLabels = R.curry(async (options = {}, query = {}) => {
175
+ const {
176
+ lean = true,
177
+ populate = [],
178
+ sort = { createdAt: -1 },
179
+ limit = 100,
180
+ } = options;
181
+
182
+ // Compose query construction and execution
183
+ const executeQuery = R.pipe(
184
+ R.when(R.always(lean), R.map(R.prop("toObject"))),
185
+ (labels) => (populate.length ? Label.populate(labels, populate) : labels),
186
+ (labels) => R.take(limit, labels),
187
+ (labels) =>
188
+ R.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt), labels)
189
+ );
190
+
191
+ // Use cache wrapper
192
+ const cachedFind = withCache(
193
+ generateCacheKey(query),
194
+ () => Label.find(query).exec(),
195
+ false
196
+ );
197
+
198
+ return executeQuery(await cachedFind());
199
+ });
200
+
201
+ // Functional update with immutable updates
202
+ const updateLabel = R.curry(async (labelId, updateData) => {
203
+ const update = R.pipe(
204
+ R.tap(async (updatedLabel) => {
205
+ // Update cache atomically
206
+ await setRedisData(
207
+ redisConfig.cache.keys.LABEL_DETAILS(labelId),
208
+ updatedLabel.toObject()
209
+ );
210
+ }),
211
+ (label) => label.save(),
212
+ (label) => {
213
+ // Immutable update
214
+ const updatedFields = R.mergeDeepRight(label.toObject(), updateData);
215
+ return Label.hydrate(updatedFields);
216
+ },
217
+ () => Label.findById(labelId)
218
+ );
219
+
220
+ return update();
221
+ });
222
+
223
+ // Functional delete with side effect management
224
+ const deleteLabel = async (labelId) => {
225
+ const performDelete = R.pipe(
226
+ R.tap(async () => {
227
+ // Clear all related caches
228
+ await deleteRedisData(redisConfig.cache.keys.LABEL_DETAILS(labelId));
229
+ }),
230
+ (label) => label.remove(),
231
+ () => Label.findById(labelId)
232
+ );
233
+
234
+ return performDelete();
235
+ };
236
+
237
+ // Advanced filtering with functional transformations
238
+ const filterLabels = R.curry((filterFn, labels) =>
239
+ R.filter(filterFn, labels)
47
240
  );
48
- };
49
241
 
50
- const updateLabelById = async (id, data) => {
51
- const updatedLabel = await Label.findByIdAndUpdate(id, data, {
52
- new: true,
53
- }).exec();
54
- if (updatedLabel) {
55
- await updateCachedLabels();
56
- }
57
- return updatedLabel;
58
- };
59
- const updateLabel = async (filter, data) => {
60
- const updatedLabel = await Label.findOneAndUpdate(filter, data, {
61
- new: true,
62
- }).exec();
63
- if (updatedLabel) {
64
- await updateCachedLabels();
65
- }
66
- return updatedLabel;
242
+ // Public API
243
+ return {
244
+ createLabel,
245
+ findLabels,
246
+ updateLabel,
247
+ deleteLabel,
248
+ filterLabels,
249
+ };
67
250
  };
68
251
 
69
- const deleteLabel = async (id) => {
70
- const deletedLabel = await Label.findByIdAndDelete(id).exec();
71
- if (deletedLabel) {
72
- await updateCachedLabels();
73
- }
74
- return deletedLabel;
252
+ // Dependency Injection
253
+ const dependencies = {
254
+ Label,
255
+ redisConfig: createConfig(process.env),
75
256
  };
76
257
 
77
- module.exports = {
78
- createLabel,
79
- findLabelById,
80
- findLabel,
81
- findAllLabels,
82
- updateLabel,
83
- deleteLabel,
84
- updateLabelById,
85
- };
258
+ module.exports = createLabelRepository(dependencies);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shuttlepro-shared",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -16,13 +16,14 @@
16
16
  "@babel/plugin-proposal-decorators": "^7.22.5",
17
17
  "@babel/preset-env": "^7.22.5",
18
18
  "@babel/register": "^7.22.5",
19
- "redis": "^4.6.14",
19
+ "bull": "^4.10.4",
20
+ "cors": "^2.8.5",
20
21
  "express": "^4.17.1",
22
+ "helmet": "^8.0.0",
21
23
  "jsonwebtoken": "^9.0.2",
22
24
  "mongoose": "^8.7.1",
23
- "cors": "^2.8.5",
24
- "helmet": "^8.0.0",
25
- "bull": "^4.10.4",
25
+ "ramda": "^0.30.1",
26
+ "redis": "^4.6.14",
26
27
  "socket.io": "^4.8.1"
27
28
  }
28
29
  }