@airdraft/cloud 0.1.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/CHANGELOG.md +5 -0
- package/dist/billing.d.ts +72 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +204 -0
- package/dist/billing.js.map +1 -0
- package/dist/callback.d.ts +39 -0
- package/dist/callback.d.ts.map +1 -0
- package/dist/callback.js +178 -0
- package/dist/callback.js.map +1 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +23 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/invite.d.ts +28 -0
- package/dist/invite.d.ts.map +1 -0
- package/dist/invite.js +155 -0
- package/dist/invite.js.map +1 -0
- package/dist/jwt.d.ts +9 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +32 -0
- package/dist/jwt.js.map +1 -0
- package/dist/project.d.ts +30 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +145 -0
- package/dist/project.js.map +1 -0
- package/dist/relay.d.ts +35 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +101 -0
- package/dist/relay.js.map +1 -0
- package/dist/runtime.d.ts +48 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +186 -0
- package/dist/runtime.js.map +1 -0
- package/dist/state.d.ts +40 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +58 -0
- package/dist/state.js.map +1 -0
- package/dist/team.d.ts +31 -0
- package/dist/team.d.ts.map +1 -0
- package/dist/team.js +150 -0
- package/dist/team.js.map +1 -0
- package/dist/types.d.ts +161 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook.d.ts +29 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +125 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +45 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { CloudAuthAdapter } from '@airdraft/cloud-auth';
|
|
2
|
+
import type { CloudDb, TokenCache } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal @untools/pay client interface used by billing handlers.
|
|
5
|
+
* The concrete implementation is provided by the cloud app via `lib/pay.ts`.
|
|
6
|
+
*/
|
|
7
|
+
export interface PayClient {
|
|
8
|
+
checkout: {
|
|
9
|
+
create(opts: {
|
|
10
|
+
amount: number;
|
|
11
|
+
currency: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
url: string;
|
|
16
|
+
reference: string;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
webhook: {
|
|
20
|
+
verify(req: Request, provider: string): Promise<{
|
|
21
|
+
verified: boolean;
|
|
22
|
+
data: Record<string, unknown>;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves a team's plan (Redis-cached, 5-min TTL) and checks a named limit.
|
|
28
|
+
*
|
|
29
|
+
* Returns `{ allowed: boolean; current: number; max: number }`.
|
|
30
|
+
* `max: -1` means unlimited.
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkLimit(db: CloudDb, teamId: string, key: string, current: number, cache?: TokenCache): Promise<{
|
|
33
|
+
allowed: boolean;
|
|
34
|
+
current: number;
|
|
35
|
+
max: number;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves a team's plan and returns the value of a named feature flag.
|
|
39
|
+
* Returns `false` if the plan or feature is not found.
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkFeature(db: CloudDb, teamId: string, key: string, cache?: TokenCache): Promise<boolean | number>;
|
|
42
|
+
/**
|
|
43
|
+
* GET /v1/billing/plans
|
|
44
|
+
*
|
|
45
|
+
* Returns all active plan definitions. Public — no auth required.
|
|
46
|
+
*/
|
|
47
|
+
export declare function handleGetPlans(req: Request, db: CloudDb): Promise<Response>;
|
|
48
|
+
/**
|
|
49
|
+
* POST /v1/billing/upgrade
|
|
50
|
+
*
|
|
51
|
+
* Body: `{ planSlug: string; provider: string }`
|
|
52
|
+
*
|
|
53
|
+
* Creates a @untools/pay checkout session for the requested plan upgrade.
|
|
54
|
+
* Returns `{ checkoutUrl, reference }`.
|
|
55
|
+
* Requires `owner` role.
|
|
56
|
+
*/
|
|
57
|
+
export declare function handleUpgradePlan(req: Request, db: CloudDb, auth: CloudAuthAdapter, pay: PayClient, teamSlug: string): Promise<Response>;
|
|
58
|
+
/**
|
|
59
|
+
* POST /v1/billing/webhooks/:provider
|
|
60
|
+
*
|
|
61
|
+
* Verifies the provider webhook signature and fulfills plan upgrade.
|
|
62
|
+
* Updates `TeamDoc.planSlug` on successful payment.
|
|
63
|
+
*/
|
|
64
|
+
export declare function handleWebhookSettlement(req: Request, provider: 'paystack' | 'flutterwave' | '100pay', db: CloudDb, pay: PayClient): Promise<Response>;
|
|
65
|
+
/**
|
|
66
|
+
* GET /v1/teams/:teamSlug/usage
|
|
67
|
+
*
|
|
68
|
+
* Returns current usage stats and plan limits for the team.
|
|
69
|
+
* Requires membership.
|
|
70
|
+
*/
|
|
71
|
+
export declare function handleGetUsage(req: Request, db: CloudDb, auth: CloudAuthAdapter, teamSlug: string): Promise<Response>;
|
|
72
|
+
//# sourceMappingURL=billing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAW,UAAU,EAAE,MAAM,YAAY,CAAA;AAM9D;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE;QACR,MAAM,CAAC,IAAI,EAAE;YACX,MAAM,EAAE,MAAM,CAAA;YACd,QAAQ,EAAE,MAAM,CAAA;YAChB,QAAQ,EAAE,MAAM,CAAA;YAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SACnC,GAAG,OAAO,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAChD,CAAA;IACD,OAAO,EAAE;QACP,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;YAAE,QAAQ,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAC,CAAA;KACtG,CAAA;CACF;AA2BD;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,UAAU,GACjB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAK7D;AAMD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,UAAU,GACjB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CAG3B;AAuCD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,QAAQ,CAAC,CAInB;AAMD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,gBAAgB,EACtB,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,CAAC,CA4CnB;AAMD;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,UAAU,GAAG,aAAa,GAAG,QAAQ,EAC/C,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,QAAQ,CAAC,CAyBnB;AAMD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,gBAAgB,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,CAAC,CA8BnB"}
|
package/dist/billing.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
function json(body, status = 200) {
|
|
5
|
+
return new Response(JSON.stringify(body), {
|
|
6
|
+
status,
|
|
7
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function err(code, message, status) {
|
|
11
|
+
return json({ error: { code, message } }, status);
|
|
12
|
+
}
|
|
13
|
+
const PLAN_CACHE_TTL_S = 5 * 60; // 5 minutes
|
|
14
|
+
function planCacheKey(teamId) {
|
|
15
|
+
return `plan:${teamId}`;
|
|
16
|
+
}
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// checkLimit
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a team's plan (Redis-cached, 5-min TTL) and checks a named limit.
|
|
22
|
+
*
|
|
23
|
+
* Returns `{ allowed: boolean; current: number; max: number }`.
|
|
24
|
+
* `max: -1` means unlimited.
|
|
25
|
+
*/
|
|
26
|
+
export async function checkLimit(db, teamId, key, current, cache) {
|
|
27
|
+
const plan = await resolvePlan(db, teamId, cache);
|
|
28
|
+
const max = plan?.limits[key] ?? -1;
|
|
29
|
+
if (max === -1)
|
|
30
|
+
return { allowed: true, current, max };
|
|
31
|
+
return { allowed: current < max, current, max };
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// checkFeature
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a team's plan and returns the value of a named feature flag.
|
|
38
|
+
* Returns `false` if the plan or feature is not found.
|
|
39
|
+
*/
|
|
40
|
+
export async function checkFeature(db, teamId, key, cache) {
|
|
41
|
+
const plan = await resolvePlan(db, teamId, cache);
|
|
42
|
+
return plan?.features[key] ?? false;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// resolvePlan (internal)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
async function resolvePlan(db, teamId, cache) {
|
|
48
|
+
if (cache) {
|
|
49
|
+
const cached = await cache.get(planCacheKey(teamId));
|
|
50
|
+
if (cached) {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(cached);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// invalid cache — fall through to DB
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const { ObjectId } = await import('mongodb');
|
|
60
|
+
const team = await db.teams.findOne({ _id: new ObjectId(teamId) });
|
|
61
|
+
if (!team)
|
|
62
|
+
return null;
|
|
63
|
+
const plan = await db.plans.findOne({ slug: team.planSlug, isActive: true });
|
|
64
|
+
if (cache && plan) {
|
|
65
|
+
await cache.set(planCacheKey(teamId), JSON.stringify(plan), PLAN_CACHE_TTL_S);
|
|
66
|
+
}
|
|
67
|
+
return plan;
|
|
68
|
+
}
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// handleGetPlans
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
/**
|
|
73
|
+
* GET /v1/billing/plans
|
|
74
|
+
*
|
|
75
|
+
* Returns all active plan definitions. Public — no auth required.
|
|
76
|
+
*/
|
|
77
|
+
export async function handleGetPlans(req, db) {
|
|
78
|
+
void req;
|
|
79
|
+
const plans = await db.plans.find({ isActive: true }).toArray();
|
|
80
|
+
return json({ data: plans });
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// handleUpgradePlan
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* POST /v1/billing/upgrade
|
|
87
|
+
*
|
|
88
|
+
* Body: `{ planSlug: string; provider: string }`
|
|
89
|
+
*
|
|
90
|
+
* Creates a @untools/pay checkout session for the requested plan upgrade.
|
|
91
|
+
* Returns `{ checkoutUrl, reference }`.
|
|
92
|
+
* Requires `owner` role.
|
|
93
|
+
*/
|
|
94
|
+
export async function handleUpgradePlan(req, db, auth, pay, teamSlug) {
|
|
95
|
+
const identity = await auth.verifySession(req);
|
|
96
|
+
if (!identity)
|
|
97
|
+
return err('UNAUTHORIZED', 'Not authenticated', 401);
|
|
98
|
+
const team = await db.teams.findOne({ slug: teamSlug });
|
|
99
|
+
if (!team)
|
|
100
|
+
return err('TEAM_NOT_FOUND', 'Team not found', 404);
|
|
101
|
+
const membership = await db.memberships.findOne({
|
|
102
|
+
teamId: team._id.toString(),
|
|
103
|
+
userId: identity.userId,
|
|
104
|
+
});
|
|
105
|
+
if (!membership || membership.role !== 'owner') {
|
|
106
|
+
return err('FORBIDDEN', 'Requires owner role', 403);
|
|
107
|
+
}
|
|
108
|
+
let body;
|
|
109
|
+
try {
|
|
110
|
+
body = await req.json();
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return err('INVALID_BODY', 'Request body must be JSON', 400);
|
|
114
|
+
}
|
|
115
|
+
const planSlug = typeof body.planSlug === 'string' ? body.planSlug : '';
|
|
116
|
+
if (!planSlug)
|
|
117
|
+
return err('INVALID_PLAN', 'planSlug is required', 400);
|
|
118
|
+
const provider = typeof body.provider === 'string' ? body.provider : '';
|
|
119
|
+
if (!provider)
|
|
120
|
+
return err('INVALID_PROVIDER', 'provider is required', 400);
|
|
121
|
+
const plan = await db.plans.findOne({ slug: planSlug, isActive: true });
|
|
122
|
+
if (!plan)
|
|
123
|
+
return err('PLAN_NOT_FOUND', 'Plan not found', 404);
|
|
124
|
+
const session = await pay.checkout.create({
|
|
125
|
+
amount: plan.priceMonthly,
|
|
126
|
+
currency: plan.currency,
|
|
127
|
+
provider,
|
|
128
|
+
metadata: {
|
|
129
|
+
teamId: team._id.toString(),
|
|
130
|
+
teamSlug: team.slug,
|
|
131
|
+
planSlug,
|
|
132
|
+
userId: identity.userId,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
return json({ data: { checkoutUrl: session.url, reference: session.reference } });
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// handleWebhookSettlement
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
/**
|
|
141
|
+
* POST /v1/billing/webhooks/:provider
|
|
142
|
+
*
|
|
143
|
+
* Verifies the provider webhook signature and fulfills plan upgrade.
|
|
144
|
+
* Updates `TeamDoc.planSlug` on successful payment.
|
|
145
|
+
*/
|
|
146
|
+
export async function handleWebhookSettlement(req, provider, db, pay) {
|
|
147
|
+
const { verified, data } = await pay.webhook.verify(req, provider);
|
|
148
|
+
if (!verified)
|
|
149
|
+
return err('INVALID_SIGNATURE', 'Webhook signature verification failed', 400);
|
|
150
|
+
const status = data['status'];
|
|
151
|
+
if (status !== 'success' && status !== 'successful') {
|
|
152
|
+
// Non-success event — acknowledge without action
|
|
153
|
+
return new Response(null, { status: 200 });
|
|
154
|
+
}
|
|
155
|
+
const metadata = (data['metadata'] ?? {});
|
|
156
|
+
const teamId = typeof metadata['teamId'] === 'string' ? metadata['teamId'] : null;
|
|
157
|
+
const planSlug = typeof metadata['planSlug'] === 'string' ? metadata['planSlug'] : null;
|
|
158
|
+
if (!teamId || !planSlug) {
|
|
159
|
+
return err('MISSING_METADATA', 'Webhook payload missing teamId or planSlug', 400);
|
|
160
|
+
}
|
|
161
|
+
const { ObjectId } = await import('mongodb');
|
|
162
|
+
await db.teams.updateOne({ _id: new ObjectId(teamId) }, { $set: { planSlug } });
|
|
163
|
+
return new Response(null, { status: 200 });
|
|
164
|
+
}
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// handleGetUsage
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
/**
|
|
169
|
+
* GET /v1/teams/:teamSlug/usage
|
|
170
|
+
*
|
|
171
|
+
* Returns current usage stats and plan limits for the team.
|
|
172
|
+
* Requires membership.
|
|
173
|
+
*/
|
|
174
|
+
export async function handleGetUsage(req, db, auth, teamSlug) {
|
|
175
|
+
const identity = await auth.verifySession(req);
|
|
176
|
+
if (!identity)
|
|
177
|
+
return err('UNAUTHORIZED', 'Not authenticated', 401);
|
|
178
|
+
const team = await db.teams.findOne({ slug: teamSlug });
|
|
179
|
+
if (!team)
|
|
180
|
+
return err('TEAM_NOT_FOUND', 'Team not found', 404);
|
|
181
|
+
const membership = await db.memberships.findOne({
|
|
182
|
+
teamId: team._id.toString(),
|
|
183
|
+
userId: identity.userId,
|
|
184
|
+
});
|
|
185
|
+
if (!membership)
|
|
186
|
+
return err('FORBIDDEN', 'Not a member of this team', 403);
|
|
187
|
+
const teamId = team._id.toString();
|
|
188
|
+
const [projectCount, memberCount, plan] = await Promise.all([
|
|
189
|
+
db.projects.countDocuments({ teamId }),
|
|
190
|
+
db.memberships.countDocuments({ teamId }),
|
|
191
|
+
db.plans.findOne({ slug: team.planSlug, isActive: true }),
|
|
192
|
+
]);
|
|
193
|
+
return json({
|
|
194
|
+
data: {
|
|
195
|
+
planSlug: team.planSlug,
|
|
196
|
+
plan: plan ?? null,
|
|
197
|
+
usage: {
|
|
198
|
+
projects: projectCount,
|
|
199
|
+
members: memberCount,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=billing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CloudDb } from './types.js';
|
|
2
|
+
export interface CallbackOptions {
|
|
3
|
+
/** `AIRDRAFT_CALLBACK_SECRET` env var — used to verify the CSRF state token. */
|
|
4
|
+
callbackSecret: string;
|
|
5
|
+
/** GitHub App numeric ID — `GITHUB_APP_ID` env var. */
|
|
6
|
+
githubAppId: string;
|
|
7
|
+
/** GitHub App PEM private key — `GITHUB_APP_PRIVATE_KEY` env var. */
|
|
8
|
+
githubAppPrivateKey: string;
|
|
9
|
+
db: CloudDb;
|
|
10
|
+
/**
|
|
11
|
+
* Base URL for the cloud dashboard (e.g. `https://app.airdraft.space`).
|
|
12
|
+
* Used to build post-connection redirect URLs.
|
|
13
|
+
*/
|
|
14
|
+
dashboardBaseUrl: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Handler for `GET /github/callback`.
|
|
18
|
+
*
|
|
19
|
+
* Processes the GitHub App installation callback after a user installs or
|
|
20
|
+
* updates the Airdraft GitHub App. Validates the CSRF state token, verifies
|
|
21
|
+
* the installation, writes the project connection, and issues a project API key.
|
|
22
|
+
*
|
|
23
|
+
* **Single repo granted:** auto-selects, completes connection immediately.
|
|
24
|
+
* **Multiple repos granted:** redirects to the dashboard repo picker step.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* export async function GET(req: Request) {
|
|
28
|
+
* return handleCallback(req, {
|
|
29
|
+
* callbackSecret: process.env.AIRDRAFT_CALLBACK_SECRET!,
|
|
30
|
+
* githubAppId: process.env.GITHUB_APP_ID!,
|
|
31
|
+
* githubAppPrivateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
|
|
32
|
+
* db,
|
|
33
|
+
* dashboardBaseUrl: 'https://app.airdraft.space',
|
|
34
|
+
* })
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function handleCallback(req: Request, opts: CallbackOptions): Promise<Response>;
|
|
39
|
+
//# sourceMappingURL=callback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../src/callback.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAMzC,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAA;IACtB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IACnB,qEAAqE;IACrE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,EAAE,EAAE,OAAO,CAAA;IACX;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAyDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,QAAQ,CAAC,CA8InB"}
|
package/dist/callback.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { generateApiKey } from '@airdraft/auth';
|
|
2
|
+
import { signGitHubAppJwt } from './jwt.js';
|
|
3
|
+
import { verifyState } from './state.js';
|
|
4
|
+
async function getInstallationToken(installationId, appId, privateKey) {
|
|
5
|
+
const jwt = await signGitHubAppJwt(appId, privateKey);
|
|
6
|
+
const res = await fetch(`https://api.github.com/app/installations/${installationId}/access_tokens`, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
Authorization: `Bearer ${jwt}`,
|
|
10
|
+
Accept: 'application/vnd.github+json',
|
|
11
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok)
|
|
15
|
+
throw new Error(`GitHub token API returned ${res.status}`);
|
|
16
|
+
const body = await res.json();
|
|
17
|
+
return body.token;
|
|
18
|
+
}
|
|
19
|
+
async function listInstallationRepos(installationToken) {
|
|
20
|
+
const res = await fetch('https://api.github.com/installation/repositories?per_page=100', {
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${installationToken}`,
|
|
23
|
+
Accept: 'application/vnd.github+json',
|
|
24
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
throw new Error(`GitHub repos API returned ${res.status}`);
|
|
29
|
+
const body = await res.json();
|
|
30
|
+
return body.repositories;
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// handleCallback
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Handler for `GET /github/callback`.
|
|
37
|
+
*
|
|
38
|
+
* Processes the GitHub App installation callback after a user installs or
|
|
39
|
+
* updates the Airdraft GitHub App. Validates the CSRF state token, verifies
|
|
40
|
+
* the installation, writes the project connection, and issues a project API key.
|
|
41
|
+
*
|
|
42
|
+
* **Single repo granted:** auto-selects, completes connection immediately.
|
|
43
|
+
* **Multiple repos granted:** redirects to the dashboard repo picker step.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* export async function GET(req: Request) {
|
|
47
|
+
* return handleCallback(req, {
|
|
48
|
+
* callbackSecret: process.env.AIRDRAFT_CALLBACK_SECRET!,
|
|
49
|
+
* githubAppId: process.env.GITHUB_APP_ID!,
|
|
50
|
+
* githubAppPrivateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
|
|
51
|
+
* db,
|
|
52
|
+
* dashboardBaseUrl: 'https://app.airdraft.space',
|
|
53
|
+
* })
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export async function handleCallback(req, opts) {
|
|
58
|
+
const url = new URL(req.url);
|
|
59
|
+
const installationIdStr = url.searchParams.get('installation_id');
|
|
60
|
+
const setupAction = url.searchParams.get('setup_action');
|
|
61
|
+
const stateToken = url.searchParams.get('state');
|
|
62
|
+
// -------------------------------------------------------------------------
|
|
63
|
+
// Unmatched installation — no state token (e.g. GitHub Marketplace)
|
|
64
|
+
// -------------------------------------------------------------------------
|
|
65
|
+
if (!stateToken) {
|
|
66
|
+
if (installationIdStr) {
|
|
67
|
+
const connectUrl = `${opts.dashboardBaseUrl}/connect?installation_id=${installationIdStr}`;
|
|
68
|
+
return Response.redirect(connectUrl, 302);
|
|
69
|
+
}
|
|
70
|
+
return new Response('Bad Request: missing state', { status: 400 });
|
|
71
|
+
}
|
|
72
|
+
// -------------------------------------------------------------------------
|
|
73
|
+
// 1. Verify CSRF state token
|
|
74
|
+
// -------------------------------------------------------------------------
|
|
75
|
+
const stateResult = verifyState(stateToken, opts.callbackSecret);
|
|
76
|
+
if (!stateResult.valid) {
|
|
77
|
+
const message = stateResult.reason === 'expired'
|
|
78
|
+
? 'Link expired — please try connecting again.'
|
|
79
|
+
: 'Invalid state token.';
|
|
80
|
+
return new Response(message, { status: 400 });
|
|
81
|
+
}
|
|
82
|
+
const { projectId, userId, mode, port } = stateResult.payload;
|
|
83
|
+
const installationId = parseInt(installationIdStr ?? '', 10);
|
|
84
|
+
if (isNaN(installationId)) {
|
|
85
|
+
return new Response('Bad Request: missing installation_id', { status: 400 });
|
|
86
|
+
}
|
|
87
|
+
// -------------------------------------------------------------------------
|
|
88
|
+
// 2. Load and validate project
|
|
89
|
+
// -------------------------------------------------------------------------
|
|
90
|
+
const { ObjectId } = await import('mongodb');
|
|
91
|
+
let projectOid;
|
|
92
|
+
try {
|
|
93
|
+
projectOid = new ObjectId(projectId);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return new Response('Bad Request: invalid projectId', { status: 400 });
|
|
97
|
+
}
|
|
98
|
+
const project = await opts.db.projects.findOne({ _id: projectOid });
|
|
99
|
+
if (!project)
|
|
100
|
+
return new Response('Not Found: project not found', { status: 404 });
|
|
101
|
+
const team = await opts.db.teams.findOne({ _id: new ObjectId(project.teamId) });
|
|
102
|
+
if (!team)
|
|
103
|
+
return new Response('Not Found: team not found', { status: 404 });
|
|
104
|
+
// -------------------------------------------------------------------------
|
|
105
|
+
// 3. Verify userId is still admin/owner of the project's team
|
|
106
|
+
// -------------------------------------------------------------------------
|
|
107
|
+
const membership = await opts.db.memberships.findOne({
|
|
108
|
+
teamId: project.teamId,
|
|
109
|
+
userId,
|
|
110
|
+
role: { $in: ['owner', 'admin'] },
|
|
111
|
+
});
|
|
112
|
+
if (!membership) {
|
|
113
|
+
return new Response('Forbidden: insufficient permissions', { status: 403 });
|
|
114
|
+
}
|
|
115
|
+
// -------------------------------------------------------------------------
|
|
116
|
+
// 4. Verify installation via GitHub API + get accessible repos
|
|
117
|
+
// -------------------------------------------------------------------------
|
|
118
|
+
let installationToken;
|
|
119
|
+
let repos;
|
|
120
|
+
try {
|
|
121
|
+
installationToken = await getInstallationToken(installationId, opts.githubAppId, opts.githubAppPrivateKey);
|
|
122
|
+
repos = await listInstallationRepos(installationToken);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
126
|
+
return new Response(`GitHub App installation could not be verified: ${msg}`, { status: 502 });
|
|
127
|
+
}
|
|
128
|
+
if (repos.length === 0) {
|
|
129
|
+
return new Response('No repositories accessible. Please grant access to at least one repository.', {
|
|
130
|
+
status: 400,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// -------------------------------------------------------------------------
|
|
134
|
+
// 5. Repo selection
|
|
135
|
+
// -------------------------------------------------------------------------
|
|
136
|
+
if (repos.length > 1) {
|
|
137
|
+
// Redirect to dashboard repo picker — connection is not written yet
|
|
138
|
+
const pickerUrl = `${opts.dashboardBaseUrl}/${team.slug}/${project.slug}/settings/github?installation_id=${installationId}&step=pick-repo`;
|
|
139
|
+
return Response.redirect(pickerUrl, 302);
|
|
140
|
+
}
|
|
141
|
+
// Single repo — auto-select
|
|
142
|
+
const selectedRepo = repos[0];
|
|
143
|
+
// -------------------------------------------------------------------------
|
|
144
|
+
// 6. Write project.github + generate / rotate project API key
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
const { key: apiKey, hash: apiKeyHash } = generateApiKey();
|
|
147
|
+
await opts.db.projects.updateOne({ _id: projectOid }, {
|
|
148
|
+
$set: {
|
|
149
|
+
github: {
|
|
150
|
+
installationId,
|
|
151
|
+
repo: selectedRepo.full_name,
|
|
152
|
+
branch: selectedRepo.default_branch,
|
|
153
|
+
connectedAt: new Date(),
|
|
154
|
+
connectedBy: userId,
|
|
155
|
+
},
|
|
156
|
+
apiKeyHash,
|
|
157
|
+
updatedAt: new Date(),
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
// -------------------------------------------------------------------------
|
|
161
|
+
// 7. Redirect
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
if (mode === 'cli' && port !== undefined) {
|
|
164
|
+
// CLI mode — redirect to local server with credentials
|
|
165
|
+
const cliCallbackUrl = new URL(`http://localhost:${port}/callback`);
|
|
166
|
+
cliCallbackUrl.searchParams.set('project_key', apiKey);
|
|
167
|
+
cliCallbackUrl.searchParams.set('repo', selectedRepo.full_name);
|
|
168
|
+
cliCallbackUrl.searchParams.set('branch', selectedRepo.default_branch);
|
|
169
|
+
return Response.redirect(cliCallbackUrl.toString(), 302);
|
|
170
|
+
}
|
|
171
|
+
// Dashboard mode — redirect to project settings with one-time key in query param
|
|
172
|
+
// (Dashboard shows the key once and removes it from the URL on load)
|
|
173
|
+
const successUrl = new URL(`/${team.slug}/${project.slug}/settings/github`, opts.dashboardBaseUrl);
|
|
174
|
+
successUrl.searchParams.set('connected', '1');
|
|
175
|
+
successUrl.searchParams.set('project_key', apiKey); // shown once, then discarded
|
|
176
|
+
return Response.redirect(successUrl.toString(), 302);
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../src/callback.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signs a short-lived CLI JWT for the given userId.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const token = await signCliToken(identity.userId, process.env.AIRDRAFT_CLI_SECRET!)
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
export declare function signCliToken(userId: string, secret: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Verifies a CLI JWT and returns the userId, or `null` if invalid/expired.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const userId = verifyCliToken(token, process.env.AIRDRAFT_CLI_SECRET!)
|
|
14
|
+
* if (!userId) return Response.json({ error: { code: 'UNAUTHORIZED', message: 'Invalid CLI token' } }, { status: 401 })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function verifyCliToken(token: string, secret: string): string | null;
|
|
18
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAgBA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQnE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B3E"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// CLI JWT helpers — HS256 via Node.js crypto (no extra dependencies)
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const CLI_TOKEN_TTL_S = 15 * 60; // 15 minutes
|
|
6
|
+
function b64url(input) {
|
|
7
|
+
return Buffer.from(input, 'utf8').toString('base64url');
|
|
8
|
+
}
|
|
9
|
+
function hmacSign(data, secret) {
|
|
10
|
+
return createHmac('sha256', secret).update(data).digest('base64url');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Signs a short-lived CLI JWT for the given userId.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const token = await signCliToken(identity.userId, process.env.AIRDRAFT_CLI_SECRET!)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function signCliToken(userId, secret) {
|
|
20
|
+
const now = Math.floor(Date.now() / 1000);
|
|
21
|
+
const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
|
|
22
|
+
const payload = b64url(JSON.stringify({ sub: userId, type: 'cli', iat: now, exp: now + CLI_TOKEN_TTL_S }));
|
|
23
|
+
const sig = hmacSign(`${header}.${payload}`, secret);
|
|
24
|
+
return `${header}.${payload}.${sig}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Verifies a CLI JWT and returns the userId, or `null` if invalid/expired.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* const userId = verifyCliToken(token, process.env.AIRDRAFT_CLI_SECRET!)
|
|
31
|
+
* if (!userId) return Response.json({ error: { code: 'UNAUTHORIZED', message: 'Invalid CLI token' } }, { status: 401 })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function verifyCliToken(token, secret) {
|
|
35
|
+
const parts = token.split('.');
|
|
36
|
+
if (parts.length !== 3)
|
|
37
|
+
return null;
|
|
38
|
+
const [header, payload, sig] = parts;
|
|
39
|
+
const expected = hmacSign(`${header}.${payload}`, secret);
|
|
40
|
+
const expectedBuf = Buffer.from(expected, 'utf8');
|
|
41
|
+
const actualBuf = Buffer.from(sig, 'utf8');
|
|
42
|
+
if (expectedBuf.length !== actualBuf.length ||
|
|
43
|
+
!timingSafeEqual(expectedBuf, actualBuf)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (parsed.type !== 'cli' || typeof parsed.sub !== 'string')
|
|
54
|
+
return null;
|
|
55
|
+
if (typeof parsed.exp === 'number' && parsed.exp < Math.floor(Date.now() / 1000))
|
|
56
|
+
return null;
|
|
57
|
+
return parsed.sub;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEzD,8EAA8E;AAC9E,qEAAqE;AACrE,8EAA8E;AAE9E,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,CAAA,CAAC,aAAa;AAE7C,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;AACzD,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,OAAO,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;AACtE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,MAAc;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IACnE,MAAM,OAAO,GAAG,MAAM,CACpB,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,eAAe,EAAE,CAAC,CACnF,CAAA;IACD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,EAAE,MAAM,CAAC,CAAA;IACpD,OAAO,GAAG,MAAM,IAAI,OAAO,IAAI,GAAG,EAAE,CAAA;AACtC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,MAAc;IAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,KAAK,CAAA;IAEpC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,EAAE,MAAM,CAAC,CAAA;IACzD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACjD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC1C,IACE,WAAW,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;QACvC,CAAC,eAAe,CAAC,WAAW,EAAE,SAAS,CAAC,EACxC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,MAAwD,CAAA;IAC5D,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxE,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAE7F,OAAO,MAAM,CAAC,GAAG,CAAA;AACnB,CAAC"}
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MongoClient } from 'mongodb';
|
|
2
|
+
import type { CloudDb } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a typed `CloudDb` handle from a connected `MongoClient`.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const client = new MongoClient(process.env.MONGODB_URI!)
|
|
8
|
+
* await client.connect()
|
|
9
|
+
* export const db = createCloudDb(client)
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function createCloudDb(client: MongoClient, dbName?: string): CloudDb;
|
|
13
|
+
//# sourceMappingURL=db.d.ts.map
|
package/dist/db.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,OAAO,EAA0F,MAAM,YAAY,CAAA;AAEjI;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,SAAa,GAAG,OAAO,CAY/E"}
|