rwsdk 1.0.0-beta.4 → 1.0.0-beta.41

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 (139) hide show
  1. package/dist/lib/constants.d.mts +1 -0
  2. package/dist/lib/constants.mjs +7 -4
  3. package/dist/lib/e2e/browser.mjs +6 -2
  4. package/dist/lib/e2e/constants.d.mts +4 -0
  5. package/dist/lib/e2e/constants.mjs +49 -12
  6. package/dist/lib/e2e/dev.mjs +37 -49
  7. package/dist/lib/e2e/environment.d.mts +2 -0
  8. package/dist/lib/e2e/environment.mjs +201 -64
  9. package/dist/lib/e2e/index.d.mts +1 -0
  10. package/dist/lib/e2e/index.mjs +1 -0
  11. package/dist/lib/e2e/poll.d.mts +1 -1
  12. package/dist/lib/e2e/release.d.mts +1 -0
  13. package/dist/lib/e2e/release.mjs +16 -32
  14. package/dist/lib/e2e/tarball.mjs +2 -34
  15. package/dist/lib/e2e/testHarness.d.mts +34 -3
  16. package/dist/lib/e2e/testHarness.mjs +219 -90
  17. package/dist/lib/e2e/utils.d.mts +1 -0
  18. package/dist/lib/e2e/utils.mjs +15 -0
  19. package/dist/runtime/client/client.d.ts +35 -0
  20. package/dist/runtime/client/client.js +35 -0
  21. package/dist/runtime/client/navigation.d.ts +49 -0
  22. package/dist/runtime/client/navigation.js +80 -31
  23. package/dist/runtime/entries/clientSSR.d.ts +1 -0
  24. package/dist/runtime/entries/clientSSR.js +3 -0
  25. package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
  26. package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
  27. package/dist/runtime/entries/router.d.ts +1 -0
  28. package/dist/runtime/entries/routerClient.d.ts +1 -0
  29. package/dist/runtime/entries/routerClient.js +1 -0
  30. package/dist/runtime/entries/worker.d.ts +2 -0
  31. package/dist/runtime/entries/worker.js +2 -0
  32. package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
  33. package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
  34. package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
  35. package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
  36. package/dist/runtime/lib/db/createDb.d.ts +1 -2
  37. package/dist/runtime/lib/db/createDb.js +4 -0
  38. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
  39. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
  40. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
  41. package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
  42. package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
  43. package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
  44. package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
  45. package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
  46. package/dist/runtime/lib/links.d.ts +21 -7
  47. package/dist/runtime/lib/links.js +82 -24
  48. package/dist/runtime/lib/links.test.js +20 -0
  49. package/dist/runtime/lib/manifest.d.ts +1 -1
  50. package/dist/runtime/lib/manifest.js +7 -4
  51. package/dist/runtime/lib/realtime/client.js +8 -2
  52. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  53. package/dist/runtime/lib/router.d.ts +153 -36
  54. package/dist/runtime/lib/router.js +169 -20
  55. package/dist/runtime/lib/router.test.js +241 -0
  56. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
  57. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
  58. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
  59. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
  60. package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
  61. package/dist/runtime/lib/types.js +1 -0
  62. package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
  63. package/dist/runtime/render/renderToStream.d.ts +4 -2
  64. package/dist/runtime/render/renderToStream.js +53 -24
  65. package/dist/runtime/render/renderToString.d.ts +3 -6
  66. package/dist/runtime/requestInfo/types.d.ts +4 -1
  67. package/dist/runtime/requestInfo/utils.d.ts +9 -0
  68. package/dist/runtime/requestInfo/utils.js +44 -0
  69. package/dist/runtime/requestInfo/worker.d.ts +0 -1
  70. package/dist/runtime/requestInfo/worker.js +3 -10
  71. package/dist/runtime/script.d.ts +1 -3
  72. package/dist/runtime/script.js +1 -10
  73. package/dist/runtime/state.d.ts +3 -0
  74. package/dist/runtime/state.js +13 -0
  75. package/dist/runtime/worker.d.ts +3 -1
  76. package/dist/runtime/worker.js +32 -0
  77. package/dist/scripts/debug-sync.mjs +18 -20
  78. package/dist/scripts/worker-run.d.mts +1 -1
  79. package/dist/scripts/worker-run.mjs +59 -113
  80. package/dist/use-synced-state/SyncedStateServer.d.mts +21 -0
  81. package/dist/use-synced-state/SyncedStateServer.mjs +128 -0
  82. package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
  83. package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +109 -0
  84. package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
  85. package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
  86. package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
  87. package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
  88. package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
  89. package/dist/use-synced-state/__tests__/worker.test.mjs +69 -0
  90. package/dist/use-synced-state/client-core.d.ts +26 -0
  91. package/dist/use-synced-state/client-core.js +39 -0
  92. package/dist/use-synced-state/client.d.ts +3 -0
  93. package/dist/use-synced-state/client.js +4 -0
  94. package/dist/use-synced-state/constants.d.mts +1 -0
  95. package/dist/use-synced-state/constants.mjs +1 -0
  96. package/dist/use-synced-state/useSyncedState.d.ts +20 -0
  97. package/dist/use-synced-state/useSyncedState.js +58 -0
  98. package/dist/use-synced-state/worker.d.mts +13 -0
  99. package/dist/use-synced-state/worker.mjs +69 -0
  100. package/dist/vite/buildApp.mjs +34 -2
  101. package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
  102. package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
  103. package/dist/vite/configPlugin.mjs +9 -14
  104. package/dist/vite/constants.d.mts +1 -0
  105. package/dist/vite/constants.mjs +1 -0
  106. package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
  107. package/dist/vite/devServerTimingPlugin.mjs +4 -0
  108. package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
  109. package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
  110. package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
  111. package/dist/vite/directivesPlugin.mjs +4 -4
  112. package/dist/vite/envResolvers.d.mts +11 -0
  113. package/dist/vite/envResolvers.mjs +20 -0
  114. package/dist/vite/getViteEsbuild.mjs +2 -1
  115. package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
  116. package/dist/vite/hmrStabilityPlugin.mjs +73 -0
  117. package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
  118. package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
  119. package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
  120. package/dist/vite/linkerPlugin.d.mts +2 -1
  121. package/dist/vite/linkerPlugin.mjs +11 -3
  122. package/dist/vite/linkerPlugin.test.mjs +15 -0
  123. package/dist/vite/miniflareHMRPlugin.mjs +6 -38
  124. package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
  125. package/dist/vite/redwoodPlugin.mjs +8 -10
  126. package/dist/vite/runDirectivesScan.mjs +72 -18
  127. package/dist/vite/ssrBridgePlugin.mjs +132 -40
  128. package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
  129. package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
  130. package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
  131. package/dist/vite/staleDepRetryPlugin.mjs +74 -0
  132. package/dist/vite/statePlugin.d.mts +4 -0
  133. package/dist/vite/statePlugin.mjs +62 -0
  134. package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
  135. package/dist/vite/virtualPlugin.mjs +6 -7
  136. package/package.json +27 -10
  137. package/dist/vite/manifestPlugin.d.mts +0 -4
  138. package/dist/vite/manifestPlugin.mjs +0 -63
  139. /package/dist/runtime/lib/{rwContext.js → links.test.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { sql } from "kysely";
1
2
  (_it = "createTable") => {
2
3
  const migrations = {
3
4
  "001_init": {
@@ -21,8 +22,11 @@
21
22
  await db.schema
22
23
  .createTable("users")
23
24
  .addColumn("username", "text", (col) => col.notNull())
24
- .addColumn("age", "integer", (col) => col.defaultTo(18))
25
25
  .addColumn("active", "boolean", (col) => col.defaultTo(true))
26
+ .addColumn("anotherBoolean", "boolean", (col) => col.defaultTo(sql `true`))
27
+ .addColumn("email", "text", (col) => col)
28
+ .addColumn("favoriteColor", "text", (col) => col.unique())
29
+ .addColumn("name", "text", (col) => col.defaultTo("John Doe"))
26
30
  .execute(),
27
31
  ];
28
32
  },
@@ -30,4 +34,102 @@
30
34
  };
31
35
  (_test) => { };
32
36
  };
33
- export {};
37
+ (_it = "createTable column without callback is nullable") => {
38
+ const migrations = {
39
+ "001_init": {
40
+ async up(db) {
41
+ return [
42
+ await db.schema
43
+ .createTable("posts")
44
+ .addColumn("title", "text")
45
+ .addColumn("body", "text")
46
+ .execute(),
47
+ ];
48
+ },
49
+ },
50
+ };
51
+ (_test) => { };
52
+ };
53
+ (_it = "createTable with primaryKey is non-nullable") => {
54
+ const migrations = {
55
+ "001_init": {
56
+ async up(db) {
57
+ return [
58
+ await db.schema
59
+ .createTable("users")
60
+ .addColumn("id", "integer", (col) => col.primaryKey())
61
+ .addColumn("email", "text", (col) => col.notNull())
62
+ .execute(),
63
+ ];
64
+ },
65
+ },
66
+ };
67
+ (_test) => { };
68
+ };
69
+ (_it = "createTable with unique but no notNull is nullable") => {
70
+ const migrations = {
71
+ "001_init": {
72
+ async up(db) {
73
+ return [
74
+ await db.schema
75
+ .createTable("products")
76
+ .addColumn("sku", "text", (col) => col.unique())
77
+ .addColumn("name", "text", (col) => col)
78
+ .execute(),
79
+ ];
80
+ },
81
+ },
82
+ };
83
+ (_test) => { };
84
+ };
85
+ (_it = "defaultTo makes columns non-nullable in Database type") => {
86
+ const migrations = {
87
+ "001_init": {
88
+ async up(db) {
89
+ return [
90
+ await db.schema
91
+ .createTable("users")
92
+ .addColumn("status", "text", (col) => col.defaultTo("active"))
93
+ .addColumn("count", "integer", (col) => col.defaultTo(0))
94
+ .execute(),
95
+ ];
96
+ },
97
+ },
98
+ };
99
+ (_test) => { };
100
+ };
101
+ // --- Insert/Update Type Tests ---
102
+ (_it = "makes autoIncrement columns optional on insert") => {
103
+ const migrations = {
104
+ "001_init": {
105
+ async up(db) {
106
+ return [
107
+ await db.schema
108
+ .createTable("users")
109
+ .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
110
+ .addColumn("username", "text", (col) => col.notNull())
111
+ .execute(),
112
+ ];
113
+ },
114
+ },
115
+ };
116
+ const db = {};
117
+ db.insertInto("users").values({ username: "test" });
118
+ };
119
+ (_it = "makes defaultTo columns optional on insert") => {
120
+ const migrations = {
121
+ "001_init": {
122
+ async up(db) {
123
+ return [
124
+ await db.schema
125
+ .createTable("users")
126
+ .addColumn("username", "text", (col) => col.notNull())
127
+ .addColumn("status", "text", (col) => col.notNull().defaultTo("active"))
128
+ .execute(),
129
+ ];
130
+ },
131
+ },
132
+ };
133
+ const db = {};
134
+ db.insertInto("users").values({ username: "test" });
135
+ };
@@ -1,2 +1,3 @@
1
1
  export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
2
2
  export type Expect<T extends true> = T;
3
+ export type OmitInternals<T> = Omit<T, "__kyselySchema">;
@@ -1,9 +1,11 @@
1
1
  import { sql } from "kysely";
2
+ import { ColumnDescriptor } from "./builders/columnDefinition";
2
3
  type DataTypeExpression = string | typeof sql;
3
- export type AddColumnOp<K extends string, T extends DataTypeExpression> = {
4
+ export type AddColumnOp<K extends string, T extends DataTypeExpression, TDescriptor extends ColumnDescriptor> = {
4
5
  op: "addColumn";
5
6
  name: K;
6
7
  type: T;
8
+ descriptor: TDescriptor;
7
9
  };
8
10
  export type DropColumnOp<K extends string> = {
9
11
  op: "dropColumn";
@@ -14,10 +16,11 @@ export type RenameColumnOp<KFrom extends string, KTo extends string> = {
14
16
  from: KFrom;
15
17
  to: KTo;
16
18
  };
17
- export type ModifyColumnOp<K extends string, T extends DataTypeExpression> = {
19
+ export type ModifyColumnOp<K extends string, T extends DataTypeExpression, TDescriptor extends ColumnDescriptor> = {
18
20
  op: "modifyColumn";
19
21
  name: K;
20
22
  type: T;
23
+ descriptor: TDescriptor;
21
24
  };
22
25
  export type Alteration = {
23
26
  kind: "setDataType";
@@ -37,7 +40,7 @@ export type AlterColumnOp<K extends string, TAlteration extends Alteration> = {
37
40
  name: K;
38
41
  alteration: TAlteration;
39
42
  };
40
- export type AlterOperation = AddColumnOp<any, any> | DropColumnOp<any> | RenameColumnOp<any, any> | AlterColumnOp<any, any> | ModifyColumnOp<any, any>;
43
+ export type AlterOperation = AddColumnOp<any, any, any> | DropColumnOp<any> | RenameColumnOp<any, any> | AlterColumnOp<any, any> | ModifyColumnOp<any, any, any>;
41
44
  export type SqlToTsType<T extends string | typeof sql> = T extends "text" ? string : T extends "integer" ? number : T extends "blob" ? Uint8Array : T extends "real" ? number : T extends "boolean" ? boolean : T extends typeof sql ? any : never;
42
45
  export type Prettify<T> = {
43
46
  [K in keyof T]: T[K];
@@ -57,17 +60,64 @@ export type Cast<A, B> = A extends B ? A : B;
57
60
  /**
58
61
  * Applies a single alteration operation to a schema.
59
62
  */
60
- type ApplyOp<TSchema, THeadOp> = THeadOp extends AddColumnOp<infer K, infer T> ? Prettify<TSchema & {
61
- [P in K]: SqlToTsType<T>;
63
+ type ApplyOp<TSchema, THeadOp> = THeadOp extends AddColumnOp<infer K, any, infer TDescriptor> ? Prettify<TSchema & {
64
+ [P in K]: TDescriptor;
62
65
  }> : THeadOp extends DropColumnOp<infer K> ? Omit<TSchema, K> : THeadOp extends RenameColumnOp<infer KFrom, infer KTo> ? KFrom extends keyof TSchema ? Prettify<Omit<TSchema, KFrom> & {
63
66
  [P in KTo]: TSchema[KFrom];
64
- }> : TSchema : THeadOp extends AlterColumnOp<infer K, infer TAlt> ? TAlt extends {
67
+ }> : TSchema : THeadOp extends AlterColumnOp<infer K, infer TAlt> ? K extends keyof TSchema ? TAlt extends {
65
68
  kind: "setDataType";
66
69
  dataType: infer DT extends string;
67
70
  } ? Prettify<Omit<TSchema, K> & {
68
- [P in K]: SqlToTsType<DT>;
69
- }> : TSchema : THeadOp extends ModifyColumnOp<infer K, infer T> ? Prettify<Omit<TSchema, K> & {
70
- [P in K]: SqlToTsType<T>;
71
+ [P in K]: {
72
+ tsType: SqlToTsType<DT>;
73
+ isNullable: TSchema[K] extends {
74
+ isNullable: infer N;
75
+ } ? N : true;
76
+ hasDefault: TSchema[K] extends {
77
+ hasDefault: infer D;
78
+ } ? D : false;
79
+ isAutoIncrement: TSchema[K] extends {
80
+ isAutoIncrement: infer A;
81
+ } ? A : false;
82
+ };
83
+ }> : TAlt extends {
84
+ kind: "setDefault";
85
+ } ? Prettify<Omit<TSchema, K> & {
86
+ [P in K]: TSchema[K] extends ColumnDescriptor ? {
87
+ tsType: TSchema[K]["tsType"];
88
+ isNullable: false;
89
+ hasDefault: true;
90
+ isAutoIncrement: TSchema[K]["isAutoIncrement"];
91
+ } : TSchema[K];
92
+ }> : TAlt extends {
93
+ kind: "dropDefault";
94
+ } ? Prettify<Omit<TSchema, K> & {
95
+ [P in K]: TSchema[K] extends ColumnDescriptor ? {
96
+ tsType: TSchema[K]["tsType"];
97
+ isNullable: TSchema[K]["isNullable"];
98
+ hasDefault: false;
99
+ isAutoIncrement: TSchema[K]["isAutoIncrement"];
100
+ } : TSchema[K];
101
+ }> : TAlt extends {
102
+ kind: "setNotNull";
103
+ } ? Prettify<Omit<TSchema, K> & {
104
+ [P in K]: TSchema[K] extends ColumnDescriptor ? {
105
+ tsType: TSchema[K]["tsType"];
106
+ isNullable: false;
107
+ hasDefault: TSchema[K]["hasDefault"];
108
+ isAutoIncrement: TSchema[K]["isAutoIncrement"];
109
+ } : TSchema[K];
110
+ }> : TAlt extends {
111
+ kind: "dropNotNull";
112
+ } ? Prettify<Omit<TSchema, K> & {
113
+ [P in K]: TSchema[K] extends ColumnDescriptor ? {
114
+ tsType: TSchema[K]["tsType"];
115
+ isNullable: true;
116
+ hasDefault: TSchema[K]["hasDefault"];
117
+ isAutoIncrement: TSchema[K]["isAutoIncrement"];
118
+ } : TSchema[K];
119
+ }> : TSchema : TSchema : THeadOp extends ModifyColumnOp<infer K, any, infer TDescriptor> ? Prettify<Omit<TSchema, K> & {
120
+ [P in K]: TDescriptor;
71
121
  }> : TSchema;
72
122
  /**
73
123
  * Recursively processes a list of alteration operations (AST)
@@ -1,14 +1,28 @@
1
- type ParseRoute<T extends string> = T extends `${infer Start}:${infer Param}/${infer Rest}` ? {
1
+ import type { RouteDefinition, RouteMiddleware } from "./router";
2
+ type PathParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}` ? {
2
3
  [K in Param]: string;
3
- } & ParseRoute<Rest> : T extends `${infer Start}:${infer Param}` ? {
4
+ } & PathParams<Rest> : Path extends `${string}:${infer Param}` ? {
4
5
  [K in Param]: string;
5
- } : T extends `${infer Start}*${infer Rest}` ? {
6
+ } : Path extends `${string}*${infer Rest}` ? {
6
7
  $0: string;
7
- } & ParseRoute<Rest> : T extends `${infer Start}*` ? {
8
+ } & PathParams<Rest> : Path extends `${string}*` ? {
8
9
  $0: string;
9
10
  } : {};
10
- type LinkFunction<T extends readonly string[]> = {
11
- <Path extends T[number]>(path: Path, params?: ParseRoute<Path> extends Record<string, never> ? undefined : ParseRoute<Path>): string;
11
+ type ParamsForPath<Path extends string> = PathParams<Path> extends Record<string, never> ? undefined : PathParams<Path>;
12
+ export type LinkFunction<Paths extends string> = {
13
+ <Path extends Paths>(path: Path, params?: ParamsForPath<Path>): string;
12
14
  };
13
- export declare function defineLinks<const T extends readonly string[]>(routes: T): LinkFunction<T>;
15
+ type RoutePaths<Value> = Value extends RouteDefinition<infer Path, any> ? Path : Value extends readonly (infer Item)[] ? RouteArrayPaths<Value> : Value extends RouteMiddleware<any> ? never : never;
16
+ type RouteArrayPaths<Routes extends readonly any[]> = number extends Routes["length"] ? RoutePaths<Routes[number]> : Routes extends readonly [infer Head, ...infer Tail] ? RoutePaths<Head> | RouteArrayPaths<Tail> : never;
17
+ type AppRoutes<App> = App extends {
18
+ __rwRoutes: infer Routes;
19
+ } ? Routes : never;
20
+ export type AppRoutePaths<App> = RoutePaths<AppRoutes<App>>;
21
+ export type AppLink<App> = LinkFunction<AppRoutePaths<App>>;
22
+ export declare function linkFor<App>(): AppLink<App>;
23
+ export declare function createLinks<App>(_app?: App): AppLink<App>;
24
+ export declare function defineLinks<App extends {
25
+ __rwRoutes: any;
26
+ }>(): AppLink<App>;
27
+ export declare function defineLinks<const T extends readonly string[]>(routes: T): LinkFunction<T[number]>;
14
28
  export {};
@@ -1,38 +1,96 @@
1
+ export function linkFor() {
2
+ return createLinkFunction();
3
+ }
4
+ export function createLinks(_app) {
5
+ return linkFor();
6
+ }
7
+ // Implementation
1
8
  export function defineLinks(routes) {
2
- // Validate routes at runtime
9
+ // If no routes provided, this is the app type overload
10
+ // At runtime, we can't distinguish, but the type system ensures
11
+ // this only happens when called as defineLinks<App>()
12
+ // We delegate to linkFor which handles app types correctly
13
+ if (routes === undefined) {
14
+ // This branch is only reachable when called as defineLinks<App>()
15
+ // The return type is AppLink<App> due to the overload
16
+ // We use linkFor internally which doesn't need runtime route validation
17
+ return linkFor();
18
+ }
19
+ // Original implementation for route arrays
3
20
  routes.forEach((route) => {
4
21
  if (typeof route !== "string") {
5
22
  throw new Error(`Invalid route: ${route}. Routes must be strings.`);
6
23
  }
7
24
  });
8
- return (path, params) => {
25
+ const link = createLinkFunction();
26
+ return ((path, params) => {
9
27
  if (!routes.includes(path)) {
10
28
  throw new Error(`Invalid route: ${path}`);
11
29
  }
12
- if (!params)
30
+ return link(path, params);
31
+ });
32
+ }
33
+ const TOKEN_REGEX = /:([a-zA-Z0-9_]+)|\*/g;
34
+ function createLinkFunction() {
35
+ return ((path, params) => {
36
+ const expectsParams = hasRouteParameters(path);
37
+ if (!params || Object.keys(params).length === 0) {
38
+ if (expectsParams) {
39
+ throw new Error(`Route ${path} requires an object of parameters`);
40
+ }
13
41
  return path;
14
- let result = path;
15
- // Replace named parameters
16
- for (const [key, value] of Object.entries(params)) {
17
- if (key.startsWith("$")) {
18
- // Replace each star with its corresponding $ parameter
19
- const starIndex = parseInt(key.slice(1));
20
- const stars = result.match(/\*/g) || [];
21
- if (starIndex >= stars.length) {
22
- throw new Error(`Parameter ${key} has no corresponding * in route`);
23
- }
24
- // Replace the nth star with the value
25
- let count = 0;
26
- result = result.replace(/\*/g, (match) => count++ === starIndex ? value : match);
42
+ }
43
+ return interpolate(path, params);
44
+ });
45
+ }
46
+ function hasRouteParameters(path) {
47
+ TOKEN_REGEX.lastIndex = 0;
48
+ const result = TOKEN_REGEX.test(path);
49
+ TOKEN_REGEX.lastIndex = 0;
50
+ return result;
51
+ }
52
+ function interpolate(template, params) {
53
+ let result = "";
54
+ let lastIndex = 0;
55
+ let wildcardIndex = 0;
56
+ const consumed = new Set();
57
+ TOKEN_REGEX.lastIndex = 0;
58
+ let match;
59
+ while ((match = TOKEN_REGEX.exec(template)) !== null) {
60
+ result += template.slice(lastIndex, match.index);
61
+ if (match[1]) {
62
+ const name = match[1];
63
+ const value = params[name];
64
+ if (value === undefined) {
65
+ throw new Error(`Missing parameter "${name}" for route ${template}`);
27
66
  }
28
- else {
29
- // Handle named parameters
30
- if (typeof value !== "string") {
31
- throw new Error(`Parameter ${key} must be a string`);
32
- }
33
- result = result.replace(`:${key}`, value);
67
+ result += encodeURIComponent(value);
68
+ consumed.add(name);
69
+ }
70
+ else {
71
+ const key = `$${wildcardIndex}`;
72
+ const value = params[key];
73
+ if (value === undefined) {
74
+ throw new Error(`Missing parameter "${key}" for route ${template}`);
34
75
  }
76
+ result += encodeWildcardValue(value);
77
+ consumed.add(key);
78
+ wildcardIndex += 1;
79
+ }
80
+ lastIndex = TOKEN_REGEX.lastIndex;
81
+ }
82
+ result += template.slice(lastIndex);
83
+ for (const key of Object.keys(params)) {
84
+ if (!consumed.has(key)) {
85
+ throw new Error(`Parameter "${key}" is not used by route ${template}`);
35
86
  }
36
- return result;
37
- };
87
+ }
88
+ TOKEN_REGEX.lastIndex = 0;
89
+ return result;
90
+ }
91
+ function encodeWildcardValue(value) {
92
+ return value
93
+ .split("/")
94
+ .map((segment) => encodeURIComponent(segment))
95
+ .join("/");
38
96
  }
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { defineLinks } from "./links";
3
+ const link = defineLinks(["/", "/users/:id", "/files/*"]);
4
+ describe("link helpers", () => {
5
+ it("returns static routes without parameters", () => {
6
+ expect(link("/")).toBe("/");
7
+ });
8
+ it("replaces named parameters with encoded values", () => {
9
+ expect(link("/users/:id", { id: "user id" })).toBe("/users/user%20id");
10
+ });
11
+ it("replaces wildcard parameters preserving path segments", () => {
12
+ expect(link("/files/*", { $0: "docs/Guide Document.md" })).toBe("/files/docs/Guide%20Document.md");
13
+ });
14
+ it("throws when parameters are missing", () => {
15
+ expect(() => link("/users/:id")).toThrowError(/requires an object of parameters/i);
16
+ });
17
+ it("throws when extra parameters are supplied", () => {
18
+ expect(() => link("/users/:id", { id: "123", extra: "value" })).toThrowError(/is not used by route/i);
19
+ });
20
+ });
@@ -8,4 +8,4 @@ export interface ManifestChunk {
8
8
  css?: string[];
9
9
  assets?: string[];
10
10
  }
11
- export declare const getManifest: () => Promise<Manifest>;
11
+ export declare function getManifest(): Promise<Manifest>;
@@ -1,14 +1,17 @@
1
1
  let manifest;
2
- export const getManifest = async () => {
2
+ export async function getManifest() {
3
3
  if (manifest) {
4
4
  return manifest;
5
5
  }
6
6
  if (import.meta.env.VITE_IS_DEV_SERVER) {
7
+ // In dev, there's no manifest, so we can use an empty object.
7
8
  manifest = {};
8
9
  }
9
10
  else {
10
- const { default: prodManifest } = await import("virtual:rwsdk:manifest.js");
11
- manifest = prodManifest;
11
+ // context(justinvdm, 2 Oct 2025): In production, the manifest is a
12
+ // placeholder string that will be replaced by the linker plugin with the
13
+ // actual manifest JSON object.
14
+ manifest = "__RWSDK_MANIFEST_PLACEHOLDER__";
12
15
  }
13
16
  return manifest;
14
- };
17
+ }
@@ -1,7 +1,13 @@
1
- import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
1
+ // context(justinvdm, 14 Aug 2025): `react-server-dom-webpack` uses this globa ___webpack_require__ global,
2
+ // so we need to import our client entry point (which sets it), before importing
3
+ // prettier-ignore
2
4
  import { initClient } from "../../client/client";
3
- import { packMessage, unpackMessage, } from "./protocol";
5
+ // prettier-ignore
6
+ import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
7
+ // prettier-ignore
4
8
  import { MESSAGE_TYPE } from "./shared";
9
+ // prettier-ignore
10
+ import { packMessage, unpackMessage, } from "./protocol";
5
11
  const DEFAULT_KEY = "default";
6
12
  export const initRealtimeClient = ({ key = DEFAULT_KEY, handleResponse, } = {}) => {
7
13
  const transport = realtimeTransport({ key, handleResponse });
@@ -1,3 +1,3 @@
1
1
  import type { RealtimeDurableObject } from "./durableObject";
2
2
  export { renderRealtimeClients } from "./renderRealtimeClients";
3
- export declare const realtimeRoute: (getDurableObjectNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<RealtimeDurableObject>) => import("../router").RouteDefinition<import("../../worker").RequestInfo<any, import("../../worker").DefaultAppContext>>;
3
+ export declare const realtimeRoute: (getDurableObjectNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<RealtimeDurableObject>) => import("../router").RouteDefinition<"/__realtime", import("../../worker").RequestInfo<any, import("../../worker").DefaultAppContext>>;