@farm.js/convex 0.0.0-stage → 0.1.0-beta.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 +22 -0
- package/README.md +63 -2
- package/dist/ConvexClientProvider.d.ts +3 -0
- package/dist/ConvexClientProvider.d.ts.map +1 -0
- package/dist/ConvexClientProvider.js +6 -0
- package/dist/adapter-client.d.ts +3 -0
- package/dist/adapter-client.d.ts.map +1 -0
- package/dist/adapter-client.js +21 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +3 -0
- package/dist/clients.d.ts +3 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +10 -0
- package/dist/component/_generated/api.d.ts +38 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +30 -0
- package/dist/component/_generated/component.d.ts +57 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +10 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +10 -0
- package/dist/component/_generated/server.d.ts +133 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +2 -0
- package/dist/component/rateLimits.d.ts +15 -0
- package/dist/component/rateLimits.d.ts.map +1 -0
- package/dist/component/rateLimits.js +40 -0
- package/dist/component/schema.d.ts +24 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +13 -0
- package/dist/component/storage.d.ts +17 -0
- package/dist/component/storage.d.ts.map +1 -0
- package/dist/component/storage.js +62 -0
- package/dist/component/validators.d.ts +8 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +5 -0
- package/dist/component.d.ts +12 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/component.js +52 -0
- package/dist/detect.d.ts +6 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +18 -0
- package/dist/dev.d.ts +10 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +122 -0
- package/dist/environment.d.ts +6 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +62 -0
- package/dist/extensions.d.ts +8 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +55 -0
- package/dist/forward.d.ts +2 -0
- package/dist/forward.d.ts.map +1 -0
- package/dist/forward.js +38 -0
- package/dist/functions.d.ts +42 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/functions.js +90 -0
- package/dist/homes.d.ts +11 -0
- package/dist/homes.d.ts.map +1 -0
- package/dist/homes.js +81 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/known.d.ts +7 -0
- package/dist/known.d.ts.map +1 -0
- package/dist/known.js +33 -0
- package/dist/pairs.d.ts +6 -0
- package/dist/pairs.d.ts.map +1 -0
- package/dist/pairs.js +28 -0
- package/dist/rate-limit.d.ts +4 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +16 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +14 -0
- package/dist/secret.d.ts +3 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +8 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +70 -0
- package/dist/site-token.d.ts +4 -0
- package/dist/site-token.d.ts.map +1 -0
- package/dist/site-token.js +26 -0
- package/dist/storage.d.ts +6 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +49 -0
- package/dist/token-auth.d.ts +13 -0
- package/dist/token-auth.d.ts.map +1 -0
- package/dist/token-auth.js +69 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +76 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "./_generated/server";
|
|
3
|
+
import { rateLimitResultValidator } from "./validators";
|
|
4
|
+
export const increment = mutation({
|
|
5
|
+
args: { key: v.string(), windowMs: v.number() },
|
|
6
|
+
returns: rateLimitResultValidator,
|
|
7
|
+
handler: async (context, { key, windowMs }) => {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
const existing = await context.db
|
|
10
|
+
.query("rateLimits")
|
|
11
|
+
.withIndex("by_key", (index) => index.eq("key", key))
|
|
12
|
+
.unique();
|
|
13
|
+
if (!existing) {
|
|
14
|
+
const resetAt = now + windowMs;
|
|
15
|
+
await context.db.insert("rateLimits", { key, count: 1, resetAt });
|
|
16
|
+
return { count: 1, resetAt };
|
|
17
|
+
}
|
|
18
|
+
if (existing.resetAt <= now) {
|
|
19
|
+
const resetAt = now + windowMs;
|
|
20
|
+
await context.db.patch(existing._id, { count: 1, resetAt });
|
|
21
|
+
return { count: 1, resetAt };
|
|
22
|
+
}
|
|
23
|
+
const count = existing.count + 1;
|
|
24
|
+
await context.db.patch(existing._id, { count });
|
|
25
|
+
return { count, resetAt: existing.resetAt };
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
export const read = query({
|
|
29
|
+
args: { key: v.string(), now: v.number() },
|
|
30
|
+
returns: v.union(rateLimitResultValidator, v.null()),
|
|
31
|
+
handler: async (context, { key, now }) => {
|
|
32
|
+
const existing = await context.db
|
|
33
|
+
.query("rateLimits")
|
|
34
|
+
.withIndex("by_key", (index) => index.eq("key", key))
|
|
35
|
+
.unique();
|
|
36
|
+
if (!existing || existing.resetAt <= now)
|
|
37
|
+
return null;
|
|
38
|
+
return { count: existing.count, resetAt: existing.resetAt };
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare const _default: import("convex/server").SchemaDefinition<{
|
|
2
|
+
rateLimits: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
3
|
+
key: string;
|
|
4
|
+
count: number;
|
|
5
|
+
resetAt: number;
|
|
6
|
+
}, {
|
|
7
|
+
key: import("convex/values").VString<string, "required">;
|
|
8
|
+
count: import("convex/values").VFloat64<number, "required">;
|
|
9
|
+
resetAt: import("convex/values").VFloat64<number, "required">;
|
|
10
|
+
}, "required", "key" | "count" | "resetAt">, {
|
|
11
|
+
by_key: ["key", "_creationTime"];
|
|
12
|
+
}, {}, {}>;
|
|
13
|
+
storageItems: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
14
|
+
key: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}, {
|
|
17
|
+
key: import("convex/values").VString<string, "required">;
|
|
18
|
+
value: import("convex/values").VString<string, "required">;
|
|
19
|
+
}, "required", "key" | "value">, {
|
|
20
|
+
by_key: ["key", "_creationTime"];
|
|
21
|
+
}, {}, {}>;
|
|
22
|
+
}, true>;
|
|
23
|
+
export default _default;
|
|
24
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAGA,wBAUG"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
export default defineSchema({
|
|
4
|
+
rateLimits: defineTable({
|
|
5
|
+
key: v.string(),
|
|
6
|
+
count: v.number(),
|
|
7
|
+
resetAt: v.number(),
|
|
8
|
+
}).index("by_key", ["key"]),
|
|
9
|
+
storageItems: defineTable({
|
|
10
|
+
key: v.string(),
|
|
11
|
+
value: v.string(),
|
|
12
|
+
}).index("by_key", ["key"]),
|
|
13
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const getItem: import("convex/server").RegisteredQuery<"public", {
|
|
2
|
+
key: string;
|
|
3
|
+
}, Promise<string | null>>;
|
|
4
|
+
export declare const setItem: import("convex/server").RegisteredMutation<"public", {
|
|
5
|
+
key: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}, Promise<null>>;
|
|
8
|
+
export declare const removeItem: import("convex/server").RegisteredMutation<"public", {
|
|
9
|
+
key: string;
|
|
10
|
+
}, Promise<null>>;
|
|
11
|
+
export declare const listKeys: import("convex/server").RegisteredQuery<"public", {
|
|
12
|
+
prefix: string;
|
|
13
|
+
}, Promise<string[]>>;
|
|
14
|
+
export declare const clearItems: import("convex/server").RegisteredMutation<"public", {
|
|
15
|
+
prefix: string;
|
|
16
|
+
}, Promise<number>>;
|
|
17
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/component/storage.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,OAAO;;0BAOlB,CAAC;AAEH,eAAO,MAAM,OAAO;;;iBAYlB,CAAC;AAEH,eAAO,MAAM,UAAU;;iBAQrB,CAAC;AAEH,eAAO,MAAM,QAAQ;;qBAOnB,CAAC;AAEH,eAAO,MAAM,UAAU;;mBAQrB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "./_generated/server";
|
|
3
|
+
const CLEAR_BATCH_SIZE = 500;
|
|
4
|
+
function findItem(context, key) {
|
|
5
|
+
return context.db
|
|
6
|
+
.query("storageItems")
|
|
7
|
+
.withIndex("by_key", (index) => index.eq("key", key))
|
|
8
|
+
.unique();
|
|
9
|
+
}
|
|
10
|
+
function queryItemsWithPrefix(context, prefix) {
|
|
11
|
+
return context.db
|
|
12
|
+
.query("storageItems")
|
|
13
|
+
.withIndex("by_key", (index) => index.gte("key", prefix).lt("key", `${prefix}`));
|
|
14
|
+
}
|
|
15
|
+
export const getItem = query({
|
|
16
|
+
args: { key: v.string() },
|
|
17
|
+
returns: v.union(v.string(), v.null()),
|
|
18
|
+
handler: async (context, { key }) => {
|
|
19
|
+
const item = await findItem(context, key);
|
|
20
|
+
return item?.value ?? null;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
export const setItem = mutation({
|
|
24
|
+
args: { key: v.string(), value: v.string() },
|
|
25
|
+
returns: v.null(),
|
|
26
|
+
handler: async (context, { key, value }) => {
|
|
27
|
+
const item = await findItem(context, key);
|
|
28
|
+
if (item) {
|
|
29
|
+
await context.db.patch(item._id, { value });
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
await context.db.insert("storageItems", { key, value });
|
|
33
|
+
return null;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
export const removeItem = mutation({
|
|
37
|
+
args: { key: v.string() },
|
|
38
|
+
returns: v.null(),
|
|
39
|
+
handler: async (context, { key }) => {
|
|
40
|
+
const item = await findItem(context, key);
|
|
41
|
+
if (item)
|
|
42
|
+
await context.db.delete(item._id);
|
|
43
|
+
return null;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
export const listKeys = query({
|
|
47
|
+
args: { prefix: v.string() },
|
|
48
|
+
returns: v.array(v.string()),
|
|
49
|
+
handler: async (context, { prefix }) => {
|
|
50
|
+
const items = await queryItemsWithPrefix(context, prefix).collect();
|
|
51
|
+
return items.map((item) => item.key);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
export const clearItems = mutation({
|
|
55
|
+
args: { prefix: v.string() },
|
|
56
|
+
returns: v.number(),
|
|
57
|
+
handler: async (context, { prefix }) => {
|
|
58
|
+
const items = await queryItemsWithPrefix(context, prefix).take(CLEAR_BATCH_SIZE);
|
|
59
|
+
await Promise.all(items.map((item) => context.db.delete(item._id)));
|
|
60
|
+
return items.length;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const rateLimitResultValidator: import("convex/values").VObject<{
|
|
2
|
+
count: number;
|
|
3
|
+
resetAt: number;
|
|
4
|
+
}, {
|
|
5
|
+
count: import("convex/values").VFloat64<number, "required">;
|
|
6
|
+
resetAt: import("convex/values").VFloat64<number, "required">;
|
|
7
|
+
}, "required", "count" | "resetAt">;
|
|
8
|
+
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../src/component/validators.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB;;;;;;mCAGnC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ConvexComponentOptions, ConvexExtension } from "./types.js";
|
|
2
|
+
import type { ConvexService } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Any Convex component, wired into Farm by convex({ use }). Well-known components, such as
|
|
5
|
+
* "@convex-dev/better-auth" or "@convex-dev/stripe", come with their routes and defaults.
|
|
6
|
+
*/
|
|
7
|
+
export declare function component(packageName: string, options?: ConvexComponentOptions): ConvexExtension;
|
|
8
|
+
/** Whether an extension signs visitors in: a token, a site token, a token path or a provider. */
|
|
9
|
+
export declare function signsVisitorsIn(extension: ConvexExtension): boolean;
|
|
10
|
+
/** The kind of service an extension provides; signing visitors in means auth. */
|
|
11
|
+
export declare function readExtensionService(extension: ConvexExtension): ConvexService | undefined;
|
|
12
|
+
//# sourceMappingURL=component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAwB,MAAM,YAAY,CAAC;AAChG,OAAO,KAAK,EAAwB,aAAa,EAAE,MAAM,YAAY,CAAC;AA8BtE;;;GAGG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAiBjB;AAED,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAInE;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS,CAE1F"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { KNOWN_COMPONENTS } from "./known.js";
|
|
2
|
+
function toRoutes(routes) {
|
|
3
|
+
if (!routes)
|
|
4
|
+
return [];
|
|
5
|
+
if (Array.isArray(routes))
|
|
6
|
+
return [...routes];
|
|
7
|
+
return Object.entries(routes).map(([path, to]) => ({ path, to }));
|
|
8
|
+
}
|
|
9
|
+
function toWebhookRoute(packageName, known, { webhooks, convexPath }) {
|
|
10
|
+
if (!webhooks)
|
|
11
|
+
return [];
|
|
12
|
+
const to = convexPath ?? known?.webhook?.convexPath;
|
|
13
|
+
const path = webhooks === true ? known?.webhook?.path : webhooks;
|
|
14
|
+
if (!to || !path) {
|
|
15
|
+
throw new TypeError(`component("${packageName}") has no default webhook paths. Pass webhooks: "/your/path" and convexPath.`);
|
|
16
|
+
}
|
|
17
|
+
return [{ path, methods: ["POST"], to }];
|
|
18
|
+
}
|
|
19
|
+
function withoutUndefined(extension) {
|
|
20
|
+
return Object.fromEntries(Object.entries(extension).filter(([, value]) => value !== undefined));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Any Convex component, wired into Farm by convex({ use }). Well-known components, such as
|
|
24
|
+
* "@convex-dev/better-auth" or "@convex-dev/stripe", come with their routes and defaults.
|
|
25
|
+
*/
|
|
26
|
+
export function component(packageName, options = {}) {
|
|
27
|
+
if (!packageName.trim())
|
|
28
|
+
throw new TypeError("component() needs the component's package name.");
|
|
29
|
+
const known = KNOWN_COMPONENTS[packageName];
|
|
30
|
+
return withoutUndefined({
|
|
31
|
+
name: options.name ?? known?.name ?? packageName,
|
|
32
|
+
component: packageName,
|
|
33
|
+
service: known?.service,
|
|
34
|
+
routes: [
|
|
35
|
+
...(known?.routes ?? []),
|
|
36
|
+
...toRoutes(options.routes),
|
|
37
|
+
...toWebhookRoute(packageName, known, options),
|
|
38
|
+
],
|
|
39
|
+
token: options.token,
|
|
40
|
+
siteToken: options.token ? undefined : known?.siteToken,
|
|
41
|
+
tokenPath: options.tokenPath ?? known?.tokenPath,
|
|
42
|
+
provider: options.provider,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Whether an extension signs visitors in: a token, a site token, a token path or a provider. */
|
|
46
|
+
export function signsVisitorsIn(extension) {
|
|
47
|
+
return Boolean(extension.token || extension.siteToken || extension.tokenPath || extension.provider);
|
|
48
|
+
}
|
|
49
|
+
/** The kind of service an extension provides; signing visitors in means auth. */
|
|
50
|
+
export function readExtensionService(extension) {
|
|
51
|
+
return extension.service ?? (signsVisitorsIn(extension) ? "auth" : undefined);
|
|
52
|
+
}
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConvexExtension, ConvexHomes } from "./types.js";
|
|
2
|
+
/** The well-known components installed in convex.config.ts, minus services whose home is Farm. */
|
|
3
|
+
export declare function detectExtensions(root: string, functionsDirectory: string, homes: ConvexHomes): ConvexExtension[];
|
|
4
|
+
/** An extension listed in `use` replaces the detected one for the same component. */
|
|
5
|
+
export declare function mergeExtensions(detected: readonly ConvexExtension[], explicit: readonly ConvexExtension[]): ConvexExtension[];
|
|
6
|
+
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE/D,kGAAkG;AAClG,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,MAAM,EAC1B,KAAK,EAAE,WAAW,GACjB,eAAe,EAAE,CAQnB;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,eAAe,EAAE,CAGnB"}
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { component, readExtensionService } from "./component.js";
|
|
2
|
+
import { findComponentPackages, readConvexConfigSource } from "./homes.js";
|
|
3
|
+
import { KNOWN_COMPONENTS } from "./known.js";
|
|
4
|
+
/** The well-known components installed in convex.config.ts, minus services whose home is Farm. */
|
|
5
|
+
export function detectExtensions(root, functionsDirectory, homes) {
|
|
6
|
+
const source = readConvexConfigSource(root, functionsDirectory) ?? "";
|
|
7
|
+
return findComponentPackages(source)
|
|
8
|
+
.flatMap((name) => (KNOWN_COMPONENTS[name] ? [component(name)] : []))
|
|
9
|
+
.filter((extension) => {
|
|
10
|
+
const service = readExtensionService(extension);
|
|
11
|
+
return !service || homes[service] !== "farm";
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** An extension listed in `use` replaces the detected one for the same component. */
|
|
15
|
+
export function mergeExtensions(detected, explicit) {
|
|
16
|
+
const listed = new Set(explicit.map(({ component }) => component).filter(Boolean));
|
|
17
|
+
return [...detected.filter(({ component }) => !listed.has(component)), ...explicit];
|
|
18
|
+
}
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ConvexDevOptions, ConvexDevState } from "./types.js";
|
|
2
|
+
import type { ConvexPluginInput } from "./types.js";
|
|
3
|
+
export declare function resolveDevOptions(dev: boolean | ConvexDevOptions | undefined): ConvexDevOptions | undefined;
|
|
4
|
+
export declare function isDeploymentChosen(root: string): boolean;
|
|
5
|
+
export declare function startConvexDev(root: string, options: ConvexDevOptions, state: ConvexDevState): void;
|
|
6
|
+
export declare function stopConvexDev(state: ConvexDevState | undefined): void;
|
|
7
|
+
/** Tell the developer to restart once convex dev has written the new deployment's URL. */
|
|
8
|
+
export declare function announceNewDeployment(root: string, interval?: number): () => void;
|
|
9
|
+
export declare function createConvexPlugin(input: ConvexPluginInput): import("@farm.js/core").FarmPlugin<ConvexDevState, Record<string, never>, unknown, undefined, unknown, false, import("@farm.js/core").PluginRoutes>;
|
|
10
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAwB,MAAM,YAAY,CAAC;AACzF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAKpD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,GAC1C,gBAAgB,GAAG,SAAS,CAG9B;AAiBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGxD;AASD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,cAAc,GACpB,IAAI,CA2BN;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAIrE;AAID,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAM,GAAG,MAAM,IAAI,CAY9E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,uJAkC1D"}
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, unwatchFile, watchFile } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { styleText } from "node:util";
|
|
6
|
+
import { definePlugin } from "@farm.js/core/plugin";
|
|
7
|
+
import { checkServiceHomes } from "./homes.js";
|
|
8
|
+
const CONVEX_BINARY = process.platform === "win32" ? "convex.cmd" : "convex";
|
|
9
|
+
const OUTPUT_LABEL = "convex │ ";
|
|
10
|
+
export function resolveDevOptions(dev) {
|
|
11
|
+
if (dev === false)
|
|
12
|
+
return undefined;
|
|
13
|
+
return dev === true || dev === undefined ? {} : dev;
|
|
14
|
+
}
|
|
15
|
+
function findConvexBinary(root) {
|
|
16
|
+
const binaryPath = path.join(root, "node_modules", ".bin", CONVEX_BINARY);
|
|
17
|
+
return existsSync(binaryPath) ? binaryPath : undefined;
|
|
18
|
+
}
|
|
19
|
+
function readEnvironmentFile(root) {
|
|
20
|
+
try {
|
|
21
|
+
return readFileSync(path.join(root, ".env.local"), "utf8");
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
25
|
+
return "";
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Before a deployment is chosen, convex dev asks questions on the terminal, so it keeps it.
|
|
30
|
+
export function isDeploymentChosen(root) {
|
|
31
|
+
if (process.env.CONVEX_DEPLOYMENT || process.env.CONVEX_AGENT_MODE)
|
|
32
|
+
return true;
|
|
33
|
+
return /^CONVEX_DEPLOYMENT=\S/m.test(readEnvironmentFile(root));
|
|
34
|
+
}
|
|
35
|
+
function writeLabelledLines(source, target) {
|
|
36
|
+
const label = styleText("dim", OUTPUT_LABEL, { stream: target });
|
|
37
|
+
createInterface({ input: source, crlfDelay: Infinity }).on("line", (line) => {
|
|
38
|
+
target.write(`${label}${line}\n`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function startConvexDev(root, options, state) {
|
|
42
|
+
const binaryPath = findConvexBinary(root);
|
|
43
|
+
if (!binaryPath) {
|
|
44
|
+
console.warn("[farm:convex] convex is not installed in this app, so `convex dev` was not started.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const labelOutput = isDeploymentChosen(root);
|
|
48
|
+
const child = spawn(binaryPath, ["dev", ...(options.args ?? [])], {
|
|
49
|
+
cwd: root,
|
|
50
|
+
stdio: labelOutput ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
51
|
+
shell: process.platform === "win32",
|
|
52
|
+
env: labelOutput && process.stdout.isTTY ? { ...process.env, FORCE_COLOR: "1" } : process.env,
|
|
53
|
+
});
|
|
54
|
+
state.child = child;
|
|
55
|
+
if (labelOutput && child.stdout && child.stderr) {
|
|
56
|
+
writeLabelledLines(child.stdout, process.stdout);
|
|
57
|
+
writeLabelledLines(child.stderr, process.stderr);
|
|
58
|
+
}
|
|
59
|
+
const stopWithFarm = () => stopConvexDev(state);
|
|
60
|
+
process.once("exit", stopWithFarm);
|
|
61
|
+
child.once("exit", () => {
|
|
62
|
+
process.off("exit", stopWithFarm);
|
|
63
|
+
// A runner that exits late must not forget the one that replaced it.
|
|
64
|
+
if (state.child === child)
|
|
65
|
+
state.child = undefined;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export function stopConvexDev(state) {
|
|
69
|
+
if (!state?.child || state.child.exitCode !== null)
|
|
70
|
+
return;
|
|
71
|
+
state.child.kill("SIGINT");
|
|
72
|
+
state.child = undefined;
|
|
73
|
+
}
|
|
74
|
+
const DEPLOYMENT_URL_PATTERN = /^(?:VITE_)?CONVEX_URL=\S/m;
|
|
75
|
+
/** Tell the developer to restart once convex dev has written the new deployment's URL. */
|
|
76
|
+
export function announceNewDeployment(root, interval = 500) {
|
|
77
|
+
const filePath = path.join(root, ".env.local");
|
|
78
|
+
console.warn("[farm:convex] CONVEX_URL is not set yet. convex dev will create a deployment; restart farm dev when it is ready.");
|
|
79
|
+
const check = () => {
|
|
80
|
+
if (!DEPLOYMENT_URL_PATTERN.test(readEnvironmentFile(root)))
|
|
81
|
+
return;
|
|
82
|
+
unwatchFile(filePath, check);
|
|
83
|
+
console.log("[farm:convex] Convex deployment is ready. Restart farm dev to connect to it.");
|
|
84
|
+
};
|
|
85
|
+
watchFile(filePath, { interval }, check);
|
|
86
|
+
return () => unwatchFile(filePath, check);
|
|
87
|
+
}
|
|
88
|
+
export function createConvexPlugin(input) {
|
|
89
|
+
let root = process.cwd();
|
|
90
|
+
return definePlugin({
|
|
91
|
+
name: "farm:convex",
|
|
92
|
+
configure(config) {
|
|
93
|
+
const view = config;
|
|
94
|
+
root = path.resolve(view.root ?? root);
|
|
95
|
+
if (input.waitingForDeployment)
|
|
96
|
+
return;
|
|
97
|
+
checkServiceHomes({ ...view, root }, input.functionsDirectory, input.homes, input.extensions);
|
|
98
|
+
},
|
|
99
|
+
setup: () => ({}),
|
|
100
|
+
dev: {
|
|
101
|
+
server(_viteServer, { state, lifecycle }) {
|
|
102
|
+
if (input.waitingForDeployment) {
|
|
103
|
+
const stopWatching = announceNewDeployment(root);
|
|
104
|
+
lifecycle.onShutdown(stopWatching);
|
|
105
|
+
if (!input.dev) {
|
|
106
|
+
console.warn("[farm:convex] CONVEX_URL is not set. Run `npx convex dev` to create a deployment.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!input.dev)
|
|
111
|
+
return;
|
|
112
|
+
// Vite closes its HTTP server on every restart (an edited .env does that), and Farm
|
|
113
|
+
// runs this hook once per farm dev, so stop only on shutdown.
|
|
114
|
+
startConvexDev(root, input.dev, state);
|
|
115
|
+
lifecycle.onShutdown(() => stopConvexDev(state));
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
runtime: {
|
|
119
|
+
close: ({ state }) => stopConvexDev(state),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConvexDeploymentURLs, ConvexIntegrationInput } from "./types.js";
|
|
2
|
+
export declare function hasDeploymentURL(input: ConvexIntegrationInput): boolean;
|
|
3
|
+
export declare function readEnvironmentValue(names: readonly string[]): string | undefined;
|
|
4
|
+
export declare function deriveSiteURL(url: URL): string | undefined;
|
|
5
|
+
export declare function resolveDeploymentURLs(input: ConvexIntegrationInput): ConvexDeploymentURLs;
|
|
6
|
+
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAS/E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAEvE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAEjF;AAyBD,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAM1D;AA8BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,sBAAsB,GAAG,oBAAoB,CAMzF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const CLOUD_SUFFIX = ".convex.cloud";
|
|
2
|
+
const SITE_SUFFIX = ".convex.site";
|
|
3
|
+
const LOCAL_HOSTS = new Set(["127.0.0.1", "localhost", "0.0.0.0"]);
|
|
4
|
+
const URL_ENVIRONMENT_NAMES = ["CONVEX_URL", "VITE_CONVEX_URL"];
|
|
5
|
+
const SITE_URL_ENVIRONMENT_NAMES = ["CONVEX_SITE_URL", "VITE_CONVEX_SITE_URL"];
|
|
6
|
+
export function hasDeploymentURL(input) {
|
|
7
|
+
return Boolean(input.url ?? readEnvironmentValue(URL_ENVIRONMENT_NAMES));
|
|
8
|
+
}
|
|
9
|
+
export function readEnvironmentValue(names) {
|
|
10
|
+
return names.map((name) => process.env[name]?.trim()).find((value) => Boolean(value));
|
|
11
|
+
}
|
|
12
|
+
function parseURL(value, name) {
|
|
13
|
+
if (!URL.canParse(value)) {
|
|
14
|
+
throw new Error(`Convex integration: ${name} is not a valid URL (received "${value}").`);
|
|
15
|
+
}
|
|
16
|
+
return new URL(value);
|
|
17
|
+
}
|
|
18
|
+
function checkDeploymentURL(url) {
|
|
19
|
+
if (url.hostname.endsWith(SITE_SUFFIX)) {
|
|
20
|
+
throw new Error(`Convex integration: CONVEX_URL points at the HTTP actions URL (${SITE_SUFFIX}). Use the deployment URL ending in ${CLOUD_SUFFIX}, and put the ${SITE_SUFFIX} URL in CONVEX_SITE_URL.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function checkSiteURL(url) {
|
|
24
|
+
if (url.hostname.endsWith(CLOUD_SUFFIX)) {
|
|
25
|
+
throw new Error(`Convex integration: CONVEX_SITE_URL points at the deployment URL (${CLOUD_SUFFIX}). Use the HTTP actions URL ending in ${SITE_SUFFIX}.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function deriveSiteURL(url) {
|
|
29
|
+
if (url.hostname.endsWith(CLOUD_SUFFIX)) {
|
|
30
|
+
return `${url.protocol}//${url.hostname.slice(0, -CLOUD_SUFFIX.length)}${SITE_SUFFIX}`;
|
|
31
|
+
}
|
|
32
|
+
if (!LOCAL_HOSTS.has(url.hostname) || !url.port)
|
|
33
|
+
return undefined;
|
|
34
|
+
return `${url.protocol}//${url.hostname}:${Number(url.port) + 1}`;
|
|
35
|
+
}
|
|
36
|
+
function trimTrailingSlash(value) {
|
|
37
|
+
return value.replace(/\/+$/, "");
|
|
38
|
+
}
|
|
39
|
+
function resolveURL(input) {
|
|
40
|
+
const rawURL = input.url ?? readEnvironmentValue(URL_ENVIRONMENT_NAMES);
|
|
41
|
+
if (!rawURL) {
|
|
42
|
+
throw new Error("Convex integration requires CONVEX_URL. Run `npx convex dev` once to create a deployment and write CONVEX_URL to .env.local, or pass convex({ url }).");
|
|
43
|
+
}
|
|
44
|
+
const url = parseURL(rawURL, "CONVEX_URL");
|
|
45
|
+
checkDeploymentURL(url);
|
|
46
|
+
return url;
|
|
47
|
+
}
|
|
48
|
+
function resolveSiteURL(input, url) {
|
|
49
|
+
const rawSiteURL = input.siteURL ?? readEnvironmentValue(SITE_URL_ENVIRONMENT_NAMES) ?? deriveSiteURL(url);
|
|
50
|
+
if (!rawSiteURL) {
|
|
51
|
+
throw new Error("Convex integration could not work out CONVEX_SITE_URL from CONVEX_URL. Set CONVEX_SITE_URL to your deployment's HTTP actions URL, or pass convex({ siteURL }).");
|
|
52
|
+
}
|
|
53
|
+
checkSiteURL(parseURL(rawSiteURL, "CONVEX_SITE_URL"));
|
|
54
|
+
return rawSiteURL;
|
|
55
|
+
}
|
|
56
|
+
export function resolveDeploymentURLs(input) {
|
|
57
|
+
const url = resolveURL(input);
|
|
58
|
+
return {
|
|
59
|
+
url: trimTrailingSlash(url.href),
|
|
60
|
+
siteURL: trimTrailingSlash(resolveSiteURL(input, url)),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FarmIntegrationRoute } from "@farm.js/core";
|
|
2
|
+
import type { ConvexExtension } from "./types.js";
|
|
3
|
+
type ExtensionValueKey = "token" | "siteToken" | "tokenPath" | "provider";
|
|
4
|
+
export declare function checkExtensions(extensions: readonly ConvexExtension[]): void;
|
|
5
|
+
export declare function pickExtensionValue<Key extends ExtensionValueKey>(extensions: readonly ConvexExtension[], key: Key): ConvexExtension[Key];
|
|
6
|
+
export declare function createExtensionRoutes(extensions: readonly ConvexExtension[], siteURL: string): FarmIntegrationRoute[];
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD,KAAK,iBAAiB,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;AA+C1E,wBAAgB,eAAe,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,GAAG,IAAI,CAM5E;AAED,wBAAgB,kBAAkB,CAAC,GAAG,SAAS,iBAAiB,EAC9D,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,GAAG,EAAE,GAAG,GACP,eAAe,CAAC,GAAG,CAAC,CAEtB;AAED,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,OAAO,EAAE,MAAM,GACd,oBAAoB,EAAE,CAQxB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readExtensionService } from "./component.js";
|
|
2
|
+
import { forwardRequest } from "./forward.js";
|
|
3
|
+
const DEFAULT_METHODS = ["GET", "POST"];
|
|
4
|
+
function checkSingleOwner(extensions, keys, label, option) {
|
|
5
|
+
const owners = extensions
|
|
6
|
+
.filter((extension) => keys.some((key) => extension[key] !== undefined))
|
|
7
|
+
.map(({ name }) => name);
|
|
8
|
+
if (owners.length > 1) {
|
|
9
|
+
throw new Error(`Convex integration: ${owners.join(" and ")} both ${label}. Keep one, or pass convex({ ${option} }) to choose.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function checkServiceOwners(extensions) {
|
|
13
|
+
const owners = new Map();
|
|
14
|
+
for (const extension of extensions) {
|
|
15
|
+
const service = readExtensionService(extension);
|
|
16
|
+
if (!service)
|
|
17
|
+
continue;
|
|
18
|
+
const other = owners.get(service);
|
|
19
|
+
if (other) {
|
|
20
|
+
throw new Error(`Convex integration: ${other} and ${extension.name} both provide ${service}. Keep one.`);
|
|
21
|
+
}
|
|
22
|
+
owners.set(service, extension.name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function checkRoutePaths(extensions) {
|
|
26
|
+
const owners = new Map();
|
|
27
|
+
for (const { name, routes = [] } of extensions) {
|
|
28
|
+
for (const { path, to } of routes) {
|
|
29
|
+
if (!path.startsWith("/") || (to !== undefined && !to.startsWith("/"))) {
|
|
30
|
+
throw new Error(`Convex integration: ${name} routes must be paths starting with "/".`);
|
|
31
|
+
}
|
|
32
|
+
const owner = owners.get(path);
|
|
33
|
+
if (owner)
|
|
34
|
+
throw new Error(`Convex integration: ${owner} and ${name} both route ${path}.`);
|
|
35
|
+
owners.set(path, name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function checkExtensions(extensions) {
|
|
40
|
+
checkSingleOwner(extensions, ["token", "siteToken"], "read a visitor token", "token");
|
|
41
|
+
checkSingleOwner(extensions, ["tokenPath"], "set a browser token path", "tokenPath");
|
|
42
|
+
checkSingleOwner(extensions, ["provider"], "set a provider", "provider");
|
|
43
|
+
checkServiceOwners(extensions);
|
|
44
|
+
checkRoutePaths(extensions);
|
|
45
|
+
}
|
|
46
|
+
export function pickExtensionValue(extensions, key) {
|
|
47
|
+
return extensions.find((extension) => extension[key] !== undefined)?.[key];
|
|
48
|
+
}
|
|
49
|
+
export function createExtensionRoutes(extensions, siteURL) {
|
|
50
|
+
return extensions.flatMap(({ routes = [] }) => routes.map((route) => ({
|
|
51
|
+
path: route.path,
|
|
52
|
+
methods: route.methods ?? DEFAULT_METHODS,
|
|
53
|
+
handler: (request) => forwardRequest(request, siteURL, route.to),
|
|
54
|
+
})));
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forward.d.ts","sourceRoot":"","sources":["../src/forward.ts"],"names":[],"mappings":"AA+BA,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC,CAYnB"}
|
package/dist/forward.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const DROPPED_REQUEST_HEADERS = ["connection", "content-length", "host", "transfer-encoding"];
|
|
2
|
+
const DROPPED_RESPONSE_HEADERS = ["content-encoding", "content-length", "transfer-encoding"];
|
|
3
|
+
const BODYLESS_METHODS = new Set(["GET", "HEAD"]);
|
|
4
|
+
function readProtocol(url) {
|
|
5
|
+
return url.protocol.replace(/:$/, "");
|
|
6
|
+
}
|
|
7
|
+
function createForwardHeaders(request, incomingURL) {
|
|
8
|
+
const headers = new Headers(request.headers);
|
|
9
|
+
DROPPED_REQUEST_HEADERS.forEach((name) => headers.delete(name));
|
|
10
|
+
headers.set("accept-encoding", "identity");
|
|
11
|
+
headers.set("x-forwarded-host", incomingURL.host);
|
|
12
|
+
headers.set("x-forwarded-proto", readProtocol(incomingURL));
|
|
13
|
+
headers.set("x-better-auth-forwarded-host", incomingURL.host);
|
|
14
|
+
headers.set("x-better-auth-forwarded-proto", readProtocol(incomingURL));
|
|
15
|
+
return headers;
|
|
16
|
+
}
|
|
17
|
+
function copyResponse(response) {
|
|
18
|
+
const headers = new Headers(response.headers);
|
|
19
|
+
DROPPED_RESPONSE_HEADERS.forEach((name) => headers.delete(name));
|
|
20
|
+
return new Response(response.body, {
|
|
21
|
+
status: response.status,
|
|
22
|
+
statusText: response.statusText,
|
|
23
|
+
headers,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function forwardRequest(request, siteURL, targetPath) {
|
|
27
|
+
const incomingURL = new URL(request.url);
|
|
28
|
+
const pathname = targetPath ?? incomingURL.pathname;
|
|
29
|
+
const targetURL = new URL(`${pathname}${incomingURL.search}`, siteURL);
|
|
30
|
+
const init = {
|
|
31
|
+
method: request.method,
|
|
32
|
+
headers: createForwardHeaders(request, incomingURL),
|
|
33
|
+
body: BODYLESS_METHODS.has(request.method) ? undefined : request.body,
|
|
34
|
+
redirect: "manual",
|
|
35
|
+
duplex: "half",
|
|
36
|
+
};
|
|
37
|
+
return copyResponse(await fetch(targetURL, init));
|
|
38
|
+
}
|