monapi 0.1.0 → 1.0.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/README.md +65 -3
- package/dist/index.d.mts +93 -4
- package/dist/index.d.ts +93 -4
- package/dist/index.js +522 -353
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +513 -354
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var express = require('express');
|
|
4
3
|
var mongoose = require('mongoose');
|
|
4
|
+
var express = require('express');
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
7
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
8
|
+
}) : x)(function(x) {
|
|
9
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
10
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
11
|
+
});
|
|
7
12
|
|
|
8
13
|
// src/types/query.ts
|
|
9
14
|
var FieldType = /* @__PURE__ */ ((FieldType2) => {
|
|
@@ -283,6 +288,43 @@ var BadRequestError = class _BadRequestError extends MonapiError {
|
|
|
283
288
|
}
|
|
284
289
|
};
|
|
285
290
|
|
|
291
|
+
// src/core/permission-checker.ts
|
|
292
|
+
var ROUTE_TO_CRUD = {
|
|
293
|
+
list: "find",
|
|
294
|
+
get: "find",
|
|
295
|
+
create: "create",
|
|
296
|
+
update: "update",
|
|
297
|
+
patch: "patch",
|
|
298
|
+
delete: "delete"
|
|
299
|
+
};
|
|
300
|
+
async function checkPermissions(req, collection, routeOp, permissions) {
|
|
301
|
+
if (!permissions) return;
|
|
302
|
+
const permission = permissions[routeOp];
|
|
303
|
+
if (!permission) return;
|
|
304
|
+
const user = req.user;
|
|
305
|
+
if (!user) throw new UnauthorizedError();
|
|
306
|
+
const crudOp = ROUTE_TO_CRUD[routeOp] || routeOp;
|
|
307
|
+
const allowed = await evaluatePermission(permission, {
|
|
308
|
+
user,
|
|
309
|
+
collection,
|
|
310
|
+
operation: crudOp,
|
|
311
|
+
data: req.body,
|
|
312
|
+
id: req.params.id,
|
|
313
|
+
req: req.raw
|
|
314
|
+
});
|
|
315
|
+
if (!allowed) throw new ForbiddenError();
|
|
316
|
+
}
|
|
317
|
+
async function evaluatePermission(permission, ctx) {
|
|
318
|
+
if (Array.isArray(permission)) {
|
|
319
|
+
if (!ctx.user.roles || ctx.user.roles.length === 0) return false;
|
|
320
|
+
return permission.some((role) => ctx.user.roles?.includes(role));
|
|
321
|
+
}
|
|
322
|
+
if (typeof permission === "function") {
|
|
323
|
+
return permission(ctx);
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
286
328
|
// src/engine/filter-parser.ts
|
|
287
329
|
var OPERATOR_MAP = {
|
|
288
330
|
eq: "$eq",
|
|
@@ -525,376 +567,448 @@ function extractPagination(queryParams, defaultLimit = DEFAULT_LIMIT, maxLimit =
|
|
|
525
567
|
return { page, limit };
|
|
526
568
|
}
|
|
527
569
|
|
|
528
|
-
// src/
|
|
529
|
-
function
|
|
530
|
-
const user = params.req.user;
|
|
570
|
+
// src/core/crud-operations.ts
|
|
571
|
+
function createCtx(collection, operation, req, res, extra = {}) {
|
|
531
572
|
return {
|
|
532
|
-
collection
|
|
533
|
-
operation
|
|
534
|
-
user,
|
|
535
|
-
query:
|
|
536
|
-
data:
|
|
537
|
-
id:
|
|
538
|
-
result:
|
|
539
|
-
req:
|
|
540
|
-
res:
|
|
573
|
+
collection,
|
|
574
|
+
operation,
|
|
575
|
+
user: req.user,
|
|
576
|
+
query: extra.query,
|
|
577
|
+
data: extra.data,
|
|
578
|
+
id: extra.id,
|
|
579
|
+
result: extra.result,
|
|
580
|
+
req: req.raw,
|
|
581
|
+
res: res.raw,
|
|
541
582
|
meta: {}
|
|
542
583
|
};
|
|
543
584
|
}
|
|
544
|
-
async function
|
|
545
|
-
if (!hooks) return
|
|
546
|
-
const
|
|
547
|
-
if (!
|
|
585
|
+
async function runHook(hooks, hookName, ctx, logger) {
|
|
586
|
+
if (!hooks) return;
|
|
587
|
+
const fn = hooks[hookName];
|
|
588
|
+
if (!fn) return;
|
|
548
589
|
try {
|
|
549
|
-
await
|
|
590
|
+
await fn(ctx);
|
|
550
591
|
} catch (error) {
|
|
551
592
|
if (logger) {
|
|
552
|
-
logger.error(`Hook '${hookName}' failed for
|
|
593
|
+
logger.error(`Hook '${hookName}' failed for '${ctx.collection}': ${error.message}`);
|
|
553
594
|
}
|
|
554
595
|
throw error;
|
|
555
596
|
}
|
|
556
|
-
return ctx;
|
|
557
597
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
598
|
+
async function listDocuments(req, res, opts) {
|
|
599
|
+
const { collectionName, model, adapter, config, defaults, logger } = opts;
|
|
600
|
+
const mongoQuery = buildQuery(req.query, {
|
|
601
|
+
adapter,
|
|
602
|
+
queryConfig: config.query ?? defaults?.query,
|
|
603
|
+
defaultLimit: defaults?.pagination?.limit,
|
|
604
|
+
maxLimit: defaults?.pagination?.maxLimit,
|
|
605
|
+
maxRegexLength: defaults?.security?.maxRegexLength
|
|
606
|
+
});
|
|
607
|
+
const ctx = createCtx(collectionName, "find", req, res, { query: mongoQuery });
|
|
608
|
+
await runHook(config.hooks, "beforeFind", ctx, logger);
|
|
609
|
+
if (ctx.preventDefault) return { statusCode: 0, data: null };
|
|
610
|
+
const query = ctx.query ?? mongoQuery;
|
|
611
|
+
const [docs, total] = await Promise.all([
|
|
612
|
+
model.find(query.filter).sort(query.sort).skip(query.skip ?? 0).limit(query.limit ?? 10).select(query.projection ?? {}).lean().exec(),
|
|
613
|
+
model.countDocuments(query.filter).exec()
|
|
614
|
+
]);
|
|
615
|
+
const { page, limit } = extractPagination(
|
|
616
|
+
req.query,
|
|
617
|
+
defaults?.pagination?.limit,
|
|
618
|
+
defaults?.pagination?.maxLimit
|
|
619
|
+
);
|
|
620
|
+
ctx.result = docs;
|
|
621
|
+
await runHook(config.hooks, "afterFind", ctx, logger);
|
|
562
622
|
return {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
update: config.handlers?.update ?? createUpdateHandler(collectionName, model, adapter, config, logger),
|
|
567
|
-
patch: config.handlers?.patch ?? createPatchHandler(collectionName, model, adapter, config, logger),
|
|
568
|
-
delete: config.handlers?.delete ?? createDeleteHandler(collectionName, model, config, logger)
|
|
623
|
+
statusCode: 200,
|
|
624
|
+
data: ctx.result ?? docs,
|
|
625
|
+
meta: buildPaginationMeta(total, page, limit)
|
|
569
626
|
};
|
|
570
627
|
}
|
|
571
|
-
function
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
collection: collectionName,
|
|
583
|
-
operation: "find",
|
|
584
|
-
req,
|
|
585
|
-
res,
|
|
586
|
-
query: mongoQuery
|
|
587
|
-
});
|
|
588
|
-
await executeHook(config.hooks, "beforeFind", ctx, logger);
|
|
589
|
-
if (ctx.preventDefault) {
|
|
590
|
-
return;
|
|
628
|
+
async function getDocument(req, res, opts) {
|
|
629
|
+
const { collectionName, model, config, logger } = opts;
|
|
630
|
+
const { id } = req.params;
|
|
631
|
+
let projection;
|
|
632
|
+
if (req.query.fields) {
|
|
633
|
+
const fields = typeof req.query.fields === "string" ? req.query.fields.split(",") : req.query.fields;
|
|
634
|
+
projection = {};
|
|
635
|
+
for (const f of fields) {
|
|
636
|
+
const trimmed = f.trim();
|
|
637
|
+
if (trimmed && !trimmed.startsWith("$")) {
|
|
638
|
+
projection[trimmed] = 1;
|
|
591
639
|
}
|
|
592
|
-
const query = ctx.query ?? mongoQuery;
|
|
593
|
-
const [docs, total] = await Promise.all([
|
|
594
|
-
model.find(query.filter).sort(query.sort).skip(query.skip ?? 0).limit(query.limit ?? 10).select(query.projection ?? {}).lean().exec(),
|
|
595
|
-
model.countDocuments(query.filter).exec()
|
|
596
|
-
]);
|
|
597
|
-
const { page, limit } = extractPagination(req.query, defaults?.pagination?.limit, defaults?.pagination?.maxLimit);
|
|
598
|
-
ctx.result = docs;
|
|
599
|
-
await executeHook(config.hooks, "afterFind", ctx, logger);
|
|
600
|
-
const response = {
|
|
601
|
-
data: ctx.result ?? docs,
|
|
602
|
-
meta: buildPaginationMeta(total, page, limit)
|
|
603
|
-
};
|
|
604
|
-
res.json(response);
|
|
605
|
-
} catch (error) {
|
|
606
|
-
next(error);
|
|
607
640
|
}
|
|
608
|
-
}
|
|
641
|
+
}
|
|
642
|
+
const ctx = createCtx(collectionName, "find", req, res, { id });
|
|
643
|
+
await runHook(config.hooks, "beforeFind", ctx, logger);
|
|
644
|
+
if (ctx.preventDefault) return { statusCode: 0, data: null };
|
|
645
|
+
let query = model.findById(id);
|
|
646
|
+
if (projection) query = query.select(projection);
|
|
647
|
+
const doc = await query.lean().exec();
|
|
648
|
+
if (!doc) throw new NotFoundError(collectionName, id);
|
|
649
|
+
ctx.result = doc;
|
|
650
|
+
await runHook(config.hooks, "afterFind", ctx, logger);
|
|
651
|
+
return { statusCode: 200, data: ctx.result ?? doc };
|
|
609
652
|
}
|
|
610
|
-
function
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
626
|
-
const ctx = createHookContext({
|
|
627
|
-
collection: collectionName,
|
|
628
|
-
operation: "find",
|
|
629
|
-
req,
|
|
630
|
-
res,
|
|
631
|
-
id
|
|
632
|
-
});
|
|
633
|
-
await executeHook(config.hooks, "beforeFind", ctx, logger);
|
|
634
|
-
if (ctx.preventDefault) return;
|
|
635
|
-
let query = model.findById(id);
|
|
636
|
-
if (projection) query = query.select(projection);
|
|
637
|
-
const doc = await query.lean().exec();
|
|
638
|
-
if (!doc) {
|
|
639
|
-
throw new NotFoundError(collectionName, id);
|
|
640
|
-
}
|
|
641
|
-
ctx.result = doc;
|
|
642
|
-
await executeHook(config.hooks, "afterFind", ctx, logger);
|
|
643
|
-
const response = { data: ctx.result ?? doc };
|
|
644
|
-
res.json(response);
|
|
645
|
-
} catch (error) {
|
|
646
|
-
next(error);
|
|
647
|
-
}
|
|
648
|
-
};
|
|
653
|
+
async function createDocument(req, res, opts) {
|
|
654
|
+
const { collectionName, model, adapter, config, logger } = opts;
|
|
655
|
+
const data = req.body;
|
|
656
|
+
const validation = await adapter.validate(data);
|
|
657
|
+
if (!validation.valid) {
|
|
658
|
+
throw new ValidationError("Validation failed", validation.errors);
|
|
659
|
+
}
|
|
660
|
+
const ctx = createCtx(collectionName, "create", req, res, { data: validation.data ?? data });
|
|
661
|
+
await runHook(config.hooks, "beforeCreate", ctx, logger);
|
|
662
|
+
if (ctx.preventDefault) return { statusCode: 0, data: null };
|
|
663
|
+
const doc = await model.create(ctx.data ?? data);
|
|
664
|
+
const result = doc.toObject();
|
|
665
|
+
ctx.result = result;
|
|
666
|
+
await runHook(config.hooks, "afterCreate", ctx, logger);
|
|
667
|
+
return { statusCode: 201, data: ctx.result ?? result };
|
|
649
668
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
if (ctx.preventDefault) return;
|
|
667
|
-
const doc = await model.create(ctx.data ?? data);
|
|
668
|
-
const result = doc.toObject();
|
|
669
|
-
ctx.result = result;
|
|
670
|
-
await executeHook(config.hooks, "afterCreate", ctx, logger);
|
|
671
|
-
const response = { data: ctx.result ?? result };
|
|
672
|
-
res.status(201).json(response);
|
|
673
|
-
} catch (error) {
|
|
674
|
-
next(error);
|
|
675
|
-
}
|
|
676
|
-
};
|
|
669
|
+
async function updateDocument(req, res, opts) {
|
|
670
|
+
const { collectionName, model, adapter, config, logger } = opts;
|
|
671
|
+
const { id } = req.params;
|
|
672
|
+
const data = req.body;
|
|
673
|
+
const validation = await adapter.validate(data);
|
|
674
|
+
if (!validation.valid) {
|
|
675
|
+
throw new ValidationError("Validation failed", validation.errors);
|
|
676
|
+
}
|
|
677
|
+
const ctx = createCtx(collectionName, "update", req, res, { id, data: validation.data ?? data });
|
|
678
|
+
await runHook(config.hooks, "beforeUpdate", ctx, logger);
|
|
679
|
+
if (ctx.preventDefault) return { statusCode: 0, data: null };
|
|
680
|
+
const doc = await model.findByIdAndUpdate(id, ctx.data ?? data, { new: true, runValidators: true, overwrite: true }).lean().exec();
|
|
681
|
+
if (!doc) throw new NotFoundError(collectionName, id);
|
|
682
|
+
ctx.result = doc;
|
|
683
|
+
await runHook(config.hooks, "afterUpdate", ctx, logger);
|
|
684
|
+
return { statusCode: 200, data: ctx.result ?? doc };
|
|
677
685
|
}
|
|
678
|
-
function
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
collection: collectionName,
|
|
689
|
-
operation: "update",
|
|
690
|
-
req,
|
|
691
|
-
res,
|
|
692
|
-
id,
|
|
693
|
-
data: validation.data ?? data
|
|
694
|
-
});
|
|
695
|
-
await executeHook(config.hooks, "beforeUpdate", ctx, logger);
|
|
696
|
-
if (ctx.preventDefault) return;
|
|
697
|
-
const doc = await model.findByIdAndUpdate(id, ctx.data ?? data, { new: true, runValidators: true, overwrite: true }).lean().exec();
|
|
698
|
-
if (!doc) {
|
|
699
|
-
throw new NotFoundError(collectionName, id);
|
|
700
|
-
}
|
|
701
|
-
ctx.result = doc;
|
|
702
|
-
await executeHook(config.hooks, "afterUpdate", ctx, logger);
|
|
703
|
-
const response = { data: ctx.result ?? doc };
|
|
704
|
-
res.json(response);
|
|
705
|
-
} catch (error) {
|
|
706
|
-
next(error);
|
|
686
|
+
async function patchDocument(req, res, opts) {
|
|
687
|
+
const { collectionName, model, config, logger } = opts;
|
|
688
|
+
const { id } = req.params;
|
|
689
|
+
const data = req.body;
|
|
690
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
691
|
+
throw new ValidationError("Request body must be an object");
|
|
692
|
+
}
|
|
693
|
+
for (const key of Object.keys(data)) {
|
|
694
|
+
if (key.startsWith("$")) {
|
|
695
|
+
throw new ValidationError(`Invalid field name: ${key}`);
|
|
707
696
|
}
|
|
708
|
-
}
|
|
697
|
+
}
|
|
698
|
+
const ctx = createCtx(collectionName, "patch", req, res, { id, data });
|
|
699
|
+
await runHook(config.hooks, "beforeUpdate", ctx, logger);
|
|
700
|
+
if (ctx.preventDefault) return { statusCode: 0, data: null };
|
|
701
|
+
const doc = await model.findByIdAndUpdate(id, { $set: ctx.data ?? data }, { new: true, runValidators: true }).lean().exec();
|
|
702
|
+
if (!doc) throw new NotFoundError(collectionName, id);
|
|
703
|
+
ctx.result = doc;
|
|
704
|
+
await runHook(config.hooks, "afterUpdate", ctx, logger);
|
|
705
|
+
return { statusCode: 200, data: ctx.result ?? doc };
|
|
709
706
|
}
|
|
710
|
-
function
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (!doc) {
|
|
735
|
-
throw new NotFoundError(collectionName, id);
|
|
736
|
-
}
|
|
737
|
-
ctx.result = doc;
|
|
738
|
-
await executeHook(config.hooks, "afterUpdate", ctx, logger);
|
|
739
|
-
const response = { data: ctx.result ?? doc };
|
|
740
|
-
res.json(response);
|
|
741
|
-
} catch (error) {
|
|
742
|
-
next(error);
|
|
743
|
-
}
|
|
707
|
+
async function deleteDocument(req, res, opts) {
|
|
708
|
+
const { collectionName, model, config, logger } = opts;
|
|
709
|
+
const { id } = req.params;
|
|
710
|
+
const ctx = createCtx(collectionName, "delete", req, res, { id });
|
|
711
|
+
await runHook(config.hooks, "beforeDelete", ctx, logger);
|
|
712
|
+
if (ctx.preventDefault) return { statusCode: 0, data: null };
|
|
713
|
+
const doc = await model.findByIdAndDelete(id).lean().exec();
|
|
714
|
+
if (!doc) throw new NotFoundError(collectionName, id);
|
|
715
|
+
ctx.result = doc;
|
|
716
|
+
await runHook(config.hooks, "afterDelete", ctx, logger);
|
|
717
|
+
return { statusCode: 200, data: ctx.result ?? doc };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/adapters/framework/express.ts
|
|
721
|
+
function toMonapiRequest(req) {
|
|
722
|
+
return {
|
|
723
|
+
params: req.params,
|
|
724
|
+
query: req.query,
|
|
725
|
+
body: req.body,
|
|
726
|
+
headers: req.headers,
|
|
727
|
+
method: req.method,
|
|
728
|
+
path: req.path,
|
|
729
|
+
user: req.user,
|
|
730
|
+
raw: req
|
|
744
731
|
};
|
|
745
732
|
}
|
|
746
|
-
function
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const doc = await model.findByIdAndDelete(id).lean().exec();
|
|
760
|
-
if (!doc) {
|
|
761
|
-
throw new NotFoundError(collectionName, id);
|
|
762
|
-
}
|
|
763
|
-
ctx.result = doc;
|
|
764
|
-
await executeHook(config.hooks, "afterDelete", ctx, logger);
|
|
765
|
-
res.json({ data: ctx.result ?? doc });
|
|
766
|
-
} catch (error) {
|
|
767
|
-
next(error);
|
|
733
|
+
function toMonapiResponse(res) {
|
|
734
|
+
const wrapper = {
|
|
735
|
+
raw: res,
|
|
736
|
+
status(code) {
|
|
737
|
+
res.status(code);
|
|
738
|
+
return wrapper;
|
|
739
|
+
},
|
|
740
|
+
json(data) {
|
|
741
|
+
res.json(data);
|
|
742
|
+
},
|
|
743
|
+
setHeader(key, value) {
|
|
744
|
+
res.setHeader(key, value);
|
|
745
|
+
return wrapper;
|
|
768
746
|
}
|
|
769
747
|
};
|
|
748
|
+
return wrapper;
|
|
770
749
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
750
|
+
var ExpressAdapter = class {
|
|
751
|
+
constructor() {
|
|
752
|
+
this.name = "express";
|
|
753
|
+
}
|
|
754
|
+
createRouter(collections, options) {
|
|
755
|
+
const mainRouter = express.Router();
|
|
756
|
+
const basePath = options?.basePath ?? "";
|
|
757
|
+
for (const [name, ctx] of collections) {
|
|
758
|
+
const collectionRouter = this.buildCollectionRouter(ctx, options?.authMiddleware);
|
|
759
|
+
const path = basePath ? `${basePath}/${name}` : `/${name}`;
|
|
760
|
+
mainRouter.use(path, collectionRouter);
|
|
761
|
+
}
|
|
762
|
+
return mainRouter;
|
|
763
|
+
}
|
|
764
|
+
wrapHandler(handler) {
|
|
765
|
+
return async (req, res, next) => {
|
|
766
|
+
try {
|
|
767
|
+
await handler(toMonapiRequest(req), toMonapiResponse(res));
|
|
768
|
+
} catch (error) {
|
|
769
|
+
next(error);
|
|
787
770
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
createErrorHandler(logger) {
|
|
774
|
+
return (err, _req, res, _next) => {
|
|
775
|
+
if (err instanceof MonapiError) {
|
|
776
|
+
if (logger) logger.warn(`${err.code}: ${err.message}`, { statusCode: err.statusCode });
|
|
777
|
+
res.status(err.statusCode).json({
|
|
778
|
+
error: { code: err.code, message: err.message, details: err.details }
|
|
779
|
+
});
|
|
791
780
|
return;
|
|
792
781
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
782
|
+
if (logger) logger.error(`Unhandled error: ${err.message}`, { stack: err.stack });
|
|
783
|
+
const message = process.env.NODE_ENV === "production" ? "Internal server error" : err.message;
|
|
784
|
+
res.status(500).json({ error: { code: "INTERNAL_ERROR", message } });
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
buildCollectionRouter(ctx, authMiddleware) {
|
|
788
|
+
const router = express.Router();
|
|
789
|
+
const { name, config } = ctx;
|
|
790
|
+
const ops = ["list", "get", "create", "update", "patch", "delete"];
|
|
791
|
+
const opHandlers = {
|
|
792
|
+
list: (mReq, mRes) => this.handleOp(listDocuments, mReq, mRes, ctx),
|
|
793
|
+
get: (mReq, mRes) => this.handleOp(getDocument, mReq, mRes, ctx),
|
|
794
|
+
create: (mReq, mRes) => this.handleOp(createDocument, mReq, mRes, ctx),
|
|
795
|
+
update: (mReq, mRes) => this.handleOp(updateDocument, mReq, mRes, ctx),
|
|
796
|
+
patch: (mReq, mRes) => this.handleOp(patchDocument, mReq, mRes, ctx),
|
|
797
|
+
delete: (mReq, mRes) => this.handleOp(deleteDocument, mReq, mRes, ctx)
|
|
798
|
+
};
|
|
799
|
+
for (const op of ops) {
|
|
800
|
+
const stack = [];
|
|
801
|
+
if (authMiddleware) stack.push(authMiddleware);
|
|
802
|
+
if (config.middleware?.all) stack.push(...config.middleware.all);
|
|
803
|
+
if (config.middleware?.[op]) stack.push(...config.middleware[op]);
|
|
804
|
+
if (config.permissions) {
|
|
805
|
+
stack.push(async (req, _res, next) => {
|
|
806
|
+
try {
|
|
807
|
+
await checkPermissions(toMonapiRequest(req), name, op, config.permissions);
|
|
808
|
+
next();
|
|
809
|
+
} catch (error) {
|
|
810
|
+
next(error);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
796
813
|
}
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
814
|
+
const handler = config.handlers?.[op] ? config.handlers[op] : this.wrapHandler(opHandlers[op]);
|
|
815
|
+
switch (op) {
|
|
816
|
+
case "list":
|
|
817
|
+
router.get("/", ...stack, handler);
|
|
818
|
+
break;
|
|
819
|
+
case "get":
|
|
820
|
+
router.get("/:id", ...stack, handler);
|
|
821
|
+
break;
|
|
822
|
+
case "create":
|
|
823
|
+
router.post("/", ...stack, handler);
|
|
824
|
+
break;
|
|
825
|
+
case "update":
|
|
826
|
+
router.put("/:id", ...stack, handler);
|
|
827
|
+
break;
|
|
828
|
+
case "patch":
|
|
829
|
+
router.patch("/:id", ...stack, handler);
|
|
830
|
+
break;
|
|
831
|
+
case "delete":
|
|
832
|
+
router.delete("/:id", ...stack, handler);
|
|
833
|
+
break;
|
|
808
834
|
}
|
|
809
|
-
next();
|
|
810
|
-
} catch (error) {
|
|
811
|
-
next(error);
|
|
812
835
|
}
|
|
836
|
+
return router;
|
|
837
|
+
}
|
|
838
|
+
async handleOp(operation, mReq, mRes, ctx) {
|
|
839
|
+
const result = await operation(mReq, mRes, {
|
|
840
|
+
collectionName: ctx.name,
|
|
841
|
+
model: ctx.model,
|
|
842
|
+
adapter: ctx.adapter,
|
|
843
|
+
config: ctx.config,
|
|
844
|
+
defaults: ctx.defaults,
|
|
845
|
+
logger: ctx.logger
|
|
846
|
+
});
|
|
847
|
+
if (result.statusCode === 0) return;
|
|
848
|
+
if (result.meta) {
|
|
849
|
+
mRes.status(result.statusCode).json({ data: result.data, meta: result.meta });
|
|
850
|
+
} else {
|
|
851
|
+
mRes.status(result.statusCode).json({ data: result.data });
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
// src/adapters/framework/hono.ts
|
|
857
|
+
function toMonapiRequest2(c) {
|
|
858
|
+
return {
|
|
859
|
+
params: c.req.param() || {},
|
|
860
|
+
query: c.req.query() || {},
|
|
861
|
+
body: null,
|
|
862
|
+
// body is async in Hono - set separately
|
|
863
|
+
headers: Object.fromEntries(c.req.raw.headers.entries()),
|
|
864
|
+
method: c.req.method,
|
|
865
|
+
path: c.req.path,
|
|
866
|
+
user: c.get("user"),
|
|
867
|
+
raw: c
|
|
813
868
|
};
|
|
814
869
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
870
|
+
function toMonapiResponse2(c) {
|
|
871
|
+
let statusCode = 200;
|
|
872
|
+
let responseData = null;
|
|
873
|
+
const wrapper = {
|
|
874
|
+
raw: c,
|
|
875
|
+
status(code) {
|
|
876
|
+
statusCode = code;
|
|
877
|
+
return wrapper;
|
|
878
|
+
},
|
|
879
|
+
json(data) {
|
|
880
|
+
responseData = data;
|
|
881
|
+
},
|
|
882
|
+
setHeader(key, value) {
|
|
883
|
+
c.header(key, value);
|
|
884
|
+
return wrapper;
|
|
819
885
|
}
|
|
820
|
-
|
|
886
|
+
};
|
|
887
|
+
wrapper._getResponse = () => ({ statusCode, data: responseData });
|
|
888
|
+
return wrapper;
|
|
889
|
+
}
|
|
890
|
+
var HonoAdapter = class {
|
|
891
|
+
constructor() {
|
|
892
|
+
this.name = "hono";
|
|
821
893
|
}
|
|
822
|
-
|
|
823
|
-
|
|
894
|
+
/**
|
|
895
|
+
* Returns a Hono app instance with all collection routes registered.
|
|
896
|
+
* Expects Hono to be available at runtime (peer dependency).
|
|
897
|
+
*/
|
|
898
|
+
createRouter(collections, options) {
|
|
899
|
+
let Hono;
|
|
900
|
+
try {
|
|
901
|
+
Hono = __require("hono").Hono;
|
|
902
|
+
} catch {
|
|
903
|
+
throw new Error(
|
|
904
|
+
"Hono is required for the Hono adapter. Install it: npm install hono"
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
const app = new Hono();
|
|
908
|
+
if (options?.authMiddleware) {
|
|
909
|
+
app.use("*", options.authMiddleware);
|
|
910
|
+
}
|
|
911
|
+
app.onError((err, c) => {
|
|
912
|
+
if (err instanceof MonapiError) {
|
|
913
|
+
return c.json(
|
|
914
|
+
{ error: { code: err.code, message: err.message, details: err.details } },
|
|
915
|
+
err.statusCode
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
const message = process.env.NODE_ENV === "production" ? "Internal server error" : err.message;
|
|
919
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
920
|
+
});
|
|
921
|
+
for (const [name, ctx] of collections) {
|
|
922
|
+
this.registerCollectionRoutes(app, `/${name}`, ctx);
|
|
923
|
+
}
|
|
924
|
+
return app;
|
|
824
925
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
926
|
+
wrapHandler(handler) {
|
|
927
|
+
return async (c) => {
|
|
928
|
+
const mReq = toMonapiRequest2(c);
|
|
929
|
+
if (["POST", "PUT", "PATCH"].includes(c.req.method)) {
|
|
930
|
+
try {
|
|
931
|
+
mReq.body = await c.req.json();
|
|
932
|
+
} catch {
|
|
933
|
+
mReq.body = {};
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const mRes = toMonapiResponse2(c);
|
|
937
|
+
await handler(mReq, mRes);
|
|
938
|
+
const { statusCode, data } = mRes._getResponse();
|
|
939
|
+
if (data !== null) {
|
|
940
|
+
return c.json(data, statusCode);
|
|
941
|
+
}
|
|
942
|
+
};
|
|
830
943
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
const router = express.Router();
|
|
840
|
-
const handlers = createCRUDHandlers({ collectionName, model, adapter, config, defaults, logger });
|
|
841
|
-
const operations = ["list", "get", "create", "update", "patch", "delete"];
|
|
842
|
-
const middlewareStacks = {};
|
|
843
|
-
for (const op of operations) {
|
|
844
|
-
const stack = [];
|
|
845
|
-
if (authMiddleware) {
|
|
846
|
-
stack.push(authMiddleware);
|
|
847
|
-
}
|
|
848
|
-
if (config.middleware?.all) {
|
|
849
|
-
stack.push(...config.middleware.all);
|
|
850
|
-
}
|
|
851
|
-
const opMiddleware = config.middleware?.[op];
|
|
852
|
-
if (opMiddleware) {
|
|
853
|
-
stack.push(...opMiddleware);
|
|
854
|
-
}
|
|
855
|
-
if (config.permissions) {
|
|
856
|
-
stack.push(createPermissionMiddleware(collectionName, op, config.permissions));
|
|
857
|
-
}
|
|
858
|
-
middlewareStacks[op] = stack;
|
|
859
|
-
}
|
|
860
|
-
router.get("/", ...middlewareStacks.list, handlers.list);
|
|
861
|
-
router.get("/:id", ...middlewareStacks.get, handlers.get);
|
|
862
|
-
router.post("/", ...middlewareStacks.create, handlers.create);
|
|
863
|
-
router.put("/:id", ...middlewareStacks.update, handlers.update);
|
|
864
|
-
router.patch("/:id", ...middlewareStacks.patch, handlers.patch);
|
|
865
|
-
router.delete("/:id", ...middlewareStacks.delete, handlers.delete);
|
|
866
|
-
return router;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// src/middleware/error-handler.ts
|
|
870
|
-
function createErrorHandler(logger) {
|
|
871
|
-
return (err, _req, res, _next) => {
|
|
872
|
-
if (err instanceof MonapiError) {
|
|
873
|
-
if (logger) {
|
|
874
|
-
logger.warn(`${err.code}: ${err.message}`, { statusCode: err.statusCode });
|
|
944
|
+
createErrorHandler(logger) {
|
|
945
|
+
return (err, c) => {
|
|
946
|
+
if (err instanceof MonapiError) {
|
|
947
|
+
if (logger) logger.warn(`${err.code}: ${err.message}`);
|
|
948
|
+
return c.json(
|
|
949
|
+
{ error: { code: err.code, message: err.message, details: err.details } },
|
|
950
|
+
err.statusCode
|
|
951
|
+
);
|
|
875
952
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
953
|
+
if (logger) logger.error(`Unhandled error: ${err.message}`);
|
|
954
|
+
const message = process.env.NODE_ENV === "production" ? "Internal server error" : err.message;
|
|
955
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
registerCollectionRoutes(app, prefix, ctx) {
|
|
959
|
+
const { name, config } = ctx;
|
|
960
|
+
const createHandler = (op, handler) => {
|
|
961
|
+
return async (c) => {
|
|
962
|
+
const mReq = toMonapiRequest2(c);
|
|
963
|
+
if (["POST", "PUT", "PATCH"].includes(c.req.method)) {
|
|
964
|
+
try {
|
|
965
|
+
mReq.body = await c.req.json();
|
|
966
|
+
} catch {
|
|
967
|
+
mReq.body = {};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (config.permissions) {
|
|
971
|
+
await checkPermissions(mReq, name, op, config.permissions);
|
|
972
|
+
}
|
|
973
|
+
const mRes = toMonapiResponse2(c);
|
|
974
|
+
const result = await handler(mReq, mRes, {
|
|
975
|
+
collectionName: ctx.name,
|
|
976
|
+
model: ctx.model,
|
|
977
|
+
adapter: ctx.adapter,
|
|
978
|
+
config: ctx.config,
|
|
979
|
+
defaults: ctx.defaults,
|
|
980
|
+
logger: ctx.logger
|
|
981
|
+
});
|
|
982
|
+
if (result.statusCode === 0) return c.body(null, 204);
|
|
983
|
+
if (result.meta) {
|
|
984
|
+
return c.json({ data: result.data, meta: result.meta }, result.statusCode);
|
|
881
985
|
}
|
|
986
|
+
return c.json({ data: result.data }, result.statusCode);
|
|
882
987
|
};
|
|
883
|
-
res.status(err.statusCode).json(response2);
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
if (logger) {
|
|
887
|
-
logger.error(`Unhandled error: ${err.message}`, { stack: err.stack });
|
|
888
|
-
}
|
|
889
|
-
const message = process.env.NODE_ENV === "production" ? "Internal server error" : err.message;
|
|
890
|
-
const response = {
|
|
891
|
-
error: {
|
|
892
|
-
code: "INTERNAL_ERROR",
|
|
893
|
-
message
|
|
894
|
-
}
|
|
895
988
|
};
|
|
896
|
-
|
|
897
|
-
|
|
989
|
+
app.get(prefix, createHandler("list", listDocuments));
|
|
990
|
+
app.get(`${prefix}/:id`, createHandler("get", getDocument));
|
|
991
|
+
app.post(prefix, createHandler("create", createDocument));
|
|
992
|
+
app.put(`${prefix}/:id`, createHandler("update", updateDocument));
|
|
993
|
+
app.patch(`${prefix}/:id`, createHandler("patch", patchDocument));
|
|
994
|
+
app.delete(`${prefix}/:id`, createHandler("delete", deleteDocument));
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
// src/adapters/framework/index.ts
|
|
999
|
+
function resolveFrameworkAdapter(framework) {
|
|
1000
|
+
if (!framework || framework === "express") {
|
|
1001
|
+
return new ExpressAdapter();
|
|
1002
|
+
}
|
|
1003
|
+
if (typeof framework === "string") {
|
|
1004
|
+
switch (framework) {
|
|
1005
|
+
case "hono":
|
|
1006
|
+
return new HonoAdapter();
|
|
1007
|
+
default:
|
|
1008
|
+
throw new Error(`Unknown framework: ${framework}. Use 'express', 'hono', or pass a custom FrameworkAdapter.`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return framework;
|
|
898
1012
|
}
|
|
899
1013
|
|
|
900
1014
|
// src/utils/logger.ts
|
|
@@ -921,9 +1035,10 @@ var Monapi = class {
|
|
|
921
1035
|
this.collections = /* @__PURE__ */ new Map();
|
|
922
1036
|
this.config = config;
|
|
923
1037
|
this.logger = config.logger ?? defaultLogger;
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1038
|
+
this.frameworkAdapter = resolveFrameworkAdapter(
|
|
1039
|
+
config.framework
|
|
1040
|
+
);
|
|
1041
|
+
this.logger.debug(`Using framework adapter: ${this.frameworkAdapter.name}`);
|
|
927
1042
|
}
|
|
928
1043
|
/**
|
|
929
1044
|
* Register a collection resource.
|
|
@@ -932,43 +1047,56 @@ var Monapi = class {
|
|
|
932
1047
|
resource(name, collectionConfig) {
|
|
933
1048
|
const adapter = collectionConfig.adapter ?? createSchemaAdapter(collectionConfig.schema);
|
|
934
1049
|
const model = this.resolveModel(name, collectionConfig, adapter);
|
|
935
|
-
this.collections.set(name, {
|
|
1050
|
+
this.collections.set(name, {
|
|
1051
|
+
name,
|
|
1052
|
+
model,
|
|
1053
|
+
adapter,
|
|
1054
|
+
config: collectionConfig,
|
|
1055
|
+
defaults: this.config.defaults,
|
|
1056
|
+
logger: this.logger
|
|
1057
|
+
});
|
|
936
1058
|
this.logger.debug(`Registered resource: ${name}`, {
|
|
937
1059
|
fields: adapter.getFields()
|
|
938
1060
|
});
|
|
939
1061
|
return this;
|
|
940
1062
|
}
|
|
941
1063
|
/**
|
|
942
|
-
* Generate the
|
|
1064
|
+
* Generate the framework-specific router with all registered collection routes.
|
|
1065
|
+
*
|
|
1066
|
+
* - Express: returns an Express Router
|
|
1067
|
+
* - Fastify: returns a Fastify plugin function
|
|
1068
|
+
* - Hono: returns a Hono app instance
|
|
1069
|
+
* - NestJS: returns a dynamic module definition
|
|
943
1070
|
*/
|
|
944
1071
|
router() {
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
logger: this.logger,
|
|
955
|
-
authMiddleware: this.authMiddleware
|
|
956
|
-
});
|
|
1072
|
+
const result = this.frameworkAdapter.createRouter(this.collections, {
|
|
1073
|
+
basePath: this.config.basePath,
|
|
1074
|
+
authMiddleware: this.config.auth?.middleware
|
|
1075
|
+
});
|
|
1076
|
+
if (this.frameworkAdapter.name === "express") {
|
|
1077
|
+
result.use(this.frameworkAdapter.createErrorHandler(this.logger));
|
|
1078
|
+
}
|
|
1079
|
+
for (const [name] of this.collections) {
|
|
1080
|
+
const basePath = this.config.basePath ?? "";
|
|
957
1081
|
const path = basePath ? `${basePath}/${name}` : `/${name}`;
|
|
958
|
-
|
|
959
|
-
this.logger.info(`Mounted routes: ${path}`);
|
|
1082
|
+
this.logger.info(`Mounted routes: ${path} [${this.frameworkAdapter.name}]`);
|
|
960
1083
|
}
|
|
961
|
-
|
|
962
|
-
|
|
1084
|
+
return result;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Get the framework adapter instance.
|
|
1088
|
+
*/
|
|
1089
|
+
getFrameworkAdapter() {
|
|
1090
|
+
return this.frameworkAdapter;
|
|
963
1091
|
}
|
|
964
1092
|
/**
|
|
965
|
-
* Get a registered collection's model
|
|
1093
|
+
* Get a registered collection's model.
|
|
966
1094
|
*/
|
|
967
1095
|
getModel(name) {
|
|
968
1096
|
return this.collections.get(name)?.model;
|
|
969
1097
|
}
|
|
970
1098
|
/**
|
|
971
|
-
* Get a registered collection's adapter
|
|
1099
|
+
* Get a registered collection's adapter.
|
|
972
1100
|
*/
|
|
973
1101
|
getAdapter(name) {
|
|
974
1102
|
return this.collections.get(name)?.adapter;
|
|
@@ -995,9 +1123,42 @@ var Monapi = class {
|
|
|
995
1123
|
}
|
|
996
1124
|
};
|
|
997
1125
|
|
|
1126
|
+
// src/middleware/error-handler.ts
|
|
1127
|
+
function createErrorHandler(logger) {
|
|
1128
|
+
return (err, _req, res, _next) => {
|
|
1129
|
+
if (err instanceof MonapiError) {
|
|
1130
|
+
if (logger) {
|
|
1131
|
+
logger.warn(`${err.code}: ${err.message}`, { statusCode: err.statusCode });
|
|
1132
|
+
}
|
|
1133
|
+
const response2 = {
|
|
1134
|
+
error: {
|
|
1135
|
+
code: err.code,
|
|
1136
|
+
message: err.message,
|
|
1137
|
+
details: err.details
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
res.status(err.statusCode).json(response2);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (logger) {
|
|
1144
|
+
logger.error(`Unhandled error: ${err.message}`, { stack: err.stack });
|
|
1145
|
+
}
|
|
1146
|
+
const message = process.env.NODE_ENV === "production" ? "Internal server error" : err.message;
|
|
1147
|
+
const response = {
|
|
1148
|
+
error: {
|
|
1149
|
+
code: "INTERNAL_ERROR",
|
|
1150
|
+
message
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
res.status(500).json(response);
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
|
|
998
1157
|
exports.BadRequestError = BadRequestError;
|
|
1158
|
+
exports.ExpressAdapter = ExpressAdapter;
|
|
999
1159
|
exports.FieldType = FieldType;
|
|
1000
1160
|
exports.ForbiddenError = ForbiddenError;
|
|
1161
|
+
exports.HonoAdapter = HonoAdapter;
|
|
1001
1162
|
exports.Monapi = Monapi;
|
|
1002
1163
|
exports.MonapiError = MonapiError;
|
|
1003
1164
|
exports.MongooseAdapter = MongooseAdapter;
|
|
@@ -1007,9 +1168,17 @@ exports.UnauthorizedError = UnauthorizedError;
|
|
|
1007
1168
|
exports.ValidationError = ValidationError;
|
|
1008
1169
|
exports.buildPaginationMeta = buildPaginationMeta;
|
|
1009
1170
|
exports.buildQuery = buildQuery;
|
|
1171
|
+
exports.checkPermissions = checkPermissions;
|
|
1172
|
+
exports.createDocument = createDocument;
|
|
1010
1173
|
exports.createErrorHandler = createErrorHandler;
|
|
1011
1174
|
exports.createSchemaAdapter = createSchemaAdapter;
|
|
1175
|
+
exports.deleteDocument = deleteDocument;
|
|
1012
1176
|
exports.detectSchemaType = detectSchemaType;
|
|
1177
|
+
exports.getDocument = getDocument;
|
|
1178
|
+
exports.listDocuments = listDocuments;
|
|
1013
1179
|
exports.parseFilters = parseFilters;
|
|
1180
|
+
exports.patchDocument = patchDocument;
|
|
1181
|
+
exports.resolveFrameworkAdapter = resolveFrameworkAdapter;
|
|
1182
|
+
exports.updateDocument = updateDocument;
|
|
1014
1183
|
//# sourceMappingURL=index.js.map
|
|
1015
1184
|
//# sourceMappingURL=index.js.map
|