@fonderie/adapter-koa 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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/index.cjs +125 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +36 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fonderie, Inc.
|
|
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/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @fonderie/adapter-koa
|
|
2
|
+
|
|
3
|
+
Run Fonderie bricks inside your existing Koa app. `bridge()` builds the
|
|
4
|
+
Fonderie context for every request, `adapt()` converts any Fonderie
|
|
5
|
+
middleware into a native Koa one, and `mount()` attaches whole modules.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @fonderie/adapter-koa
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { bridge, adapt, requireAuth } from '@fonderie/adapter-koa';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Register `bridge(fonderie)` as global middleware first, then use the
|
|
20
|
+
re-exported guards (`requireAuth`, `requireWorkspace`, `requirePermission`,
|
|
21
|
+
`requireFeature`) directly on routes.
|
|
22
|
+
|
|
23
|
+
`koaContextToWeb` converts Koa contexts to web-standard `Request` objects
|
|
24
|
+
for the Fonderie pipeline.
|
|
25
|
+
|
|
26
|
+
## Why this exists
|
|
27
|
+
|
|
28
|
+
You've shipped this plumbing before — auth, teams, billing, messaging —
|
|
29
|
+
and the next project will ask for it again. Fonderie packages it once:
|
|
30
|
+
plain TypeScript modules for
|
|
31
|
+
[`@fonderie/core`](https://github.com/fonderie-js/sdk/tree/main/packages/core),
|
|
32
|
+
PostgreSQL-backed, self-hosted, MIT. No external control plane, no
|
|
33
|
+
per-seat anything. Register the modules you need; skip the ones you don't.
|
|
34
|
+
|
|
35
|
+
**This package owns** the border crossing. It translates Koa contexts and
|
|
36
|
+
middleware conventions into Fonderie's, so the bricks run inside an app you
|
|
37
|
+
already have instead of demanding a rewrite.
|
|
38
|
+
|
|
39
|
+
Browse the whole set at
|
|
40
|
+
[fonderie-js/sdk](https://github.com/fonderie-js/sdk) · follow
|
|
41
|
+
[@fonderiejs](https://x.com/fonderiejs)
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT © Fonderie, Inc.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
OPERATIONS: () => import_permissions2.OPERATIONS,
|
|
24
|
+
adapt: () => adapt,
|
|
25
|
+
bridge: () => bridge,
|
|
26
|
+
koaContextToWeb: () => koaContextToWeb,
|
|
27
|
+
mount: () => mount,
|
|
28
|
+
requireAuth: () => requireAuth,
|
|
29
|
+
requireFeature: () => requireFeature,
|
|
30
|
+
requirePermission: () => requirePermission,
|
|
31
|
+
webResponseToKoa: () => webResponseToKoa,
|
|
32
|
+
withWorkspace: () => withWorkspace
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
var import_middlewares = require("@fonderie/core/middlewares");
|
|
36
|
+
var import_workspaces = require("@fonderie/workspaces");
|
|
37
|
+
var import_permissions = require("@fonderie/permissions");
|
|
38
|
+
var import_billing = require("@fonderie/billing");
|
|
39
|
+
var import_permissions2 = require("@fonderie/permissions");
|
|
40
|
+
function koaContextToWeb(ctx) {
|
|
41
|
+
const encrypted = ctx.req.socket.encrypted;
|
|
42
|
+
const protocol = encrypted ? "https" : "http";
|
|
43
|
+
const host = ctx.request.headers["host"] ?? "localhost";
|
|
44
|
+
const url = `${protocol}://${host}${ctx.request.url}`;
|
|
45
|
+
const headers = new Headers();
|
|
46
|
+
for (const [key, value] of Object.entries(ctx.request.headers)) {
|
|
47
|
+
if (!value) continue;
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
for (const v of value) headers.append(key, v);
|
|
50
|
+
} else {
|
|
51
|
+
headers.set(key, value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const method = ctx.request.method.toUpperCase();
|
|
55
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
56
|
+
return new Request(url, {
|
|
57
|
+
headers,
|
|
58
|
+
method,
|
|
59
|
+
body: hasBody ? ctx.request.rawBody ?? null : null
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function webResponseToKoa(webRes, ctx) {
|
|
63
|
+
ctx.response.status = webRes.status;
|
|
64
|
+
webRes.headers.forEach((value, key) => ctx.response.set(key, value));
|
|
65
|
+
ctx.response.body = await webRes.text();
|
|
66
|
+
}
|
|
67
|
+
function bridge(fonderie) {
|
|
68
|
+
return async (ctx, next) => {
|
|
69
|
+
const webReq = koaContextToWeb(ctx);
|
|
70
|
+
ctx.state["_fonderie"] = await fonderie.buildContext(webReq.clone());
|
|
71
|
+
await next();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function adapt(middleware) {
|
|
75
|
+
return async (ctx, next) => {
|
|
76
|
+
const fCtx = ctx.state["_fonderie"];
|
|
77
|
+
if (!fCtx) throw new Error("[fonderie] bridge() must be registered before adapt()");
|
|
78
|
+
let continued = false;
|
|
79
|
+
const result = await middleware(fCtx, async () => {
|
|
80
|
+
continued = true;
|
|
81
|
+
return new Response();
|
|
82
|
+
});
|
|
83
|
+
if (continued) {
|
|
84
|
+
await next();
|
|
85
|
+
} else {
|
|
86
|
+
await webResponseToKoa(result, ctx);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
var requireAuth = adapt(import_middlewares.requireAuth);
|
|
91
|
+
function withWorkspace(store) {
|
|
92
|
+
return adapt((0, import_workspaces.withWorkspace)(store));
|
|
93
|
+
}
|
|
94
|
+
function requirePermission(operation, permissionKey) {
|
|
95
|
+
return adapt((0, import_permissions.requirePermission)(operation, permissionKey));
|
|
96
|
+
}
|
|
97
|
+
function requireFeature(key) {
|
|
98
|
+
return adapt((0, import_billing.requireFeature)(key));
|
|
99
|
+
}
|
|
100
|
+
function mount(app, fonderie) {
|
|
101
|
+
app.use(async (ctx, next) => {
|
|
102
|
+
const webReq = koaContextToWeb(ctx);
|
|
103
|
+
ctx.state["_fonderie"] = await fonderie.buildContext(webReq.clone());
|
|
104
|
+
await next();
|
|
105
|
+
if (ctx.body === void 0) {
|
|
106
|
+
const webRes = await fonderie.handle(webReq);
|
|
107
|
+
await webResponseToKoa(webRes, ctx);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return app;
|
|
111
|
+
}
|
|
112
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
113
|
+
0 && (module.exports = {
|
|
114
|
+
OPERATIONS,
|
|
115
|
+
adapt,
|
|
116
|
+
bridge,
|
|
117
|
+
koaContextToWeb,
|
|
118
|
+
mount,
|
|
119
|
+
requireAuth,
|
|
120
|
+
requireFeature,
|
|
121
|
+
requirePermission,
|
|
122
|
+
webResponseToKoa,
|
|
123
|
+
withWorkspace
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport type Koa from 'koa';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KoaMiddleware<S = any, C = any> = Koa.Middleware<S, C>;\n\nimport type { FonderieApp, IFonderieContext, Middleware } from '@fonderie/core';\nimport { requireAuth as _requireAuth } from '@fonderie/core/middlewares';\nimport { withWorkspace as _withWorkspace } from '@fonderie/workspaces';\nimport { requirePermission as _requirePermission } from '@fonderie/permissions';\nimport { requireFeature as _requireFeature } from '@fonderie/billing';\n\nexport { OPERATIONS } from '@fonderie/permissions';\n\n// Minimal Koa shape used internally for Web Standard translation.\n// Application code uses Koa's own context types (Koa.ParameterizedContext).\nexport interface KoaContext {\n\trequest: {\n\t\turl: string;\n\t\tmethod: string;\n\t\trawBody?: string;\n\t\theaders: Record<string, string | string[] | undefined>;\n\t};\n\tresponse: {\n\t\tbody: unknown;\n\t\tstatus: number;\n\t\tset(key: string, value: string): void;\n\t};\n\treq: IncomingMessage;\n\tstate: Record<string, unknown>;\n}\n\nexport type KoaNext = () => Promise<void>;\n\n// ── Web Standard ↔ Koa translation ───────────────────────────────\n\nexport function koaContextToWeb(ctx: KoaContext): Request {\n\tconst encrypted = (ctx.req.socket as { encrypted?: boolean }).encrypted;\n\tconst protocol = encrypted ? 'https' : 'http';\n\tconst host = ctx.request.headers['host'] ?? 'localhost';\n\tconst url = `${protocol}://${host}${ctx.request.url}`;\n\n\tconst headers = new Headers();\n\tfor (const [key, value] of Object.entries(ctx.request.headers)) {\n\t\tif (!value) continue;\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const v of value) headers.append(key, v);\n\t\t} else {\n\t\t\theaders.set(key, value);\n\t\t}\n\t}\n\n\tconst method = ctx.request.method.toUpperCase();\n\tconst hasBody = method !== 'GET' && method !== 'HEAD';\n\n\treturn new Request(url, {\n\t\theaders,\n\t\tmethod,\n\t\tbody: hasBody ? (ctx.request.rawBody ?? null) : null,\n\t});\n}\n\nexport async function webResponseToKoa(webRes: Response, ctx: KoaContext): Promise<void> {\n\tctx.response.status = webRes.status;\n\twebRes.headers.forEach((value, key) => ctx.response.set(key, value));\n\tctx.response.body = await webRes.text();\n}\n\n// ── bridge ────────────────────────────────────────────────────────\n//\n// Koa middleware. Populates ctx.state._fonderie with the fonderie context\n// (user, workspace, meta) for all subsequent route handlers.\n// Requires koa-bodyparser (or equivalent) to run first so rawBody is set.\n//\n// app.use(bodyParser())\n// app.use(bridge(fonderie))\n\nexport function bridge(fonderie: FonderieApp): KoaMiddleware {\n\treturn async (ctx, next) => {\n\t\tconst webReq = koaContextToWeb(ctx as unknown as KoaContext);\n\t\tctx.state['_fonderie'] = await fonderie.buildContext(webReq.clone());\n\t\tawait next();\n\t};\n}\n\n// ── adapt ─────────────────────────────────────────────────────────\n//\n// Low-level escape hatch — wraps any fonderie Middleware into a Koa\n// middleware function. Use this for custom fonderie middleware; prefer the\n// named exports below for the built-in fonderie guards.\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function adapt(middleware: Middleware): KoaMiddleware<any, any> {\n\treturn async (ctx, next) => {\n\t\tconst fCtx = (ctx.state as Record<string, unknown>)['_fonderie'] as\n\t\t\t| IFonderieContext\n\t\t\t| undefined;\n\t\tif (!fCtx) throw new Error('[fonderie] bridge() must be registered before adapt()');\n\n\t\tlet continued = false;\n\t\tconst result = await middleware(fCtx, async () => {\n\t\t\tcontinued = true;\n\t\t\treturn new Response();\n\t\t});\n\n\t\tif (continued) {\n\t\t\tawait next();\n\t\t} else {\n\t\t\tawait webResponseToKoa(result, ctx as unknown as KoaContext);\n\t\t}\n\t};\n}\n\n// ── Pre-adapted middleware ────────────────────────────────────────\n//\n// Drop-in replacements for the fonderie middleware functions — no adapt()\n// needed. Import directly from this package instead of from the source\n// packages, and use them as native Koa middleware.\n//\n// router.get('/jobs', requireAuth, withWorkspace(store), ...)\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const requireAuth: KoaMiddleware<any, any> = adapt(_requireAuth);\n\nexport function withWorkspace(\n\tstore: Parameters<typeof _withWorkspace>[0],\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): KoaMiddleware<any, any> {\n\treturn adapt(_withWorkspace(store));\n}\n\nexport function requirePermission(\n\toperation: Parameters<typeof _requirePermission>[0],\n\tpermissionKey: Parameters<typeof _requirePermission>[1],\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): KoaMiddleware<any, any> {\n\treturn adapt(_requirePermission(operation, permissionKey));\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function requireFeature(key: string): KoaMiddleware<any, any> {\n\treturn adapt(_requireFeature(key));\n}\n\n// ── mount ─────────────────────────────────────────────────────────\n//\n// Wires up fonderie to a Koa app. Uses Koa's onion model to register a\n// single wrap-around middleware: builds fonderie context, calls next()\n// so user routes run, then falls back to fonderie infra only if the\n// request was not handled (ctx.body is still undefined).\n//\n// Routes registered after mount() are included automatically — Koa\n// composes all middlewares lazily at request time, not at registration.\n//\n// app.use(bodyParser())\n// const api = mount(app, fonderie) // returns same app\n// api.use(router.routes())\n// api.use(router.allowedMethods())\n// app.listen(port)\n\nexport function mount(app: Koa, fonderie: FonderieApp): Koa {\n\tapp.use(async (ctx, next) => {\n\t\tconst webReq = koaContextToWeb(ctx as unknown as KoaContext);\n\t\tctx.state['_fonderie'] = await fonderie.buildContext(webReq.clone());\n\t\tawait next();\n\t\tif (ctx.body === undefined) {\n\t\t\tconst webRes = await fonderie.handle(webReq);\n\t\t\tawait webResponseToKoa(webRes, ctx as unknown as KoaContext);\n\t\t}\n\t});\n\treturn app;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,yBAA4C;AAC5C,wBAAgD;AAChD,yBAAwD;AACxD,qBAAkD;AAElD,IAAAA,sBAA2B;AAwBpB,SAAS,gBAAgB,KAA0B;AACzD,QAAM,YAAa,IAAI,IAAI,OAAmC;AAC9D,QAAM,WAAW,YAAY,UAAU;AACvC,QAAM,OAAO,IAAI,QAAQ,QAAQ,MAAM,KAAK;AAC5C,QAAM,MAAM,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,QAAQ,GAAG;AAEnD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,QAAQ,OAAO,GAAG;AAC/D,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,QAAQ,KAAK,GAAG;AACzB,iBAAW,KAAK,MAAO,SAAQ,OAAO,KAAK,CAAC;AAAA,IAC7C,OAAO;AACN,cAAQ,IAAI,KAAK,KAAK;AAAA,IACvB;AAAA,EACD;AAEA,QAAM,SAAS,IAAI,QAAQ,OAAO,YAAY;AAC9C,QAAM,UAAU,WAAW,SAAS,WAAW;AAE/C,SAAO,IAAI,QAAQ,KAAK;AAAA,IACvB;AAAA,IACA;AAAA,IACA,MAAM,UAAW,IAAI,QAAQ,WAAW,OAAQ;AAAA,EACjD,CAAC;AACF;AAEA,eAAsB,iBAAiB,QAAkB,KAAgC;AACxF,MAAI,SAAS,SAAS,OAAO;AAC7B,SAAO,QAAQ,QAAQ,CAAC,OAAO,QAAQ,IAAI,SAAS,IAAI,KAAK,KAAK,CAAC;AACnE,MAAI,SAAS,OAAO,MAAM,OAAO,KAAK;AACvC;AAWO,SAAS,OAAO,UAAsC;AAC5D,SAAO,OAAO,KAAK,SAAS;AAC3B,UAAM,SAAS,gBAAgB,GAA4B;AAC3D,QAAI,MAAM,WAAW,IAAI,MAAM,SAAS,aAAa,OAAO,MAAM,CAAC;AACnE,UAAM,KAAK;AAAA,EACZ;AACD;AASO,SAAS,MAAM,YAAiD;AACtE,SAAO,OAAO,KAAK,SAAS;AAC3B,UAAM,OAAQ,IAAI,MAAkC,WAAW;AAG/D,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,uDAAuD;AAElF,QAAI,YAAY;AAChB,UAAM,SAAS,MAAM,WAAW,MAAM,YAAY;AACjD,kBAAY;AACZ,aAAO,IAAI,SAAS;AAAA,IACrB,CAAC;AAED,QAAI,WAAW;AACd,YAAM,KAAK;AAAA,IACZ,OAAO;AACN,YAAM,iBAAiB,QAAQ,GAA4B;AAAA,IAC5D;AAAA,EACD;AACD;AAWO,IAAM,cAAuC,MAAM,mBAAAC,WAAY;AAE/D,SAAS,cACf,OAE0B;AAC1B,SAAO,UAAM,kBAAAC,eAAe,KAAK,CAAC;AACnC;AAEO,SAAS,kBACf,WACA,eAE0B;AAC1B,SAAO,UAAM,mBAAAC,mBAAmB,WAAW,aAAa,CAAC;AAC1D;AAGO,SAAS,eAAe,KAAsC;AACpE,SAAO,UAAM,eAAAC,gBAAgB,GAAG,CAAC;AAClC;AAkBO,SAAS,MAAM,KAAU,UAA4B;AAC3D,MAAI,IAAI,OAAO,KAAK,SAAS;AAC5B,UAAM,SAAS,gBAAgB,GAA4B;AAC3D,QAAI,MAAM,WAAW,IAAI,MAAM,SAAS,aAAa,OAAO,MAAM,CAAC;AACnE,UAAM,KAAK;AACX,QAAI,IAAI,SAAS,QAAW;AAC3B,YAAM,SAAS,MAAM,SAAS,OAAO,MAAM;AAC3C,YAAM,iBAAiB,QAAQ,GAA4B;AAAA,IAC5D;AAAA,EACD,CAAC;AACD,SAAO;AACR;","names":["import_permissions","_requireAuth","_withWorkspace","_requirePermission","_requireFeature"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IncomingMessage } from 'node:http';
|
|
2
|
+
import Koa from 'koa';
|
|
3
|
+
import { Middleware, FonderieApp } from '@fonderie/core';
|
|
4
|
+
import { withWorkspace as withWorkspace$1 } from '@fonderie/workspaces';
|
|
5
|
+
import { requirePermission as requirePermission$1 } from '@fonderie/permissions';
|
|
6
|
+
export { OPERATIONS } from '@fonderie/permissions';
|
|
7
|
+
|
|
8
|
+
type KoaMiddleware<S = any, C = any> = Koa.Middleware<S, C>;
|
|
9
|
+
|
|
10
|
+
interface KoaContext {
|
|
11
|
+
request: {
|
|
12
|
+
url: string;
|
|
13
|
+
method: string;
|
|
14
|
+
rawBody?: string;
|
|
15
|
+
headers: Record<string, string | string[] | undefined>;
|
|
16
|
+
};
|
|
17
|
+
response: {
|
|
18
|
+
body: unknown;
|
|
19
|
+
status: number;
|
|
20
|
+
set(key: string, value: string): void;
|
|
21
|
+
};
|
|
22
|
+
req: IncomingMessage;
|
|
23
|
+
state: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
type KoaNext = () => Promise<void>;
|
|
26
|
+
declare function koaContextToWeb(ctx: KoaContext): Request;
|
|
27
|
+
declare function webResponseToKoa(webRes: Response, ctx: KoaContext): Promise<void>;
|
|
28
|
+
declare function bridge(fonderie: FonderieApp): KoaMiddleware;
|
|
29
|
+
declare function adapt(middleware: Middleware): KoaMiddleware<any, any>;
|
|
30
|
+
declare const requireAuth: KoaMiddleware<any, any>;
|
|
31
|
+
declare function withWorkspace(store: Parameters<typeof withWorkspace$1>[0]): KoaMiddleware<any, any>;
|
|
32
|
+
declare function requirePermission(operation: Parameters<typeof requirePermission$1>[0], permissionKey: Parameters<typeof requirePermission$1>[1]): KoaMiddleware<any, any>;
|
|
33
|
+
declare function requireFeature(key: string): KoaMiddleware<any, any>;
|
|
34
|
+
declare function mount(app: Koa, fonderie: FonderieApp): Koa;
|
|
35
|
+
|
|
36
|
+
export { type KoaContext, type KoaNext, adapt, bridge, koaContextToWeb, mount, requireAuth, requireFeature, requirePermission, webResponseToKoa, withWorkspace };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IncomingMessage } from 'node:http';
|
|
2
|
+
import Koa from 'koa';
|
|
3
|
+
import { Middleware, FonderieApp } from '@fonderie/core';
|
|
4
|
+
import { withWorkspace as withWorkspace$1 } from '@fonderie/workspaces';
|
|
5
|
+
import { requirePermission as requirePermission$1 } from '@fonderie/permissions';
|
|
6
|
+
export { OPERATIONS } from '@fonderie/permissions';
|
|
7
|
+
|
|
8
|
+
type KoaMiddleware<S = any, C = any> = Koa.Middleware<S, C>;
|
|
9
|
+
|
|
10
|
+
interface KoaContext {
|
|
11
|
+
request: {
|
|
12
|
+
url: string;
|
|
13
|
+
method: string;
|
|
14
|
+
rawBody?: string;
|
|
15
|
+
headers: Record<string, string | string[] | undefined>;
|
|
16
|
+
};
|
|
17
|
+
response: {
|
|
18
|
+
body: unknown;
|
|
19
|
+
status: number;
|
|
20
|
+
set(key: string, value: string): void;
|
|
21
|
+
};
|
|
22
|
+
req: IncomingMessage;
|
|
23
|
+
state: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
type KoaNext = () => Promise<void>;
|
|
26
|
+
declare function koaContextToWeb(ctx: KoaContext): Request;
|
|
27
|
+
declare function webResponseToKoa(webRes: Response, ctx: KoaContext): Promise<void>;
|
|
28
|
+
declare function bridge(fonderie: FonderieApp): KoaMiddleware;
|
|
29
|
+
declare function adapt(middleware: Middleware): KoaMiddleware<any, any>;
|
|
30
|
+
declare const requireAuth: KoaMiddleware<any, any>;
|
|
31
|
+
declare function withWorkspace(store: Parameters<typeof withWorkspace$1>[0]): KoaMiddleware<any, any>;
|
|
32
|
+
declare function requirePermission(operation: Parameters<typeof requirePermission$1>[0], permissionKey: Parameters<typeof requirePermission$1>[1]): KoaMiddleware<any, any>;
|
|
33
|
+
declare function requireFeature(key: string): KoaMiddleware<any, any>;
|
|
34
|
+
declare function mount(app: Koa, fonderie: FonderieApp): Koa;
|
|
35
|
+
|
|
36
|
+
export { type KoaContext, type KoaNext, adapt, bridge, koaContextToWeb, mount, requireAuth, requireFeature, requirePermission, webResponseToKoa, withWorkspace };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { requireAuth as _requireAuth } from "@fonderie/core/middlewares";
|
|
3
|
+
import { withWorkspace as _withWorkspace } from "@fonderie/workspaces";
|
|
4
|
+
import { requirePermission as _requirePermission } from "@fonderie/permissions";
|
|
5
|
+
import { requireFeature as _requireFeature } from "@fonderie/billing";
|
|
6
|
+
import { OPERATIONS } from "@fonderie/permissions";
|
|
7
|
+
function koaContextToWeb(ctx) {
|
|
8
|
+
const encrypted = ctx.req.socket.encrypted;
|
|
9
|
+
const protocol = encrypted ? "https" : "http";
|
|
10
|
+
const host = ctx.request.headers["host"] ?? "localhost";
|
|
11
|
+
const url = `${protocol}://${host}${ctx.request.url}`;
|
|
12
|
+
const headers = new Headers();
|
|
13
|
+
for (const [key, value] of Object.entries(ctx.request.headers)) {
|
|
14
|
+
if (!value) continue;
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
for (const v of value) headers.append(key, v);
|
|
17
|
+
} else {
|
|
18
|
+
headers.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const method = ctx.request.method.toUpperCase();
|
|
22
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
23
|
+
return new Request(url, {
|
|
24
|
+
headers,
|
|
25
|
+
method,
|
|
26
|
+
body: hasBody ? ctx.request.rawBody ?? null : null
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function webResponseToKoa(webRes, ctx) {
|
|
30
|
+
ctx.response.status = webRes.status;
|
|
31
|
+
webRes.headers.forEach((value, key) => ctx.response.set(key, value));
|
|
32
|
+
ctx.response.body = await webRes.text();
|
|
33
|
+
}
|
|
34
|
+
function bridge(fonderie) {
|
|
35
|
+
return async (ctx, next) => {
|
|
36
|
+
const webReq = koaContextToWeb(ctx);
|
|
37
|
+
ctx.state["_fonderie"] = await fonderie.buildContext(webReq.clone());
|
|
38
|
+
await next();
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function adapt(middleware) {
|
|
42
|
+
return async (ctx, next) => {
|
|
43
|
+
const fCtx = ctx.state["_fonderie"];
|
|
44
|
+
if (!fCtx) throw new Error("[fonderie] bridge() must be registered before adapt()");
|
|
45
|
+
let continued = false;
|
|
46
|
+
const result = await middleware(fCtx, async () => {
|
|
47
|
+
continued = true;
|
|
48
|
+
return new Response();
|
|
49
|
+
});
|
|
50
|
+
if (continued) {
|
|
51
|
+
await next();
|
|
52
|
+
} else {
|
|
53
|
+
await webResponseToKoa(result, ctx);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var requireAuth = adapt(_requireAuth);
|
|
58
|
+
function withWorkspace(store) {
|
|
59
|
+
return adapt(_withWorkspace(store));
|
|
60
|
+
}
|
|
61
|
+
function requirePermission(operation, permissionKey) {
|
|
62
|
+
return adapt(_requirePermission(operation, permissionKey));
|
|
63
|
+
}
|
|
64
|
+
function requireFeature(key) {
|
|
65
|
+
return adapt(_requireFeature(key));
|
|
66
|
+
}
|
|
67
|
+
function mount(app, fonderie) {
|
|
68
|
+
app.use(async (ctx, next) => {
|
|
69
|
+
const webReq = koaContextToWeb(ctx);
|
|
70
|
+
ctx.state["_fonderie"] = await fonderie.buildContext(webReq.clone());
|
|
71
|
+
await next();
|
|
72
|
+
if (ctx.body === void 0) {
|
|
73
|
+
const webRes = await fonderie.handle(webReq);
|
|
74
|
+
await webResponseToKoa(webRes, ctx);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return app;
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
OPERATIONS,
|
|
81
|
+
adapt,
|
|
82
|
+
bridge,
|
|
83
|
+
koaContextToWeb,
|
|
84
|
+
mount,
|
|
85
|
+
requireAuth,
|
|
86
|
+
requireFeature,
|
|
87
|
+
requirePermission,
|
|
88
|
+
webResponseToKoa,
|
|
89
|
+
withWorkspace
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport type Koa from 'koa';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KoaMiddleware<S = any, C = any> = Koa.Middleware<S, C>;\n\nimport type { FonderieApp, IFonderieContext, Middleware } from '@fonderie/core';\nimport { requireAuth as _requireAuth } from '@fonderie/core/middlewares';\nimport { withWorkspace as _withWorkspace } from '@fonderie/workspaces';\nimport { requirePermission as _requirePermission } from '@fonderie/permissions';\nimport { requireFeature as _requireFeature } from '@fonderie/billing';\n\nexport { OPERATIONS } from '@fonderie/permissions';\n\n// Minimal Koa shape used internally for Web Standard translation.\n// Application code uses Koa's own context types (Koa.ParameterizedContext).\nexport interface KoaContext {\n\trequest: {\n\t\turl: string;\n\t\tmethod: string;\n\t\trawBody?: string;\n\t\theaders: Record<string, string | string[] | undefined>;\n\t};\n\tresponse: {\n\t\tbody: unknown;\n\t\tstatus: number;\n\t\tset(key: string, value: string): void;\n\t};\n\treq: IncomingMessage;\n\tstate: Record<string, unknown>;\n}\n\nexport type KoaNext = () => Promise<void>;\n\n// ── Web Standard ↔ Koa translation ───────────────────────────────\n\nexport function koaContextToWeb(ctx: KoaContext): Request {\n\tconst encrypted = (ctx.req.socket as { encrypted?: boolean }).encrypted;\n\tconst protocol = encrypted ? 'https' : 'http';\n\tconst host = ctx.request.headers['host'] ?? 'localhost';\n\tconst url = `${protocol}://${host}${ctx.request.url}`;\n\n\tconst headers = new Headers();\n\tfor (const [key, value] of Object.entries(ctx.request.headers)) {\n\t\tif (!value) continue;\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const v of value) headers.append(key, v);\n\t\t} else {\n\t\t\theaders.set(key, value);\n\t\t}\n\t}\n\n\tconst method = ctx.request.method.toUpperCase();\n\tconst hasBody = method !== 'GET' && method !== 'HEAD';\n\n\treturn new Request(url, {\n\t\theaders,\n\t\tmethod,\n\t\tbody: hasBody ? (ctx.request.rawBody ?? null) : null,\n\t});\n}\n\nexport async function webResponseToKoa(webRes: Response, ctx: KoaContext): Promise<void> {\n\tctx.response.status = webRes.status;\n\twebRes.headers.forEach((value, key) => ctx.response.set(key, value));\n\tctx.response.body = await webRes.text();\n}\n\n// ── bridge ────────────────────────────────────────────────────────\n//\n// Koa middleware. Populates ctx.state._fonderie with the fonderie context\n// (user, workspace, meta) for all subsequent route handlers.\n// Requires koa-bodyparser (or equivalent) to run first so rawBody is set.\n//\n// app.use(bodyParser())\n// app.use(bridge(fonderie))\n\nexport function bridge(fonderie: FonderieApp): KoaMiddleware {\n\treturn async (ctx, next) => {\n\t\tconst webReq = koaContextToWeb(ctx as unknown as KoaContext);\n\t\tctx.state['_fonderie'] = await fonderie.buildContext(webReq.clone());\n\t\tawait next();\n\t};\n}\n\n// ── adapt ─────────────────────────────────────────────────────────\n//\n// Low-level escape hatch — wraps any fonderie Middleware into a Koa\n// middleware function. Use this for custom fonderie middleware; prefer the\n// named exports below for the built-in fonderie guards.\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function adapt(middleware: Middleware): KoaMiddleware<any, any> {\n\treturn async (ctx, next) => {\n\t\tconst fCtx = (ctx.state as Record<string, unknown>)['_fonderie'] as\n\t\t\t| IFonderieContext\n\t\t\t| undefined;\n\t\tif (!fCtx) throw new Error('[fonderie] bridge() must be registered before adapt()');\n\n\t\tlet continued = false;\n\t\tconst result = await middleware(fCtx, async () => {\n\t\t\tcontinued = true;\n\t\t\treturn new Response();\n\t\t});\n\n\t\tif (continued) {\n\t\t\tawait next();\n\t\t} else {\n\t\t\tawait webResponseToKoa(result, ctx as unknown as KoaContext);\n\t\t}\n\t};\n}\n\n// ── Pre-adapted middleware ────────────────────────────────────────\n//\n// Drop-in replacements for the fonderie middleware functions — no adapt()\n// needed. Import directly from this package instead of from the source\n// packages, and use them as native Koa middleware.\n//\n// router.get('/jobs', requireAuth, withWorkspace(store), ...)\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const requireAuth: KoaMiddleware<any, any> = adapt(_requireAuth);\n\nexport function withWorkspace(\n\tstore: Parameters<typeof _withWorkspace>[0],\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): KoaMiddleware<any, any> {\n\treturn adapt(_withWorkspace(store));\n}\n\nexport function requirePermission(\n\toperation: Parameters<typeof _requirePermission>[0],\n\tpermissionKey: Parameters<typeof _requirePermission>[1],\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n): KoaMiddleware<any, any> {\n\treturn adapt(_requirePermission(operation, permissionKey));\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function requireFeature(key: string): KoaMiddleware<any, any> {\n\treturn adapt(_requireFeature(key));\n}\n\n// ── mount ─────────────────────────────────────────────────────────\n//\n// Wires up fonderie to a Koa app. Uses Koa's onion model to register a\n// single wrap-around middleware: builds fonderie context, calls next()\n// so user routes run, then falls back to fonderie infra only if the\n// request was not handled (ctx.body is still undefined).\n//\n// Routes registered after mount() are included automatically — Koa\n// composes all middlewares lazily at request time, not at registration.\n//\n// app.use(bodyParser())\n// const api = mount(app, fonderie) // returns same app\n// api.use(router.routes())\n// api.use(router.allowedMethods())\n// app.listen(port)\n\nexport function mount(app: Koa, fonderie: FonderieApp): Koa {\n\tapp.use(async (ctx, next) => {\n\t\tconst webReq = koaContextToWeb(ctx as unknown as KoaContext);\n\t\tctx.state['_fonderie'] = await fonderie.buildContext(webReq.clone());\n\t\tawait next();\n\t\tif (ctx.body === undefined) {\n\t\t\tconst webRes = await fonderie.handle(webReq);\n\t\t\tawait webResponseToKoa(webRes, ctx as unknown as KoaContext);\n\t\t}\n\t});\n\treturn app;\n}\n"],"mappings":";AAOA,SAAS,eAAe,oBAAoB;AAC5C,SAAS,iBAAiB,sBAAsB;AAChD,SAAS,qBAAqB,0BAA0B;AACxD,SAAS,kBAAkB,uBAAuB;AAElD,SAAS,kBAAkB;AAwBpB,SAAS,gBAAgB,KAA0B;AACzD,QAAM,YAAa,IAAI,IAAI,OAAmC;AAC9D,QAAM,WAAW,YAAY,UAAU;AACvC,QAAM,OAAO,IAAI,QAAQ,QAAQ,MAAM,KAAK;AAC5C,QAAM,MAAM,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,QAAQ,GAAG;AAEnD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,QAAQ,OAAO,GAAG;AAC/D,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,QAAQ,KAAK,GAAG;AACzB,iBAAW,KAAK,MAAO,SAAQ,OAAO,KAAK,CAAC;AAAA,IAC7C,OAAO;AACN,cAAQ,IAAI,KAAK,KAAK;AAAA,IACvB;AAAA,EACD;AAEA,QAAM,SAAS,IAAI,QAAQ,OAAO,YAAY;AAC9C,QAAM,UAAU,WAAW,SAAS,WAAW;AAE/C,SAAO,IAAI,QAAQ,KAAK;AAAA,IACvB;AAAA,IACA;AAAA,IACA,MAAM,UAAW,IAAI,QAAQ,WAAW,OAAQ;AAAA,EACjD,CAAC;AACF;AAEA,eAAsB,iBAAiB,QAAkB,KAAgC;AACxF,MAAI,SAAS,SAAS,OAAO;AAC7B,SAAO,QAAQ,QAAQ,CAAC,OAAO,QAAQ,IAAI,SAAS,IAAI,KAAK,KAAK,CAAC;AACnE,MAAI,SAAS,OAAO,MAAM,OAAO,KAAK;AACvC;AAWO,SAAS,OAAO,UAAsC;AAC5D,SAAO,OAAO,KAAK,SAAS;AAC3B,UAAM,SAAS,gBAAgB,GAA4B;AAC3D,QAAI,MAAM,WAAW,IAAI,MAAM,SAAS,aAAa,OAAO,MAAM,CAAC;AACnE,UAAM,KAAK;AAAA,EACZ;AACD;AASO,SAAS,MAAM,YAAiD;AACtE,SAAO,OAAO,KAAK,SAAS;AAC3B,UAAM,OAAQ,IAAI,MAAkC,WAAW;AAG/D,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,uDAAuD;AAElF,QAAI,YAAY;AAChB,UAAM,SAAS,MAAM,WAAW,MAAM,YAAY;AACjD,kBAAY;AACZ,aAAO,IAAI,SAAS;AAAA,IACrB,CAAC;AAED,QAAI,WAAW;AACd,YAAM,KAAK;AAAA,IACZ,OAAO;AACN,YAAM,iBAAiB,QAAQ,GAA4B;AAAA,IAC5D;AAAA,EACD;AACD;AAWO,IAAM,cAAuC,MAAM,YAAY;AAE/D,SAAS,cACf,OAE0B;AAC1B,SAAO,MAAM,eAAe,KAAK,CAAC;AACnC;AAEO,SAAS,kBACf,WACA,eAE0B;AAC1B,SAAO,MAAM,mBAAmB,WAAW,aAAa,CAAC;AAC1D;AAGO,SAAS,eAAe,KAAsC;AACpE,SAAO,MAAM,gBAAgB,GAAG,CAAC;AAClC;AAkBO,SAAS,MAAM,KAAU,UAA4B;AAC3D,MAAI,IAAI,OAAO,KAAK,SAAS;AAC5B,UAAM,SAAS,gBAAgB,GAA4B;AAC3D,QAAI,MAAM,WAAW,IAAI,MAAM,SAAS,aAAa,OAAO,MAAM,CAAC;AACnE,UAAM,KAAK;AACX,QAAI,IAAI,SAAS,QAAW;AAC3B,YAAM,SAAS,MAAM,SAAS,OAAO,MAAM;AAC3C,YAAM,iBAAiB,QAAQ,GAA4B;AAAA,IAC5D;AAAA,EACD,CAAC;AACD,SAAO;AACR;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fonderie/adapter-koa",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Koa adapter for fonderie-js — bridge(), adapt(), mount() to use fonderie middleware in native Koa routes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"fonderie-js",
|
|
7
|
+
"koa",
|
|
8
|
+
"adapter",
|
|
9
|
+
"middleware",
|
|
10
|
+
"saas",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.cjs",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"test": "tsx --test src/__tests__/*.test.ts",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"lint": "biome lint src",
|
|
34
|
+
"format": "biome format --write src",
|
|
35
|
+
"check": "biome check --write src"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@fonderie/core": "^0.1.0",
|
|
39
|
+
"@fonderie/workspaces": "^1.0.0",
|
|
40
|
+
"@fonderie/permissions": "^1.0.0",
|
|
41
|
+
"@fonderie/billing": "^1.0.0",
|
|
42
|
+
"koa": "^2.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@fonderie/workspaces": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@fonderie/permissions": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@fonderie/billing": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@fonderie/core": "../core",
|
|
57
|
+
"@fonderie/workspaces": "../workspaces",
|
|
58
|
+
"@fonderie/permissions": "../permissions",
|
|
59
|
+
"@fonderie/billing": "../billing",
|
|
60
|
+
"koa": "^2.16.1",
|
|
61
|
+
"@types/koa": "^2.15.0",
|
|
62
|
+
"@types/node": "^25.6.0",
|
|
63
|
+
"tsx": "^4.21.0",
|
|
64
|
+
"tsup": "^8.5.1",
|
|
65
|
+
"typescript": "^6.0.3"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
},
|
|
70
|
+
"files": [
|
|
71
|
+
"dist",
|
|
72
|
+
"LICENSE",
|
|
73
|
+
"README.md"
|
|
74
|
+
],
|
|
75
|
+
"repository": {
|
|
76
|
+
"type": "git",
|
|
77
|
+
"url": "git+https://github.com/fonderie-js/sdk.git",
|
|
78
|
+
"directory": "packages/adapter-koa"
|
|
79
|
+
},
|
|
80
|
+
"homepage": "https://github.com/fonderie-js/sdk/tree/main/packages/adapter-koa#readme",
|
|
81
|
+
"bugs": {
|
|
82
|
+
"url": "https://github.com/fonderie-js/sdk/issues"
|
|
83
|
+
}
|
|
84
|
+
}
|