rwsdk 1.0.0-alpha.3 → 1.0.0-alpha.5

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.
Files changed (72) hide show
  1. package/dist/lib/getShortName.mjs +6 -1
  2. package/dist/lib/getShortName.test.mjs +25 -0
  3. package/dist/lib/hasPkgScript.d.mts +4 -1
  4. package/dist/lib/hasPkgScript.mjs +9 -6
  5. package/dist/lib/hasPkgScript.test.d.mts +1 -0
  6. package/dist/lib/hasPkgScript.test.mjs +33 -0
  7. package/dist/lib/jsonUtils.mjs +3 -0
  8. package/dist/lib/jsonUtils.test.d.mts +1 -0
  9. package/dist/lib/jsonUtils.test.mjs +90 -0
  10. package/dist/lib/normalizeModulePath.d.mts +5 -0
  11. package/dist/lib/normalizeModulePath.mjs +1 -1
  12. package/dist/lib/normalizeModulePath.test.d.mts +1 -0
  13. package/dist/lib/{normalizeModulePath.test.js → normalizeModulePath.test.mjs} +20 -1
  14. package/dist/runtime/lib/memoizeOnId.test.d.ts +1 -0
  15. package/dist/runtime/lib/memoizeOnId.test.js +49 -0
  16. package/dist/runtime/lib/realtime/protocol.test.d.ts +1 -0
  17. package/dist/runtime/lib/realtime/protocol.test.js +107 -0
  18. package/dist/runtime/lib/realtime/shared.test.d.ts +1 -0
  19. package/dist/runtime/lib/realtime/shared.test.js +18 -0
  20. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.d.ts +1 -0
  21. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.js +66 -0
  22. package/dist/runtime/lib/router.test.js +1 -1
  23. package/dist/runtime/lib/turnstile/verifyTurnstileToken.d.ts +2 -1
  24. package/dist/runtime/lib/turnstile/verifyTurnstileToken.js +6 -6
  25. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.d.ts +1 -0
  26. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.js +49 -0
  27. package/dist/vite/checkIsUsingPrisma.d.mts +4 -0
  28. package/dist/vite/checkIsUsingPrisma.mjs +2 -2
  29. package/dist/vite/checkIsUsingPrisma.test.d.mts +1 -0
  30. package/dist/vite/checkIsUsingPrisma.test.mjs +30 -0
  31. package/dist/vite/configPlugin.mjs +22 -0
  32. package/dist/vite/createDirectiveLookupPlugin.d.mts +9 -0
  33. package/dist/vite/createDirectiveLookupPlugin.mjs +33 -29
  34. package/dist/vite/createDirectiveLookupPlugin.test.d.mts +1 -0
  35. package/dist/vite/createDirectiveLookupPlugin.test.mjs +40 -0
  36. package/dist/vite/directiveModulesDevPlugin.d.mts +2 -0
  37. package/dist/vite/directiveModulesDevPlugin.mjs +3 -3
  38. package/dist/vite/directiveModulesDevPlugin.test.d.mts +1 -0
  39. package/dist/vite/directiveModulesDevPlugin.test.mjs +59 -0
  40. package/dist/vite/directivesPlugin.d.mts +1 -0
  41. package/dist/vite/directivesPlugin.mjs +1 -1
  42. package/dist/vite/directivesPlugin.test.d.mts +1 -0
  43. package/dist/vite/directivesPlugin.test.mjs +24 -0
  44. package/dist/vite/ensureAliasArray.test.d.mts +1 -0
  45. package/dist/vite/ensureAliasArray.test.mjs +71 -0
  46. package/dist/vite/findSpecifiers.mjs +2 -1
  47. package/dist/vite/findSpecifiers.test.d.mts +1 -0
  48. package/dist/vite/findSpecifiers.test.mjs +202 -0
  49. package/dist/vite/findSsrSpecifiers.test.d.mts +1 -0
  50. package/dist/vite/findSsrSpecifiers.test.mjs +99 -0
  51. package/dist/vite/hasDirective.test.d.mts +1 -0
  52. package/dist/vite/hasDirective.test.mjs +109 -0
  53. package/dist/vite/isJsFile.test.d.mts +1 -0
  54. package/dist/vite/isJsFile.test.mjs +38 -0
  55. package/dist/vite/linkerPlugin.d.mts +8 -0
  56. package/dist/vite/linkerPlugin.mjs +30 -22
  57. package/dist/vite/linkerPlugin.test.d.mts +1 -0
  58. package/dist/vite/linkerPlugin.test.mjs +41 -0
  59. package/dist/vite/miniflareHMRPlugin.d.mts +5 -0
  60. package/dist/vite/miniflareHMRPlugin.mjs +2 -2
  61. package/dist/vite/miniflareHMRPlugin.test.d.mts +1 -0
  62. package/dist/vite/miniflareHMRPlugin.test.mjs +42 -0
  63. package/dist/vite/redwoodPlugin.d.mts +7 -0
  64. package/dist/vite/redwoodPlugin.mjs +7 -3
  65. package/dist/vite/redwoodPlugin.test.d.mts +1 -0
  66. package/dist/vite/redwoodPlugin.test.mjs +34 -0
  67. package/dist/vite/runDirectivesScan.d.mts +19 -0
  68. package/dist/vite/runDirectivesScan.mjs +52 -44
  69. package/dist/vite/runDirectivesScan.test.d.mts +1 -0
  70. package/dist/vite/runDirectivesScan.test.mjs +73 -0
  71. package/package.json +1 -1
  72. /package/dist/lib/{normalizeModulePath.test.d.ts → getShortName.test.d.mts} +0 -0
@@ -1,2 +1,7 @@
1
1
  import { relative } from "node:path";
2
- export const getShortName = (file, root) => file.startsWith(root) ? relative(root, file) : file;
2
+ import path from "node:path";
3
+ export const getShortName = (file, root) => file === root
4
+ ? ""
5
+ : file.startsWith(root + path.sep)
6
+ ? relative(root, file)
7
+ : file;
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { getShortName } from "./getShortName.mjs";
3
+ import path from "node:path";
4
+ describe("getShortName", () => {
5
+ it("should return the relative path if the file is inside the root", () => {
6
+ const root = path.join("/Users", "test", "project");
7
+ const file = path.join(root, "src", "index.ts");
8
+ expect(getShortName(file, root)).toBe(path.join("src", "index.ts"));
9
+ });
10
+ it("should return the original path if the file is outside the root", () => {
11
+ const root = path.join("/Users", "test", "project");
12
+ const file = path.join("/Users", "test", "another", "project", "src", "index.ts");
13
+ expect(getShortName(file, root)).toBe(file);
14
+ });
15
+ it("should return an empty string if the paths are identical", () => {
16
+ const root = path.join("/Users", "test", "project");
17
+ const file = path.join("/Users", "test", "project");
18
+ expect(getShortName(file, root)).toBe("");
19
+ });
20
+ it("should handle paths that are substrings of each other correctly", () => {
21
+ const root = path.join("/Users", "test", "project");
22
+ const file = path.join("/Users", "test", "project-longer", "src", "index.ts");
23
+ expect(getShortName(file, root)).toBe(file);
24
+ });
25
+ });
@@ -1 +1,4 @@
1
- export declare const hasPkgScript: (projectRootDir: string, script: string) => Promise<any>;
1
+ import { readFile as fsReadFile } from "fs/promises";
2
+ export declare let _pkgCache: Record<string, any> | undefined;
3
+ export declare const hasPkgScript: (projectRootDir: string, script: string, readFile?: typeof fsReadFile) => Promise<any>;
4
+ export declare const _resetPkgCache: () => void;
@@ -1,9 +1,12 @@
1
- import { readFile } from "fs/promises";
1
+ import { readFile as fsReadFile } from "fs/promises";
2
2
  import { resolve } from "path";
3
- let pkg;
4
- export const hasPkgScript = async (projectRootDir, script) => {
5
- if (!pkg) {
6
- pkg = JSON.parse(await readFile(resolve(projectRootDir, "package.json"), "utf-8"));
3
+ export let _pkgCache;
4
+ export const hasPkgScript = async (projectRootDir, script, readFile = fsReadFile) => {
5
+ if (!_pkgCache) {
6
+ _pkgCache = JSON.parse((await readFile(resolve(projectRootDir, "package.json"))).toString());
7
7
  }
8
- return pkg.scripts?.[script];
8
+ return _pkgCache?.scripts?.[script];
9
+ };
10
+ export const _resetPkgCache = () => {
11
+ _pkgCache = undefined;
9
12
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { hasPkgScript, _resetPkgCache } from "./hasPkgScript.mjs";
3
+ // Manually reset the cache before each test
4
+ beforeEach(() => {
5
+ _resetPkgCache();
6
+ });
7
+ describe("hasPkgScript", () => {
8
+ it("should return the script if it exists", async () => {
9
+ const fakeReadFile = async () => JSON.stringify({ scripts: { "dev:init": "command" } });
10
+ const result = await hasPkgScript("/test", "dev:init", fakeReadFile);
11
+ expect(result).toBe("command");
12
+ });
13
+ it("should return undefined if the script does not exist", async () => {
14
+ const fakeReadFile = async () => JSON.stringify({ scripts: { test: "command" } });
15
+ const result = await hasPkgScript("/test", "dev:init", fakeReadFile);
16
+ expect(result).toBeUndefined();
17
+ });
18
+ it("should return undefined if scripts block does not exist", async () => {
19
+ const fakeReadFile = async () => JSON.stringify({});
20
+ const result = await hasPkgScript("/test", "dev:init", fakeReadFile);
21
+ expect(result).toBeUndefined();
22
+ });
23
+ it("should cache the package.json read", async () => {
24
+ let readCount = 0;
25
+ const fakeReadFile = async () => {
26
+ readCount++;
27
+ return JSON.stringify({ scripts: { "dev:init": "command" } });
28
+ };
29
+ await hasPkgScript("/test", "dev:init", fakeReadFile);
30
+ await hasPkgScript("/test", "dev:init", fakeReadFile);
31
+ expect(readCount).toBe(1);
32
+ });
33
+ });
@@ -158,6 +158,9 @@ export function parseJson(input, defaultValue, findUuid = false) {
158
158
  }
159
159
  }
160
160
  }
161
+ if (lastJson !== null) {
162
+ return lastJson;
163
+ }
161
164
  return defaultValue;
162
165
  }
163
166
  catch (error) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { extractLastJson, extractAllJson, parseJson } from "./jsonUtils.mjs";
3
+ describe("jsonUtils", () => {
4
+ describe("extractLastJson", () => {
5
+ it("should extract the last JSON object from a string", () => {
6
+ const output = 'some text {"a":1} some other text {"b":2, "c": {"d": 3}} end';
7
+ expect(extractLastJson(output)).toEqual({ b: 2, c: { d: 3 } });
8
+ });
9
+ it("should extract the last JSON array from a string", () => {
10
+ const output = "start [1, 2] middle [3, 4, [5]] end";
11
+ expect(extractLastJson(output)).toEqual([3, 4, [5]]);
12
+ });
13
+ it("should return the object if the string is just JSON", () => {
14
+ const output = '{"a":1}';
15
+ expect(extractLastJson(output)).toEqual({ a: 1 });
16
+ });
17
+ it("should handle nested structures correctly", () => {
18
+ const output = '{"a":{"b":{"c":"d"}}}';
19
+ expect(extractLastJson(output)).toEqual({ a: { b: { c: "d" } } });
20
+ });
21
+ it("should return null if no valid JSON is found", () => {
22
+ const output = "this is just some text without json";
23
+ expect(extractLastJson(output)).toBeNull();
24
+ });
25
+ it("should return null for malformed JSON", () => {
26
+ const output = '{"a":1, "b":}';
27
+ expect(extractLastJson(output)).toBeNull();
28
+ });
29
+ it("should handle undefined and empty string input", () => {
30
+ expect(extractLastJson(undefined)).toBeNull();
31
+ expect(extractLastJson("")).toBeNull();
32
+ });
33
+ });
34
+ describe("extractAllJson", () => {
35
+ it("should extract all JSON objects from a string", () => {
36
+ const output = '{"a":1} some text {"b":2} and then {"c":3, "d": [4]}';
37
+ expect(extractAllJson(output)).toEqual([
38
+ { a: 1 },
39
+ { b: 2 },
40
+ { c: 3, d: [4] },
41
+ ]);
42
+ });
43
+ it("should extract all JSON arrays from a string", () => {
44
+ const output = "[1,2] then [3,4]";
45
+ expect(extractAllJson(output)).toEqual([
46
+ [1, 2],
47
+ [3, 4],
48
+ ]);
49
+ });
50
+ it("should handle a mix of objects and arrays", () => {
51
+ const output = '{"a":1} [2,3] {"b":4}';
52
+ expect(extractAllJson(output)).toEqual([{ a: 1 }, [2, 3], { b: 4 }]);
53
+ });
54
+ it("should return an empty array if no JSON is found", () => {
55
+ const output = "no json here";
56
+ expect(extractAllJson(output)).toEqual([]);
57
+ });
58
+ it("should ignore malformed JSON", () => {
59
+ const output = '{"a":1} {"b":2,} [3,4]';
60
+ expect(extractAllJson(output)).toEqual([{ a: 1 }, [3, 4]]);
61
+ });
62
+ });
63
+ describe("parseJson", () => {
64
+ it("should parse the last JSON object by default", () => {
65
+ const output = '{"a":1} {"b":2}';
66
+ expect(parseJson(output, {})).toEqual({ b: 2 });
67
+ });
68
+ it("should return the default value if no JSON is found", () => {
69
+ const output = "no json";
70
+ expect(parseJson(output, { default: true })).toEqual({ default: true });
71
+ });
72
+ it("should find an object with a uuid property when requested", () => {
73
+ const output = '{"a":1} {"uuid":"123-abc", "data": "yes"} {"c":3}';
74
+ expect(parseJson(output, {}, true)).toEqual({
75
+ uuid: "123-abc",
76
+ data: "yes",
77
+ });
78
+ });
79
+ it("should return the last object if findUuid is true but no object with uuid is found", () => {
80
+ const output = '{"a":1} {"b":2}';
81
+ expect(parseJson(output, {}, true)).toEqual({ b: 2 });
82
+ });
83
+ it("should return the default value if findUuid is true and no JSON is found", () => {
84
+ const output = "no json";
85
+ expect(parseJson(output, { default: true }, true)).toEqual({
86
+ default: true,
87
+ });
88
+ });
89
+ });
90
+ });
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Find the number of common ancestor segments between two absolute paths.
3
+ * Returns the count of shared directory segments from the root.
4
+ */
5
+ export declare function findCommonAncestorDepth(path1: string, path2: string): number;
1
6
  /**
2
7
  * Normalize a module path to a consistent form.
3
8
  * Returns slash-prefixed paths for files within project root,
@@ -4,7 +4,7 @@ import { normalizePath as normalizePathSeparators } from "vite";
4
4
  * Find the number of common ancestor segments between two absolute paths.
5
5
  * Returns the count of shared directory segments from the root.
6
6
  */
7
- function findCommonAncestorDepth(path1, path2) {
7
+ export function findCommonAncestorDepth(path1, path2) {
8
8
  const segments1 = path1.split("/").filter(Boolean);
9
9
  const segments2 = path2.split("/").filter(Boolean);
10
10
  let commonLength = 0;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,24 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { normalizeModulePath } from "./normalizeModulePath.mjs";
2
+ import { normalizeModulePath, findCommonAncestorDepth, } from "./normalizeModulePath.mjs";
3
+ describe("findCommonAncestorDepth", () => {
4
+ it("should return the correct depth for common paths", () => {
5
+ expect(findCommonAncestorDepth("/a/b/c", "/a/b/d")).toBe(2);
6
+ });
7
+ it("should return 0 for completely different paths", () => {
8
+ expect(findCommonAncestorDepth("/a/b/c", "/d/e/f")).toBe(0);
9
+ });
10
+ it("should handle identical paths", () => {
11
+ expect(findCommonAncestorDepth("/a/b/c", "/a/b/c")).toBe(3);
12
+ });
13
+ it("should handle paths of different lengths", () => {
14
+ expect(findCommonAncestorDepth("/a/b/c", "/a/b/c/d/e")).toBe(3);
15
+ });
16
+ it("should handle the root path", () => {
17
+ expect(findCommonAncestorDepth("/", "/a/b")).toBe(0);
18
+ expect(findCommonAncestorDepth("/a", "/")).toBe(0);
19
+ expect(findCommonAncestorDepth("/", "/")).toBe(0);
20
+ });
21
+ });
3
22
  describe("normalizeModulePath", () => {
4
23
  describe("1. Project-local paths", () => {
5
24
  it("Relative file", () => {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { memoizeOnId } from "./memoizeOnId";
3
+ describe("memoizeOnId", () => {
4
+ it("should call the function only once for the same id", () => {
5
+ const fn = vi.fn((id) => `result-${id}`);
6
+ const memoizedFn = memoizeOnId(fn);
7
+ const result1 = memoizedFn("a");
8
+ const result2 = memoizedFn("a");
9
+ expect(result1).toBe("result-a");
10
+ expect(result2).toBe("result-a");
11
+ expect(fn).toHaveBeenCalledTimes(1);
12
+ expect(fn).toHaveBeenCalledWith("a");
13
+ });
14
+ it("should call the function again for a different id", () => {
15
+ const fn = vi.fn((id) => `result-${id}`);
16
+ const memoizedFn = memoizeOnId(fn);
17
+ const result1 = memoizedFn("a");
18
+ const result2 = memoizedFn("b");
19
+ expect(result1).toBe("result-a");
20
+ expect(result2).toBe("result-b");
21
+ expect(fn).toHaveBeenCalledTimes(2);
22
+ expect(fn).toHaveBeenCalledWith("a");
23
+ expect(fn).toHaveBeenCalledWith("b");
24
+ });
25
+ it("should return the correct cached value for multiple calls", () => {
26
+ const fn = vi.fn((id) => ({ id }));
27
+ const memoizedFn = memoizeOnId(fn);
28
+ const resultA1 = memoizedFn("a");
29
+ const resultB1 = memoizedFn("b");
30
+ const resultA2 = memoizedFn("a");
31
+ expect(resultA1).toEqual({ id: "a" });
32
+ expect(resultB1).toEqual({ id: "b" });
33
+ expect(resultA2).toBe(resultA1); // Should be the same object reference
34
+ expect(fn).toHaveBeenCalledTimes(2);
35
+ });
36
+ it("should handle object properties that exist on Object.prototype", () => {
37
+ const fn = vi.fn((id) => `result-${id}`);
38
+ const memoizedFn = memoizeOnId(fn);
39
+ const result1 = memoizedFn("constructor");
40
+ const result2 = memoizedFn("toString");
41
+ const result3 = memoizedFn("constructor");
42
+ expect(result1).toBe("result-constructor");
43
+ expect(result2).toBe("result-toString");
44
+ expect(result3).toBe("result-constructor");
45
+ expect(fn).toHaveBeenCalledTimes(2);
46
+ expect(fn).toHaveBeenCalledWith("constructor");
47
+ expect(fn).toHaveBeenCalledWith("toString");
48
+ });
49
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,107 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { packMessage, unpackMessage } from "./protocol";
3
+ import { MESSAGE_TYPE } from "./shared";
4
+ describe("Realtime Protocol pack/unpack", () => {
5
+ const textEncoder = new TextEncoder();
6
+ // Helper to generate a UUID string of the correct length
7
+ const generateId = () => "a".repeat(36);
8
+ const testMessages = [
9
+ {
10
+ type: MESSAGE_TYPE.ACTION_REQUEST,
11
+ id: "my-action",
12
+ args: { foo: "bar" },
13
+ requestId: generateId(),
14
+ clientUrl: "http://localhost:3000/",
15
+ },
16
+ {
17
+ type: MESSAGE_TYPE.ACTION_START,
18
+ id: generateId(),
19
+ status: 200,
20
+ },
21
+ {
22
+ type: MESSAGE_TYPE.ACTION_CHUNK,
23
+ id: generateId(),
24
+ payload: textEncoder.encode("chunk data"),
25
+ },
26
+ {
27
+ type: MESSAGE_TYPE.ACTION_END,
28
+ id: generateId(),
29
+ },
30
+ {
31
+ type: MESSAGE_TYPE.ACTION_ERROR,
32
+ id: generateId(),
33
+ error: "Something went wrong",
34
+ },
35
+ {
36
+ type: MESSAGE_TYPE.RSC_START,
37
+ id: generateId(),
38
+ status: 200,
39
+ },
40
+ {
41
+ type: MESSAGE_TYPE.RSC_CHUNK,
42
+ id: generateId(),
43
+ payload: textEncoder.encode("rsc chunk"),
44
+ },
45
+ {
46
+ type: MESSAGE_TYPE.RSC_END,
47
+ id: generateId(),
48
+ },
49
+ ];
50
+ testMessages.forEach((message) => {
51
+ it(`should correctly pack and unpack a ${Object.keys(MESSAGE_TYPE).find((key) => MESSAGE_TYPE[key] === message.type) || "UNKNOWN"} message`, () => {
52
+ const packed = packMessage(message);
53
+ const unpacked = unpackMessage(packed);
54
+ expect(unpacked).toEqual(message);
55
+ });
56
+ });
57
+ describe("Error Handling", () => {
58
+ it("should throw an error for an unknown message type on pack", () => {
59
+ const invalidMessage = { type: 999 };
60
+ expect(() => packMessage(invalidMessage)).toThrow("Unknown message type for packing");
61
+ });
62
+ it("should throw an error for an unknown message type on unpack", () => {
63
+ const invalidData = new Uint8Array([99, 1, 2, 3]);
64
+ expect(() => unpackMessage(invalidData)).toThrow("Unknown message type for unpacking: 99");
65
+ });
66
+ it("should throw an error for an empty message on unpack", () => {
67
+ const emptyData = new Uint8Array([]);
68
+ expect(() => unpackMessage(emptyData)).toThrow("Cannot unpack empty message");
69
+ });
70
+ const invalidLengthTests = [
71
+ { type: MESSAGE_TYPE.ACTION_START, name: "START" },
72
+ { type: MESSAGE_TYPE.RSC_START, name: "START" },
73
+ { type: MESSAGE_TYPE.ACTION_CHUNK, name: "CHUNK" },
74
+ { type: MESSAGE_TYPE.RSC_CHUNK, name: "CHUNK" },
75
+ { type: MESSAGE_TYPE.ACTION_END, name: "END" },
76
+ { type: MESSAGE_TYPE.RSC_END, name: "END" },
77
+ { type: MESSAGE_TYPE.ACTION_ERROR, name: "ERROR" },
78
+ ];
79
+ invalidLengthTests.forEach(({ type, name }) => {
80
+ it(`should throw for invalid ${name} message length on unpack`, () => {
81
+ const invalidData = new Uint8Array([type, 1, 2, 3]); // Too short
82
+ expect(() => unpackMessage(invalidData)).toThrow(`Invalid ${name} message length`);
83
+ });
84
+ });
85
+ const invalidIdTests = [
86
+ { type: MESSAGE_TYPE.ACTION_START, name: "START" },
87
+ { type: MESSAGE_TYPE.RSC_START, name: "START" },
88
+ { type: MESSAGE_TYPE.ACTION_CHUNK, name: "CHUNK" },
89
+ { type: MESSAGE_TYPE.RSC_CHUNK, name: "CHUNK" },
90
+ { type: MESSAGE_TYPE.ACTION_END, name: "END" },
91
+ { type: MESSAGE_TYPE.RSC_END, name: "END" },
92
+ { type: MESSAGE_TYPE.ACTION_ERROR, name: "ERROR" },
93
+ ];
94
+ invalidIdTests.forEach(({ type, name }) => {
95
+ it(`should throw for invalid ID length on ${name} message pack`, () => {
96
+ const message = {
97
+ type,
98
+ id: "short-id",
99
+ status: 200, // For START types
100
+ payload: new Uint8Array(), // For CHUNK types
101
+ error: "", // For ERROR type
102
+ };
103
+ expect(() => packMessage(message)).toThrow(`Invalid message ID length for ${name} message`);
104
+ });
105
+ });
106
+ });
107
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { MESSAGE_TYPE } from "./shared";
3
+ describe("Realtime Shared Constants", () => {
4
+ it("MESSAGE_TYPE should match snapshot", () => {
5
+ expect(MESSAGE_TYPE).toMatchInlineSnapshot(`
6
+ {
7
+ "ACTION_CHUNK": 5,
8
+ "ACTION_END": 6,
9
+ "ACTION_ERROR": 7,
10
+ "ACTION_REQUEST": 3,
11
+ "ACTION_START": 4,
12
+ "RSC_CHUNK": 1,
13
+ "RSC_END": 2,
14
+ "RSC_START": 0,
15
+ }
16
+ `);
17
+ });
18
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { validateUpgradeRequest } from "./validateUpgradeRequest";
3
+ describe("validateUpgradeRequest", () => {
4
+ it("should return valid for a correct WebSocket upgrade request", () => {
5
+ const request = new Request("http://localhost:8787/ws", {
6
+ headers: {
7
+ Upgrade: "websocket",
8
+ Origin: "http://localhost:8787",
9
+ },
10
+ });
11
+ const result = validateUpgradeRequest(request);
12
+ expect(result.valid).toBe(true);
13
+ });
14
+ it("should return invalid if Upgrade header is not 'websocket'", async () => {
15
+ const request = new Request("http://localhost:8787/ws", {
16
+ headers: {
17
+ Upgrade: "not-websocket",
18
+ Origin: "http://localhost:8787",
19
+ },
20
+ });
21
+ const result = validateUpgradeRequest(request);
22
+ expect(result.valid).toBe(false);
23
+ if (!result.valid) {
24
+ expect(result.response?.status).toBe(400);
25
+ await expect(result.response?.text()).resolves.toBe("Expected WebSocket");
26
+ }
27
+ });
28
+ it("should return invalid if Upgrade header is missing", () => {
29
+ const request = new Request("http://localhost:8787/ws", {
30
+ headers: {
31
+ Origin: "http://localhost:8787",
32
+ },
33
+ });
34
+ const result = validateUpgradeRequest(request);
35
+ expect(result.valid).toBe(false);
36
+ if (!result.valid) {
37
+ expect(result.response?.status).toBe(400);
38
+ }
39
+ });
40
+ it("should return invalid if Origin header is missing", async () => {
41
+ const request = new Request("http://localhost:8787/ws", {
42
+ headers: {
43
+ Upgrade: "websocket",
44
+ },
45
+ });
46
+ const result = validateUpgradeRequest(request);
47
+ expect(result.valid).toBe(false);
48
+ if (!result.valid) {
49
+ expect(result.response?.status).toBe(403);
50
+ await expect(result.response?.text()).resolves.toBe("Invalid origin");
51
+ }
52
+ });
53
+ it("should return invalid if Origin does not match request URL", () => {
54
+ const request = new Request("http://localhost:8787/ws", {
55
+ headers: {
56
+ Upgrade: "websocket",
57
+ Origin: "http://another-domain.com",
58
+ },
59
+ });
60
+ const result = validateUpgradeRequest(request);
61
+ expect(result.valid).toBe(false);
62
+ if (!result.valid) {
63
+ expect(result.response?.status).toBe(403);
64
+ }
65
+ });
66
+ });
@@ -22,7 +22,7 @@ describe("matchPath", () => {
22
22
  expect(matchPath("/files/*/", "/files/document.pdf/")).toEqual({
23
23
  $0: "document.pdf",
24
24
  });
25
- expect(matchPath("/data/*\/content/", "/data/archive/content/")).toEqual({
25
+ expect(matchPath("/data/*/content/", "/data/archive/content/")).toEqual({
26
26
  $0: "archive",
27
27
  });
28
28
  expect(matchPath("/assets/*/*/", "/assets/images/pic.png/")).toEqual({
@@ -1,4 +1,5 @@
1
- export declare const verifyTurnstileToken: ({ token, secretKey, }: {
1
+ export declare const verifyTurnstileToken: ({ token, secretKey, fetchFn, }: {
2
2
  token: string;
3
3
  secretKey: string;
4
+ fetchFn?: typeof fetch;
4
5
  }) => Promise<boolean>;
@@ -1,10 +1,10 @@
1
- export const verifyTurnstileToken = async ({ token, secretKey, }) => {
2
- const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
3
- method: "POST",
4
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5
- body: new URLSearchParams({ secret: secretKey, response: token }),
6
- });
1
+ export const verifyTurnstileToken = async ({ token, secretKey, fetchFn = fetch, }) => {
7
2
  try {
3
+ const response = await fetchFn("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
4
+ method: "POST",
5
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6
+ body: new URLSearchParams({ secret: secretKey, response: token }),
7
+ });
8
8
  const data = (await response.json());
9
9
  return data?.success === true;
10
10
  }
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { verifyTurnstileToken } from "./verifyTurnstileToken";
3
+ describe("verifyTurnstileToken", () => {
4
+ it("should return true for a successful verification", async () => {
5
+ const mockFetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({ success: true }), {
6
+ status: 200,
7
+ headers: { "Content-Type": "application/json" },
8
+ }));
9
+ const result = await verifyTurnstileToken({
10
+ token: "valid-token",
11
+ secretKey: "secret",
12
+ fetchFn: mockFetch,
13
+ });
14
+ expect(result).toBe(true);
15
+ expect(mockFetch).toHaveBeenCalledWith("https://challenges.cloudflare.com/turnstile/v0/siteverify", expect.any(Object));
16
+ });
17
+ it("should return false for a failed verification", async () => {
18
+ const mockFetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({ success: false }), {
19
+ status: 200,
20
+ headers: { "Content-Type": "application/json" },
21
+ }));
22
+ const result = await verifyTurnstileToken({
23
+ token: "invalid-token",
24
+ secretKey: "secret",
25
+ fetchFn: mockFetch,
26
+ });
27
+ expect(result).toBe(false);
28
+ });
29
+ it("should return false if the fetch call fails", async () => {
30
+ const mockFetch = vi.fn().mockRejectedValue(new Error("Network error"));
31
+ const result = await verifyTurnstileToken({
32
+ token: "any-token",
33
+ secretKey: "secret",
34
+ fetchFn: mockFetch,
35
+ });
36
+ expect(result).toBe(false);
37
+ });
38
+ it("should return false for a non-JSON response", async () => {
39
+ const mockFetch = vi
40
+ .fn()
41
+ .mockResolvedValue(new Response("not json", { status: 200 }));
42
+ const result = await verifyTurnstileToken({
43
+ token: "any-token",
44
+ secretKey: "secret",
45
+ fetchFn: mockFetch,
46
+ });
47
+ expect(result).toBe(false);
48
+ });
49
+ });
@@ -1,6 +1,10 @@
1
1
  export type PrismaCheckResult = {
2
2
  isUsingPrisma: boolean;
3
3
  };
4
+ export declare const isUsingPrisma: ({ projectRootDir, resolver, }: {
5
+ projectRootDir: string;
6
+ resolver?: (path: string, request: string) => string | false;
7
+ }) => boolean;
4
8
  export declare const checkPrismaStatus: ({ projectRootDir, }: {
5
9
  projectRootDir: string;
6
10
  }) => PrismaCheckResult;
@@ -1,7 +1,7 @@
1
1
  import enhancedResolve from "enhanced-resolve";
2
- const isUsingPrisma = ({ projectRootDir }) => {
2
+ export const isUsingPrisma = ({ projectRootDir, resolver = enhancedResolve.sync, }) => {
3
3
  try {
4
- const prismaClientPath = enhancedResolve.sync(projectRootDir, "@prisma/client");
4
+ const prismaClientPath = resolver(projectRootDir, "@prisma/client");
5
5
  if (!prismaClientPath) {
6
6
  return false;
7
7
  }
@@ -0,0 +1 @@
1
+ export {};