@unchainedshop/core-quotations 1.1.3

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 (47) hide show
  1. package/.npm/package/README +7 -0
  2. package/.npm/package/npm-shrinkwrap.json +190 -0
  3. package/.versions +55 -0
  4. package/README.md +5 -0
  5. package/lib/db/QuotationStatus.d.ts +7 -0
  6. package/lib/db/QuotationStatus.js +9 -0
  7. package/lib/db/QuotationStatus.js.map +1 -0
  8. package/lib/db/QuotationsCollection.d.ts +3 -0
  9. package/lib/db/QuotationsCollection.js +33 -0
  10. package/lib/db/QuotationsCollection.js.map +1 -0
  11. package/lib/db/QuotationsSchema.d.ts +1 -0
  12. package/lib/db/QuotationsSchema.js +31 -0
  13. package/lib/db/QuotationsSchema.js.map +1 -0
  14. package/lib/director/QuotationAdapter.d.ts +2 -0
  15. package/lib/director/QuotationAdapter.js +40 -0
  16. package/lib/director/QuotationAdapter.js.map +1 -0
  17. package/lib/director/QuotationDirector.d.ts +2 -0
  18. package/lib/director/QuotationDirector.js +86 -0
  19. package/lib/director/QuotationDirector.js.map +1 -0
  20. package/lib/director/QuotationError.d.ts +6 -0
  21. package/lib/director/QuotationError.js +8 -0
  22. package/lib/director/QuotationError.js.map +1 -0
  23. package/lib/module/configureQuotationsModule.d.ts +3 -0
  24. package/lib/module/configureQuotationsModule.js +263 -0
  25. package/lib/module/configureQuotationsModule.js.map +1 -0
  26. package/lib/quotations-index.d.ts +5 -0
  27. package/lib/quotations-index.js +6 -0
  28. package/lib/quotations-index.js.map +1 -0
  29. package/lib/quotations-settings.d.ts +10 -0
  30. package/lib/quotations-settings.js +8 -0
  31. package/lib/quotations-settings.js.map +1 -0
  32. package/package.js +34 -0
  33. package/package.json +44 -0
  34. package/plugins/manual.ts +27 -0
  35. package/quotations.js +15 -0
  36. package/src/db/QuotationStatus.ts +7 -0
  37. package/src/db/QuotationsCollection.ts +37 -0
  38. package/src/db/QuotationsSchema.js +35 -0
  39. package/src/director/QuotationAdapter.ts +52 -0
  40. package/src/director/QuotationDirector.ts +100 -0
  41. package/src/director/QuotationError.ts +6 -0
  42. package/src/module/configureQuotationsModule.ts +386 -0
  43. package/src/quotations-index.ts +8 -0
  44. package/src/quotations-settings.js +9 -0
  45. package/tests/quotations-index.test.ts +73 -0
  46. package/tsconfig.build.json +26 -0
  47. package/tsconfig.json +8 -0
@@ -0,0 +1,100 @@
1
+ import { Context } from '@unchainedshop/types/api';
2
+ import { LogLevel, log } from '@unchainedshop/logger';
3
+ import {
4
+ IQuotationAdapter,
5
+ IQuotationDirector,
6
+ QuotationContext,
7
+ } from '@unchainedshop/types/quotations';
8
+ import { BaseDirector } from '@unchainedshop/utils';
9
+ import { QuotationError } from './QuotationError';
10
+
11
+ const baseDirector = BaseDirector<IQuotationAdapter>('QuotationDirector', {
12
+ adapterSortKey: 'orderIndex',
13
+ });
14
+
15
+ const findAppropriateAdapters = (quotationContext: QuotationContext, requestContext: Context) =>
16
+ baseDirector.getAdapters({
17
+ adapterFilter: (Adapter: IQuotationAdapter) => {
18
+ const activated = Adapter.isActivatedFor(quotationContext, requestContext);
19
+ if (!activated) {
20
+ log(`Quotation Director -> ${Adapter.key} (${Adapter.version}) skipped`, {
21
+ level: LogLevel.Warning,
22
+ });
23
+ }
24
+ return activated;
25
+ },
26
+ });
27
+
28
+ export const QuotationDirector: IQuotationDirector = {
29
+ ...baseDirector,
30
+
31
+ actions: (quotationContext, requestContext) => {
32
+ const context = { ...quotationContext, ...requestContext };
33
+
34
+ const Adapter = findAppropriateAdapters(quotationContext, requestContext)?.shift();
35
+
36
+ if (!Adapter) {
37
+ throw new Error('No suitable quotation plugin available for this context');
38
+ }
39
+
40
+ const adapter = Adapter.actions(context);
41
+
42
+ return {
43
+ configurationError: () => {
44
+ try {
45
+ return adapter.configurationError();
46
+ } catch (error) {
47
+ log('QuotationDirector -> Error while checking for configurationError', {
48
+ level: LogLevel.Warning,
49
+ ...error,
50
+ });
51
+ return QuotationError.ADAPTER_NOT_FOUND;
52
+ }
53
+ },
54
+
55
+ isManualRequestVerificationRequired: async () => {
56
+ try {
57
+ return adapter.isManualRequestVerificationRequired();
58
+ } catch (error) {
59
+ log('QuotationDirector -> Error while checking if is manual request verification required', {
60
+ level: LogLevel.Error,
61
+ ...error,
62
+ });
63
+ return null;
64
+ }
65
+ },
66
+
67
+ isManualProposalRequired: async () => {
68
+ try {
69
+ return adapter.isManualProposalRequired();
70
+ } catch (error) {
71
+ log('QuotationDirector -> Error while checking if is manual proposal required', {
72
+ level: LogLevel.Error,
73
+ ...error,
74
+ });
75
+ return null;
76
+ }
77
+ },
78
+
79
+ quote: adapter.quote,
80
+ rejectRequest: adapter.rejectRequest,
81
+ submitRequest: adapter.submitRequest,
82
+ verifyRequest: adapter.verifyRequest,
83
+
84
+ transformItemConfiguration: async ({ quantity, configuration }) => {
85
+ try {
86
+ return adapter.transformItemConfiguration({
87
+ quantity,
88
+ configuration,
89
+ });
90
+ } catch (error) {
91
+ log('QuotationDirector -> Error while transforming item configuration', {
92
+ level: LogLevel.Error,
93
+ ...error,
94
+ });
95
+ return null;
96
+ }
97
+ },
98
+ };
99
+ },
100
+ };
@@ -0,0 +1,6 @@
1
+ export enum QuotationError {
2
+ ADAPTER_NOT_FOUND = 'ADAPTER_NOT_FOUND',
3
+ NOT_IMPLEMENTED = 'NOT_IMPLEMENTED',
4
+ INCOMPLETE_CONFIGURATION = 'INCOMPLETE_CONFIGURATION',
5
+ WRONG_CREDENTIALS = 'WRONG_CREDENTIALS',
6
+ }
@@ -0,0 +1,386 @@
1
+ import { Context } from '@unchainedshop/types/api';
2
+ import { ModuleInput, ModuleMutations, Update } from '@unchainedshop/types/common';
3
+ import {
4
+ Quotation,
5
+ QuotationQuery,
6
+ QuotationsModule,
7
+ QuotationsSettingsOptions,
8
+ } from '@unchainedshop/types/quotations';
9
+ import { emit, registerEvents } from '@unchainedshop/events';
10
+ import { log } from '@unchainedshop/logger';
11
+ import { generateDbFilterById, generateDbMutations, buildSortOptions } from '@unchainedshop/utils';
12
+ import { QuotationsCollection } from '../db/QuotationsCollection';
13
+ import { QuotationsSchema } from '../db/QuotationsSchema';
14
+ import { QuotationStatus } from '../db/QuotationStatus';
15
+ import { QuotationDirector } from '../quotations-index';
16
+ import { quotationsSettings } from '../quotations-settings';
17
+
18
+ const QUOTATION_EVENTS: string[] = ['QUOTATION_REQUEST_CREATE', 'QUOTATION_REMOVE', 'QUOTATION_UPDATE'];
19
+
20
+ const buildFindSelector = (query: QuotationQuery = {}) => {
21
+ const selector: { userId?: string; $text?: any } = {};
22
+ if (query.userId) {
23
+ selector.userId = query.userId;
24
+ }
25
+ if (query.queryString) {
26
+ selector.$text = { $search: query.queryString };
27
+ }
28
+
29
+ return selector;
30
+ };
31
+
32
+ const isExpired: QuotationsModule['isExpired'] = (quotation, { referenceDate }) => {
33
+ const relevantDate = referenceDate ? new Date(referenceDate) : new Date();
34
+ const expiryDate = new Date(quotation.expires);
35
+ const isQuotationExpired = relevantDate.getTime() > expiryDate.getTime();
36
+ return isQuotationExpired;
37
+ };
38
+
39
+ export const configureQuotationsModule = async ({
40
+ db,
41
+ options: quotationsOptions = {},
42
+ }: ModuleInput<QuotationsSettingsOptions>): Promise<QuotationsModule> => {
43
+ registerEvents(QUOTATION_EVENTS);
44
+
45
+ quotationsSettings.configureSettings(quotationsOptions);
46
+
47
+ const Quotations = await QuotationsCollection(db);
48
+
49
+ const mutations = generateDbMutations<Quotation>(
50
+ Quotations,
51
+ QuotationsSchema,
52
+ ) as ModuleMutations<Quotation>;
53
+
54
+ const findNewQuotationNumber = async (quotation: Quotation, index = 0) => {
55
+ // let quotationNumber = null;
56
+ // let i = 0;
57
+ // while (!quotationNumber) {
58
+ const newHashID = quotationsSettings.quotationNumberHashFn(quotation, index);
59
+ if ((await Quotations.countDocuments({ quotationNumber: newHashID }, { limit: 1 })) === 0) {
60
+ return newHashID;
61
+ }
62
+ return findNewQuotationNumber(quotation, index + 1);
63
+ // }
64
+ // return quotationNumber;
65
+ };
66
+
67
+ const findNextStatus = async (
68
+ quotation: Quotation,
69
+ requestContext: Context,
70
+ ): Promise<QuotationStatus> => {
71
+ let status = quotation.status as QuotationStatus;
72
+ const director = await QuotationDirector.actions({ quotation }, requestContext);
73
+
74
+ if (status === QuotationStatus.REQUESTED) {
75
+ if (!(await director.isManualRequestVerificationRequired())) {
76
+ status = QuotationStatus.PROCESSING;
77
+ }
78
+ }
79
+ if (status === QuotationStatus.PROCESSING) {
80
+ if (!(await director.isManualProposalRequired())) {
81
+ status = QuotationStatus.PROPOSED;
82
+ }
83
+ }
84
+ return status;
85
+ };
86
+
87
+ const updateStatus: QuotationsModule['updateStatus'] = async (
88
+ quotationId,
89
+ { status, info = '' },
90
+ userId,
91
+ ) => {
92
+ const selector = generateDbFilterById(quotationId);
93
+ const quotation = await Quotations.findOne(selector, {});
94
+
95
+ if (quotation.status === status) return quotation;
96
+
97
+ const date = new Date();
98
+ const $set: Partial<Quotation> = {
99
+ status,
100
+ updated: new Date(),
101
+ updatedBy: userId,
102
+ };
103
+
104
+ switch (status) {
105
+ // explicitly use fallthrough here!
106
+ case QuotationStatus.FULLFILLED:
107
+ if (!quotation.fullfilled) {
108
+ $set.fullfilled = date;
109
+ }
110
+ $set.expires = date;
111
+ case QuotationStatus.PROCESSING: // eslint-disable-line no-fallthrough
112
+ if (!quotation.quotationNumber) {
113
+ $set.quotationNumber = findNewQuotationNumber(quotation);
114
+ }
115
+ break;
116
+ case QuotationStatus.REJECTED:
117
+ $set.expires = date;
118
+ $set.rejected = date;
119
+ break;
120
+ default:
121
+ break;
122
+ }
123
+
124
+ const modifier: Update<Quotation> = {
125
+ $set,
126
+ $push: {
127
+ log: {
128
+ date,
129
+ status,
130
+ info,
131
+ },
132
+ },
133
+ };
134
+
135
+ log(`New Status: ${status}`, { quotationId });
136
+
137
+ await Quotations.updateOne(selector, modifier);
138
+
139
+ const updatedQuotation = await Quotations.findOne(selector, {});
140
+
141
+ emit('QUOTATION_UPDATE', { quotation, field: 'status' });
142
+
143
+ return updatedQuotation;
144
+ };
145
+
146
+ const processQuotation = async (
147
+ initialQuotation: Quotation,
148
+ params: { quotationContext?: any },
149
+ requestContext: Context,
150
+ ) => {
151
+ const { modules, userId } = requestContext;
152
+
153
+ const quotationId = initialQuotation._id;
154
+ let quotation = initialQuotation;
155
+ let nextStatus = await findNextStatus(quotation, requestContext);
156
+ const director = await QuotationDirector.actions({ quotation }, requestContext);
157
+
158
+ if (quotation.status === QuotationStatus.REQUESTED && nextStatus !== QuotationStatus.REQUESTED) {
159
+ await director.submitRequest(params.quotationContext);
160
+ }
161
+
162
+ quotation = await modules.quotations.findQuotation({ quotationId });
163
+ nextStatus = await findNextStatus(quotation, requestContext);
164
+ if (nextStatus !== QuotationStatus.PROCESSING) {
165
+ await director.verifyRequest(params.quotationContext);
166
+ }
167
+
168
+ quotation = await modules.quotations.findQuotation({ quotationId });
169
+ nextStatus = await findNextStatus(quotation, requestContext);
170
+ if (nextStatus === QuotationStatus.REJECTED) {
171
+ await director.rejectRequest(params.quotationContext);
172
+ }
173
+
174
+ quotation = await modules.quotations.findQuotation({ quotationId });
175
+ nextStatus = await findNextStatus(quotation, requestContext);
176
+ if (nextStatus === QuotationStatus.PROPOSED) {
177
+ const proposal = await director.quote();
178
+ quotation = await modules.quotations.updateProposal(quotation._id, proposal, userId);
179
+ nextStatus = await findNextStatus(quotation, requestContext);
180
+ }
181
+
182
+ return updateStatus(
183
+ quotation._id,
184
+ { status: nextStatus, info: 'quotation processed' },
185
+ requestContext.userId,
186
+ );
187
+ };
188
+
189
+ const sendStatusToCustomer = async (quotation: Quotation, requestContext: Context) => {
190
+ const { modules, userId } = requestContext;
191
+
192
+ const user = await modules.users.findUserById(quotation.userId);
193
+ const locale = modules.users.userLocale(user, requestContext);
194
+
195
+ await modules.worker.addWork(
196
+ {
197
+ type: 'MESSAGE',
198
+ retries: 0,
199
+ input: {
200
+ locale,
201
+ template: 'QUOTATION_STATUS',
202
+ quotationId: quotation._id,
203
+ },
204
+ },
205
+ userId,
206
+ );
207
+
208
+ return quotation;
209
+ };
210
+
211
+ const updateQuotationFields =
212
+ (fieldKeys: Array<string>) => async (quotationId: string, values: any, userId?: string) => {
213
+ log(`Update quotation fields ${fieldKeys.join(', ').toUpperCase()}`, {
214
+ quotationId,
215
+ userId,
216
+ });
217
+
218
+ const modifier = {
219
+ $set: fieldKeys.reduce((set, key) => ({ ...set, [key]: values[key] }), {}),
220
+ };
221
+
222
+ await mutations.update(quotationId, modifier, userId);
223
+
224
+ const selector = generateDbFilterById(quotationId);
225
+ const quotation = await Quotations.findOne(selector, {});
226
+
227
+ emit('QUOTATION_UPDATE', { quotation, fields: fieldKeys });
228
+
229
+ return quotation;
230
+ };
231
+
232
+ return {
233
+ // Queries
234
+ count: async (query) => {
235
+ const quotationCount = await Quotations.countDocuments(buildFindSelector(query));
236
+ return quotationCount;
237
+ },
238
+
239
+ findQuotation: async ({ quotationId }, options) => {
240
+ const selector = generateDbFilterById(quotationId);
241
+ return Quotations.findOne(selector, options);
242
+ },
243
+
244
+ findQuotations: async ({ limit, offset, sort, ...query }, options) => {
245
+ const quotations = Quotations.find(buildFindSelector(query), {
246
+ limit,
247
+ skip: offset,
248
+ sort: buildSortOptions(sort),
249
+ ...options,
250
+ });
251
+
252
+ return quotations.toArray();
253
+ },
254
+
255
+ // Transformations
256
+ normalizedStatus: (quotation) => {
257
+ return quotation.status === null
258
+ ? QuotationStatus.REQUESTED
259
+ : (quotation.status as QuotationStatus);
260
+ },
261
+
262
+ isExpired,
263
+
264
+ isProposalValid: (quotation) => {
265
+ return quotation.status === QuotationStatus.PROPOSED && !isExpired(quotation);
266
+ },
267
+
268
+ // Processing
269
+ fullfillQuotation: async (quotationId, info, requestContext) => {
270
+ const selector = generateDbFilterById(quotationId);
271
+ const quotation = await Quotations.findOne(selector, {});
272
+
273
+ if (quotation.status === QuotationStatus.FULLFILLED) return quotation;
274
+
275
+ let updatedQuotation = await updateStatus(
276
+ quotation._id,
277
+ {
278
+ status: QuotationStatus.FULLFILLED,
279
+ info: JSON.stringify(info),
280
+ },
281
+ requestContext.userId,
282
+ );
283
+
284
+ updatedQuotation = await processQuotation(updatedQuotation, {}, requestContext);
285
+
286
+ return sendStatusToCustomer(updatedQuotation, requestContext);
287
+ },
288
+
289
+ proposeQuotation: async (quotation, { quotationContext }, requestContext) => {
290
+ if (quotation.status !== QuotationStatus.PROCESSING) return quotation;
291
+
292
+ let updatedQuotation = await updateStatus(
293
+ quotation._id,
294
+ {
295
+ status: QuotationStatus.PROPOSED,
296
+ info: 'proposed manually',
297
+ },
298
+ requestContext.userId,
299
+ );
300
+
301
+ updatedQuotation = await processQuotation(updatedQuotation, { quotationContext }, requestContext);
302
+
303
+ return sendStatusToCustomer(updatedQuotation, requestContext);
304
+ },
305
+
306
+ rejectQuotation: async (quotation, { quotationContext }, requestContext) => {
307
+ if (quotation.status === QuotationStatus.FULLFILLED) return quotation;
308
+
309
+ let updatedQuotation = await updateStatus(
310
+ quotation._id,
311
+ {
312
+ status: QuotationStatus.REJECTED,
313
+ info: 'rejected manually',
314
+ },
315
+ requestContext.userId,
316
+ );
317
+
318
+ updatedQuotation = await processQuotation(updatedQuotation, { quotationContext }, requestContext);
319
+
320
+ return sendStatusToCustomer(updatedQuotation, requestContext);
321
+ },
322
+
323
+ verifyQuotation: async (quotation, { quotationContext }, requestContext) => {
324
+ if (quotation.status !== QuotationStatus.REQUESTED) return quotation;
325
+
326
+ let updatedQuotation = await updateStatus(
327
+ quotation._id,
328
+ {
329
+ status: QuotationStatus.PROCESSING,
330
+ info: 'verified elligibility manually',
331
+ },
332
+ requestContext.userId,
333
+ );
334
+
335
+ updatedQuotation = await processQuotation(updatedQuotation, { quotationContext }, requestContext);
336
+
337
+ return sendStatusToCustomer(updatedQuotation, requestContext);
338
+ },
339
+
340
+ transformItemConfiguration: async (quotation, configuration, requestContext) => {
341
+ const director = await QuotationDirector.actions({ quotation }, requestContext);
342
+ return director.transformItemConfiguration(configuration);
343
+ },
344
+
345
+ // Mutations
346
+ create: async ({ countryCode, ...quotationData }, requestContext) => {
347
+ const { services, userId } = requestContext;
348
+
349
+ log('Create Quotation', { userId });
350
+
351
+ const currency = await services.countries.resolveDefaultCurrencyCode(
352
+ {
353
+ isoCode: countryCode,
354
+ },
355
+ requestContext,
356
+ );
357
+
358
+ const quotationId = await mutations.create(
359
+ {
360
+ ...quotationData,
361
+ configuration: quotationData.configuration || [],
362
+ countryCode,
363
+ currency,
364
+ log: [],
365
+ status: QuotationStatus.REQUESTED,
366
+ },
367
+ userId,
368
+ );
369
+
370
+ const newQuotation = await Quotations.findOne(generateDbFilterById(quotationId), {});
371
+
372
+ let quotation = await processQuotation(newQuotation, {}, requestContext);
373
+
374
+ quotation = await sendStatusToCustomer(quotation, requestContext);
375
+
376
+ emit('QUOTATION_REQUEST_CREATE', { quotation });
377
+
378
+ return quotation;
379
+ },
380
+
381
+ updateContext: updateQuotationFields(['context']),
382
+ updateProposal: updateQuotationFields(['price', 'expires', 'meta']),
383
+
384
+ updateStatus,
385
+ };
386
+ };
@@ -0,0 +1,8 @@
1
+ export { configureQuotationsModule } from './module/configureQuotationsModule';
2
+
3
+ export { QuotationStatus } from './db/QuotationStatus';
4
+
5
+ export { QuotationAdapter } from './director/QuotationAdapter';
6
+ export { QuotationDirector } from './director/QuotationDirector';
7
+
8
+ export { quotationsSettings } from './quotations-settings';
@@ -0,0 +1,9 @@
1
+ import { generateRandomHash } from '@unchainedshop/utils';
2
+
3
+ export const quotationsSettings = {
4
+ quotationNumberHashFn: null,
5
+
6
+ configureSettings({ quotationNumberHashFn = generateRandomHash } = {}) {
7
+ this.quotationNumberHashFn = quotationNumberHashFn;
8
+ },
9
+ };
@@ -0,0 +1,73 @@
1
+ import { Context } from '@unchainedshop/types/api';
2
+ import { QuotationsModule } from '@unchainedshop/types/quotations';
3
+ import { UsersModule } from '@unchainedshop/types/user';
4
+ import { assert } from 'chai';
5
+ import { configureQuotationsModule } from 'meteor/unchained:core-quotations';
6
+ import { configureUsersModule } from 'meteor/unchained:core-users';
7
+ import { initDb } from 'meteor/unchained:mongodb';
8
+ import '../plugins/manual';
9
+
10
+ describe('Test exports', () => {
11
+ const context: {
12
+ modules: { quotations: QuotationsModule; users: UsersModule };
13
+ services: { countries: { resolveDefaultCurrencyCode: () => string } };
14
+ userId: string;
15
+ } = {
16
+ modules: {
17
+ quotations: null,
18
+ users: null,
19
+ },
20
+ services: {
21
+ countries: {
22
+ resolveDefaultCurrencyCode: () => 'CHF',
23
+ },
24
+ },
25
+ userId: 'Test-User-1234',
26
+ };
27
+
28
+ before(async () => {
29
+ const db = await initDb();
30
+ const quotationsModule = await configureQuotationsModule({ db }).catch(
31
+ (error) => {
32
+ console.error(error);
33
+ throw error;
34
+ }
35
+ );
36
+
37
+ const usersModules = await configureUsersModule({ db }).catch((error) => {
38
+ console.error(error);
39
+ throw error;
40
+ });
41
+
42
+ context.modules.quotations = quotationsModule;
43
+ context.modules.users = usersModules;
44
+ });
45
+
46
+ it('Insert quotation', async () => {
47
+ let quotation = await context.modules.quotations.create(
48
+ {
49
+ countryCode: 'CH',
50
+ productId: 'Product-123',
51
+ userId: 'Test-User-1',
52
+ },
53
+ context as Context
54
+ );
55
+
56
+ assert.ok(quotation);
57
+ const quotationId = quotation._id;
58
+ quotation = await context.modules.quotations.findQuotation({
59
+ quotationId,
60
+ });
61
+
62
+ assert.ok(quotation);
63
+
64
+ await context.modules.quotations.updateProposal(
65
+ quotationId,
66
+ {
67
+ price: 1000,
68
+ meta: { something: 'Test' },
69
+ },
70
+ context.userId
71
+ );
72
+ });
73
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "allowSyntheticDefaultImports": true,
5
+ "declaration": true,
6
+ "esModuleInterop": true,
7
+ "experimentalDecorators": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "lib": ["esnext"],
10
+ "module": "esnext",
11
+ "moduleResolution": "node",
12
+ "noImplicitReturns": true,
13
+ "noUnusedLocals": false,
14
+ "outDir": "lib",
15
+ "preserveWatchOutput": true,
16
+ "skipLibCheck": true,
17
+ "sourceMap": true,
18
+ "target": "esnext",
19
+ "types": ["node"],
20
+ "baseUrl": ".", // This must be specified if "paths" is.
21
+ "paths": {
22
+ "meteor/unchained:*": ["node_modules/@unchainedshop/types/index.d.ts"]
23
+ }
24
+ },
25
+ "include": ["src"]
26
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.build.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": ["node", "mocha"]
6
+ },
7
+ "include": ["src", "tests", "plugins"]
8
+ }