create-100x-mobile 0.4.11 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,6 +43,18 @@ const rootLayout_2 = require("../templates/instant/rootLayout");
43
43
  const settingsScreen_2 = require("../templates/instant/settingsScreen");
44
44
  const tabsLayout_2 = require("../templates/instant/tabsLayout");
45
45
  const todosScreen_2 = require("../templates/instant/todosScreen");
46
+ const authLayout_2 = require("../templates/instant/magic/authLayout");
47
+ const rootLayout_3 = require("../templates/instant/magic/rootLayout");
48
+ const settingsScreen_3 = require("../templates/instant/magic/settingsScreen");
49
+ const signIn_2 = require("../templates/instant/magic/signIn");
50
+ const tabsLayout_3 = require("../templates/instant/magic/tabsLayout");
51
+ // Cloudflare templates
52
+ const alchemyRun_1 = require("../templates/cloudflare/alchemyRun");
53
+ const cloudflareStorage_1 = require("../templates/cloudflare/cloudflareStorage");
54
+ const instantCloudflareLib_1 = require("../templates/cloudflare/instantCloudflareLib");
55
+ const worker_1 = require("../templates/cloudflare/worker");
56
+ const workerMigration_1 = require("../templates/cloudflare/workerMigration");
57
+ const workerTsconfig_1 = require("../templates/cloudflare/workerTsconfig");
46
58
  // Component templates
47
59
  const addTodoForm_1 = require("../templates/components/addTodoForm");
48
60
  const emptyState_1 = require("../templates/components/emptyState");
@@ -50,32 +62,92 @@ const filterTabs_1 = require("../templates/components/filterTabs");
50
62
  const todoItem_1 = require("../templates/components/todoItem");
51
63
  // Hook templates
52
64
  const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
53
- function buildTemplateFiles(projectName, expoVersion, backend) {
65
+ function buildTemplateFiles(projectName, expoVersion, backend, instantAuthMode = "clerk") {
54
66
  if (backend === "instantdb") {
55
- const files = [
56
- // Config
57
- ["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion, backend)],
58
- ["app.json", (0, appJson_1.appJsonTemplate)(projectName, backend)],
59
- ["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
60
- [".gitignore", (0, gitignore_1.gitignoreTemplate)()],
61
- [".env.example", (0, envExample_1.envExampleTemplate)(backend)],
62
- ["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
63
- [".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
64
- ["eas.json", (0, easJson_1.easJsonTemplate)()],
65
- ["README.md", (0, readme_1.readmeTemplate)(projectName, backend)],
66
- // Instant app
67
- ["app/_layout.tsx", (0, rootLayout_2.instantRootLayoutTemplate)()],
68
- ["app/+not-found.tsx", (0, notFound_1.notFoundTemplate)()],
69
- ["app/providers/AuthProvider.tsx", (0, authProvider_2.instantAuthProviderTemplate)()],
70
- ["app/(auth)/_layout.tsx", (0, authLayout_1.authLayoutTemplate)()],
71
- ["app/(auth)/sign-in.tsx", (0, signIn_1.signInTemplate)()],
72
- ["app/(tabs)/_layout.tsx", (0, tabsLayout_2.instantTabsLayoutTemplate)()],
73
- ["app/(tabs)/index.tsx", (0, todosScreen_2.instantTodosScreenTemplate)()],
74
- ["app/(tabs)/settings.tsx", (0, settingsScreen_2.instantSettingsScreenTemplate)()],
75
- ["lib/instant.ts", (0, instantLib_1.instantLibTemplate)()],
76
- // Hooks
77
- ["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
78
- ];
67
+ const cloudflareFiles = (0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode)
68
+ ? [
69
+ ["alchemy.run.ts", (0, alchemyRun_1.alchemyRunTemplate)(projectName)],
70
+ ["lib/cloudflareStorage.ts", (0, cloudflareStorage_1.cloudflareStorageTemplate)()],
71
+ ["lib/instant.ts", (0, instantCloudflareLib_1.instantCloudflareLibTemplate)()],
72
+ ["worker/src/worker.ts", (0, worker_1.storageWorkerTemplate)()],
73
+ ["worker/tsconfig.json", (0, workerTsconfig_1.workerTsconfigTemplate)()],
74
+ ["worker/migrations/0001_uploads.sql", (0, workerMigration_1.workerMigrationTemplate)()],
75
+ ]
76
+ : [];
77
+ const files = (0, scaffold_1.isMagicCodeInstantAuthMode)(instantAuthMode)
78
+ ? [
79
+ // Config
80
+ [
81
+ "package.json",
82
+ (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion, backend, instantAuthMode),
83
+ ],
84
+ [
85
+ "app.json",
86
+ (0, appJson_1.appJsonTemplate)(projectName, backend, instantAuthMode),
87
+ ],
88
+ ["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
89
+ [".gitignore", (0, gitignore_1.gitignoreTemplate)()],
90
+ [".env.example", (0, envExample_1.envExampleTemplate)(backend, instantAuthMode)],
91
+ ["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
92
+ [".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
93
+ ["eas.json", (0, easJson_1.easJsonTemplate)()],
94
+ [
95
+ "README.md",
96
+ (0, readme_1.readmeTemplate)(projectName, backend, instantAuthMode),
97
+ ],
98
+ // Instant app
99
+ ["app/_layout.tsx", (0, rootLayout_3.magicRootLayoutTemplate)()],
100
+ ["app/+not-found.tsx", (0, notFound_1.notFoundTemplate)()],
101
+ ["app/(auth)/_layout.tsx", (0, authLayout_2.magicAuthLayoutTemplate)()],
102
+ ["app/(auth)/sign-in.tsx", (0, signIn_2.magicSignInTemplate)()],
103
+ ["app/(tabs)/_layout.tsx", (0, tabsLayout_3.magicTabsLayoutTemplate)()],
104
+ ["app/(tabs)/index.tsx", (0, todosScreen_2.instantTodosScreenTemplate)()],
105
+ [
106
+ "app/(tabs)/settings.tsx",
107
+ (0, settingsScreen_3.magicSettingsScreenTemplate)((0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode)
108
+ ? "InstantDB + magic code auth + Cloudflare storage"
109
+ : "InstantDB + magic code auth"),
110
+ ],
111
+ ...((0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode)
112
+ ? []
113
+ : [["lib/instant.ts", (0, instantLib_1.instantLibTemplate)()]]),
114
+ // Hooks
115
+ ["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
116
+ ...cloudflareFiles,
117
+ ]
118
+ : [
119
+ // Config
120
+ [
121
+ "package.json",
122
+ (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion, backend, instantAuthMode),
123
+ ],
124
+ [
125
+ "app.json",
126
+ (0, appJson_1.appJsonTemplate)(projectName, backend, instantAuthMode),
127
+ ],
128
+ ["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
129
+ [".gitignore", (0, gitignore_1.gitignoreTemplate)()],
130
+ [".env.example", (0, envExample_1.envExampleTemplate)(backend, instantAuthMode)],
131
+ ["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
132
+ [".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
133
+ ["eas.json", (0, easJson_1.easJsonTemplate)()],
134
+ [
135
+ "README.md",
136
+ (0, readme_1.readmeTemplate)(projectName, backend, instantAuthMode),
137
+ ],
138
+ // Instant app
139
+ ["app/_layout.tsx", (0, rootLayout_2.instantRootLayoutTemplate)()],
140
+ ["app/+not-found.tsx", (0, notFound_1.notFoundTemplate)()],
141
+ ["app/providers/AuthProvider.tsx", (0, authProvider_2.instantAuthProviderTemplate)()],
142
+ ["app/(auth)/_layout.tsx", (0, authLayout_1.authLayoutTemplate)()],
143
+ ["app/(auth)/sign-in.tsx", (0, signIn_1.signInTemplate)()],
144
+ ["app/(tabs)/_layout.tsx", (0, tabsLayout_2.instantTabsLayoutTemplate)()],
145
+ ["app/(tabs)/index.tsx", (0, todosScreen_2.instantTodosScreenTemplate)()],
146
+ ["app/(tabs)/settings.tsx", (0, settingsScreen_2.instantSettingsScreenTemplate)()],
147
+ ["lib/instant.ts", (0, instantLib_1.instantLibTemplate)()],
148
+ // Hooks
149
+ ["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
150
+ ];
79
151
  return files.map(([path, content]) => ({ path, content }));
80
152
  }
81
153
  const files = [
@@ -166,14 +238,19 @@ async function cmdNew(rawArgs) {
166
238
  if (backend === "convex" && options.clerkDomain) {
167
239
  options.clerkDomain = (0, steps_1.normalizeClerkDomain)(options.clerkDomain);
168
240
  }
241
+ const instantAuthMode = backend === "instantdb" ? await (0, steps_1.resolveInstantAuthMode)(options) : "clerk";
169
242
  const expoVersion = await (0, steps_1.resolveExpoVersion)(options);
170
243
  const clerk = backend === "convex"
171
244
  ? await (0, steps_1.promptClerkSetup)(options)
172
245
  : { publishableKey: "", domain: "", jwtCreated: false };
173
246
  const instant = backend === "instantdb"
174
- ? await (0, steps_1.promptInstantSetup)(options)
175
- : { appId: "", clerkPublishableKey: "", clerkClientName: "clerk" };
176
- if (backend === "instantdb" && instant.appId && instant.clerkPublishableKey) {
247
+ ? await (0, steps_1.promptInstantSetup)(options, instantAuthMode)
248
+ : null;
249
+ if (backend === "instantdb" &&
250
+ instant &&
251
+ instant.authMode === "clerk" &&
252
+ instant.appId &&
253
+ instant.clerkPublishableKey) {
177
254
  prompts_1.log.info("");
178
255
  prompts_1.log.info(picocolors_1.default.bold("Instant Auth setup required"));
179
256
  prompts_1.log.info(picocolors_1.default.dim("In Instant Dashboard → Auth tab, add a Clerk auth client before running the app."));
@@ -190,8 +267,8 @@ async function cmdNew(rawArgs) {
190
267
  const structureSpinner = (0, prompts_1.spinner)();
191
268
  currentStep++;
192
269
  structureSpinner.start(`Creating project structure ${stepLabel()}`);
193
- await (0, scaffold_1.createProjectDirectories)(projectDir, backend);
194
- await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion, backend));
270
+ await (0, scaffold_1.createProjectDirectories)(projectDir, backend, instantAuthMode);
271
+ await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion, backend, instantAuthMode));
195
272
  await (0, scaffold_1.writeDefaultAssetFiles)(projectDir);
196
273
  structureSpinner.stop("Project files created.");
197
274
  currentStep++;
@@ -203,7 +280,19 @@ async function cmdNew(rawArgs) {
203
280
  else {
204
281
  prompts_1.log.step(`Configuring InstantDB ${stepLabel()}`);
205
282
  await (0, steps_1.writeInstantEnvironment)(projectDir, instant);
206
- if (instant.appId && instant.clerkPublishableKey) {
283
+ if (instant && (0, scaffold_1.isMagicCodeInstantAuthMode)(instant.authMode)) {
284
+ if (instant.appId) {
285
+ prompts_1.log.success((0, scaffold_1.isCloudflareInstantAuthMode)(instant.authMode)
286
+ ? "InstantDB magic code and Cloudflare environment configured."
287
+ : "InstantDB magic code environment configured.");
288
+ }
289
+ else {
290
+ prompts_1.log.info(picocolors_1.default.dim("Set EXPO_PUBLIC_INSTANT_APP_ID in .env.local."));
291
+ }
292
+ }
293
+ else if (instant?.authMode === "clerk" &&
294
+ instant.appId &&
295
+ instant.clerkPublishableKey) {
207
296
  prompts_1.log.success("InstantDB and Clerk environment configured.");
208
297
  }
209
298
  else {
@@ -222,7 +311,7 @@ async function cmdNew(rawArgs) {
222
311
  }
223
312
  currentStep++;
224
313
  prompts_1.log.step(`Installing Expo dependencies ${stepLabel()}`);
225
- const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir, backend);
314
+ const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir, backend, instantAuthMode);
226
315
  if (resolved) {
227
316
  prompts_1.log.success("Expo dependencies resolved.");
228
317
  }
@@ -272,7 +361,7 @@ async function cmdNew(rawArgs) {
272
361
  }
273
362
  currentStep++;
274
363
  prompts_1.log.step(`Running health check ${stepLabel()}`);
275
- const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, backend, clerk.publishableKey);
364
+ const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, backend, instantAuthMode, clerk.publishableKey);
276
365
  for (const check of healthChecks) {
277
366
  if (check.ok) {
278
367
  prompts_1.log.info(` ${picocolors_1.default.green("✓")} ${check.label}`);
@@ -283,9 +372,22 @@ async function cmdNew(rawArgs) {
283
372
  }
284
373
  prompts_1.log.info("");
285
374
  if (backend === "instantdb") {
286
- const instantReady = instant.appId && instant.clerkPublishableKey;
375
+ const isCloudflareMode = instant?.authMode && (0, scaffold_1.isCloudflareInstantAuthMode)(instant.authMode);
376
+ const isMagicCodeMode = instant?.authMode && (0, scaffold_1.isMagicCodeInstantAuthMode)(instant.authMode);
377
+ const instantReady = isMagicCodeMode
378
+ ? Boolean(instant?.appId)
379
+ : Boolean(instant?.authMode === "clerk" &&
380
+ instant.appId &&
381
+ instant.clerkPublishableKey);
287
382
  if (instantReady) {
288
- prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
383
+ prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green(isCloudflareMode
384
+ ? "Your app is ready! Deploy Cloudflare storage next."
385
+ : "Your app is ready!")));
386
+ }
387
+ else if (isMagicCodeMode) {
388
+ prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green(isCloudflareMode
389
+ ? "Project scaffolded with InstantDB magic code + Cloudflare!"
390
+ : "Project scaffolded with InstantDB magic code!")));
289
391
  }
290
392
  else {
291
393
  prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Project scaffolded with InstantDB!")));
@@ -300,18 +402,35 @@ async function cmdNew(rawArgs) {
300
402
  prompts_1.log.info(` ${picocolors_1.default.cyan("bun install")} ${picocolors_1.default.dim("# or npm install")}`);
301
403
  prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
302
404
  }
303
- if (!instantReady) {
405
+ if (isCloudflareMode) {
304
406
  prompts_1.log.info("");
305
- prompts_1.log.info(picocolors_1.default.dim(" Add EXPO_PUBLIC_INSTANT_APP_ID and EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY to .env.local."));
306
- prompts_1.log.info(picocolors_1.default.dim(" Create one with: npx instant-cli init-without-files --title " +
307
- projectName));
308
- prompts_1.log.info(picocolors_1.default.dim(" In Instant Auth tab, add your Clerk app and set EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME."));
407
+ prompts_1.log.info(picocolors_1.default.dim(" Cloudflare setup:"));
408
+ prompts_1.log.info(picocolors_1.default.dim(" INSTANT_APP_ID is written to .env.local for Alchemy deploys."));
409
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:configure")}`);
410
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:login")}`);
411
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:deploy")}`);
412
+ prompts_1.log.info(picocolors_1.default.dim(" Add the printed storageWorkerUrl to EXPO_PUBLIC_STORAGE_WORKER_URL in .env.local."));
309
413
  }
310
- else {
414
+ if (isMagicCodeMode) {
415
+ if (!instantReady) {
416
+ prompts_1.log.info("");
417
+ prompts_1.log.info(picocolors_1.default.dim(" Add EXPO_PUBLIC_INSTANT_APP_ID to .env.local."));
418
+ prompts_1.log.info(picocolors_1.default.dim(" Create one with: npx instant-cli init-without-files --title " +
419
+ projectName));
420
+ }
421
+ }
422
+ else if (instant?.authMode === "clerk" && instantReady) {
311
423
  prompts_1.log.info("");
312
424
  prompts_1.log.info(picocolors_1.default.dim(" In Instant Dashboard → Auth tab, add a Clerk auth client using your publishable key."));
313
425
  prompts_1.log.info(picocolors_1.default.dim(` Use client name "${instant.clerkClientName}" (EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME).`));
314
426
  }
427
+ else {
428
+ prompts_1.log.info("");
429
+ prompts_1.log.info(picocolors_1.default.dim(" Add EXPO_PUBLIC_INSTANT_APP_ID and EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY to .env.local."));
430
+ prompts_1.log.info(picocolors_1.default.dim(" Create one with: npx instant-cli init-without-files --title " +
431
+ projectName));
432
+ prompts_1.log.info(picocolors_1.default.dim(" In Instant Auth tab, add your Clerk app and set EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME."));
433
+ }
315
434
  }
316
435
  else if (clerk.publishableKey && clerk.domain) {
317
436
  prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.alchemyRunTemplate = alchemyRunTemplate;
4
+ function alchemyRunTemplate(projectName) {
5
+ const serializedProjectName = JSON.stringify(projectName);
6
+ return `import { readFileSync } from "node:fs";
7
+ import alchemy from "alchemy";
8
+ import { D1Database, R2Bucket, Worker } from "alchemy/cloudflare";
9
+
10
+ const appName = ${serializedProjectName};
11
+ const stage = process.env.ALCHEMY_STAGE ?? "dev";
12
+ const resourceName = (suffix: string): string =>
13
+ [appName, stage, suffix].join("-");
14
+ const localEnv = readLocalEnvFile(".env.local");
15
+ const instantAppId =
16
+ process.env.INSTANT_APP_ID ??
17
+ process.env.EXPO_PUBLIC_INSTANT_APP_ID ??
18
+ localEnv.INSTANT_APP_ID ??
19
+ localEnv.EXPO_PUBLIC_INSTANT_APP_ID;
20
+ const instantAdminToken =
21
+ process.env.INSTANT_ADMIN_TOKEN ?? localEnv.INSTANT_ADMIN_TOKEN;
22
+ const allowedOrigins =
23
+ process.env.ALLOWED_ORIGINS ??
24
+ localEnv.ALLOWED_ORIGINS ??
25
+ "http://localhost:3000,http://localhost:8080,http://localhost:8081,http://localhost:19006";
26
+ const maxUploadBytes =
27
+ process.env.MAX_UPLOAD_BYTES ?? localEnv.MAX_UPLOAD_BYTES ?? "26214400";
28
+ const userStorageLimitBytes =
29
+ process.env.USER_STORAGE_LIMIT_BYTES ??
30
+ localEnv.USER_STORAGE_LIMIT_BYTES ??
31
+ "524288000";
32
+ const dailyUploadLimit =
33
+ process.env.DAILY_UPLOAD_LIMIT ?? localEnv.DAILY_UPLOAD_LIMIT ?? "100";
34
+ const allowedContentTypes =
35
+ process.env.ALLOWED_CONTENT_TYPES ??
36
+ localEnv.ALLOWED_CONTENT_TYPES ??
37
+ "image/jpeg,image/png,image/webp,application/pdf,text/plain";
38
+
39
+ interface FileSystemError extends Error {
40
+ code?: string;
41
+ }
42
+
43
+ function readLocalEnvFile(path: string): Record<string, string> {
44
+ try {
45
+ const contents = readFileSync(path, "utf8");
46
+ const entries: [string, string][] = contents
47
+ .split(/\\r?\\n/)
48
+ .map((line) => line.trim())
49
+ .filter((line) => line && !line.startsWith("#"))
50
+ .map((line) => {
51
+ const [key, ...valueParts] = line.split("=");
52
+ return [key.trim(), valueParts.join("=").trim()] as [string, string];
53
+ })
54
+ .filter(([key]) => key.length > 0);
55
+
56
+ return Object.fromEntries(
57
+ entries,
58
+ );
59
+ } catch (error) {
60
+ if (isFileSystemError(error) && error.code === "ENOENT") {
61
+ return {};
62
+ }
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ function isFileSystemError(error: unknown): error is FileSystemError {
68
+ return error instanceof Error;
69
+ }
70
+
71
+ if (!instantAppId) {
72
+ throw new Error(
73
+ "Set INSTANT_APP_ID or EXPO_PUBLIC_INSTANT_APP_ID before deploying Cloudflare storage.",
74
+ );
75
+ }
76
+
77
+ if (!instantAdminToken) {
78
+ throw new Error(
79
+ "Set INSTANT_ADMIN_TOKEN before deploying Cloudflare storage so the Worker can sync metadata to InstantDB.",
80
+ );
81
+ }
82
+
83
+ const app = await alchemy(appName, {
84
+ stage,
85
+ });
86
+
87
+ const storageBucket = await R2Bucket("storage", {
88
+ name: resourceName("storage"),
89
+ });
90
+
91
+ const metadataDatabase = await D1Database("metadata", {
92
+ name: resourceName("metadata"),
93
+ migrationsDir: "./worker/migrations",
94
+ });
95
+
96
+ export const storageApi = await Worker("storage-api", {
97
+ name: resourceName("storage-api"),
98
+ entrypoint: "./worker/src/worker.ts",
99
+ compatibilityDate: "2026-04-24",
100
+ compatibilityFlags: ["nodejs_compat"],
101
+ url: true,
102
+ bindings: {
103
+ STORAGE: storageBucket,
104
+ DB: metadataDatabase,
105
+ INSTANT_APP_ID: instantAppId,
106
+ INSTANT_ADMIN_TOKEN: instantAdminToken,
107
+ ALLOWED_ORIGINS: allowedOrigins,
108
+ MAX_UPLOAD_BYTES: maxUploadBytes,
109
+ USER_STORAGE_LIMIT_BYTES: userStorageLimitBytes,
110
+ DAILY_UPLOAD_LIMIT: dailyUploadLimit,
111
+ ALLOWED_CONTENT_TYPES: allowedContentTypes,
112
+ },
113
+ observability: {
114
+ enabled: true,
115
+ },
116
+ });
117
+
118
+ console.log({ storageWorkerUrl: storageApi.url });
119
+
120
+ await app.finalize();
121
+ `;
122
+ }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cloudflareStorageTemplate = cloudflareStorageTemplate;
4
+ function cloudflareStorageTemplate() {
5
+ return `import { instantDb } from "@/lib/instant";
6
+
7
+ const storageWorkerUrl = process.env.EXPO_PUBLIC_STORAGE_WORKER_URL;
8
+
9
+ export interface CloudflareUpload {
10
+ id: string;
11
+ key: string;
12
+ ownerId: string;
13
+ fileName: string;
14
+ contentType: string;
15
+ size: number;
16
+ createdAt: string;
17
+ url: string;
18
+ }
19
+
20
+ export interface UploadCloudflareObjectInput {
21
+ body: Blob | ArrayBuffer | Uint8Array;
22
+ contentType: string;
23
+ fileName: string;
24
+ }
25
+
26
+ export interface SyncCloudflareUploadInput {
27
+ upload: CloudflareUpload;
28
+ }
29
+
30
+ export function isCloudflareStorageConfigured(): boolean {
31
+ return Boolean(storageWorkerUrl);
32
+ }
33
+
34
+ function requireStorageWorkerUrl(): string {
35
+ if (!storageWorkerUrl) {
36
+ throw new Error(
37
+ "Missing EXPO_PUBLIC_STORAGE_WORKER_URL. Deploy the Cloudflare Worker and add its URL to .env.local.",
38
+ );
39
+ }
40
+ return storageWorkerUrl;
41
+ }
42
+
43
+ function validateUploadInput(input: UploadCloudflareObjectInput): void {
44
+ if (!input.fileName.trim()) {
45
+ throw new Error("fileName is required to upload a Cloudflare object.");
46
+ }
47
+ if (!input.contentType.trim()) {
48
+ throw new Error("contentType is required to upload a Cloudflare object.");
49
+ }
50
+ }
51
+
52
+ async function readErrorResponse(response: Response): Promise<string> {
53
+ const responseBody = await response.text();
54
+ return \`Cloudflare storage request failed. status=\${response.status} body=\${responseBody}\`;
55
+ }
56
+
57
+ async function requireInstantRefreshToken(): Promise<string> {
58
+ if (!instantDb) {
59
+ throw new Error(
60
+ "InstantDB is not configured. Add EXPO_PUBLIC_INSTANT_APP_ID before using Cloudflare storage.",
61
+ );
62
+ }
63
+
64
+ const user = await instantDb.getAuth();
65
+ const token = user?.refresh_token;
66
+ if (!token) {
67
+ throw new Error("Sign in with InstantDB before using Cloudflare storage.");
68
+ }
69
+ return token;
70
+ }
71
+
72
+ export async function uploadCloudflareObject(
73
+ input: UploadCloudflareObjectInput,
74
+ ): Promise<CloudflareUpload> {
75
+ validateUploadInput(input);
76
+ const workerUrl = requireStorageWorkerUrl();
77
+ const token = await requireInstantRefreshToken();
78
+ const response = await fetch(\`\${workerUrl}/uploads\`, {
79
+ method: "POST",
80
+ headers: {
81
+ authorization: \`Bearer \${token}\`,
82
+ "content-type": input.contentType,
83
+ "x-file-name": input.fileName,
84
+ },
85
+ body: input.body,
86
+ });
87
+
88
+ if (!response.ok) {
89
+ throw new Error(await readErrorResponse(response));
90
+ }
91
+
92
+ return (await response.json()) as CloudflareUpload;
93
+ }
94
+
95
+ export async function syncCloudflareUploadToInstant(
96
+ input: SyncCloudflareUploadInput,
97
+ ): Promise<string> {
98
+ if (!instantDb) {
99
+ throw new Error(
100
+ "InstantDB is not configured. Add EXPO_PUBLIC_INSTANT_APP_ID before reading synced Cloudflare uploads.",
101
+ );
102
+ }
103
+
104
+ requireStorageWorkerUrl();
105
+ return input.upload.id;
106
+ }
107
+ `;
108
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instantCloudflareLibTemplate = instantCloudflareLibTemplate;
4
+ function instantCloudflareLibTemplate() {
5
+ return `import "react-native-get-random-values";
6
+ import { init, i, id, type InstaQLEntity } from "@instantdb/react-native";
7
+
8
+ const appId = process.env.EXPO_PUBLIC_INSTANT_APP_ID;
9
+
10
+ export const schema = i.schema({
11
+ entities: {
12
+ todos: i.entity({
13
+ text: i.string(),
14
+ completed: i.boolean(),
15
+ createdAt: i.number().indexed(),
16
+ }),
17
+ cloudflareObjects: i.entity({
18
+ uploadId: i.string().indexed(),
19
+ key: i.string(),
20
+ ownerId: i.string().indexed(),
21
+ fileName: i.string(),
22
+ contentType: i.string(),
23
+ size: i.number(),
24
+ workerUrl: i.string(),
25
+ createdAt: i.number().indexed(),
26
+ }),
27
+ },
28
+ });
29
+
30
+ export type InstantTodo = InstaQLEntity<typeof schema, "todos">;
31
+ export type InstantCloudflareObject = InstaQLEntity<
32
+ typeof schema,
33
+ "cloudflareObjects"
34
+ >;
35
+ export const createId = id;
36
+
37
+ export const isInstantConfigured = Boolean(appId);
38
+ export const instantDb = isInstantConfigured
39
+ ? init({ appId: appId as string, schema })
40
+ : null;
41
+ `;
42
+ }