@samebase/convex-hosting 0.3.2 → 0.4.1

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 (193) hide show
  1. package/CHANGELOG.md +17 -132
  2. package/README.md +188 -406
  3. package/dist/cli/convex.d.ts +63 -0
  4. package/dist/cli/convex.js +108 -0
  5. package/dist/cli/convex.js.map +1 -0
  6. package/dist/cli/files.d.ts +18 -0
  7. package/dist/cli/files.js +106 -0
  8. package/dist/cli/files.js.map +1 -0
  9. package/dist/cli/index.d.ts +1 -11
  10. package/dist/cli/index.js +3 -58
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/main.d.ts +5 -0
  13. package/dist/cli/main.js +377 -0
  14. package/dist/cli/main.js.map +1 -0
  15. package/dist/client/index.d.ts +8 -115
  16. package/dist/client/index.js +65 -242
  17. package/dist/client/index.js.map +1 -1
  18. package/dist/client/types.d.ts +5 -0
  19. package/dist/client/version-module.d.ts +6 -0
  20. package/dist/component/_generated/api.d.ts +0 -5
  21. package/dist/component/_generated/api.js.map +1 -1
  22. package/dist/component/_generated/component.d.ts +20 -18
  23. package/dist/component/_generated/dataModel.d.ts +0 -1
  24. package/dist/component/_generated/server.d.ts +12 -1
  25. package/dist/component/_generated/server.js +2 -0
  26. package/dist/component/_generated/server.js.map +1 -1
  27. package/dist/component/convex.config.d.ts +0 -1
  28. package/dist/component/lib.d.ts +175 -156
  29. package/dist/component/lib.js +396 -710
  30. package/dist/component/lib.js.map +1 -1
  31. package/dist/component/schema.d.ts +76 -72
  32. package/dist/component/schema.js +35 -59
  33. package/dist/component/schema.js.map +1 -1
  34. package/dist/workers-shared/asset-worker/src/analytics.d.ts +39 -0
  35. package/dist/workers-shared/asset-worker/src/analytics.js +86 -0
  36. package/dist/workers-shared/asset-worker/src/analytics.js.map +1 -0
  37. package/dist/workers-shared/asset-worker/src/compatibility-flags.d.ts +19 -0
  38. package/dist/workers-shared/asset-worker/src/compatibility-flags.js +29 -0
  39. package/dist/workers-shared/asset-worker/src/compatibility-flags.js.map +1 -0
  40. package/dist/workers-shared/asset-worker/src/configuration.d.ts +3 -0
  41. package/dist/workers-shared/asset-worker/src/configuration.js +30 -0
  42. package/dist/workers-shared/asset-worker/src/configuration.js.map +1 -0
  43. package/dist/workers-shared/asset-worker/src/constants.d.ts +1 -0
  44. package/dist/workers-shared/asset-worker/src/constants.js +3 -0
  45. package/dist/workers-shared/asset-worker/src/constants.js.map +1 -0
  46. package/dist/workers-shared/asset-worker/src/handler.d.ts +28 -0
  47. package/dist/workers-shared/asset-worker/src/handler.js +717 -0
  48. package/dist/workers-shared/asset-worker/src/handler.js.map +1 -0
  49. package/dist/workers-shared/asset-worker/src/types.d.ts +13 -0
  50. package/dist/workers-shared/asset-worker/src/types.js +2 -0
  51. package/dist/workers-shared/asset-worker/src/types.js.map +1 -0
  52. package/dist/workers-shared/asset-worker/src/utils/base-path.d.ts +29 -0
  53. package/dist/workers-shared/asset-worker/src/utils/base-path.js +56 -0
  54. package/dist/workers-shared/asset-worker/src/utils/base-path.js.map +1 -0
  55. package/dist/workers-shared/asset-worker/src/utils/headers.d.ts +11 -0
  56. package/dist/workers-shared/asset-worker/src/utils/headers.js +77 -0
  57. package/dist/workers-shared/asset-worker/src/utils/headers.js.map +1 -0
  58. package/dist/workers-shared/asset-worker/src/utils/rules-engine.d.ts +23 -0
  59. package/dist/workers-shared/asset-worker/src/utils/rules-engine.js +133 -0
  60. package/dist/workers-shared/asset-worker/src/utils/rules-engine.js.map +1 -0
  61. package/dist/workers-shared/asset-worker/src/worker.d.ts +14 -0
  62. package/dist/workers-shared/asset-worker/src/worker.js +12 -0
  63. package/dist/workers-shared/asset-worker/src/worker.js.map +1 -0
  64. package/dist/workers-shared/utils/base-path.d.ts +32 -0
  65. package/dist/workers-shared/utils/base-path.js +94 -0
  66. package/dist/workers-shared/utils/base-path.js.map +1 -0
  67. package/dist/workers-shared/utils/configuration/constants.d.ts +15 -0
  68. package/dist/workers-shared/utils/configuration/constants.js +16 -0
  69. package/dist/workers-shared/utils/configuration/constants.js.map +1 -0
  70. package/dist/workers-shared/utils/configuration/constructConfiguration.d.ts +12 -0
  71. package/dist/workers-shared/utils/configuration/constructConfiguration.js +103 -0
  72. package/dist/workers-shared/utils/configuration/constructConfiguration.js.map +1 -0
  73. package/dist/workers-shared/utils/configuration/parseHeaders.d.ts +5 -0
  74. package/dist/workers-shared/utils/configuration/parseHeaders.js +181 -0
  75. package/dist/workers-shared/utils/configuration/parseHeaders.js.map +1 -0
  76. package/dist/workers-shared/utils/configuration/parseRedirects.d.ts +8 -0
  77. package/dist/workers-shared/utils/configuration/parseRedirects.js +129 -0
  78. package/dist/workers-shared/utils/configuration/parseRedirects.js.map +1 -0
  79. package/dist/workers-shared/utils/configuration/parseStaticRouting.d.ts +2 -0
  80. package/dist/workers-shared/utils/configuration/parseStaticRouting.js +94 -0
  81. package/dist/workers-shared/utils/configuration/parseStaticRouting.js.map +1 -0
  82. package/dist/workers-shared/utils/configuration/types.d.ts +38 -0
  83. package/dist/workers-shared/utils/configuration/types.js +2 -0
  84. package/dist/workers-shared/utils/configuration/types.js.map +1 -0
  85. package/dist/workers-shared/utils/configuration/validateURL.d.ts +3 -0
  86. package/dist/workers-shared/utils/configuration/validateURL.js +56 -0
  87. package/dist/workers-shared/utils/configuration/validateURL.js.map +1 -0
  88. package/dist/workers-shared/utils/constants.d.ts +28 -0
  89. package/dist/workers-shared/utils/constants.js +34 -0
  90. package/dist/workers-shared/utils/constants.js.map +1 -0
  91. package/dist/workers-shared/utils/helpers.d.ts +22 -0
  92. package/dist/workers-shared/utils/helpers.js +73 -0
  93. package/dist/workers-shared/utils/helpers.js.map +1 -0
  94. package/dist/workers-shared/utils/responses.d.ts +43 -0
  95. package/dist/workers-shared/utils/responses.js +125 -0
  96. package/dist/workers-shared/utils/responses.js.map +1 -0
  97. package/dist/workers-shared/utils/tracing.d.ts +3 -0
  98. package/dist/workers-shared/utils/tracing.js +29 -0
  99. package/dist/workers-shared/utils/tracing.js.map +1 -0
  100. package/dist/workers-shared/utils/types.d.ts +131 -0
  101. package/dist/workers-shared/utils/types.js +72 -0
  102. package/dist/workers-shared/utils/types.js.map +1 -0
  103. package/package.json +21 -37
  104. package/INTEGRATION.md +0 -399
  105. package/dist/cli/args.d.ts +0 -28
  106. package/dist/cli/args.d.ts.map +0 -1
  107. package/dist/cli/args.js +0 -148
  108. package/dist/cli/args.js.map +0 -1
  109. package/dist/cli/argumentChunks.d.ts +0 -3
  110. package/dist/cli/argumentChunks.d.ts.map +0 -1
  111. package/dist/cli/argumentChunks.js +0 -29
  112. package/dist/cli/argumentChunks.js.map +0 -1
  113. package/dist/cli/collectFiles.d.ts +0 -10
  114. package/dist/cli/collectFiles.d.ts.map +0 -1
  115. package/dist/cli/collectFiles.js +0 -53
  116. package/dist/cli/collectFiles.js.map +0 -1
  117. package/dist/cli/commands.d.ts +0 -8
  118. package/dist/cli/commands.d.ts.map +0 -1
  119. package/dist/cli/commands.js +0 -56
  120. package/dist/cli/commands.js.map +0 -1
  121. package/dist/cli/componentName.d.ts +0 -32
  122. package/dist/cli/componentName.d.ts.map +0 -1
  123. package/dist/cli/componentName.js +0 -43
  124. package/dist/cli/componentName.js.map +0 -1
  125. package/dist/cli/deploy.d.ts +0 -16
  126. package/dist/cli/deploy.d.ts.map +0 -1
  127. package/dist/cli/deploy.js +0 -283
  128. package/dist/cli/deploy.js.map +0 -1
  129. package/dist/cli/deployEnvironment.d.ts +0 -10
  130. package/dist/cli/deployEnvironment.d.ts.map +0 -1
  131. package/dist/cli/deployEnvironment.js +0 -12
  132. package/dist/cli/deployEnvironment.js.map +0 -1
  133. package/dist/cli/index.d.ts.map +0 -1
  134. package/dist/cli/upload.d.ts +0 -22
  135. package/dist/cli/upload.d.ts.map +0 -1
  136. package/dist/cli/upload.js +0 -630
  137. package/dist/cli/upload.js.map +0 -1
  138. package/dist/client/_generated/_ignore.d.ts +0 -1
  139. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  140. package/dist/client/_generated/_ignore.js +0 -3
  141. package/dist/client/_generated/_ignore.js.map +0 -1
  142. package/dist/client/index.d.ts.map +0 -1
  143. package/dist/component/_generated/api.d.ts.map +0 -1
  144. package/dist/component/_generated/component.d.ts.map +0 -1
  145. package/dist/component/_generated/dataModel.d.ts.map +0 -1
  146. package/dist/component/_generated/server.d.ts.map +0 -1
  147. package/dist/component/convex.config.d.ts.map +0 -1
  148. package/dist/component/http.d.ts +0 -4
  149. package/dist/component/http.d.ts.map +0 -1
  150. package/dist/component/http.js +0 -113
  151. package/dist/component/http.js.map +0 -1
  152. package/dist/component/lib.d.ts.map +0 -1
  153. package/dist/component/schema.d.ts.map +0 -1
  154. package/dist/component/serving.d.ts +0 -23
  155. package/dist/component/serving.d.ts.map +0 -1
  156. package/dist/component/serving.js +0 -161
  157. package/dist/component/serving.js.map +0 -1
  158. package/dist/react/index.d.ts +0 -59
  159. package/dist/react/index.d.ts.map +0 -1
  160. package/dist/react/index.js +0 -137
  161. package/dist/react/index.js.map +0 -1
  162. package/src/cli/args.test.ts +0 -79
  163. package/src/cli/args.ts +0 -153
  164. package/src/cli/argumentChunks.test.ts +0 -27
  165. package/src/cli/argumentChunks.ts +0 -40
  166. package/src/cli/collectFiles.test.ts +0 -26
  167. package/src/cli/collectFiles.ts +0 -59
  168. package/src/cli/commands.ts +0 -68
  169. package/src/cli/componentName.test.ts +0 -41
  170. package/src/cli/componentName.ts +0 -46
  171. package/src/cli/deploy.ts +0 -336
  172. package/src/cli/deployEnvironment.test.ts +0 -27
  173. package/src/cli/deployEnvironment.ts +0 -17
  174. package/src/cli/index.ts +0 -65
  175. package/src/cli/upload.ts +0 -837
  176. package/src/client/_generated/_ignore.ts +0 -1
  177. package/src/client/index.test.ts +0 -549
  178. package/src/client/index.ts +0 -344
  179. package/src/client/setup.test.ts +0 -22
  180. package/src/component/_generated/api.ts +0 -54
  181. package/src/component/_generated/component.ts +0 -55
  182. package/src/component/_generated/dataModel.ts +0 -60
  183. package/src/component/_generated/server.ts +0 -156
  184. package/src/component/convex.config.ts +0 -3
  185. package/src/component/http.test.ts +0 -254
  186. package/src/component/http.ts +0 -133
  187. package/src/component/lib.test.ts +0 -1007
  188. package/src/component/lib.ts +0 -862
  189. package/src/component/schema.ts +0 -75
  190. package/src/component/serving.ts +0 -170
  191. package/src/component/setup.test.ts +0 -11
  192. package/src/react/index.tsx +0 -197
  193. package/src/test.ts +0 -23
@@ -1,75 +0,0 @@
1
- import { defineSchema, defineTable } from "convex/server";
2
- import { v } from "convex/values";
3
-
4
- export default defineSchema({
5
- staticAssets: defineTable({
6
- path: v.string(), // URL path, e.g., "/index.html", "/assets/main-abc123.js"
7
- storageId: v.optional(v.id("_storage")), // Reference to Convex file storage (used for HTML + non-CDN assets)
8
- blobId: v.optional(v.string()), // convex-fs blob ID (used for CDN-served assets)
9
- contentType: v.string(), // MIME type, e.g., "text/html; charset=utf-8"
10
- deploymentId: v.string(), // UUID for garbage collection
11
- })
12
- .index("by_path", ["path"])
13
- .index("by_deploymentId", ["deploymentId"])
14
- .index("by_storageId", ["storageId"]),
15
-
16
- // Publication moves replaced storage files here. Missing non-HTML paths with
17
- // an extension can still resolve to these files. A later upload removes them
18
- // after expiresAt, at least seven days after replacement.
19
- retiredAssets: defineTable({
20
- path: v.string(),
21
- storageId: v.id("_storage"),
22
- contentType: v.string(),
23
- deploymentId: v.string(),
24
- expiresAt: v.number(),
25
- })
26
- .index("by_path", ["path"])
27
- .index("by_storageId", ["storageId"])
28
- .index("by_expiresAt", ["expiresAt"]),
29
-
30
- // Uploads stage their manifest in small chunks because the Convex CLI takes
31
- // function arguments on the command line. Only publishDeployment copies a
32
- // complete staged manifest into staticAssets, so partial uploads are never
33
- // visible to HTTP requests.
34
- stagedAssets: defineTable({
35
- path: v.string(),
36
- storageId: v.optional(v.id("_storage")),
37
- blobId: v.optional(v.string()),
38
- contentType: v.string(),
39
- deploymentId: v.string(),
40
- })
41
- .index("by_deploymentId", ["deploymentId"])
42
- .index("by_storageId", ["storageId"]),
43
-
44
- // Old ConvexFS blobs are app-owned, so the component cannot delete them.
45
- // Persist the IDs until the CLI's app-level delete function succeeds. This
46
- // also makes cleanup recoverable when a publish response is lost.
47
- pendingBlobCleanup: defineTable({
48
- blobId: v.string(),
49
- }).index("by_blobId", ["blobId"]),
50
-
51
- // Storage IDs from the previous live manifest are queued by the atomic
52
- // publish, then deleted in bounded follow-up mutations. This avoids both a
53
- // large publish transaction and races with unrelated concurrent uploads.
54
- pendingStorageCleanup: defineTable({
55
- storageId: v.id("_storage"),
56
- }).index("by_storageId", ["storageId"]),
57
-
58
- // Cleanup may be queued before the first successful deployment exists, so
59
- // its count cannot live only on deploymentInfo.
60
- cleanupState: defineTable({
61
- pendingBlobCleanupCount: v.number(),
62
- }),
63
-
64
- // Singleton table to track the current deployment
65
- // Clients subscribe to this to know when to reload
66
- deploymentInfo: defineTable({
67
- currentDeploymentId: v.string(),
68
- deployedAt: v.number(), // timestamp
69
- // SPA fallback config for the current deployment. Set at upload time
70
- // (`--no-spa` turns it off). Absent means enabled. Travels with the
71
- // deploy so the serving behavior matches the code that was shipped.
72
- spaFallback: v.optional(v.boolean()),
73
- pendingBlobCleanupCount: v.optional(v.number()),
74
- }),
75
- });
@@ -1,170 +0,0 @@
1
- // Pure, dependency-free helpers shared by the HTTP handler (http.ts) and the
2
- // asset-resolution query (lib.ts). Kept separate so lib.ts can reuse them
3
- // without importing the HTTP router.
4
-
5
- const MIME_TYPES: Record<string, string> = {
6
- ".html": "text/html; charset=utf-8",
7
- ".js": "application/javascript; charset=utf-8",
8
- ".mjs": "application/javascript; charset=utf-8",
9
- ".css": "text/css; charset=utf-8",
10
- ".json": "application/json; charset=utf-8",
11
- ".png": "image/png",
12
- ".jpg": "image/jpeg",
13
- ".jpeg": "image/jpeg",
14
- ".gif": "image/gif",
15
- ".svg": "image/svg+xml",
16
- ".ico": "image/x-icon",
17
- ".webp": "image/webp",
18
- ".woff": "font/woff",
19
- ".woff2": "font/woff2",
20
- ".ttf": "font/ttf",
21
- ".txt": "text/plain; charset=utf-8",
22
- ".map": "application/json",
23
- ".webmanifest": "application/manifest+json",
24
- ".xml": "application/xml",
25
- };
26
-
27
- export function getSetupHtml(): string {
28
- return `<!DOCTYPE html>
29
- <html lang="en">
30
- <head>
31
- <meta charset="UTF-8">
32
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
- <title>Convex Hosting</title>
34
- <style>
35
- * { box-sizing: border-box; }
36
- body {
37
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
38
- max-width: 640px;
39
- margin: 0 auto;
40
- padding: 40px 20px;
41
- background: #fafafa;
42
- color: #333;
43
- line-height: 1.6;
44
- }
45
- h1 { color: #111; margin-bottom: 8px; }
46
- .subtitle { color: #666; margin-bottom: 32px; }
47
- code {
48
- background: #e8e8e8;
49
- padding: 2px 6px;
50
- border-radius: 4px;
51
- font-size: 14px;
52
- }
53
- pre {
54
- background: #1a1a1a;
55
- color: #f0f0f0;
56
- padding: 16px;
57
- border-radius: 8px;
58
- overflow-x: auto;
59
- font-size: 14px;
60
- }
61
- .step { margin-bottom: 24px; }
62
- .step-num {
63
- display: inline-block;
64
- background: #333;
65
- color: white;
66
- width: 24px;
67
- height: 24px;
68
- border-radius: 50%;
69
- text-align: center;
70
- font-size: 14px;
71
- line-height: 24px;
72
- margin-right: 8px;
73
- }
74
- a { color: #0070f3; }
75
- </style>
76
- </head>
77
- <body>
78
- <h1>Almost there!</h1>
79
- <p class="subtitle">Your Convex backend is running, but no static files have been deployed yet.</p>
80
-
81
- <div class="step">
82
- <span class="step-num">1</span>
83
- <strong>Build your frontend</strong>
84
- <pre>npm run build</pre>
85
- </div>
86
-
87
- <div class="step">
88
- <span class="step-num">2</span>
89
- <strong>Deploy your static files</strong>
90
- <pre>npx @samebase/convex-hosting deploy</pre>
91
- </div>
92
-
93
- <p>Or deploy everything in one command:</p>
94
- <pre>npm run deploy</pre>
95
-
96
- <p style="margin-top: 32px; color: #666; font-size: 14px;">
97
- Learn more at <a href="https://github.com/samebase/convex-hosting">github.com/samebase/convex-hosting</a>
98
- </p>
99
- </body>
100
- </html>`;
101
- }
102
-
103
- export function getMimeType(path: string): string {
104
- const ext = path.substring(path.lastIndexOf(".")).toLowerCase();
105
- return MIME_TYPES[ext] || "application/octet-stream";
106
- }
107
-
108
- export function hasFileExtension(path: string): boolean {
109
- const lastSegment = path.split("/").pop() || "";
110
- return lastSegment.includes(".") && !lastSegment.startsWith(".");
111
- }
112
-
113
- // Bundler content-hash suffix: e.g. `index-lj_vq_aF.js`,
114
- // `index-D3LP-ukt.js`, or `style-B71cUw87.css`. Requiring at least one
115
- // non-letter avoids treating ordinary names such as `privacy-policy.html` as
116
- // content-addressed assets.
117
- export function isHashedAsset(path: string): boolean {
118
- const match = path.match(/[-.]([\dA-Za-z_-]{6,32})\.[A-Za-z\d]+$/);
119
- return match !== null && /[\d_-]/.test(match[1]);
120
- }
121
-
122
- export function isHtmlContentType(contentType: string): boolean {
123
- return contentType.startsWith("text/html");
124
- }
125
-
126
- export function cacheControlFor(path: string): string {
127
- return !path.toLowerCase().endsWith(".html") && isHashedAsset(path)
128
- ? "public, max-age=31536000, immutable"
129
- : "public, max-age=0, must-revalidate";
130
- }
131
-
132
- /**
133
- * A plain-text response sent when no uploaded file answers. Without
134
- * `no-store`, Cloudflare in front of `*.convex.site` caches it for paths with a
135
- * static extension, so a missing file stays a cached 404 after it is uploaded.
136
- */
137
- export function errorResponse(status: number, body: string): Response {
138
- return new Response(body, {
139
- status,
140
- headers: { "Content-Type": "text/plain", "Cache-Control": "no-store" },
141
- });
142
- }
143
-
144
- /**
145
- * Decode the URL pathname before looking it up in the asset table. A malformed
146
- * escape sequence is a bad request, not a missing static asset.
147
- */
148
- export function decodeRequestPath(pathname: string): string | null {
149
- try {
150
- return decodeURIComponent(pathname);
151
- } catch {
152
- return null;
153
- }
154
- }
155
-
156
- /**
157
- * If-None-Match uses weak comparison for GET and HEAD requests. Browsers and
158
- * proxies may send a weak validator or a comma-separated list of validators.
159
- */
160
- export function etagMatches(ifNoneMatch: string | null, currentEtag: string): boolean {
161
- if (!ifNoneMatch) return false;
162
-
163
- const normalize = (value: string) => value.trim().replace(/^W\//, "").trim();
164
- const normalizedCurrent = normalize(currentEtag);
165
-
166
- return ifNoneMatch.split(",").some((candidate) => {
167
- const normalizedCandidate = normalize(candidate);
168
- return normalizedCandidate === "*" || normalizedCandidate === normalizedCurrent;
169
- });
170
- }
@@ -1,11 +0,0 @@
1
- /// <reference types="vite/client" />
2
- import { test } from "vite-plus/test";
3
- import schema from "./schema.js";
4
- import { convexTest } from "convex-test";
5
- export const modules = import.meta.glob("./**/*.*s");
6
-
7
- export function initConvexTest() {
8
- const t = convexTest(schema, modules);
9
- return t;
10
- }
11
- test("setup", () => {});
@@ -1,197 +0,0 @@
1
- "use client";
2
-
3
- import { useQuery_experimental } from "convex/react";
4
- import { useState, useMemo, type CSSProperties, type JSX } from "react";
5
- import { makeFunctionReference, type FunctionReference } from "convex/server";
6
-
7
- type DeploymentInfo = {
8
- _id: string;
9
- _creationTime: number;
10
- currentDeploymentId: string;
11
- deployedAt: number;
12
- spaFallback?: boolean;
13
- } | null;
14
-
15
- type DeploymentQueryRef = FunctionReference<
16
- "query",
17
- "public",
18
- Record<string, never>,
19
- DeploymentInfo
20
- >;
21
-
22
- const DEFAULT_QUERY_REF = makeFunctionReference<"query", Record<string, never>, DeploymentInfo>(
23
- "hosting:getCurrentDeployment",
24
- ) as DeploymentQueryRef;
25
-
26
- /* eslint-disable react-refresh/only-export-components */
27
-
28
- /**
29
- * Hook to detect when a new deployment is available.
30
- *
31
- * @param getCurrentDeployment - Optional query reference. Defaults to
32
- * `api.hosting.getCurrentDeployment` (resolved by path). If you've
33
- * re-exported the deployment query under a different module name, pass the
34
- * reference explicitly.
35
- *
36
- * @example
37
- * ```tsx
38
- * import { useDeploymentUpdates } from "@samebase/convex-hosting/react";
39
- *
40
- * function App() {
41
- * const { updateAvailable, reload } = useDeploymentUpdates();
42
- * return updateAvailable ? <button onClick={reload}>Reload</button> : null;
43
- * }
44
- * ```
45
- */
46
- export function useDeploymentUpdates(getCurrentDeployment?: DeploymentQueryRef) {
47
- const result = useQuery_experimental({
48
- query: getCurrentDeployment ?? DEFAULT_QUERY_REF,
49
- args: {},
50
- });
51
-
52
- const deployment = result.status === "success" ? result.data : null;
53
- const setupError =
54
- result.status === "error" ? deploymentQuerySetupHelp(getCurrentDeployment) : null;
55
-
56
- const [initialDeploymentId, setInitialDeploymentId] = useState<string | null>(null);
57
- const [dismissedDeploymentId, setDismissedDeploymentId] = useState<string | null>(null);
58
-
59
- if (deployment && initialDeploymentId === null) {
60
- setInitialDeploymentId(deployment.currentDeploymentId);
61
- }
62
-
63
- const updateAvailable = useMemo(() => {
64
- if (!deployment || initialDeploymentId === null) return false;
65
- const hasNew = deployment.currentDeploymentId !== initialDeploymentId;
66
- const isDismissed = deployment.currentDeploymentId === dismissedDeploymentId;
67
- return hasNew && !isDismissed;
68
- }, [deployment, initialDeploymentId, dismissedDeploymentId]);
69
-
70
- const reload = () => {
71
- window.location.reload();
72
- };
73
-
74
- const dismiss = () => {
75
- if (deployment) {
76
- setDismissedDeploymentId(deployment.currentDeploymentId);
77
- }
78
- };
79
-
80
- return {
81
- updateAvailable,
82
- reload,
83
- dismiss,
84
- deployment,
85
- /**
86
- * When set, the deployment query isn't exported from the app; the banner
87
- * won't work until the user wires it up. The string is suitable to render
88
- * during local development so the missing setup is obvious.
89
- */
90
- setupError,
91
- };
92
- }
93
-
94
- function deploymentQuerySetupHelp(ref: DeploymentQueryRef | undefined): string {
95
- if (ref) {
96
- return "Deployment query failed. Verify the function you passed to useDeploymentUpdates is deployed.";
97
- }
98
- return [
99
- "@samebase/convex-hosting: UpdateBanner requires you to expose the deployment query.",
100
- "Create convex/hosting.ts:",
101
- "",
102
- ' import { exposeDeploymentQuery } from "@samebase/convex-hosting";',
103
- ' import { components } from "./_generated/api";',
104
- " export const { getCurrentDeployment } = exposeDeploymentQuery(",
105
- " components.hosting,",
106
- " );",
107
- ].join("\n");
108
- }
109
-
110
- /**
111
- * Ready-to-use banner shown when a new deployment is available.
112
- *
113
- * @example
114
- * ```tsx
115
- * import { UpdateBanner } from "@samebase/convex-hosting/react";
116
- * <UpdateBanner />
117
- * ```
118
- */
119
- export function UpdateBanner({
120
- getCurrentDeployment,
121
- message = "A new version is available!",
122
- buttonText = "Reload",
123
- dismissable = true,
124
- className,
125
- style,
126
- }: {
127
- getCurrentDeployment?: DeploymentQueryRef;
128
- message?: string;
129
- buttonText?: string;
130
- dismissable?: boolean;
131
- className?: string;
132
- style?: CSSProperties;
133
- } = {}): JSX.Element | null {
134
- const { updateAvailable, reload, dismiss, setupError } =
135
- useDeploymentUpdates(getCurrentDeployment);
136
-
137
- if (setupError) {
138
- if (typeof console !== "undefined") {
139
- console.warn(setupError);
140
- }
141
- return null;
142
- }
143
-
144
- if (!updateAvailable) return null;
145
-
146
- const defaultStyle: CSSProperties = {
147
- position: "fixed",
148
- bottom: "1rem",
149
- right: "1rem",
150
- backgroundColor: "#1a1a2e",
151
- color: "#fff",
152
- padding: "1rem 1.5rem",
153
- borderRadius: "8px",
154
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)",
155
- display: "flex",
156
- alignItems: "center",
157
- gap: "1rem",
158
- zIndex: 9999,
159
- fontFamily: "system-ui, -apple-system, sans-serif",
160
- fontSize: "14px",
161
- ...style,
162
- };
163
-
164
- const buttonStyle: CSSProperties = {
165
- backgroundColor: "#4f46e5",
166
- color: "#fff",
167
- border: "none",
168
- padding: "0.5rem 1rem",
169
- borderRadius: "4px",
170
- cursor: "pointer",
171
- fontWeight: 500,
172
- };
173
-
174
- const dismissStyle: CSSProperties = {
175
- background: "none",
176
- border: "none",
177
- color: "#888",
178
- cursor: "pointer",
179
- padding: "0.25rem",
180
- fontSize: "18px",
181
- lineHeight: 1,
182
- };
183
-
184
- return (
185
- <div className={className} style={defaultStyle}>
186
- <span>{message}</span>
187
- <button onClick={reload} style={buttonStyle}>
188
- {buttonText}
189
- </button>
190
- {dismissable && (
191
- <button onClick={dismiss} style={dismissStyle} aria-label="Dismiss">
192
- ×
193
- </button>
194
- )}
195
- </div>
196
- );
197
- }
package/src/test.ts DELETED
@@ -1,23 +0,0 @@
1
- /// <reference types="vite/client" />
2
- import type { TestConvex } from "convex-test";
3
- import type { GenericSchema, SchemaDefinition } from "convex/server";
4
- import { componentsGeneric } from "convex/server";
5
- import type { ComponentApi } from "./component/_generated/component.js";
6
- import schema from "./component/schema.js";
7
- const modules = import.meta.glob("./component/**/*.ts");
8
-
9
- /**
10
- * Register the component with the test convex instance.
11
- * @param t - The test convex instance, e.g. from calling `convexTest`.
12
- * @param name - The name of the component, as registered in convex.config.ts.
13
- * @returns a component api to test via ctx.runMutation or for thick client
14
- * usage. Also provides types for convex-test's defineTestApp.
15
- */
16
- export function register(
17
- t: TestConvex<SchemaDefinition<GenericSchema, boolean>>,
18
- name: string = "hosting",
19
- ) {
20
- t.registerComponent(name, schema, modules);
21
- return componentsGeneric()[name] as unknown as ComponentApi;
22
- }
23
- export default { register, schema, modules };