@bobbykim/manguito-cms-api 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/dist/index.d.mts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +170 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +166 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -3
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bobby Kim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProgrammaticFieldDefinition, SchemaRegistry, StorageAdapter, ResolvedRateLimitConfig, CorsConfig, ServerAdapter, APIAdapter } from '@bobbykim/manguito-cms-core';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
3
|
import { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db';
|
|
4
4
|
|
|
5
|
+
type ResolverMap = Map<string, ProgrammaticFieldDefinition>;
|
|
6
|
+
declare function resolverKey(schema: string, field: string): string;
|
|
7
|
+
declare function validateResolverBindings(registry: SchemaRegistry, resolvers: ResolverMap): void;
|
|
8
|
+
declare function createProgrammaticResolver(resolvers: ResolverMap, options?: {
|
|
9
|
+
onError?: (message: string) => void;
|
|
10
|
+
}): {
|
|
11
|
+
resolveItem: (schema: string, row: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
12
|
+
resolveList: (schema: string, rows: Record<string, unknown>[]) => Promise<Record<string, unknown>[]>;
|
|
13
|
+
hasSchema: (schema: string) => boolean;
|
|
14
|
+
};
|
|
15
|
+
type ProgrammaticResolver = ReturnType<typeof createProgrammaticResolver>;
|
|
16
|
+
|
|
5
17
|
type CreateCmsAppOptions = {
|
|
6
18
|
/** CMS display name shown in GET /admin/api/config. Defaults to 'Manguito CMS'. */
|
|
7
19
|
name?: string;
|
|
@@ -15,6 +27,8 @@ type CreateCmsAppOptions = {
|
|
|
15
27
|
};
|
|
16
28
|
rateLimit?: ResolvedRateLimitConfig;
|
|
17
29
|
cors?: CorsConfig;
|
|
30
|
+
/** Programmatic field resolvers, keyed `${schema}::${field}`. */
|
|
31
|
+
resolvers?: ResolverMap;
|
|
18
32
|
};
|
|
19
33
|
interface ManguitoCmsAPIAdapter {
|
|
20
34
|
readonly prefix: string;
|
|
@@ -38,4 +52,4 @@ type APIAdapterOptions = {
|
|
|
38
52
|
};
|
|
39
53
|
declare function createAPIAdapter(options?: APIAdapterOptions): APIAdapter;
|
|
40
54
|
|
|
41
|
-
export { type APIAdapterOptions, type CreateCmsAppOptions, type NodeServerOptions, createAPIAdapter, createCmsApp, createServer };
|
|
55
|
+
export { type APIAdapterOptions, type CreateCmsAppOptions, type NodeServerOptions, type ProgrammaticResolver, type ResolverMap, createAPIAdapter, createCmsApp, createProgrammaticResolver, createServer, resolverKey, validateResolverBindings };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProgrammaticFieldDefinition, SchemaRegistry, StorageAdapter, ResolvedRateLimitConfig, CorsConfig, ServerAdapter, APIAdapter } from '@bobbykim/manguito-cms-core';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
3
|
import { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db';
|
|
4
4
|
|
|
5
|
+
type ResolverMap = Map<string, ProgrammaticFieldDefinition>;
|
|
6
|
+
declare function resolverKey(schema: string, field: string): string;
|
|
7
|
+
declare function validateResolverBindings(registry: SchemaRegistry, resolvers: ResolverMap): void;
|
|
8
|
+
declare function createProgrammaticResolver(resolvers: ResolverMap, options?: {
|
|
9
|
+
onError?: (message: string) => void;
|
|
10
|
+
}): {
|
|
11
|
+
resolveItem: (schema: string, row: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
12
|
+
resolveList: (schema: string, rows: Record<string, unknown>[]) => Promise<Record<string, unknown>[]>;
|
|
13
|
+
hasSchema: (schema: string) => boolean;
|
|
14
|
+
};
|
|
15
|
+
type ProgrammaticResolver = ReturnType<typeof createProgrammaticResolver>;
|
|
16
|
+
|
|
5
17
|
type CreateCmsAppOptions = {
|
|
6
18
|
/** CMS display name shown in GET /admin/api/config. Defaults to 'Manguito CMS'. */
|
|
7
19
|
name?: string;
|
|
@@ -15,6 +27,8 @@ type CreateCmsAppOptions = {
|
|
|
15
27
|
};
|
|
16
28
|
rateLimit?: ResolvedRateLimitConfig;
|
|
17
29
|
cors?: CorsConfig;
|
|
30
|
+
/** Programmatic field resolvers, keyed `${schema}::${field}`. */
|
|
31
|
+
resolvers?: ResolverMap;
|
|
18
32
|
};
|
|
19
33
|
interface ManguitoCmsAPIAdapter {
|
|
20
34
|
readonly prefix: string;
|
|
@@ -38,4 +52,4 @@ type APIAdapterOptions = {
|
|
|
38
52
|
};
|
|
39
53
|
declare function createAPIAdapter(options?: APIAdapterOptions): APIAdapter;
|
|
40
54
|
|
|
41
|
-
export { type APIAdapterOptions, type CreateCmsAppOptions, type NodeServerOptions, createAPIAdapter, createCmsApp, createServer };
|
|
55
|
+
export { type APIAdapterOptions, type CreateCmsAppOptions, type NodeServerOptions, type ProgrammaticResolver, type ResolverMap, createAPIAdapter, createCmsApp, createProgrammaticResolver, createServer, resolverKey, validateResolverBindings };
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,10 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
createAPIAdapter: () => createAPIAdapter,
|
|
34
34
|
createCmsApp: () => createCmsApp,
|
|
35
|
-
|
|
35
|
+
createProgrammaticResolver: () => createProgrammaticResolver,
|
|
36
|
+
createServer: () => createServer,
|
|
37
|
+
resolverKey: () => resolverKey,
|
|
38
|
+
validateResolverBindings: () => validateResolverBindings
|
|
36
39
|
});
|
|
37
40
|
module.exports = __toCommonJS(index_exports);
|
|
38
41
|
|
|
@@ -463,6 +466,140 @@ function buildRolesRegistry(roles) {
|
|
|
463
466
|
return Object.fromEntries(roles.map((r) => [r.name, r]));
|
|
464
467
|
}
|
|
465
468
|
|
|
469
|
+
// src/programmatic/resolve.ts
|
|
470
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
471
|
+
var LIST_CONCURRENCY = 10;
|
|
472
|
+
function resolverKey(schema, field) {
|
|
473
|
+
return `${schema}::${field}`;
|
|
474
|
+
}
|
|
475
|
+
function validateResolverBindings(registry, resolvers) {
|
|
476
|
+
const declared = /* @__PURE__ */ new Set();
|
|
477
|
+
const schemas = [
|
|
478
|
+
...Object.values(registry.content_types),
|
|
479
|
+
...Object.values(registry.taxonomy_types)
|
|
480
|
+
];
|
|
481
|
+
for (const schema of schemas) {
|
|
482
|
+
for (const field of schema.fields) {
|
|
483
|
+
if (field.field_type === "programmatic") {
|
|
484
|
+
declared.add(resolverKey(schema.name, field.name));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const missing = [];
|
|
489
|
+
for (const key of declared) if (!resolvers.has(key)) missing.push(key);
|
|
490
|
+
const orphans = [];
|
|
491
|
+
for (const key of resolvers.keys()) if (!declared.has(key)) orphans.push(key);
|
|
492
|
+
if (missing.length === 0 && orphans.length === 0) return;
|
|
493
|
+
const lines = ["\u2717 Programmatic field resolver bindings are invalid."];
|
|
494
|
+
if (missing.length > 0) {
|
|
495
|
+
lines.push("", ' Declared as `type: "programmatic"` but no resolver found:');
|
|
496
|
+
for (const k of missing) lines.push(` - ${k} (add a programmaticField in src/programmatic)`);
|
|
497
|
+
}
|
|
498
|
+
if (orphans.length > 0) {
|
|
499
|
+
lines.push("", " Resolver has no matching programmatic field in the schema:");
|
|
500
|
+
for (const k of orphans) lines.push(` - ${k}`);
|
|
501
|
+
}
|
|
502
|
+
lines.push("", "Exiting.");
|
|
503
|
+
throw new Error(lines.join("\n"));
|
|
504
|
+
}
|
|
505
|
+
function buildContext(row) {
|
|
506
|
+
return {
|
|
507
|
+
get: (name) => row[name],
|
|
508
|
+
record: row
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
async function withTimeout(p, ms) {
|
|
512
|
+
let timer;
|
|
513
|
+
const timeout = new Promise((_, reject) => {
|
|
514
|
+
timer = setTimeout(() => reject(new Error("resolver timeout")), ms);
|
|
515
|
+
});
|
|
516
|
+
try {
|
|
517
|
+
return await Promise.race([p, timeout]);
|
|
518
|
+
} finally {
|
|
519
|
+
clearTimeout(timer);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async function runOne(def, row, onError) {
|
|
523
|
+
const fallback = def.fallback ?? null;
|
|
524
|
+
try {
|
|
525
|
+
const result = await withTimeout(
|
|
526
|
+
Promise.resolve(def.resolve(buildContext(row))),
|
|
527
|
+
def.timeout ?? DEFAULT_TIMEOUT_MS
|
|
528
|
+
);
|
|
529
|
+
return { value: result === void 0 ? null : result, failed: false };
|
|
530
|
+
} catch (err) {
|
|
531
|
+
onError(
|
|
532
|
+
`\u26A0 programmatic field ${resolverKey(def.schema, def.field)} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
533
|
+
);
|
|
534
|
+
return { value: fallback, failed: true };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async function runPool(items, limit, fn) {
|
|
538
|
+
let i = 0;
|
|
539
|
+
async function worker() {
|
|
540
|
+
while (i < items.length) {
|
|
541
|
+
const idx = i++;
|
|
542
|
+
await fn(items[idx]);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, worker);
|
|
546
|
+
await Promise.all(workers);
|
|
547
|
+
}
|
|
548
|
+
function createProgrammaticResolver(resolvers, options) {
|
|
549
|
+
const onError = options?.onError ?? ((message) => {
|
|
550
|
+
process.stderr.write(message + "\n");
|
|
551
|
+
});
|
|
552
|
+
const bySchema = /* @__PURE__ */ new Map();
|
|
553
|
+
for (const def of resolvers.values()) {
|
|
554
|
+
const arr = bySchema.get(def.schema) ?? [];
|
|
555
|
+
arr.push(def);
|
|
556
|
+
bySchema.set(def.schema, arr);
|
|
557
|
+
}
|
|
558
|
+
const cache = /* @__PURE__ */ new Map();
|
|
559
|
+
async function resolveField(def, row) {
|
|
560
|
+
const itemId = row["id"] !== void 0 ? String(row["id"]) : "";
|
|
561
|
+
if (def.cache && itemId) {
|
|
562
|
+
const key = `${resolverKey(def.schema, def.field)}::${itemId}`;
|
|
563
|
+
const now = Date.now();
|
|
564
|
+
const hit = cache.get(key);
|
|
565
|
+
if (hit && hit.expires > now) return hit.value;
|
|
566
|
+
const { value: value2, failed } = await runOne(def, row, onError);
|
|
567
|
+
if (!failed) cache.set(key, { value: value2, expires: now + def.cache.ttl * 1e3 });
|
|
568
|
+
return value2;
|
|
569
|
+
}
|
|
570
|
+
const { value } = await runOne(def, row, onError);
|
|
571
|
+
return value;
|
|
572
|
+
}
|
|
573
|
+
async function resolveItem(schema, row) {
|
|
574
|
+
const defs = bySchema.get(schema);
|
|
575
|
+
if (!defs || defs.length === 0) return { ...row };
|
|
576
|
+
const out = { ...row };
|
|
577
|
+
await Promise.all(
|
|
578
|
+
defs.map(async (def) => {
|
|
579
|
+
out[def.field] = await resolveField(def, row);
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
return out;
|
|
583
|
+
}
|
|
584
|
+
async function resolveList(schema, rows) {
|
|
585
|
+
const defs = (bySchema.get(schema) ?? []).filter((d) => d.on_list === true);
|
|
586
|
+
if (defs.length === 0) return rows.map((r) => ({ ...r }));
|
|
587
|
+
const out = rows.map((r) => ({ ...r }));
|
|
588
|
+
const tasks = [];
|
|
589
|
+
for (let i = 0; i < rows.length; i++) {
|
|
590
|
+
for (const def of defs) tasks.push({ rowIndex: i, def });
|
|
591
|
+
}
|
|
592
|
+
await runPool(tasks, LIST_CONCURRENCY, async ({ rowIndex, def }) => {
|
|
593
|
+
out[rowIndex][def.field] = await resolveField(def, rows[rowIndex]);
|
|
594
|
+
});
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
function hasSchema(schema) {
|
|
598
|
+
return bySchema.has(schema);
|
|
599
|
+
}
|
|
600
|
+
return { resolveItem, resolveList, hasSchema };
|
|
601
|
+
}
|
|
602
|
+
|
|
466
603
|
// src/routes/query-params.ts
|
|
467
604
|
var SORTABLE_FIELDS = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
468
605
|
var RELATION_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -514,7 +651,7 @@ function parseFilters(url, validFields) {
|
|
|
514
651
|
function isPublished(item) {
|
|
515
652
|
return item["published"] === true;
|
|
516
653
|
}
|
|
517
|
-
function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
654
|
+
function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolver) {
|
|
518
655
|
function registerListRoute(path, handler) {
|
|
519
656
|
if (listRateLimit) {
|
|
520
657
|
app.get(path, listRateLimit, handler);
|
|
@@ -541,13 +678,13 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
541
678
|
const basePath = contentType.default_base_path;
|
|
542
679
|
const repo = repos[typeName];
|
|
543
680
|
if (!repo) continue;
|
|
544
|
-
const schemaFieldNames = /* @__PURE__ */ new Set([
|
|
545
|
-
...contentType.fields.map((f) => f.name),
|
|
546
|
-
...contentType.system_fields.map((f) => f.name)
|
|
547
|
-
]);
|
|
548
681
|
const relationFieldNames = new Set(
|
|
549
682
|
contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
|
|
550
683
|
);
|
|
684
|
+
const filterableFieldNames = /* @__PURE__ */ new Set([
|
|
685
|
+
...contentType.fields.filter((f) => f.field_type !== "programmatic").map((f) => f.name),
|
|
686
|
+
...contentType.system_fields.map((f) => f.name)
|
|
687
|
+
]);
|
|
551
688
|
if (contentType.only_one) {
|
|
552
689
|
app.get(`/api/${basePath}`, async (c) => {
|
|
553
690
|
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
@@ -557,7 +694,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
557
694
|
404
|
|
558
695
|
);
|
|
559
696
|
}
|
|
560
|
-
|
|
697
|
+
let data = result.data[0];
|
|
698
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
699
|
+
return c.json({ ok: true, data });
|
|
561
700
|
});
|
|
562
701
|
} else {
|
|
563
702
|
registerListRoute(`/api/${basePath}`, async (c) => {
|
|
@@ -600,7 +739,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
600
739
|
400
|
|
601
740
|
);
|
|
602
741
|
}
|
|
603
|
-
const filtersResult = parseFilters(c.req.url,
|
|
742
|
+
const filtersResult = parseFilters(c.req.url, filterableFieldNames);
|
|
604
743
|
if (!filtersResult.ok) {
|
|
605
744
|
return c.json(
|
|
606
745
|
{
|
|
@@ -637,6 +776,10 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
637
776
|
filters: filtersResult.filters,
|
|
638
777
|
include
|
|
639
778
|
});
|
|
779
|
+
if (resolver?.hasSchema(typeName)) {
|
|
780
|
+
const resolved = await resolver.resolveList(typeName, result.data);
|
|
781
|
+
return c.json({ ...result, data: resolved });
|
|
782
|
+
}
|
|
640
783
|
return c.json(result);
|
|
641
784
|
});
|
|
642
785
|
app.get(`/api/${basePath}/:slug`, async (c) => {
|
|
@@ -666,7 +809,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
666
809
|
404
|
|
667
810
|
);
|
|
668
811
|
}
|
|
669
|
-
|
|
812
|
+
let data = item;
|
|
813
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
814
|
+
return c.json({ ok: true, data });
|
|
670
815
|
});
|
|
671
816
|
}
|
|
672
817
|
}
|
|
@@ -692,6 +837,10 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
692
837
|
page: pagination.page,
|
|
693
838
|
per_page: pagination.per_page
|
|
694
839
|
});
|
|
840
|
+
if (resolver?.hasSchema(typeName)) {
|
|
841
|
+
const resolved = await resolver.resolveList(typeName, result.data);
|
|
842
|
+
return c.json({ ...result, data: resolved });
|
|
843
|
+
}
|
|
695
844
|
return c.json(result);
|
|
696
845
|
});
|
|
697
846
|
app.get(`/api/taxonomy/${typeName}/:id`, async (c) => {
|
|
@@ -706,7 +855,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
706
855
|
404
|
|
707
856
|
);
|
|
708
857
|
}
|
|
709
|
-
|
|
858
|
+
let data = item;
|
|
859
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
860
|
+
return c.json({ ok: true, data });
|
|
710
861
|
});
|
|
711
862
|
}
|
|
712
863
|
}
|
|
@@ -2662,7 +2813,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2662
2813
|
var import_drizzle_orm6 = require("drizzle-orm");
|
|
2663
2814
|
var API_VERSION = (() => {
|
|
2664
2815
|
try {
|
|
2665
|
-
return "0.
|
|
2816
|
+
return "0.2.0";
|
|
2666
2817
|
} catch {
|
|
2667
2818
|
return "0.0.0";
|
|
2668
2819
|
}
|
|
@@ -3078,6 +3229,9 @@ function createCmsApp(options) {
|
|
|
3078
3229
|
const cmsName = options.name ?? "Manguito CMS";
|
|
3079
3230
|
const maxFileSize = media?.max_file_size;
|
|
3080
3231
|
const rolesRegistry = buildRolesRegistry(registry.roles.roles);
|
|
3232
|
+
const resolverMap = options.resolvers ?? /* @__PURE__ */ new Map();
|
|
3233
|
+
validateResolverBindings(registry, resolverMap);
|
|
3234
|
+
const programmaticResolver = createProgrammaticResolver(resolverMap);
|
|
3081
3235
|
const app = new import_hono.Hono();
|
|
3082
3236
|
const uploadOrigins = storage.getUploadOrigins?.() ?? [];
|
|
3083
3237
|
app.use("*", createSecurityHeadersMiddleware({ connectSrc: uploadOrigins }));
|
|
@@ -3162,7 +3316,7 @@ function createCmsApp(options) {
|
|
|
3162
3316
|
paths: {}
|
|
3163
3317
|
})
|
|
3164
3318
|
);
|
|
3165
|
-
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit);
|
|
3319
|
+
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit, programmaticResolver);
|
|
3166
3320
|
registerPublicMediaRoutes(app, mediaRepo, listRateLimit);
|
|
3167
3321
|
registerConfigRoute(
|
|
3168
3322
|
app,
|
|
@@ -3223,6 +3377,9 @@ function createAPIAdapter(options = {}) {
|
|
|
3223
3377
|
0 && (module.exports = {
|
|
3224
3378
|
createAPIAdapter,
|
|
3225
3379
|
createCmsApp,
|
|
3226
|
-
|
|
3380
|
+
createProgrammaticResolver,
|
|
3381
|
+
createServer,
|
|
3382
|
+
resolverKey,
|
|
3383
|
+
validateResolverBindings
|
|
3227
3384
|
});
|
|
3228
3385
|
//# sourceMappingURL=index.js.map
|