@unchainedshop/mongodb 2.5.11 → 2.6.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.
Files changed (46) hide show
  1. package/lib/build-db-indexes.d.ts +8 -0
  2. package/lib/build-db-indexes.js +34 -0
  3. package/lib/build-db-indexes.js.map +1 -0
  4. package/lib/build-sort-option.d.ts +4 -0
  5. package/lib/build-sort-option.js +13 -0
  6. package/lib/build-sort-option.js.map +1 -0
  7. package/lib/check-id.d.ts +4 -0
  8. package/lib/check-id.js +6 -0
  9. package/lib/check-id.js.map +1 -0
  10. package/lib/find-localized-text.d.ts +5 -0
  11. package/lib/find-localized-text.js +45 -0
  12. package/lib/find-localized-text.js.map +1 -0
  13. package/lib/find-preserving-ids.d.ts +2 -0
  14. package/lib/find-preserving-ids.js +33 -0
  15. package/lib/find-preserving-ids.js.map +1 -0
  16. package/lib/generate-db-filter-by-id.d.ts +4 -0
  17. package/lib/generate-db-filter-by-id.js +5 -0
  18. package/lib/generate-db-filter-by-id.js.map +1 -0
  19. package/lib/generate-db-mutations.d.ts +10 -0
  20. package/lib/generate-db-mutations.js +73 -0
  21. package/lib/generate-db-mutations.js.map +1 -0
  22. package/lib/generate-db-object-id.d.ts +7 -0
  23. package/lib/generate-db-object-id.js +25 -0
  24. package/lib/generate-db-object-id.js.map +1 -0
  25. package/lib/initDb.js +32 -0
  26. package/lib/initDb.js.map +1 -0
  27. package/lib/mongodb-index.d.ts +11 -1
  28. package/lib/mongodb-index.js +14 -1
  29. package/lib/mongodb-index.js.map +1 -1
  30. package/package.json +8 -7
  31. package/src/build-db-indexes.ts +53 -0
  32. package/src/build-sort-option.ts +17 -0
  33. package/src/check-id.ts +13 -0
  34. package/src/find-localized-text.ts +63 -0
  35. package/src/find-preserving-ids.ts +44 -0
  36. package/src/generate-db-filter-by-id.ts +9 -0
  37. package/src/generate-db-mutations.ts +93 -0
  38. package/src/generate-db-object-id.ts +24 -0
  39. package/src/initDb.ts +36 -0
  40. package/src/mongodb-index.test.ts +71 -0
  41. package/src/mongodb-index.ts +18 -1
  42. package/lib/db/initDb.js +0 -54
  43. package/lib/db/initDb.js.map +0 -1
  44. package/src/db/initDb.ts +0 -61
  45. package/tests/mongodb-index.test.ts +0 -5
  46. /package/lib/{db/initDb.d.ts → initDb.d.ts} +0 -0
@@ -0,0 +1,8 @@
1
+ import type { Collection, Document, CreateIndexesOptions, IndexDirection } from 'mongodb';
2
+ export type Indexes<T extends Document> = Array<{
3
+ index: {
4
+ [key in keyof T]?: IndexDirection;
5
+ };
6
+ options?: CreateIndexesOptions;
7
+ }>;
8
+ export declare const buildDbIndexes: <T extends Document>(collection: Collection<T>, indexes: Indexes<T>) => Promise<boolean>;
@@ -0,0 +1,34 @@
1
+ import { log, LogLevel } from '@unchainedshop/logger';
2
+ const buildIndexes = (collection, indexes) => Promise.all(indexes.map(async ({ index, options }) => {
3
+ try {
4
+ await collection.createIndex(index, options);
5
+ return false;
6
+ }
7
+ catch (e) {
8
+ log(e, { level: LogLevel.Error });
9
+ return e;
10
+ }
11
+ }));
12
+ export const buildDbIndexes = async (collection, indexes) => {
13
+ let success = true;
14
+ const buildErrors = (await buildIndexes(collection, indexes)).filter(Boolean);
15
+ if (buildErrors.length) {
16
+ // try {
17
+ // const dropError = await collection.dropIndexes();
18
+ // } catch (e) {
19
+ // /* */
20
+ // }
21
+ // if (!dropError) {
22
+ const rebuildErrors = (await buildIndexes(collection, indexes)).filter(Boolean);
23
+ if (rebuildErrors.length) {
24
+ log(`Error building some indexes for ${collection.collectionName}`, {
25
+ level: LogLevel.Error,
26
+ ...rebuildErrors,
27
+ });
28
+ success = false;
29
+ }
30
+ // }
31
+ }
32
+ return success;
33
+ };
34
+ //# sourceMappingURL=build-db-indexes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-db-indexes.js","sourceRoot":"","sources":["../src/build-db-indexes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOtD,MAAM,YAAY,GAAG,CACnB,UAAyB,EACzB,OAAmB,EACY,EAAE,CACjC,OAAO,CAAC,GAAG,CACT,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;IACvC,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC7C,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAClC,OAAO,CAAU,CAAC;IACpB,CAAC;AACH,CAAC,CAAC,CACH,CAAC;AAEJ,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,UAAyB,EACzB,OAAmB,EACnB,EAAE;IACF,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,MAAM,WAAW,GAAG,CAAC,MAAM,YAAY,CAAI,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEjF,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;QACvB,QAAQ;QACR,sDAAsD;QACtD,gBAAgB;QAChB,UAAU;QACV,IAAI;QAEJ,oBAAoB;QACpB,MAAM,aAAa,GAAG,CAAC,MAAM,YAAY,CAAI,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEnF,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC;YACzB,GAAG,CAAC,mCAAmC,UAAU,CAAC,cAAc,EAAE,EAAE;gBAClE,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,GAAG,aAAa;aACjB,CAAC,CAAC;YACH,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;QACD,IAAI;IACN,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { SortOption } from '@unchainedshop/types/api.js';
2
+ import type { Sort } from 'mongodb';
3
+ export declare const buildSortOptions: (sort?: Array<SortOption>) => Sort;
4
+ export default buildSortOptions;
@@ -0,0 +1,13 @@
1
+ const SORT_DIRECTIONS = {
2
+ ASC: 1,
3
+ DESC: -1,
4
+ };
5
+ export const buildSortOptions = (sort = []) => {
6
+ const sortBy = {};
7
+ sort?.forEach(({ key, value }) => {
8
+ sortBy[key] = SORT_DIRECTIONS[value];
9
+ });
10
+ return sortBy;
11
+ };
12
+ export default buildSortOptions;
13
+ //# sourceMappingURL=build-sort-option.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-sort-option.js","sourceRoot":"","sources":["../src/build-sort-option.ts"],"names":[],"mappings":"AAGA,MAAM,eAAe,GAAG;IACtB,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAA0B,EAAE,EAAQ,EAAE;IACrE,MAAM,MAAM,GAAG,EAAE,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;QAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare const checkId: (value: string, error?: {
2
+ message: string;
3
+ path?: string | undefined;
4
+ } | undefined) => void;
@@ -0,0 +1,6 @@
1
+ export const checkId = (value, error) => {
2
+ if (typeof value !== 'string') {
3
+ throw error || { message: 'Invalid id' };
4
+ }
5
+ };
6
+ //# sourceMappingURL=check-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-id.js","sourceRoot":"","sources":["../src/check-id.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,KAAa,EACb,KAKa,EACP,EAAE;IACR,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,KAAK,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import 'abort-controller/polyfill.js';
2
+ import { Locale } from '@unchainedshop/types/common.js';
3
+ import type { Collection, Document, Filter } from 'mongodb';
4
+ export declare const findLocalizedText: <T extends Document>(collection: Collection<T>, selector: Filter<T>, locale: Locale) => Promise<T>;
5
+ export default findLocalizedText;
@@ -0,0 +1,45 @@
1
+ import 'abort-controller/polyfill.js';
2
+ import * as lruCache from 'lru-cache';
3
+ import { systemLocale } from '@unchainedshop/utils';
4
+ const { NODE_ENV } = process.env;
5
+ const ttl = NODE_ENV === 'production' ? 1000 * 10 : 0; // 10 seconds or 0 seconds
6
+ const textCache = new lruCache.LRUCache({ max: 50000, ttl });
7
+ const extendSelectorWithLocale = (selector, locale) => {
8
+ const localeSelector = {
9
+ locale: { $in: [locale.normalized, locale.language] },
10
+ };
11
+ return { ...localeSelector, ...selector };
12
+ };
13
+ export const findLocalizedText = async (collection, selector, locale) => {
14
+ const cacheKey = JSON.stringify({
15
+ n: collection.collectionName, // eslint-disable-line
16
+ s: selector,
17
+ l: locale,
18
+ });
19
+ const cachedText = textCache.get(cacheKey);
20
+ if (cachedText)
21
+ return cachedText;
22
+ const exactTranslation = await collection.findOne(extendSelectorWithLocale(selector, locale), {
23
+ sort: { updated: -1 },
24
+ });
25
+ if (exactTranslation) {
26
+ textCache.set(cacheKey, exactTranslation);
27
+ return exactTranslation;
28
+ }
29
+ if (systemLocale.normalized !== locale.normalized) {
30
+ const fallbackTranslation = await collection.findOne(extendSelectorWithLocale(selector, systemLocale), {
31
+ sort: { updated: -1 },
32
+ });
33
+ if (fallbackTranslation) {
34
+ textCache.set(cacheKey, fallbackTranslation);
35
+ return fallbackTranslation;
36
+ }
37
+ }
38
+ const foundText = await collection.findOne(selector, {
39
+ sort: { updated: -1 },
40
+ });
41
+ textCache.set(cacheKey, foundText);
42
+ return foundText;
43
+ };
44
+ export default findLocalizedText;
45
+ //# sourceMappingURL=find-localized-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-localized-text.js","sourceRoot":"","sources":["../src/find-localized-text.ts"],"names":[],"mappings":"AAAA,OAAO,8BAA8B,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;AAEjC,MAAM,GAAG,GAAG,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B;AAEjF,MAAM,SAAS,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;AAE7D,MAAM,wBAAwB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;IACpD,MAAM,cAAc,GAAG;QACrB,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;KACtD,CAAC;IACF,OAAO,EAAE,GAAG,cAAc,EAAE,GAAG,QAAQ,EAAE,CAAC;AAC5C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EACpC,UAAyB,EACzB,QAAmB,EACnB,MAAc,EACF,EAAE;IACd,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,CAAC,EAAE,UAAU,CAAC,cAAc,EAAE,sBAAsB;QACpD,CAAC,EAAE,QAAQ;QACX,CAAC,EAAE,MAAM;KACV,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAE3C,IAAI,UAAU;QAAE,OAAO,UAAe,CAAC;IAEvC,MAAM,gBAAgB,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE;QAC5F,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE;KACtB,CAAC,CAAC;IACH,IAAI,gBAAgB,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QAC1C,OAAO,gBAAqB,CAAC;IAC/B,CAAC;IAED,IAAI,YAAY,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU,EAAE,CAAC;QAClD,MAAM,mBAAmB,GAAG,MAAM,UAAU,CAAC,OAAO,CAClD,wBAAwB,CAAC,QAAQ,EAAE,YAAY,CAAC,EAChD;YACE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE;SACtB,CACF,CAAC;QACF,IAAI,mBAAmB,EAAE,CAAC;YACxB,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;YAC7C,OAAO,mBAAwB,CAAC;QAClC,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE;QACnD,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE;KACtB,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACnC,OAAO,SAAc,CAAC;AACxB,CAAC,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Collection, FindOptions, Filter } from 'mongodb';
2
+ export declare const findPreservingIds: <T>(collection: Collection<T>) => (selector: Filter<T>, ids: Array<string>, options?: FindOptions) => Promise<T[]>;
@@ -0,0 +1,33 @@
1
+ const { AMAZON_DOCUMENTDB_COMPAT_MODE } = process.env;
2
+ const sortByIndex = {
3
+ index: 1,
4
+ };
5
+ const sortBySequence = {
6
+ sequence: 1,
7
+ };
8
+ const defaultSort = AMAZON_DOCUMENTDB_COMPAT_MODE ? sortBySequence : sortByIndex;
9
+ export const findPreservingIds = (collection) => async (selector, ids, options) => {
10
+ const { skip, limit, sort = defaultSort } = options || {};
11
+ const filteredSelector = {
12
+ ...selector,
13
+ _id: { $in: ids },
14
+ };
15
+ const filteredPipeline = [
16
+ {
17
+ $match: filteredSelector,
18
+ },
19
+ typeof sort === 'object' &&
20
+ 'index' in sort && {
21
+ $addFields: {
22
+ index: { $indexOfArray: [ids, '$_id'] },
23
+ },
24
+ },
25
+ sort && { $sort: sort },
26
+ skip && { $skip: skip },
27
+ limit && { $limit: limit },
28
+ ].filter(Boolean);
29
+ const aggregationPointer = collection.aggregate(filteredPipeline);
30
+ const items = await aggregationPointer.toArray();
31
+ return items;
32
+ };
33
+ //# sourceMappingURL=find-preserving-ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-preserving-ids.js","sourceRoot":"","sources":["../src/find-preserving-ids.ts"],"names":[],"mappings":"AAEA,MAAM,EAAE,6BAA6B,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;AAEtD,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,cAAc,GAAG;IACrB,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF,MAAM,WAAW,GAAG,6BAA6B,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CAAC;AAEjF,MAAM,CAAC,MAAM,iBAAiB,GAC5B,CACE,UAAyB,EACgE,EAAE,CAC7F,KAAK,EAAE,QAAmB,EAAE,GAAkB,EAAE,OAAqB,EAAqB,EAAE;IAC1F,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,WAAW,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAC1D,MAAM,gBAAgB,GAAG;QACvB,GAAG,QAAQ;QACX,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;KAClB,CAAC;IAEF,MAAM,gBAAgB,GAAG;QACvB;YACE,MAAM,EAAE,gBAAgB;SACzB;QACD,OAAO,IAAI,KAAK,QAAQ;YACtB,OAAO,IAAI,IAAI,IAAI;YACjB,UAAU,EAAE;gBACV,KAAK,EAAE,EAAE,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE;aACxC;SACF;QACH,IAAI,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QACvB,IAAI,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QACvB,KAAK,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;KAC3B,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAElB,MAAM,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,CAAC;IACjD,OAAO,KAAiB,CAAC;AAC3B,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Filter } from 'mongodb';
2
+ export declare const generateDbFilterById: <T extends {
3
+ _id?: string;
4
+ }>(id: unknown, query?: Filter<T>) => Filter<T>;
@@ -0,0 +1,5 @@
1
+ export const generateDbFilterById = (id, query = {}) => {
2
+ const _id = id || null; // never undefined, else it will get the first one
3
+ return { _id, ...query };
4
+ };
5
+ //# sourceMappingURL=generate-db-filter-by-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-db-filter-by-id.js","sourceRoot":"","sources":["../src/generate-db-filter-by-id.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,EAAW,EACX,QAAmB,EAAE,EACV,EAAE;IACb,MAAM,GAAG,GAAG,EAAE,IAAI,IAAI,CAAC,CAAC,kDAAkD;IAC1E,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;AAC3B,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import SimpleSchema from 'simpl-schema';
2
+ import { TimestampFields } from '@unchainedshop/types/common.js';
3
+ import { ModuleCreateMutation, ModuleMutations } from '@unchainedshop/types/core.js';
4
+ import { Collection } from 'mongodb';
5
+ export declare const generateDbMutations: <T extends TimestampFields & {
6
+ _id?: string;
7
+ }>(collection: Collection<T>, schema: SimpleSchema, options?: {
8
+ hasCreateOnly?: boolean;
9
+ permanentlyDeleteByDefault?: boolean;
10
+ }) => ModuleMutations<T> | ModuleCreateMutation<T>;
@@ -0,0 +1,73 @@
1
+ import { checkId } from './check-id.js';
2
+ import { generateDbObjectId } from './generate-db-object-id.js';
3
+ import { generateDbFilterById } from './generate-db-filter-by-id.js';
4
+ export const generateDbMutations = (collection, schema, options) => {
5
+ if (!collection)
6
+ throw new Error('Collection is missing');
7
+ if (!schema)
8
+ throw new Error('Schema is missing');
9
+ const { hasCreateOnly, permanentlyDeleteByDefault } = options || {
10
+ hasCreateOnly: false,
11
+ permanentlyDeleteByDefault: false,
12
+ };
13
+ const deletePermanently = async (_id) => {
14
+ checkId(_id);
15
+ const filter = generateDbFilterById(_id);
16
+ const result = await collection.deleteOne(filter);
17
+ return result.deletedCount;
18
+ };
19
+ return {
20
+ create: async (doc) => {
21
+ const values = schema.clean(doc);
22
+ values.created = new Date();
23
+ schema.validate(values);
24
+ values._id = doc._id || generateDbObjectId();
25
+ const result = await collection.insertOne(values);
26
+ return result.insertedId;
27
+ },
28
+ update: hasCreateOnly
29
+ ? undefined
30
+ : async (_id, doc) => {
31
+ checkId(_id);
32
+ let modifier;
33
+ if (doc?.$set) {
34
+ const values = schema.clean(doc, { isModifier: true });
35
+ modifier = {
36
+ ...values,
37
+ $set: {
38
+ ...(values.$set || {}),
39
+ updated: new Date(),
40
+ },
41
+ };
42
+ }
43
+ else {
44
+ const values = schema.clean(doc);
45
+ modifier = {
46
+ $set: {
47
+ ...values,
48
+ updated: new Date(),
49
+ },
50
+ };
51
+ }
52
+ schema.validate(modifier, { modifier: true });
53
+ const filter = generateDbFilterById(_id, { deleted: null });
54
+ await collection.updateOne(filter, modifier);
55
+ return _id;
56
+ },
57
+ deletePermanently: hasCreateOnly ? undefined : deletePermanently,
58
+ delete: hasCreateOnly
59
+ ? undefined
60
+ : async (_id) => {
61
+ if (permanentlyDeleteByDefault) {
62
+ return deletePermanently(_id);
63
+ }
64
+ checkId(_id);
65
+ const filter = generateDbFilterById(_id, { deleted: null });
66
+ const modifier = { $set: { deleted: new Date() } };
67
+ const values = schema.clean(modifier, { isModifier: true });
68
+ const result = await collection.updateOne(filter, values);
69
+ return result.modifiedCount;
70
+ },
71
+ };
72
+ };
73
+ //# sourceMappingURL=generate-db-mutations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-db-mutations.js","sourceRoot":"","sources":["../src/generate-db-mutations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @name Random.hexString
3
+ * @summary Return a random string of `n` hexadecimal digits.
4
+ * @locus Anywhere
5
+ * @param {Number} n Length of the string
6
+ */
7
+ export declare const generateDbObjectId: (digits?: number) => string;
@@ -0,0 +1,25 @@
1
+ import crypto from 'crypto';
2
+ /**
3
+ * @name Random.hexString
4
+ * @summary Return a random string of `n` hexadecimal digits.
5
+ * @locus Anywhere
6
+ * @param {Number} n Length of the string
7
+ */
8
+ export const generateDbObjectId = (digits = 24) => {
9
+ const numBytes = Math.ceil(digits / 2);
10
+ let bytes;
11
+ // Try to get cryptographically strong randomness. Fall back to
12
+ // non-cryptographically strong if not available.
13
+ try {
14
+ bytes = crypto.randomBytes(numBytes);
15
+ }
16
+ catch (e) {
17
+ // XXX should re-throw any error except insufficient entropy
18
+ bytes = crypto.pseudoRandomBytes(numBytes);
19
+ }
20
+ const result = bytes.toString('hex');
21
+ // If the number of digits is odd, we'll have generated an extra 4 bits
22
+ // of randomness, so we need to trim the last digit.
23
+ return result.substring(0, digits);
24
+ };
25
+ //# sourceMappingURL=generate-db-object-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-db-object-id.js","sourceRoot":"","sources":["../src/generate-db-object-id.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAM,GAAG,EAAE,EAAU,EAAE;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC;IACV,+DAA+D;IAC/D,iDAAiD;IACjD,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,4DAA4D;QAC5D,KAAK,GAAG,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrC,uEAAuE;IACvE,oDAAoD;IACpD,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC,CAAC"}
package/lib/initDb.js ADDED
@@ -0,0 +1,32 @@
1
+ import { mkdirSync } from 'fs';
2
+ import { MongoClient } from 'mongodb';
3
+ let mongod;
4
+ export const startDb = async () => {
5
+ const { MongoMemoryServer } = await import('mongodb-memory-server');
6
+ try {
7
+ mkdirSync(`${process.cwd()}/.db`);
8
+ }
9
+ catch (e) {
10
+ //
11
+ }
12
+ mongod = await MongoMemoryServer.create({
13
+ instance: {
14
+ dbPath: `${process.cwd()}/.db`,
15
+ storageEngine: 'wiredTiger',
16
+ port: parseInt(process.env.PORT, 10) + 1,
17
+ },
18
+ });
19
+ return `${mongod.getUri()}unchained`;
20
+ };
21
+ export const stopDb = async () => {
22
+ await mongod.stop();
23
+ };
24
+ const initDb = async () => {
25
+ const url = process.env.MONGO_URL || (await startDb());
26
+ const client = new MongoClient(url);
27
+ await client.connect();
28
+ const db = client.db();
29
+ return db;
30
+ };
31
+ export { initDb };
32
+ //# sourceMappingURL=initDb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initDb.js","sourceRoot":"","sources":["../src/initDb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAM,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1C,IAAI,MAAM,CAAC;AAEX,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;IAChC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;IAEpE,IAAI,CAAC;QACH,SAAS,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,EAAE;IACJ,CAAC;IACD,MAAM,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC;QACtC,QAAQ,EAAE;YACR,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM;YAC9B,aAAa,EAAE,YAAY;YAC3B,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC;SACzC;KACF,CAAC,CAAC;IACH,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,IAAI,EAAE;IAC/B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,KAAK,IAAiB,EAAE;IACrC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,EAAE,CAAC;IACvB,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -1 +1,11 @@
1
- export { initDb, startDb, stopDb } from './db/initDb.js';
1
+ import * as mongodb from 'mongodb';
2
+ export { initDb, startDb, stopDb } from './initDb.js';
3
+ export { checkId } from './check-id.js';
4
+ export { generateDbObjectId } from './generate-db-object-id.js';
5
+ export { generateDbFilterById } from './generate-db-filter-by-id.js';
6
+ export { generateDbMutations } from './generate-db-mutations.js';
7
+ export { buildDbIndexes } from './build-db-indexes.js';
8
+ export { findPreservingIds } from './find-preserving-ids.js';
9
+ export { buildSortOptions } from './build-sort-option.js';
10
+ export { findLocalizedText } from './find-localized-text.js';
11
+ export { mongodb };
@@ -1,2 +1,15 @@
1
- export { initDb, startDb, stopDb } from './db/initDb.js';
1
+ import * as mongodb from 'mongodb';
2
+ export { initDb, startDb, stopDb } from './initDb.js';
3
+ /*
4
+ * Db utils
5
+ */
6
+ export { checkId } from './check-id.js';
7
+ export { generateDbObjectId } from './generate-db-object-id.js';
8
+ export { generateDbFilterById } from './generate-db-filter-by-id.js';
9
+ export { generateDbMutations } from './generate-db-mutations.js';
10
+ export { buildDbIndexes } from './build-db-indexes.js';
11
+ export { findPreservingIds } from './find-preserving-ids.js';
12
+ export { buildSortOptions } from './build-sort-option.js';
13
+ export { findLocalizedText } from './find-localized-text.js';
14
+ export { mongodb };
2
15
  //# sourceMappingURL=mongodb-index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mongodb-index.js","sourceRoot":"","sources":["../src/mongodb-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"mongodb-index.js","sourceRoot":"","sources":["../src/mongodb-index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEtD;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchainedshop/mongodb",
3
- "version": "2.5.11",
3
+ "version": "2.6.0",
4
4
  "description": "MongoDB provider for unchained platform",
5
5
  "main": "lib/mongodb-index.js",
6
6
  "exports": {
@@ -31,16 +31,17 @@
31
31
  },
32
32
  "homepage": "https://github.com/unchainedshop/unchained#readme",
33
33
  "dependencies": {
34
- "@unchainedshop/utils": "^2.5.11",
35
- "mongodb": "^5.9.1"
34
+ "@unchainedshop/utils": "^2.6.0",
35
+ "lru-cache": "^10.1.0",
36
+ "mongodb": "^6.3.0"
36
37
  },
37
38
  "optionalDependencies": {
38
- "mongodb-memory-server": "^8.13.0"
39
+ "mongodb-memory-server": "^9.1.6"
39
40
  },
40
41
  "devDependencies": {
41
- "@types/node": "^20.9.0",
42
- "@unchainedshop/types": "^2.5.11",
42
+ "@types/node": "^20.11.6",
43
+ "@unchainedshop/types": "^2.6.0",
43
44
  "jest": "^29.7.0",
44
- "typescript": "^5.3.2"
45
+ "typescript": "^5.3.3"
45
46
  }
46
47
  }
@@ -0,0 +1,53 @@
1
+ import type { Collection, Document, CreateIndexesOptions, IndexDirection } from 'mongodb';
2
+ import { log, LogLevel } from '@unchainedshop/logger';
3
+
4
+ export type Indexes<T extends Document> = Array<{
5
+ index: { [key in keyof T]?: IndexDirection }; // TODO: Support key with object path (e.g. 'product.proxy.assignments')
6
+ options?: CreateIndexesOptions;
7
+ }>;
8
+
9
+ const buildIndexes = <T>(
10
+ collection: Collection<T>,
11
+ indexes: Indexes<T>,
12
+ ): Promise<Array<false | Error>> =>
13
+ Promise.all(
14
+ indexes.map(async ({ index, options }) => {
15
+ try {
16
+ await collection.createIndex(index, options);
17
+ return false;
18
+ } catch (e: any) {
19
+ log(e, { level: LogLevel.Error });
20
+ return e as Error;
21
+ }
22
+ }),
23
+ );
24
+
25
+ export const buildDbIndexes = async <T extends Document>(
26
+ collection: Collection<T>,
27
+ indexes: Indexes<T>,
28
+ ) => {
29
+ let success = true;
30
+ const buildErrors = (await buildIndexes<T>(collection, indexes)).filter(Boolean);
31
+
32
+ if (buildErrors.length) {
33
+ // try {
34
+ // const dropError = await collection.dropIndexes();
35
+ // } catch (e) {
36
+ // /* */
37
+ // }
38
+
39
+ // if (!dropError) {
40
+ const rebuildErrors = (await buildIndexes<T>(collection, indexes)).filter(Boolean);
41
+
42
+ if (rebuildErrors.length) {
43
+ log(`Error building some indexes for ${collection.collectionName}`, {
44
+ level: LogLevel.Error,
45
+ ...rebuildErrors,
46
+ });
47
+ success = false;
48
+ }
49
+ // }
50
+ }
51
+
52
+ return success;
53
+ };
@@ -0,0 +1,17 @@
1
+ import { SortOption } from '@unchainedshop/types/api.js';
2
+ import type { Sort } from 'mongodb';
3
+
4
+ const SORT_DIRECTIONS = {
5
+ ASC: 1,
6
+ DESC: -1,
7
+ };
8
+
9
+ export const buildSortOptions = (sort: Array<SortOption> = []): Sort => {
10
+ const sortBy = {};
11
+ sort?.forEach(({ key, value }) => {
12
+ sortBy[key] = SORT_DIRECTIONS[value];
13
+ });
14
+ return sortBy;
15
+ };
16
+
17
+ export default buildSortOptions;
@@ -0,0 +1,13 @@
1
+ export const checkId = (
2
+ value: string,
3
+ error?:
4
+ | {
5
+ message: string;
6
+ path?: string | undefined;
7
+ }
8
+ | undefined,
9
+ ): void => {
10
+ if (typeof value !== 'string') {
11
+ throw error || { message: 'Invalid id' };
12
+ }
13
+ };
@@ -0,0 +1,63 @@
1
+ import 'abort-controller/polyfill.js';
2
+ import * as lruCache from 'lru-cache';
3
+ import { Locale } from '@unchainedshop/types/common.js';
4
+ import type { Collection, Document, Filter } from 'mongodb';
5
+ import { systemLocale } from '@unchainedshop/utils';
6
+
7
+ const { NODE_ENV } = process.env;
8
+
9
+ const ttl = NODE_ENV === 'production' ? 1000 * 10 : 0; // 10 seconds or 0 seconds
10
+
11
+ const textCache = new lruCache.LRUCache({ max: 50000, ttl });
12
+
13
+ const extendSelectorWithLocale = (selector, locale) => {
14
+ const localeSelector = {
15
+ locale: { $in: [locale.normalized, locale.language] },
16
+ };
17
+ return { ...localeSelector, ...selector };
18
+ };
19
+
20
+ export const findLocalizedText = async <T extends Document>(
21
+ collection: Collection<T>,
22
+ selector: Filter<T>,
23
+ locale: Locale,
24
+ ): Promise<T> => {
25
+ const cacheKey = JSON.stringify({
26
+ n: collection.collectionName, // eslint-disable-line
27
+ s: selector,
28
+ l: locale,
29
+ });
30
+
31
+ const cachedText = textCache.get(cacheKey);
32
+
33
+ if (cachedText) return cachedText as T;
34
+
35
+ const exactTranslation = await collection.findOne(extendSelectorWithLocale(selector, locale), {
36
+ sort: { updated: -1 },
37
+ });
38
+ if (exactTranslation) {
39
+ textCache.set(cacheKey, exactTranslation);
40
+ return exactTranslation as T;
41
+ }
42
+
43
+ if (systemLocale.normalized !== locale.normalized) {
44
+ const fallbackTranslation = await collection.findOne(
45
+ extendSelectorWithLocale(selector, systemLocale),
46
+ {
47
+ sort: { updated: -1 },
48
+ },
49
+ );
50
+ if (fallbackTranslation) {
51
+ textCache.set(cacheKey, fallbackTranslation);
52
+ return fallbackTranslation as T;
53
+ }
54
+ }
55
+
56
+ const foundText = await collection.findOne(selector, {
57
+ sort: { updated: -1 },
58
+ });
59
+ textCache.set(cacheKey, foundText);
60
+ return foundText as T;
61
+ };
62
+
63
+ export default findLocalizedText;
@@ -0,0 +1,44 @@
1
+ import type { Collection, FindOptions, Filter } from 'mongodb';
2
+
3
+ const { AMAZON_DOCUMENTDB_COMPAT_MODE } = process.env;
4
+
5
+ const sortByIndex = {
6
+ index: 1,
7
+ };
8
+
9
+ const sortBySequence = {
10
+ sequence: 1,
11
+ };
12
+
13
+ const defaultSort = AMAZON_DOCUMENTDB_COMPAT_MODE ? sortBySequence : sortByIndex;
14
+
15
+ export const findPreservingIds =
16
+ <T>(
17
+ collection: Collection<T>,
18
+ ): ((selector: Filter<T>, ids: Array<string>, options?: FindOptions) => Promise<Array<T>>) =>
19
+ async (selector: Filter<T>, ids: Array<string>, options?: FindOptions): Promise<Array<T>> => {
20
+ const { skip, limit, sort = defaultSort } = options || {};
21
+ const filteredSelector = {
22
+ ...selector,
23
+ _id: { $in: ids },
24
+ };
25
+
26
+ const filteredPipeline = [
27
+ {
28
+ $match: filteredSelector,
29
+ },
30
+ typeof sort === 'object' &&
31
+ 'index' in sort && {
32
+ $addFields: {
33
+ index: { $indexOfArray: [ids, '$_id'] },
34
+ },
35
+ },
36
+ sort && { $sort: sort },
37
+ skip && { $skip: skip },
38
+ limit && { $limit: limit },
39
+ ].filter(Boolean);
40
+
41
+ const aggregationPointer = collection.aggregate(filteredPipeline);
42
+ const items = await aggregationPointer.toArray();
43
+ return items as Array<T>;
44
+ };
@@ -0,0 +1,9 @@
1
+ import type { Filter } from 'mongodb';
2
+
3
+ export const generateDbFilterById = <T extends { _id?: string }>(
4
+ id: unknown,
5
+ query: Filter<T> = {},
6
+ ): Filter<T> => {
7
+ const _id = id || null; // never undefined, else it will get the first one
8
+ return { _id, ...query };
9
+ };
@@ -0,0 +1,93 @@
1
+ import SimpleSchema from 'simpl-schema';
2
+ import { TimestampFields } from '@unchainedshop/types/common.js';
3
+ import { ModuleCreateMutation, ModuleMutations } from '@unchainedshop/types/core.js';
4
+ import { Collection, UpdateFilter } from 'mongodb';
5
+ import { checkId } from './check-id.js';
6
+ import { generateDbObjectId } from './generate-db-object-id.js';
7
+ import { generateDbFilterById } from './generate-db-filter-by-id.js';
8
+
9
+ export const generateDbMutations = <T extends TimestampFields & { _id?: string }>(
10
+ collection: Collection<T>,
11
+ schema: SimpleSchema,
12
+ options?: {
13
+ hasCreateOnly?: boolean;
14
+ permanentlyDeleteByDefault?: boolean;
15
+ },
16
+ ): ModuleMutations<T> | ModuleCreateMutation<T> => {
17
+ if (!collection) throw new Error('Collection is missing');
18
+ if (!schema) throw new Error('Schema is missing');
19
+
20
+ const { hasCreateOnly, permanentlyDeleteByDefault } = options || {
21
+ hasCreateOnly: false,
22
+ permanentlyDeleteByDefault: false,
23
+ };
24
+
25
+ const deletePermanently = async (_id) => {
26
+ checkId(_id);
27
+ const filter = generateDbFilterById<T>(_id);
28
+ const result = await collection.deleteOne(filter);
29
+ return result.deletedCount;
30
+ };
31
+
32
+ return {
33
+ create: async (doc) => {
34
+ const values: any = schema.clean(doc);
35
+ values.created = new Date();
36
+ schema.validate(values);
37
+ values._id = doc._id || generateDbObjectId();
38
+
39
+ const result = await collection.insertOne(values);
40
+ return result.insertedId as string;
41
+ },
42
+
43
+ update: hasCreateOnly
44
+ ? undefined
45
+ : async (_id, doc) => {
46
+ checkId(_id);
47
+
48
+ let modifier: UpdateFilter<T>;
49
+
50
+ if ((doc as UpdateFilter<T>)?.$set) {
51
+ const values: any = schema.clean(doc as any, { isModifier: true });
52
+ modifier = {
53
+ ...values,
54
+ $set: {
55
+ ...(values.$set || {}),
56
+ updated: new Date(),
57
+ },
58
+ };
59
+ } else {
60
+ const values: any = schema.clean(doc as any);
61
+ modifier = {
62
+ $set: {
63
+ ...values,
64
+ updated: new Date(),
65
+ },
66
+ };
67
+ }
68
+
69
+ schema.validate(modifier, { modifier: true });
70
+ const filter = generateDbFilterById<T>(_id, { deleted: null });
71
+ await collection.updateOne(filter, modifier);
72
+
73
+ return _id;
74
+ },
75
+
76
+ deletePermanently: hasCreateOnly ? undefined : deletePermanently,
77
+
78
+ delete: hasCreateOnly
79
+ ? undefined
80
+ : async (_id) => {
81
+ if (permanentlyDeleteByDefault) {
82
+ return deletePermanently(_id);
83
+ }
84
+ checkId(_id);
85
+ const filter = generateDbFilterById<T>(_id, { deleted: null });
86
+ const modifier = { $set: { deleted: new Date() } };
87
+ const values = schema.clean(modifier, { isModifier: true });
88
+ const result = await collection.updateOne(filter, values);
89
+
90
+ return result.modifiedCount;
91
+ },
92
+ };
93
+ };
@@ -0,0 +1,24 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ * @name Random.hexString
5
+ * @summary Return a random string of `n` hexadecimal digits.
6
+ * @locus Anywhere
7
+ * @param {Number} n Length of the string
8
+ */
9
+ export const generateDbObjectId = (digits = 24): string => {
10
+ const numBytes = Math.ceil(digits / 2);
11
+ let bytes;
12
+ // Try to get cryptographically strong randomness. Fall back to
13
+ // non-cryptographically strong if not available.
14
+ try {
15
+ bytes = crypto.randomBytes(numBytes);
16
+ } catch (e) {
17
+ // XXX should re-throw any error except insufficient entropy
18
+ bytes = crypto.pseudoRandomBytes(numBytes);
19
+ }
20
+ const result = bytes.toString('hex');
21
+ // If the number of digits is odd, we'll have generated an extra 4 bits
22
+ // of randomness, so we need to trim the last digit.
23
+ return result.substring(0, digits);
24
+ };
package/src/initDb.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { mkdirSync } from 'fs';
2
+ import { Db, MongoClient } from 'mongodb';
3
+
4
+ let mongod;
5
+
6
+ export const startDb = async () => {
7
+ const { MongoMemoryServer } = await import('mongodb-memory-server');
8
+
9
+ try {
10
+ mkdirSync(`${process.cwd()}/.db`);
11
+ } catch (e) {
12
+ //
13
+ }
14
+ mongod = await MongoMemoryServer.create({
15
+ instance: {
16
+ dbPath: `${process.cwd()}/.db`,
17
+ storageEngine: 'wiredTiger',
18
+ port: parseInt(process.env.PORT, 10) + 1,
19
+ },
20
+ });
21
+ return `${mongod.getUri()}unchained`;
22
+ };
23
+
24
+ export const stopDb = async () => {
25
+ await mongod.stop();
26
+ };
27
+
28
+ const initDb = async (): Promise<Db> => {
29
+ const url = process.env.MONGO_URL || (await startDb());
30
+ const client = new MongoClient(url);
31
+ await client.connect();
32
+ const db = client.db();
33
+ return db;
34
+ };
35
+
36
+ export { initDb };
@@ -0,0 +1,71 @@
1
+ import { SortDirection } from '@unchainedshop/types/api.js';
2
+ import {
3
+ buildSortOptions,
4
+ checkId,
5
+ generateDbObjectId,
6
+ } from './mongodb-index.js';
7
+
8
+ describe('Mongo', () => {
9
+ it('Init', async () => {
10
+ expect(true).toBeTruthy()
11
+ });
12
+
13
+ describe('buildSortOptions', () => {
14
+ it('should return the correct db sort format', () => {
15
+ const sort = [ { key: 'name', value: SortDirection.ASC }, { key: 'age', value: SortDirection.DESC }, ];
16
+ expect(buildSortOptions(sort)).toEqual({ name: 1, age: -1 });
17
+ })
18
+
19
+ });
20
+
21
+ describe('checkId', () => {
22
+ it('with a string value', () => {
23
+ const value = '12345';
24
+ const error = { message: 'Invalid id' };
25
+
26
+ expect(() => checkId(value, error)).not.toThrow();
27
+ });
28
+
29
+ it('with a non-string value', () => {
30
+ const value: any = 12345;
31
+ const error = { message: 'Invalid id' };
32
+
33
+ expect(() => checkId(value, error)).toThrow('Invalid id');
34
+ });
35
+
36
+ it('with a different error object', () => {
37
+ const value:any = 12345;
38
+ const error = { message: 'Invalid id', path: 'name' };
39
+
40
+ expect(() => checkId(value, error)).toThrow('Invalid id');
41
+ });
42
+
43
+ it('without an error object', () => {
44
+ const value: any = 12345;
45
+
46
+ expect(() => checkId(value)).toThrow();
47
+ });
48
+
49
+ });
50
+ describe('generateDbObjectId', () => {
51
+ it('generateDbObjectId with default digits', () => {
52
+ const result = generateDbObjectId();
53
+ expect(typeof result).toBe('string');
54
+ expect(result.length).toBe(24);
55
+ });
56
+
57
+ it('generateDbObjectId with odd digits', () => {
58
+ const result = generateDbObjectId(23);
59
+ expect(typeof result).toBe('string');
60
+ expect(result.length).toBe(23);
61
+ });
62
+
63
+ it('generateDbObjectId with even digits', () => {
64
+ const result = generateDbObjectId(24);
65
+ expect(typeof result).toBe('string');
66
+ expect(result.length).toBe(24);
67
+ });
68
+
69
+ })
70
+ });
71
+
@@ -1 +1,18 @@
1
- export { initDb, startDb, stopDb } from './db/initDb.js';
1
+ import * as mongodb from 'mongodb';
2
+
3
+ export { initDb, startDb, stopDb } from './initDb.js';
4
+
5
+ /*
6
+ * Db utils
7
+ */
8
+
9
+ export { checkId } from './check-id.js';
10
+ export { generateDbObjectId } from './generate-db-object-id.js';
11
+ export { generateDbFilterById } from './generate-db-filter-by-id.js';
12
+ export { generateDbMutations } from './generate-db-mutations.js';
13
+ export { buildDbIndexes } from './build-db-indexes.js';
14
+ export { findPreservingIds } from './find-preserving-ids.js';
15
+ export { buildSortOptions } from './build-sort-option.js';
16
+ export { findLocalizedText } from './find-localized-text.js';
17
+
18
+ export { mongodb };
package/lib/db/initDb.js DELETED
@@ -1,54 +0,0 @@
1
- import { mkdirSync } from 'fs';
2
- import { MongoClient } from 'mongodb';
3
- let mongod;
4
- export const startDb = async () => {
5
- const { MongoMemoryServer } = await import('mongodb-memory-server');
6
- try {
7
- mkdirSync(`${process.cwd()}/.db`);
8
- }
9
- catch (e) {
10
- //
11
- }
12
- mongod = await MongoMemoryServer.create({
13
- instance: {
14
- dbPath: `${process.cwd()}/.db`,
15
- storageEngine: 'wiredTiger',
16
- port: parseInt(process.env.PORT, 10) + 1,
17
- },
18
- });
19
- return `${mongod.getUri()}unchained`;
20
- };
21
- export const stopDb = async () => {
22
- await mongod.stop();
23
- };
24
- const initDbNative = async () => {
25
- const url = process.env.MONGO_URL || (await startDb());
26
- const client = new MongoClient(url);
27
- await client.connect();
28
- const db = client.db();
29
- return db;
30
- };
31
- // eslint-disable-next-line
32
- // @ts-ignore
33
- const isMeteor = typeof Meteor === 'object';
34
- if (isMeteor) {
35
- const { NpmModuleMongodb } = require('meteor/npm-mongo'); // eslint-disable-line
36
- const originalFn = NpmModuleMongodb.Collection.prototype.updateOne;
37
- NpmModuleMongodb.Collection.prototype.updateOne = async function updateOne(...rest) {
38
- const result = await originalFn.bind(this)(...rest);
39
- if (!result)
40
- return result;
41
- return {
42
- result: { nModified: result.modifiedCount },
43
- ...result,
44
- };
45
- };
46
- }
47
- const initDbMeteor = async () => {
48
- const { MongoInternals } = require('meteor/mongo'); // eslint-disable-line
49
- const db = MongoInternals.defaultRemoteCollectionDriver().mongo.db; // eslint-disable-line
50
- return db;
51
- };
52
- const initDb = isMeteor ? initDbMeteor : initDbNative;
53
- export { initDb };
54
- //# sourceMappingURL=initDb.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"initDb.js","sourceRoot":"","sources":["../../src/db/initDb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAM,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1C,IAAI,MAAM,CAAC;AAEX,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;IAChC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;IAEpE,IAAI,CAAC;QACH,SAAS,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,EAAE;IACJ,CAAC;IACD,MAAM,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC;QACtC,QAAQ,EAAE;YACR,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM;YAC9B,aAAa,EAAE,YAAY;YAC3B,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC;SACzC;KACF,CAAC,CAAC;IACH,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,IAAI,EAAE;IAC/B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,KAAK,IAAiB,EAAE;IAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,EAAE,CAAC;IACvB,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,2BAA2B;AAC3B,aAAa;AACb,MAAM,QAAQ,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC;AAE5C,IAAI,QAAQ,EAAE,CAAC;IACb,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,sBAAsB;IAChF,MAAM,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC;IACnE,gBAAgB,CAAC,UAAU,CAAC,SAAS,CAAC,SAAS,GAAG,KAAK,UAAU,SAAS,CAAC,GAAG,IAAI;QAChF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM;YAAE,OAAO,MAAM,CAAC;QAC3B,OAAO;YACL,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,aAAa,EAAE;YAC3C,GAAG,MAAM;SACV,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAAG,KAAK,IAAiB,EAAE;IAC3C,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,sBAAsB;IAC1E,MAAM,EAAE,GAAG,cAAc,CAAC,6BAA6B,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,sBAAsB;IAC1F,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;AAEtD,OAAO,EAAE,MAAM,EAAE,CAAC"}
package/src/db/initDb.ts DELETED
@@ -1,61 +0,0 @@
1
- import { mkdirSync } from 'fs';
2
- import { Db, MongoClient } from 'mongodb';
3
-
4
- let mongod;
5
-
6
- export const startDb = async () => {
7
- const { MongoMemoryServer } = await import('mongodb-memory-server');
8
-
9
- try {
10
- mkdirSync(`${process.cwd()}/.db`);
11
- } catch (e) {
12
- //
13
- }
14
- mongod = await MongoMemoryServer.create({
15
- instance: {
16
- dbPath: `${process.cwd()}/.db`,
17
- storageEngine: 'wiredTiger',
18
- port: parseInt(process.env.PORT, 10) + 1,
19
- },
20
- });
21
- return `${mongod.getUri()}unchained`;
22
- };
23
-
24
- export const stopDb = async () => {
25
- await mongod.stop();
26
- };
27
-
28
- const initDbNative = async (): Promise<Db> => {
29
- const url = process.env.MONGO_URL || (await startDb());
30
- const client = new MongoClient(url);
31
- await client.connect();
32
- const db = client.db();
33
- return db;
34
- };
35
-
36
- // eslint-disable-next-line
37
- // @ts-ignore
38
- const isMeteor = typeof Meteor === 'object';
39
-
40
- if (isMeteor) {
41
- const { NpmModuleMongodb } = require('meteor/npm-mongo'); // eslint-disable-line
42
- const originalFn = NpmModuleMongodb.Collection.prototype.updateOne;
43
- NpmModuleMongodb.Collection.prototype.updateOne = async function updateOne(...rest) {
44
- const result = await originalFn.bind(this)(...rest);
45
- if (!result) return result;
46
- return {
47
- result: { nModified: result.modifiedCount },
48
- ...result,
49
- };
50
- };
51
- }
52
-
53
- const initDbMeteor = async (): Promise<Db> => {
54
- const { MongoInternals } = require('meteor/mongo'); // eslint-disable-line
55
- const db = MongoInternals.defaultRemoteCollectionDriver().mongo.db; // eslint-disable-line
56
- return db;
57
- };
58
-
59
- const initDb = isMeteor ? initDbMeteor : initDbNative;
60
-
61
- export { initDb };
@@ -1,5 +0,0 @@
1
- describe('Mongo', () => {
2
- it('Init', async () => {
3
- expect(true).toBeTruthy()
4
- });
5
- });
File without changes