convex 1.42.1 → 1.42.2
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 +17 -0
- package/dist/browser.bundle.js +1 -1
- package/dist/browser.bundle.js.map +2 -2
- package/dist/cjs/browser/simple_client.js +2 -2
- package/dist/cjs/browser/simple_client.js.map +2 -2
- package/dist/cjs/cli/codegen_templates/common.js +6 -0
- package/dist/cjs/cli/codegen_templates/common.js.map +2 -2
- package/dist/cjs/cli/dev.js +1 -0
- package/dist/cjs/cli/dev.js.map +2 -2
- package/dist/cjs/cli/generatedApi.js.map +1 -1
- package/dist/cjs/cli/lib/api.js.map +2 -2
- package/dist/cjs/cli/lib/codegen.js +2 -1
- package/dist/cjs/cli/lib/codegen.js.map +2 -2
- package/dist/cjs/cli/lib/utils/utils.js +4 -12
- package/dist/cjs/cli/lib/utils/utils.js.map +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/client.js +1 -1
- package/dist/cjs/react/client.js.map +2 -2
- package/dist/cjs/react-clerk/ConvexProviderWithClerk.js +2 -1
- package/dist/cjs/react-clerk/ConvexProviderWithClerk.js.map +2 -2
- package/dist/cjs/server/database.js.map +1 -1
- package/dist/cjs/server/impl/meta_impl.js +2 -2
- package/dist/cjs/server/impl/meta_impl.js.map +2 -2
- package/dist/cjs/server/impl/query_impl.js.map +2 -2
- package/dist/cjs/server/meta.js.map +1 -1
- package/dist/cjs/server/query.js.map +1 -1
- package/dist/cjs/server/registration.js.map +1 -1
- package/dist/cjs/server/schema.js.map +1 -1
- package/dist/cjs-types/browser/simple_client.d.ts +1 -2
- package/dist/cjs-types/browser/simple_client.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/common.d.ts +12 -0
- package/dist/cjs-types/cli/codegen_templates/common.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/common.test.d.ts +2 -0
- package/dist/cjs-types/cli/codegen_templates/common.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/dev.d.ts.map +1 -1
- package/dist/cjs-types/cli/dev.test.d.ts +2 -0
- package/dist/cjs-types/cli/dev.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/generatedApi.d.ts +0 -1
- package/dist/cjs-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/api.d.ts +0 -1
- package/dist/cjs-types/cli/lib/api.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/codegen.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/utils/utils.d.ts.map +1 -1
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cjs-types/react/client.d.ts.map +1 -1
- package/dist/cjs-types/react/client_provided_base_client.test.d.ts +2 -0
- package/dist/cjs-types/react/client_provided_base_client.test.d.ts.map +1 -0
- package/dist/cjs-types/react-clerk/ConvexProviderWithClerk.d.ts +1 -0
- package/dist/cjs-types/react-clerk/ConvexProviderWithClerk.d.ts.map +1 -1
- package/dist/cjs-types/server/database.d.ts.map +1 -1
- package/dist/cjs-types/server/impl/query_impl.d.ts.map +1 -1
- package/dist/cjs-types/server/meta.d.ts +8 -0
- package/dist/cjs-types/server/meta.d.ts.map +1 -1
- package/dist/cjs-types/server/query.d.ts.map +1 -1
- package/dist/cjs-types/server/registration.d.ts.map +1 -1
- package/dist/cjs-types/server/schema.d.ts.map +1 -1
- package/dist/cli.bundle.cjs +12 -14
- package/dist/cli.bundle.cjs.map +2 -2
- package/dist/esm/browser/simple_client.js +5 -3
- package/dist/esm/browser/simple_client.js.map +2 -2
- package/dist/esm/cli/codegen_templates/common.js +5 -0
- package/dist/esm/cli/codegen_templates/common.js.map +2 -2
- package/dist/esm/cli/dev.js +3 -1
- package/dist/esm/cli/dev.js.map +2 -2
- package/dist/esm/cli/lib/api.js.map +2 -2
- package/dist/esm/cli/lib/codegen.js +2 -1
- package/dist/esm/cli/lib/codegen.js.map +2 -2
- package/dist/esm/cli/lib/utils/utils.js +4 -12
- package/dist/esm/cli/lib/utils/utils.js.map +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/client.js +1 -1
- package/dist/esm/react/client.js.map +2 -2
- package/dist/esm/react-clerk/ConvexProviderWithClerk.js +2 -1
- package/dist/esm/react-clerk/ConvexProviderWithClerk.js.map +2 -2
- package/dist/esm/server/impl/meta_impl.js +2 -2
- package/dist/esm/server/impl/meta_impl.js.map +2 -2
- package/dist/esm/server/impl/query_impl.js.map +2 -2
- package/dist/esm/server/schema.js.map +1 -1
- package/dist/esm-types/browser/simple_client.d.ts +1 -2
- package/dist/esm-types/browser/simple_client.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/common.d.ts +12 -0
- package/dist/esm-types/cli/codegen_templates/common.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/common.test.d.ts +2 -0
- package/dist/esm-types/cli/codegen_templates/common.test.d.ts.map +1 -0
- package/dist/esm-types/cli/dev.d.ts.map +1 -1
- package/dist/esm-types/cli/dev.test.d.ts +2 -0
- package/dist/esm-types/cli/dev.test.d.ts.map +1 -0
- package/dist/esm-types/cli/generatedApi.d.ts +0 -1
- package/dist/esm-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/api.d.ts +0 -1
- package/dist/esm-types/cli/lib/api.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/codegen.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/utils/utils.d.ts.map +1 -1
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/esm-types/react/client.d.ts.map +1 -1
- package/dist/esm-types/react/client_provided_base_client.test.d.ts +2 -0
- package/dist/esm-types/react/client_provided_base_client.test.d.ts.map +1 -0
- package/dist/esm-types/react-clerk/ConvexProviderWithClerk.d.ts +1 -0
- package/dist/esm-types/react-clerk/ConvexProviderWithClerk.d.ts.map +1 -1
- package/dist/esm-types/server/database.d.ts.map +1 -1
- package/dist/esm-types/server/impl/query_impl.d.ts.map +1 -1
- package/dist/esm-types/server/meta.d.ts +8 -0
- package/dist/esm-types/server/meta.d.ts.map +1 -1
- package/dist/esm-types/server/query.d.ts.map +1 -1
- package/dist/esm-types/server/registration.d.ts.map +1 -1
- package/dist/esm-types/server/schema.d.ts.map +1 -1
- package/dist/react.bundle.js +2 -2
- package/dist/react.bundle.js.map +2 -2
- package/package.json +2 -2
- package/src/browser/simple_client.ts +12 -10
- package/src/cli/codegen_templates/common.test.ts +52 -0
- package/src/cli/codegen_templates/common.ts +17 -0
- package/src/cli/dev.test.ts +157 -0
- package/src/cli/dev.ts +7 -0
- package/src/cli/generatedApi.ts +0 -1
- package/src/cli/lib/api.ts +0 -1
- package/src/cli/lib/codegen.ts +2 -1
- package/src/cli/lib/config.test.ts +1 -4
- package/src/cli/lib/utils/utils.ts +4 -12
- package/src/index.ts +1 -1
- package/src/react/client.ts +23 -6
- package/src/react/client_provided_base_client.test.tsx +56 -0
- package/src/react-clerk/ConvexProviderWithClerk.tsx +3 -1
- package/src/server/database.ts +6 -4
- package/src/server/impl/meta_impl.ts +2 -2
- package/src/server/impl/query_impl.ts +1 -3
- package/src/server/meta.ts +8 -0
- package/src/server/query.ts +9 -6
- package/src/server/registration.ts +10 -10
- package/src/server/schema.ts +3 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "convex",
|
|
3
3
|
"description": "Client for the Convex Cloud",
|
|
4
|
-
"version": "1.42.
|
|
4
|
+
"version": "1.42.2",
|
|
5
5
|
"author": "Convex, Inc. <no-reply@convex.dev>",
|
|
6
6
|
"homepage": "https://convex.dev",
|
|
7
7
|
"repository": {
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
"fetch-retry": "~6.0.0",
|
|
249
249
|
"find-up": "^8.0.0",
|
|
250
250
|
"globals": "~17.4.0",
|
|
251
|
-
"happy-dom": "~20.
|
|
251
|
+
"happy-dom": "~20.10.0",
|
|
252
252
|
"jsdom": "~28.1.0",
|
|
253
253
|
"json5": "~2.2.3",
|
|
254
254
|
"knip": "~5.86.0",
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { validateDeploymentUrl } from "../common/index.js";
|
|
2
|
-
import {
|
|
3
|
-
BaseConvexClient,
|
|
4
|
-
BaseConvexClientOptions,
|
|
5
|
-
MutationOptions,
|
|
6
|
-
PaginatedQueryToken,
|
|
7
|
-
QueryToken,
|
|
8
|
-
UserIdentityAttributes,
|
|
9
|
-
} from "./index.js";
|
|
10
2
|
import {
|
|
11
3
|
FunctionArgs,
|
|
12
4
|
FunctionReference,
|
|
@@ -15,13 +7,23 @@ import {
|
|
|
15
7
|
} from "../server/index.js";
|
|
16
8
|
import { getFunctionName } from "../server/api.js";
|
|
17
9
|
import { AuthTokenFetcher } from "./sync/authentication_manager.js";
|
|
18
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
BaseConvexClient,
|
|
12
|
+
BaseConvexClientOptions,
|
|
13
|
+
ConnectionState,
|
|
14
|
+
MutationOptions,
|
|
15
|
+
} from "./sync/client.js";
|
|
19
16
|
import {
|
|
20
17
|
ExtendedTransition,
|
|
21
18
|
PaginatedQueryClient,
|
|
22
19
|
} from "./sync/paginated_query_client.js";
|
|
23
20
|
import { PaginatedQueryResult } from "./sync/pagination.js";
|
|
24
|
-
import {
|
|
21
|
+
import { UserIdentityAttributes } from "./sync/protocol.js";
|
|
22
|
+
import {
|
|
23
|
+
PaginatedQueryToken,
|
|
24
|
+
QueryToken,
|
|
25
|
+
serializedQueryTokenIsPaginated,
|
|
26
|
+
} from "./sync/udf_path_utils.js";
|
|
25
27
|
|
|
26
28
|
// In Node.js builds this points to a bundled WebSocket implementation. If no
|
|
27
29
|
// WebSocket implementation is manually specified or globally available,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { compareModulePaths } from "./common.js";
|
|
3
|
+
|
|
4
|
+
describe("compareModulePaths", () => {
|
|
5
|
+
test("matches default sort order for forward slash paths", () => {
|
|
6
|
+
const paths = ["fooBaz.ts", "foo/bar.ts", "foo.ts"];
|
|
7
|
+
expect([...paths].sort(compareModulePaths)).toEqual([...paths].sort());
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("orders Windows paths identically to their POSIX equivalents", () => {
|
|
11
|
+
const posixPaths = ["fooBaz.ts", "foo/bar.ts", "foo.ts"];
|
|
12
|
+
const windowsPaths = posixPaths.map((p) => p.replace(/\//g, "\\"));
|
|
13
|
+
|
|
14
|
+
const sortedPosix = [...posixPaths].sort(compareModulePaths);
|
|
15
|
+
const sortedWindows = [...windowsPaths].sort(compareModulePaths);
|
|
16
|
+
|
|
17
|
+
expect(sortedPosix).toEqual(["foo.ts", "foo/bar.ts", "fooBaz.ts"]);
|
|
18
|
+
expect(sortedWindows.map((p) => p.replace(/\\/g, "/"))).toEqual(
|
|
19
|
+
sortedPosix,
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("default sort diverges on this input, demonstrating the bug", () => {
|
|
24
|
+
// "/" (0x2F) sorts before letters while "\" (0x5C) sorts after them,
|
|
25
|
+
// so the OS-native forms of the same file set sort differently.
|
|
26
|
+
const windowsPaths = ["fooBaz.ts", "foo\\bar.ts", "foo.ts"];
|
|
27
|
+
expect([...windowsPaths].sort()).toEqual([
|
|
28
|
+
"foo.ts",
|
|
29
|
+
"fooBaz.ts",
|
|
30
|
+
"foo\\bar.ts",
|
|
31
|
+
]);
|
|
32
|
+
expect([...windowsPaths].sort(compareModulePaths)).toEqual([
|
|
33
|
+
"foo.ts",
|
|
34
|
+
"foo\\bar.ts",
|
|
35
|
+
"fooBaz.ts",
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("sorts nested directories before longer sibling file names", () => {
|
|
40
|
+
const paths = ["a/b/c.ts", "aZ.ts", "a.ts", "a/b.ts"];
|
|
41
|
+
expect([...paths].sort(compareModulePaths)).toEqual([
|
|
42
|
+
"a.ts",
|
|
43
|
+
"a/b.ts",
|
|
44
|
+
"a/b/c.ts",
|
|
45
|
+
"aZ.ts",
|
|
46
|
+
]);
|
|
47
|
+
const windows = paths.map((p) => p.replace(/\//g, "\\"));
|
|
48
|
+
expect(
|
|
49
|
+
[...windows].sort(compareModulePaths).map((p) => p.replace(/\\/g, "/")),
|
|
50
|
+
).toEqual(["a.ts", "a/b.ts", "a/b/c.ts", "aZ.ts"]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -43,3 +43,20 @@ const collator = new Intl.Collator("en-US", {
|
|
|
43
43
|
export function compareStrings(a: string, b: string): number {
|
|
44
44
|
return collator.compare(a, b);
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Comparison function for sorting module paths in codegen output.
|
|
49
|
+
*
|
|
50
|
+
* Compares the forward slash normalized form of each path so the resulting
|
|
51
|
+
* order is identical on every platform. Sorting OS-native paths directly
|
|
52
|
+
* diverges on Windows because "\" (0x5C) sorts after letters while "/"
|
|
53
|
+
* (0x2F) sorts before them. Uses plain code unit comparison to match the
|
|
54
|
+
* default Array.prototype.sort() order these paths sorted with on POSIX.
|
|
55
|
+
*
|
|
56
|
+
* Usage: modulePaths.sort(compareModulePaths)
|
|
57
|
+
*/
|
|
58
|
+
export function compareModulePaths(a: string, b: string): number {
|
|
59
|
+
const aNormalized = a.replace(/\\/g, "/");
|
|
60
|
+
const bNormalized = b.replace(/\\/g, "/");
|
|
61
|
+
return aNormalized < bNormalized ? -1 : aNormalized > bNormalized ? 1 : 0;
|
|
62
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { nodeFs } from "../bundler/fs.js";
|
|
4
|
+
import { dev } from "./dev.js";
|
|
5
|
+
import { deploymentCredentialsOrConfigure } from "./configure.js";
|
|
6
|
+
|
|
7
|
+
// In-memory filesystem — populated in beforeEach.
|
|
8
|
+
let testFiles: Map<string, string>;
|
|
9
|
+
// Everything written to stderr during a test, so we can assert on error output.
|
|
10
|
+
let stderr: string;
|
|
11
|
+
|
|
12
|
+
vi.mock("../bundler/fs.js", async (importOriginal) => {
|
|
13
|
+
const actual = await importOriginal<typeof import("../bundler/fs.js")>();
|
|
14
|
+
return {
|
|
15
|
+
...actual,
|
|
16
|
+
nodeFs: {
|
|
17
|
+
...actual.nodeFs,
|
|
18
|
+
exists: vi.fn(),
|
|
19
|
+
readUtf8File: vi.fn(),
|
|
20
|
+
writeUtf8File: vi.fn(),
|
|
21
|
+
mkdir: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// `deploymentCredentialsOrConfigure` is where the CLI prompts the user to
|
|
27
|
+
// create or select a project. We stub it out so we can assert *whether* it gets
|
|
28
|
+
// reached — the whole point of the fast-fail check is that it should not.
|
|
29
|
+
vi.mock("./configure.js", async (importOriginal) => {
|
|
30
|
+
const actual = await importOriginal<typeof import("./configure.js")>();
|
|
31
|
+
return {
|
|
32
|
+
...actual,
|
|
33
|
+
deploymentCredentialsOrConfigure: vi.fn(),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Avoid actually pushing if the action ever gets that far.
|
|
38
|
+
vi.mock("./lib/dev.js", async (importOriginal) => {
|
|
39
|
+
const actual = await importOriginal<typeof import("./lib/dev.js")>();
|
|
40
|
+
return {
|
|
41
|
+
...actual,
|
|
42
|
+
devAgainstDeployment: vi.fn(),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
vi.mock("dotenv", async (importOriginal) => {
|
|
47
|
+
const actual = await importOriginal<typeof import("dotenv")>();
|
|
48
|
+
return {
|
|
49
|
+
...actual,
|
|
50
|
+
config: vi.fn(),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
vi.mock("@sentry/node", () => ({
|
|
55
|
+
captureException: vi.fn(),
|
|
56
|
+
captureMessage: vi.fn(),
|
|
57
|
+
close: vi.fn(),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
describe("npx convex dev", () => {
|
|
61
|
+
let savedEnv: NodeJS.ProcessEnv;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
savedEnv = { ...process.env };
|
|
65
|
+
process.env = {};
|
|
66
|
+
|
|
67
|
+
testFiles = new Map();
|
|
68
|
+
stderr = "";
|
|
69
|
+
|
|
70
|
+
vi.resetAllMocks();
|
|
71
|
+
|
|
72
|
+
vi.mocked(nodeFs.exists).mockImplementation((p: string) =>
|
|
73
|
+
testFiles.has(path.resolve(p)),
|
|
74
|
+
);
|
|
75
|
+
vi.mocked(nodeFs.readUtf8File).mockImplementation((p: string) => {
|
|
76
|
+
const content = testFiles.get(path.resolve(p));
|
|
77
|
+
if (content === undefined) {
|
|
78
|
+
const err: any = new Error(
|
|
79
|
+
`ENOENT: no such file or directory, open '${p}'`,
|
|
80
|
+
);
|
|
81
|
+
err.code = "ENOENT";
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
return content;
|
|
85
|
+
});
|
|
86
|
+
vi.mocked(nodeFs.writeUtf8File).mockImplementation(
|
|
87
|
+
(p: string, content: string) => {
|
|
88
|
+
testFiles.set(path.resolve(p), content);
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// `crash` calls `process.exit`; make it throw so `parseAsync` rejects.
|
|
93
|
+
vi.spyOn(process, "exit").mockImplementation((() => {
|
|
94
|
+
throw new Error("the CLI tried to exit the process");
|
|
95
|
+
}) as any);
|
|
96
|
+
vi.spyOn(process.stderr, "write").mockImplementation((chunk: any) => {
|
|
97
|
+
stderr += chunk.toString();
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
process.env = savedEnv;
|
|
104
|
+
vi.restoreAllMocks();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("fails fast without prompting to create/select a project when run outside a Convex project", async () => {
|
|
108
|
+
// A package.json with no `convex` dependency: this directory is not a
|
|
109
|
+
// Convex project.
|
|
110
|
+
testFiles.set(
|
|
111
|
+
path.resolve("package.json"),
|
|
112
|
+
JSON.stringify({ name: "not-a-convex-app", dependencies: {} }),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
await expect(dev.parseAsync([], { from: "user" })).rejects.toThrow(
|
|
116
|
+
"the CLI tried to exit the process",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// The error must mention how to fix it...
|
|
120
|
+
expect(stderr).toContain("add `convex` to your package.json");
|
|
121
|
+
// ...and crucially, it must happen *before* we ever ask the user to create
|
|
122
|
+
// or select a project.
|
|
123
|
+
expect(deploymentCredentialsOrConfigure).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("fails fast when there is no package.json at all", async () => {
|
|
127
|
+
// Empty directory: no package.json.
|
|
128
|
+
await expect(dev.parseAsync([], { from: "user" })).rejects.toThrow(
|
|
129
|
+
"the CLI tried to exit the process",
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(deploymentCredentialsOrConfigure).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("proceeds to project configuration once the Convex dependency check passes", async () => {
|
|
136
|
+
testFiles.set(
|
|
137
|
+
path.resolve("package.json"),
|
|
138
|
+
JSON.stringify({
|
|
139
|
+
name: "convex-app",
|
|
140
|
+
dependencies: { convex: "^1.0.0" },
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Short-circuit right at the project-selection step so we don't run the
|
|
145
|
+
// rest of the dev flow; reaching this proves the dependency check passed.
|
|
146
|
+
const sentinel = new Error("reached project configuration");
|
|
147
|
+
vi.mocked(deploymentCredentialsOrConfigure).mockRejectedValue(sentinel);
|
|
148
|
+
|
|
149
|
+
// `--configure` skips deployment selection and goes straight to
|
|
150
|
+
// `deploymentCredentialsOrConfigure`.
|
|
151
|
+
await expect(
|
|
152
|
+
dev.parseAsync(["--configure"], { from: "user" }),
|
|
153
|
+
).rejects.toThrow("reached project configuration");
|
|
154
|
+
|
|
155
|
+
expect(deploymentCredentialsOrConfigure).toHaveBeenCalledTimes(1);
|
|
156
|
+
});
|
|
157
|
+
});
|
package/src/cli/dev.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { devAgainstDeployment } from "./lib/dev.js";
|
|
|
9
9
|
import {
|
|
10
10
|
CONVEX_DEPLOYMENT_ENV_VAR_NAME,
|
|
11
11
|
CONVEX_SELF_HOSTED_URL_VAR_NAME,
|
|
12
|
+
ensureHasConvexDependency,
|
|
12
13
|
} from "./lib/utils/utils.js";
|
|
13
14
|
import {
|
|
14
15
|
getDeploymentSelection,
|
|
@@ -210,6 +211,12 @@ Same format as .env.local or .env files, and overrides them.`,
|
|
|
210
211
|
|
|
211
212
|
const devOptions = await normalizeDevOptions(ctx, cmdOptions);
|
|
212
213
|
|
|
214
|
+
// Check that we're in a Convex project (a package.json with a `convex`
|
|
215
|
+
// dependency) before doing anything else, so we fail fast with a clear
|
|
216
|
+
// error instead of prompting to create/select a project and only then
|
|
217
|
+
// crashing because there's nothing to push.
|
|
218
|
+
await ensureHasConvexDependency(ctx, "run `npx convex dev`");
|
|
219
|
+
|
|
213
220
|
if (cmdOptions.configure === undefined) {
|
|
214
221
|
if (cmdOptions.team || cmdOptions.project || cmdOptions.devDeployment)
|
|
215
222
|
return await ctx.crash({
|
package/src/cli/generatedApi.ts
CHANGED
|
@@ -405,7 +405,6 @@ export interface components {
|
|
|
405
405
|
createTime: number;
|
|
406
406
|
devDeploymentName?: string | null;
|
|
407
407
|
id: components["schemas"]["ProjectId"];
|
|
408
|
-
isDemo: boolean;
|
|
409
408
|
name: components["schemas"]["ProjectName"];
|
|
410
409
|
prodDeploymentName?: string | null;
|
|
411
410
|
slug: components["schemas"]["ProjectSlug"];
|
package/src/cli/lib/api.ts
CHANGED
package/src/cli/lib/codegen.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { withTmpDir, TempDir } from "../../bundler/fs.js";
|
|
|
4
4
|
import { entryPoints } from "../../bundler/index.js";
|
|
5
5
|
import { apiCodegen } from "../codegen_templates/api.js";
|
|
6
6
|
import { apiCjsCodegen } from "../codegen_templates/api_cjs.js";
|
|
7
|
+
import { compareModulePaths } from "../codegen_templates/common.js";
|
|
7
8
|
import {
|
|
8
9
|
dynamicDataModelDTS,
|
|
9
10
|
dynamicDataModelTS,
|
|
@@ -846,7 +847,7 @@ async function doApiCodegen(
|
|
|
846
847
|
const absModulePaths = await entryPoints(ctx, functionsDir);
|
|
847
848
|
const modulePaths = absModulePaths
|
|
848
849
|
.map((p) => path.relative(functionsDir, p))
|
|
849
|
-
.sort();
|
|
850
|
+
.sort(compareModulePaths);
|
|
850
851
|
|
|
851
852
|
const writtenFiles: string[] = [];
|
|
852
853
|
|
|
@@ -257,10 +257,7 @@ test("parseProjectConfig - top-level validation", async () => {
|
|
|
257
257
|
null,
|
|
258
258
|
"✖ Expected `convex.json` to contain an object\n",
|
|
259
259
|
);
|
|
260
|
-
await assertParseError(
|
|
261
|
-
[],
|
|
262
|
-
"✖ Expected `convex.json` to contain an object\n",
|
|
263
|
-
);
|
|
260
|
+
await assertParseError([], "✖ Expected `convex.json` to contain an object\n");
|
|
264
261
|
});
|
|
265
262
|
|
|
266
263
|
test("ensureConvexFunctionsDir - creates functions directory", async () => {
|
|
@@ -602,18 +602,10 @@ export async function validateOrSelectProject(
|
|
|
602
602
|
});
|
|
603
603
|
}
|
|
604
604
|
if (!projectSlug) {
|
|
605
|
-
const nonDemoProjects = projects.filter((project) => !project.isDemo);
|
|
606
|
-
if (nonDemoProjects.length === 0) {
|
|
607
|
-
return await ctx.crash({
|
|
608
|
-
exitCode: 1,
|
|
609
|
-
errorType: "fatal",
|
|
610
|
-
printedMessage: `No existing non-demo projects! Run this command again and choose "create a new project."`,
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
605
|
// Prompt the user to select project.
|
|
614
|
-
switch (
|
|
606
|
+
switch (projects.length) {
|
|
615
607
|
case 1: {
|
|
616
|
-
const project =
|
|
608
|
+
const project = projects[0];
|
|
617
609
|
const confirmed = await promptYesNo(ctx, {
|
|
618
610
|
message: `${singleProjectPrompt} ${project.name} (${project.slug})?`,
|
|
619
611
|
});
|
|
@@ -621,12 +613,12 @@ export async function validateOrSelectProject(
|
|
|
621
613
|
if (!confirmed) {
|
|
622
614
|
return null;
|
|
623
615
|
}
|
|
624
|
-
return
|
|
616
|
+
return projects[0].slug;
|
|
625
617
|
}
|
|
626
618
|
default:
|
|
627
619
|
return await promptSearch(ctx, {
|
|
628
620
|
message: multiProjectPrompt,
|
|
629
|
-
choices:
|
|
621
|
+
choices: projects.map((project: Project) => ({
|
|
630
622
|
name: `${project.name} (${project.slug})`,
|
|
631
623
|
value: project.slug,
|
|
632
624
|
})),
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "1.42.
|
|
1
|
+
export const version = "1.42.2";
|
package/src/react/client.ts
CHANGED
|
@@ -287,7 +287,22 @@ export interface MutationOptions<Args extends Record<string, Value>> {
|
|
|
287
287
|
*
|
|
288
288
|
* @public
|
|
289
289
|
*/
|
|
290
|
-
export interface ConvexReactClientOptions extends BaseConvexClientOptions {
|
|
290
|
+
export interface ConvexReactClientOptions extends BaseConvexClientOptions {
|
|
291
|
+
/**
|
|
292
|
+
* An already-constructed {@link BaseConvexClient} to use instead of
|
|
293
|
+
* constructing one internally. When provided, this client (and its
|
|
294
|
+
* derived {@link PaginatedQueryClient}) is used for all operations.
|
|
295
|
+
*
|
|
296
|
+
* @internal
|
|
297
|
+
*/
|
|
298
|
+
baseClient?: BaseConvexClient;
|
|
299
|
+
|
|
300
|
+
// FIXME: Ideally we define `ConvexReactClientOptions` as
|
|
301
|
+
// `type ConvexReactClientOptions = BaseConvexClientOptions | { baseClient: BaseConvexClient }`
|
|
302
|
+
// so that callers won’t try to set `BaseConvexClientOptions` attributes that are ineffective.
|
|
303
|
+
// But since `baseClient` is internal at the moment, I don’t think we can do that.
|
|
304
|
+
// We can fix this if we ever make `baseClient` public.
|
|
305
|
+
}
|
|
291
306
|
|
|
292
307
|
/**
|
|
293
308
|
* A Convex client for use within React.
|
|
@@ -367,11 +382,13 @@ export class ConvexReactClient {
|
|
|
367
382
|
return this.cachedSync;
|
|
368
383
|
}
|
|
369
384
|
// BaseConvexClient and paginated query client are always created together.
|
|
370
|
-
this.cachedSync =
|
|
371
|
-
this.
|
|
372
|
-
(
|
|
373
|
-
|
|
374
|
-
|
|
385
|
+
this.cachedSync =
|
|
386
|
+
this.options.baseClient ??
|
|
387
|
+
new BaseConvexClient(
|
|
388
|
+
this.address,
|
|
389
|
+
() => {}, // Use the PaginatedQueryClient's transition instead.
|
|
390
|
+
this.options,
|
|
391
|
+
);
|
|
375
392
|
if (this.adminAuth) {
|
|
376
393
|
this.cachedSync.setAdminAuth(this.adminAuth, this.fakeUserIdentity);
|
|
377
394
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { test, expect, describe, vi } from "vitest";
|
|
2
|
+
import ws from "ws";
|
|
3
|
+
|
|
4
|
+
import { ConvexReactClient } from "./client.js";
|
|
5
|
+
import { anyApi } from "../server/api.js";
|
|
6
|
+
import { BaseConvexClient } from "../browser/index.js";
|
|
7
|
+
|
|
8
|
+
const address = "https://127.0.0.1:3001";
|
|
9
|
+
|
|
10
|
+
// A minimal subclass of `BaseConvexClient` used to prove that a
|
|
11
|
+
// caller-provided `BaseConvexClient` (of any subclass) can be plugged into a
|
|
12
|
+
// `ConvexReactClient` via the `client` option.
|
|
13
|
+
class MinimalBaseConvexClient extends BaseConvexClient {}
|
|
14
|
+
|
|
15
|
+
const testBaseConvexClient = () =>
|
|
16
|
+
new MinimalBaseConvexClient(address, () => {}, {
|
|
17
|
+
webSocketConstructor: ws as unknown as typeof WebSocket,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("ConvexReactClient with a provided BaseConvexClient", () => {
|
|
21
|
+
test("uses the provided client as its `sync` client", () => {
|
|
22
|
+
const baseClient = testBaseConvexClient();
|
|
23
|
+
const client = new ConvexReactClient(address, { baseClient });
|
|
24
|
+
expect(client.sync).toBe(baseClient);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("applies setAdminAuth called before the client is lazily instantiated", () => {
|
|
28
|
+
const baseClient = testBaseConvexClient();
|
|
29
|
+
const setAdminAuthSpy = vi.spyOn(baseClient, "setAdminAuth");
|
|
30
|
+
const client = new ConvexReactClient(address, { baseClient });
|
|
31
|
+
client.setAdminAuth("admin-token");
|
|
32
|
+
expect(setAdminAuthSpy).not.toHaveBeenCalled();
|
|
33
|
+
|
|
34
|
+
// Accessing `sync` lazily instantiates the client, applying admin auth.
|
|
35
|
+
expect(client.sync).toBe(baseClient);
|
|
36
|
+
expect(setAdminAuthSpy).toHaveBeenCalledWith("admin-token", undefined);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("queries and mutations work through the provided client", async () => {
|
|
40
|
+
const baseClient = testBaseConvexClient();
|
|
41
|
+
const client = new ConvexReactClient(address, { baseClient });
|
|
42
|
+
|
|
43
|
+
void client.mutation(
|
|
44
|
+
anyApi.myMutation.default,
|
|
45
|
+
{},
|
|
46
|
+
{
|
|
47
|
+
optimisticUpdate: (localStore) => {
|
|
48
|
+
localStore.setQuery(anyApi.myQuery.default, {}, "queryResult");
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const queryResult = client.query(anyApi.myQuery.default, {});
|
|
54
|
+
expect(await queryResult).toStrictEqual("queryResult");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -22,6 +22,7 @@ type UseAuth = () => {
|
|
|
22
22
|
// We don't use these properties but they should trigger a new token fetch.
|
|
23
23
|
orgId: string | undefined | null;
|
|
24
24
|
orgRole: string | undefined | null;
|
|
25
|
+
sessionId: string | undefined | null;
|
|
25
26
|
sessionClaims: Record<string, unknown> | undefined | null;
|
|
26
27
|
};
|
|
27
28
|
|
|
@@ -66,6 +67,7 @@ function useUseAuthFromClerk(useAuth: UseAuth) {
|
|
|
66
67
|
getToken,
|
|
67
68
|
orgId,
|
|
68
69
|
orgRole,
|
|
70
|
+
sessionId,
|
|
69
71
|
sessionClaims,
|
|
70
72
|
} = useAuth();
|
|
71
73
|
const fetchAccessToken = useCallback(
|
|
@@ -91,7 +93,7 @@ function useUseAuthFromClerk(useAuth: UseAuth) {
|
|
|
91
93
|
// Anything else from the JWT Clerk wants to be reactive goes here too.
|
|
92
94
|
// Clerk's Expo useAuth hook is not memoized so we don't include getToken.
|
|
93
95
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
|
-
[orgId, orgRole],
|
|
96
|
+
[orgId, orgRole, sessionId],
|
|
95
97
|
);
|
|
96
98
|
return useMemo(
|
|
97
99
|
() => ({
|
package/src/server/database.ts
CHANGED
|
@@ -140,8 +140,9 @@ export interface BaseTableReader<
|
|
|
140
140
|
* @see https://docs.convex.dev/database/reading-data
|
|
141
141
|
* @public
|
|
142
142
|
*/
|
|
143
|
-
export interface GenericDatabaseReader<
|
|
144
|
-
extends
|
|
143
|
+
export interface GenericDatabaseReader<
|
|
144
|
+
DataModel extends GenericDataModel,
|
|
145
|
+
> extends BaseDatabaseReader<DataModel> {
|
|
145
146
|
/**
|
|
146
147
|
* An interface to read from the system tables within Convex query functions.
|
|
147
148
|
*
|
|
@@ -216,8 +217,9 @@ export interface GenericDatabaseReaderWithTable<
|
|
|
216
217
|
* @see https://docs.convex.dev/database/writing-data
|
|
217
218
|
* @public
|
|
218
219
|
*/
|
|
219
|
-
export interface GenericDatabaseWriter<
|
|
220
|
-
extends
|
|
220
|
+
export interface GenericDatabaseWriter<
|
|
221
|
+
DataModel extends GenericDataModel,
|
|
222
|
+
> extends GenericDatabaseReader<DataModel> {
|
|
221
223
|
/**
|
|
222
224
|
* Insert a new document into a table.
|
|
223
225
|
*
|
|
@@ -54,9 +54,9 @@ async function getDeploymentMetadata(): Promise<DeploymentMetadata> {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async function getRequestMetadata(): Promise<RequestMetadata> {
|
|
57
|
-
const { ip, userAgent, requestId, scheduledFunctionId } =
|
|
57
|
+
const { ip, userAgent, requestId, scheduledFunctionId, authToken } =
|
|
58
58
|
await performAsyncSyscall("1.0/getRequestMetadata", {});
|
|
59
|
-
return { ip, userAgent, requestId, scheduledFunctionId };
|
|
59
|
+
return { ip, userAgent, requestId, scheduledFunctionId, authToken };
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export function setupQueryMeta(
|
|
@@ -41,9 +41,7 @@ type SerializedQuery = {
|
|
|
41
41
|
operators: Array<QueryOperator>;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
export class QueryInitializerImpl
|
|
45
|
-
implements QueryInitializer<GenericTableInfo>
|
|
46
|
-
{
|
|
44
|
+
export class QueryInitializerImpl implements QueryInitializer<GenericTableInfo> {
|
|
47
45
|
private tableName: string;
|
|
48
46
|
|
|
49
47
|
constructor(tableName: string) {
|
package/src/server/meta.ts
CHANGED
|
@@ -114,6 +114,14 @@ export type RequestMetadata = {
|
|
|
114
114
|
* `null` when the function was not scheduled.
|
|
115
115
|
*/
|
|
116
116
|
scheduledFunctionId: string | null;
|
|
117
|
+
/**
|
|
118
|
+
* The raw auth token (a JWT) the request was authenticated with, or `null`
|
|
119
|
+
* when the request was unauthenticated or authenticated with an admin key.
|
|
120
|
+
*
|
|
121
|
+
* This is the same token that `ctx.auth.getUserIdentity()` derives its
|
|
122
|
+
* attributes from.
|
|
123
|
+
*/
|
|
124
|
+
authToken: string | null;
|
|
117
125
|
};
|
|
118
126
|
|
|
119
127
|
/**
|
package/src/server/query.ts
CHANGED
|
@@ -26,8 +26,9 @@ import { SearchFilter, SearchFilterBuilder } from "./search_filter_builder.js";
|
|
|
26
26
|
*
|
|
27
27
|
* @public
|
|
28
28
|
*/
|
|
29
|
-
export interface QueryInitializer<
|
|
30
|
-
extends
|
|
29
|
+
export interface QueryInitializer<
|
|
30
|
+
TableInfo extends GenericTableInfo,
|
|
31
|
+
> extends Query<TableInfo> {
|
|
31
32
|
/**
|
|
32
33
|
* Query by reading all of the values out of this table.
|
|
33
34
|
*
|
|
@@ -163,8 +164,9 @@ export interface QueryInitializer<TableInfo extends GenericTableInfo>
|
|
|
163
164
|
*
|
|
164
165
|
* @public
|
|
165
166
|
*/
|
|
166
|
-
export interface Query<
|
|
167
|
-
extends
|
|
167
|
+
export interface Query<
|
|
168
|
+
TableInfo extends GenericTableInfo,
|
|
169
|
+
> extends OrderedQuery<TableInfo> {
|
|
168
170
|
/**
|
|
169
171
|
* Define the order of the query output.
|
|
170
172
|
*
|
|
@@ -179,8 +181,9 @@ export interface Query<TableInfo extends GenericTableInfo>
|
|
|
179
181
|
*
|
|
180
182
|
* @public
|
|
181
183
|
*/
|
|
182
|
-
export interface OrderedQuery<
|
|
183
|
-
extends
|
|
184
|
+
export interface OrderedQuery<
|
|
185
|
+
TableInfo extends GenericTableInfo,
|
|
186
|
+
> extends AsyncIterable<DocumentByInfo<TableInfo>> {
|
|
184
187
|
/**
|
|
185
188
|
* Filter the query output, returning only the values for which `predicate` evaluates to true.
|
|
186
189
|
*
|