pocketbomb-dynamite 0.2.0 → 0.3.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.
Files changed (41) hide show
  1. package/README.md +24 -1
  2. package/dist/cli/check.cjs +160 -0
  3. package/dist/cli/check.cjs.map +1 -0
  4. package/dist/cli/check.d.cts +21 -0
  5. package/dist/cli/check.d.ts +21 -0
  6. package/dist/cli/check.js +157 -0
  7. package/dist/cli/check.js.map +1 -0
  8. package/dist/cli/codegen.cjs +2 -1
  9. package/dist/cli/codegen.cjs.map +1 -1
  10. package/dist/cli/codegen.js +3 -2
  11. package/dist/cli/codegen.js.map +1 -1
  12. package/dist/cli/index.cjs +32 -0
  13. package/dist/cli/index.cjs.map +1 -1
  14. package/dist/cli/index.d.cts +2 -1
  15. package/dist/cli/index.d.ts +2 -1
  16. package/dist/cli/index.js +32 -1
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/customer-account/customer-account.cjs +55 -0
  19. package/dist/customer-account/customer-account.cjs.map +1 -1
  20. package/dist/customer-account/customer-account.d.cts +33 -0
  21. package/dist/customer-account/customer-account.d.ts +33 -0
  22. package/dist/customer-account/customer-account.js +52 -1
  23. package/dist/customer-account/customer-account.js.map +1 -1
  24. package/dist/customer-account/index.cjs +4 -0
  25. package/dist/customer-account/index.d.cts +2 -2
  26. package/dist/customer-account/index.d.ts +2 -2
  27. package/dist/customer-account/index.js +1 -1
  28. package/dist/react/hooks.cjs +37 -5
  29. package/dist/react/hooks.cjs.map +1 -1
  30. package/dist/react/hooks.d.cts +12 -2
  31. package/dist/react/hooks.d.ts +12 -2
  32. package/dist/react/hooks.js +37 -5
  33. package/dist/react/hooks.js.map +1 -1
  34. package/dist/react/index.d.cts +2 -2
  35. package/dist/react/index.d.ts +2 -2
  36. package/dist/ts-plugin/index.d.cts +2 -1
  37. package/dist/vite/index.cjs +6 -2
  38. package/dist/vite/index.cjs.map +1 -1
  39. package/dist/vite/index.js +6 -2
  40. package/dist/vite/index.js.map +1 -1
  41. package/package.json +7 -3
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["DEFAULT_SCHEMA_FILE","DEFAULT_ENV_FILE","fs","path","parseArgs","codegen","watchCodegen","spawn"],"sources":["../../src/cli/index.ts"],"sourcesContent":["// The `dynamite` CLI (Node.js only): codegen, dev, info.\nimport { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport type { Fuse } from \"pocketbomb-fuse/node\";\nimport {\n codegen,\n DEFAULT_ENV_FILE,\n DEFAULT_SCHEMA_FILE,\n watchCodegen,\n type GenerateResult,\n} from \"./codegen\";\n\nconst HELP = `dynamite <command> [options]\n\nCommands:\n codegen Introspect the PocketBomb GraphQL API and write the schema + the gql.tada env\n --url <url> (default $POCKETBOMB_URL)\n --public-key <key> (default $POCKETBOMB_PUBLIC_KEY)\n --schema <file> (default ${DEFAULT_SCHEMA_FILE})\n --env <file> (default ${DEFAULT_ENV_FILE})\n --watch [--interval <ms>]\n --fuse [--seed <module>] start a local server with fuse for the codegen\n dev Start fuse (unless $POCKETBOMB_URL is set), the codegen watcher and the app\n --seed <module> --data-dir <dir>\n --entry <file> run a custom server entry with Node.js (default: the Vite dev server)\n --port <port> the Vite dev server port\n info Print the PocketBomb environment and the generated files status\n`;\n\ntype Io = { log: (line: string) => void; error: (line: string) => void };\n\nconst defaultIo: Io = {\n log: (line) => process.stdout.write(line + \"\\n\"),\n error: (line) => process.stderr.write(line + \"\\n\"),\n};\n\nfunction readPackageVersion(): string {\n try {\n const pkg = JSON.parse(fs.readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\"));\n return pkg.version;\n } catch {\n return \"unknown\";\n }\n}\n\nasync function startFuseFromFlags(\n flags: { seed?: string; \"data-dir\"?: string },\n io: Io,\n): Promise<Fuse> {\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n const fuse = await startFuse({\n seed: flags.seed,\n dataDir: flags[\"data-dir\"],\n log: (line) => io.error(\"[fuse] \" + line),\n });\n Object.assign(process.env, fuse.env);\n io.error(\"[fuse] PocketBomb running on \" + fuse.url);\n return fuse;\n}\n\nfunction describeResult(result: GenerateResult, cwd: string): string {\n return (\n (result.changed ? \"generated \" : \"up to date: \") +\n path.relative(cwd, result.schemaFile) +\n \", \" +\n path.relative(cwd, result.envFile)\n );\n}\n\nasync function runCodegen(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n url: { type: \"string\" },\n \"public-key\": { type: \"string\" },\n schema: { type: \"string\" },\n env: { type: \"string\" },\n watch: { type: \"boolean\" },\n interval: { type: \"string\" },\n fuse: { type: \"boolean\" },\n seed: { type: \"string\" },\n cwd: { type: \"string\" },\n },\n });\n\n const cwd = path.resolve(values.cwd || process.cwd());\n const fuse = values.fuse ? await startFuseFromFlags(values, io) : null;\n\n try {\n const url = values.url || process.env.POCKETBOMB_URL;\n if (!url) {\n io.error(\"dynamite codegen: missing --url (or $POCKETBOMB_URL)\");\n return 2;\n }\n\n const options = {\n url,\n publicKey: values[\"public-key\"] || process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n schemaFile: values.schema,\n envFile: values.env,\n };\n\n if (!values.watch) {\n io.log(describeResult(await codegen(options), cwd));\n return 0;\n }\n\n const stop = watchCodegen({\n ...options,\n interval: values.interval ? parseInt(values.interval, 10) : undefined,\n onChange: (result) => io.log(describeResult(result, cwd)),\n onError: (err) => io.error(\"codegen: \" + String((err as Error)?.message || err)),\n });\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n stop();\n return 0;\n } finally {\n await fuse?.stop();\n }\n}\n\nasync function runDev(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n seed: { type: \"string\" },\n \"data-dir\": { type: \"string\" },\n entry: { type: \"string\" },\n port: { type: \"string\" },\n \"no-codegen\": { type: \"boolean\" },\n },\n });\n\n const cwd = process.cwd();\n const fuse = process.env.POCKETBOMB_URL ? null : await startFuseFromFlags(values, io);\n\n let stopCodegen = () => {};\n if (!values[\"no-codegen\"] && process.env.POCKETBOMB_PUBLIC_KEY) {\n stopCodegen = watchCodegen({\n url: process.env.POCKETBOMB_URL!,\n publicKey: process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n onChange: (result) => io.error(\"[codegen] \" + describeResult(result, cwd)),\n onError: (err) => io.error(\"[codegen] \" + String((err as Error)?.message || err)),\n });\n }\n\n const cleanup = async () => {\n stopCodegen();\n await fuse?.stop();\n };\n\n if (values.entry) {\n const child = spawn(process.execPath, [path.resolve(cwd, values.entry)], {\n stdio: \"inherit\",\n env: { ...process.env, NODE_ENV: process.env.NODE_ENV || \"development\" },\n });\n const forward = (signal: NodeJS.Signals) => child.kill(signal);\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n const code: number = await new Promise((resolve) => child.on(\"exit\", (c) => resolve(c ?? 1)));\n await cleanup();\n return code;\n }\n\n const { createServer } = await import(\"vite\");\n const server = await createServer({\n root: cwd,\n server: values.port ? { port: parseInt(values.port, 10) } : undefined,\n });\n await server.listen();\n server.printUrls();\n\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n await server.close();\n await cleanup();\n return 0;\n}\n\nasync function runInfo(io: Io): Promise<number> {\n const cwd = process.cwd();\n const mask = (v?: string) => (v ? v.substring(0, 6) + \"…\" : \"(not set)\");\n\n io.log(\"pocketbomb-dynamite \" + readPackageVersion());\n io.log(\"node \" + process.version);\n io.log(\"POCKETBOMB_URL \" + (process.env.POCKETBOMB_URL || \"(not set)\"));\n io.log(\"POCKETBOMB_PUBLIC_KEY \" + mask(process.env.POCKETBOMB_PUBLIC_KEY));\n io.log(\"POCKETBOMB_PRIVATE_KEY \" + (process.env.POCKETBOMB_PRIVATE_KEY ? \"(set)\" : \"(not set)\"));\n\n for (const file of [DEFAULT_SCHEMA_FILE, DEFAULT_ENV_FILE]) {\n const full = path.resolve(cwd, file);\n io.log(\n file.padEnd(23) +\n (fs.existsSync(full) ? \"generated \" + fs.statSync(full).mtime.toISOString() : \"missing\"),\n );\n }\n\n try {\n const { resolveFuseBinary } = await import(\"pocketbomb-fuse/node\");\n const resolved = resolveFuseBinary({ log: () => {} });\n io.log(\"fuse binary \" + resolved.bin + \" (\" + resolved.source + \")\");\n } catch (err) {\n io.log(\"fuse binary \" + String((err as Error).message));\n }\n\n return 0;\n}\n\n/**\n * Runs the CLI with the provided arguments (without the node and script paths).\n */\nexport async function run(argv: Array<string>, io: Io = defaultIo): Promise<number> {\n const [command, ...rest] = argv;\n\n switch (command) {\n case \"codegen\":\n return runCodegen(rest, io);\n case \"dev\":\n return runDev(rest, io);\n case \"info\":\n return runInfo(io);\n case \"--version\":\n case \"-v\":\n io.log(readPackageVersion());\n return 0;\n case undefined:\n case \"help\":\n case \"--help\":\n case \"-h\":\n io.log(HELP);\n return 0;\n default:\n io.error(\"Unknown command: \" + command + \"\\n\\n\" + HELP);\n return 2;\n }\n}\n\nexport * from \"./codegen\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["DEFAULT_SCHEMA_FILE","DEFAULT_ENV_FILE","fs","path","parseArgs","codegen","watchCodegen","spawn","check"],"sources":["../../src/cli/index.ts"],"sourcesContent":["// The `dynamite` CLI (Node.js only): codegen, dev, info.\nimport { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport type { Fuse } from \"pocketbomb-fuse/node\";\nimport { check } from \"./check\";\nimport {\n codegen,\n DEFAULT_ENV_FILE,\n DEFAULT_SCHEMA_FILE,\n watchCodegen,\n type GenerateResult,\n} from \"./codegen\";\n\nconst HELP = `dynamite <command> [options]\n\nCommands:\n codegen Introspect the PocketBomb GraphQL API and write the schema + the gql.tada env\n --url <url> (default $POCKETBOMB_URL)\n --public-key <key> (default $POCKETBOMB_PUBLIC_KEY)\n --schema <file> (default ${DEFAULT_SCHEMA_FILE})\n --env <file> (default ${DEFAULT_ENV_FILE})\n --watch [--interval <ms>]\n --fuse [--seed <module>] start a local server with fuse for the codegen\n dev Start fuse (unless $POCKETBOMB_URL is set), the codegen watcher and the app\n --seed <module> --data-dir <dir>\n --entry <file> run a custom server entry with Node.js (default: the Vite dev server)\n --port <port> the Vite dev server port\n check Validate the graphql() documents of the project against the schema\n --schema <file> (default ${DEFAULT_SCHEMA_FILE})\n info Print the PocketBomb environment and the generated files status\n`;\n\ntype Io = { log: (line: string) => void; error: (line: string) => void };\n\nconst defaultIo: Io = {\n log: (line) => process.stdout.write(line + \"\\n\"),\n error: (line) => process.stderr.write(line + \"\\n\"),\n};\n\nfunction readPackageVersion(): string {\n try {\n const pkg = JSON.parse(fs.readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\"));\n return pkg.version;\n } catch {\n return \"unknown\";\n }\n}\n\nasync function startFuseFromFlags(\n flags: { seed?: string; \"data-dir\"?: string },\n io: Io,\n): Promise<Fuse> {\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n const fuse = await startFuse({\n seed: flags.seed,\n dataDir: flags[\"data-dir\"],\n log: (line) => io.error(\"[fuse] \" + line),\n });\n Object.assign(process.env, fuse.env);\n io.error(\"[fuse] PocketBomb running on \" + fuse.url);\n return fuse;\n}\n\nfunction describeResult(result: GenerateResult, cwd: string): string {\n return (\n (result.changed ? \"generated \" : \"up to date: \") +\n path.relative(cwd, result.schemaFile) +\n \", \" +\n path.relative(cwd, result.envFile)\n );\n}\n\nasync function runCodegen(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n url: { type: \"string\" },\n \"public-key\": { type: \"string\" },\n schema: { type: \"string\" },\n env: { type: \"string\" },\n watch: { type: \"boolean\" },\n interval: { type: \"string\" },\n fuse: { type: \"boolean\" },\n seed: { type: \"string\" },\n cwd: { type: \"string\" },\n },\n });\n\n const cwd = path.resolve(values.cwd || process.cwd());\n const fuse = values.fuse ? await startFuseFromFlags(values, io) : null;\n\n try {\n const url = values.url || process.env.POCKETBOMB_URL;\n if (!url) {\n io.error(\"dynamite codegen: missing --url (or $POCKETBOMB_URL)\");\n return 2;\n }\n\n const options = {\n url,\n publicKey: values[\"public-key\"] || process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n schemaFile: values.schema,\n envFile: values.env,\n };\n\n if (!values.watch) {\n io.log(describeResult(await codegen(options), cwd));\n return 0;\n }\n\n const stop = watchCodegen({\n ...options,\n interval: values.interval ? parseInt(values.interval, 10) : undefined,\n onChange: (result) => io.log(describeResult(result, cwd)),\n onError: (err) => io.error(\"codegen: \" + String((err as Error)?.message || err)),\n });\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n stop();\n return 0;\n } finally {\n await fuse?.stop();\n }\n}\n\nasync function runDev(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n seed: { type: \"string\" },\n \"data-dir\": { type: \"string\" },\n entry: { type: \"string\" },\n port: { type: \"string\" },\n \"no-codegen\": { type: \"boolean\" },\n },\n });\n\n const cwd = process.cwd();\n const fuse = process.env.POCKETBOMB_URL ? null : await startFuseFromFlags(values, io);\n\n let stopCodegen = () => {};\n if (!values[\"no-codegen\"] && process.env.POCKETBOMB_PUBLIC_KEY) {\n stopCodegen = watchCodegen({\n url: process.env.POCKETBOMB_URL!,\n publicKey: process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n onChange: (result) => io.error(\"[codegen] \" + describeResult(result, cwd)),\n onError: (err) => io.error(\"[codegen] \" + String((err as Error)?.message || err)),\n });\n }\n\n const cleanup = async () => {\n stopCodegen();\n await fuse?.stop();\n };\n\n if (values.entry) {\n const child = spawn(process.execPath, [path.resolve(cwd, values.entry)], {\n stdio: \"inherit\",\n env: { ...process.env, NODE_ENV: process.env.NODE_ENV || \"development\" },\n });\n const forward = (signal: NodeJS.Signals) => child.kill(signal);\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n const code: number = await new Promise((resolve) => child.on(\"exit\", (c) => resolve(c ?? 1)));\n await cleanup();\n return code;\n }\n\n const { createServer } = await import(\"vite\");\n const server = await createServer({\n root: cwd,\n server: values.port ? { port: parseInt(values.port, 10) } : undefined,\n });\n await server.listen();\n server.printUrls();\n\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n await server.close();\n await cleanup();\n return 0;\n}\n\nasync function runCheck(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: { schema: { type: \"string\" }, cwd: { type: \"string\" } },\n });\n const cwd = path.resolve(values.cwd || process.cwd());\n\n let result;\n try {\n result = await check({ cwd, schemaFile: values.schema });\n } catch (err) {\n io.error(\"dynamite check: \" + (err as Error).message);\n return 2;\n }\n\n for (const p of result.problems) {\n io.error(`${path.relative(cwd, p.file)}:${p.line}:${p.column} ${p.message}`);\n }\n if (result.problems.length) {\n io.error(\n `dynamite check: ${result.problems.length} problem(s) in ${result.documents} documents`,\n );\n return 1;\n }\n io.log(`dynamite check: ${result.documents} documents OK`);\n return 0;\n}\n\nasync function runInfo(io: Io): Promise<number> {\n const cwd = process.cwd();\n const mask = (v?: string) => (v ? v.substring(0, 6) + \"…\" : \"(not set)\");\n\n io.log(\"pocketbomb-dynamite \" + readPackageVersion());\n io.log(\"node \" + process.version);\n io.log(\"POCKETBOMB_URL \" + (process.env.POCKETBOMB_URL || \"(not set)\"));\n io.log(\"POCKETBOMB_PUBLIC_KEY \" + mask(process.env.POCKETBOMB_PUBLIC_KEY));\n io.log(\"POCKETBOMB_PRIVATE_KEY \" + (process.env.POCKETBOMB_PRIVATE_KEY ? \"(set)\" : \"(not set)\"));\n\n for (const file of [DEFAULT_SCHEMA_FILE, DEFAULT_ENV_FILE]) {\n const full = path.resolve(cwd, file);\n io.log(\n file.padEnd(23) +\n (fs.existsSync(full) ? \"generated \" + fs.statSync(full).mtime.toISOString() : \"missing\"),\n );\n }\n\n try {\n const { resolveFuseBinary } = await import(\"pocketbomb-fuse/node\");\n const resolved = resolveFuseBinary({ log: () => {} });\n io.log(\"fuse binary \" + resolved.bin + \" (\" + resolved.source + \")\");\n } catch (err) {\n io.log(\"fuse binary \" + String((err as Error).message));\n }\n\n return 0;\n}\n\n/**\n * Runs the CLI with the provided arguments (without the node and script paths).\n */\nexport async function run(argv: Array<string>, io: Io = defaultIo): Promise<number> {\n const [command, ...rest] = argv;\n\n switch (command) {\n case \"codegen\":\n return runCodegen(rest, io);\n case \"dev\":\n return runDev(rest, io);\n case \"check\":\n return runCheck(rest, io);\n case \"info\":\n return runInfo(io);\n case \"--version\":\n case \"-v\":\n io.log(readPackageVersion());\n return 0;\n case undefined:\n case \"help\":\n case \"--help\":\n case \"-h\":\n io.log(HELP);\n return 0;\n default:\n io.error(\"Unknown command: \" + command + \"\\n\\n\" + HELP);\n return 2;\n }\n}\n\nexport * from \"./check\";\nexport * from \"./codegen\";\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { CheckProblem, CheckResult, check } from "./check.cjs";
1
2
  import { DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE } from "../shared/constants.cjs";
2
3
  import { CodegenOptions, GenerateOptions, GenerateResult, IntrospectOptions, WatchOptions, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, watchCodegen, withoutEmptyDescriptions } from "./codegen.cjs";
3
4
  type Io = {
@@ -8,5 +9,5 @@ type Io = {
8
9
  * Runs the CLI with the provided arguments (without the node and script paths).
9
10
  */
10
11
  export declare function run(argv: Array<string>, io?: Io): Promise<number>;
11
- export { CodegenOptions, DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE, GenerateOptions, GenerateResult, IntrospectOptions, WatchOptions, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, watchCodegen, withoutEmptyDescriptions };
12
+ export { CheckProblem, CheckResult, CodegenOptions, DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE, GenerateOptions, GenerateResult, IntrospectOptions, WatchOptions, check, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, watchCodegen, withoutEmptyDescriptions };
12
13
  //# sourceMappingURL=index.d.cts.map
@@ -1,3 +1,4 @@
1
+ import { CheckProblem, CheckResult, check } from "./check.js";
1
2
  import { DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE } from "../shared/constants.js";
2
3
  import { CodegenOptions, GenerateOptions, GenerateResult, IntrospectOptions, WatchOptions, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, watchCodegen, withoutEmptyDescriptions } from "./codegen.js";
3
4
  type Io = {
@@ -8,5 +9,5 @@ type Io = {
8
9
  * Runs the CLI with the provided arguments (without the node and script paths).
9
10
  */
10
11
  export declare function run(argv: Array<string>, io?: Io): Promise<number>;
11
- export { CodegenOptions, DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE, GenerateOptions, GenerateResult, IntrospectOptions, WatchOptions, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, watchCodegen, withoutEmptyDescriptions };
12
+ export { CheckProblem, CheckResult, CodegenOptions, DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE, GenerateOptions, GenerateResult, IntrospectOptions, WatchOptions, check, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, watchCodegen, withoutEmptyDescriptions };
12
13
  //# sourceMappingURL=index.d.ts.map
package/dist/cli/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE } from "../shared/constants.js";
2
2
  import { codegen, generate, introspect, introspectionToEnv, introspectionToSDL, watchCodegen, withoutEmptyDescriptions } from "./codegen.js";
3
+ import { check } from "./check.js";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import { spawn } from "node:child_process";
@@ -19,6 +20,8 @@ Commands:
19
20
  --seed <module> --data-dir <dir>
20
21
  --entry <file> run a custom server entry with Node.js (default: the Vite dev server)
21
22
  --port <port> the Vite dev server port
23
+ check Validate the graphql() documents of the project against the schema
24
+ --schema <file> (default ${DEFAULT_SCHEMA_FILE})
22
25
  info Print the PocketBomb environment and the generated files status
23
26
  `;
24
27
  const defaultIo = {
@@ -151,6 +154,33 @@ async function runDev(argv, io) {
151
154
  await cleanup();
152
155
  return 0;
153
156
  }
157
+ async function runCheck(argv, io) {
158
+ const { values } = parseArgs({
159
+ args: argv,
160
+ options: {
161
+ schema: { type: "string" },
162
+ cwd: { type: "string" }
163
+ }
164
+ });
165
+ const cwd = path.resolve(values.cwd || process.cwd());
166
+ let result;
167
+ try {
168
+ result = await check({
169
+ cwd,
170
+ schemaFile: values.schema
171
+ });
172
+ } catch (err) {
173
+ io.error("dynamite check: " + err.message);
174
+ return 2;
175
+ }
176
+ for (const p of result.problems) io.error(`${path.relative(cwd, p.file)}:${p.line}:${p.column} ${p.message}`);
177
+ if (result.problems.length) {
178
+ io.error(`dynamite check: ${result.problems.length} problem(s) in ${result.documents} documents`);
179
+ return 1;
180
+ }
181
+ io.log(`dynamite check: ${result.documents} documents OK`);
182
+ return 0;
183
+ }
154
184
  async function runInfo(io) {
155
185
  const cwd = process.cwd();
156
186
  const mask = (v) => v ? v.substring(0, 6) + "…" : "(not set)";
@@ -180,6 +210,7 @@ async function run(argv, io = defaultIo) {
180
210
  switch (command) {
181
211
  case "codegen": return runCodegen(rest, io);
182
212
  case "dev": return runDev(rest, io);
213
+ case "check": return runCheck(rest, io);
183
214
  case "info": return runInfo(io);
184
215
  case "--version":
185
216
  case "-v":
@@ -196,6 +227,6 @@ async function run(argv, io = defaultIo) {
196
227
  return 2;
197
228
  }
198
229
  }
199
- export { DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, run, watchCodegen, withoutEmptyDescriptions };
230
+ export { DEFAULT_ENV_FILE, DEFAULT_SCHEMA_FILE, check, codegen, generate, introspect, introspectionToEnv, introspectionToSDL, run, watchCodegen, withoutEmptyDescriptions };
200
231
 
201
232
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["// The `dynamite` CLI (Node.js only): codegen, dev, info.\nimport { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport type { Fuse } from \"pocketbomb-fuse/node\";\nimport {\n codegen,\n DEFAULT_ENV_FILE,\n DEFAULT_SCHEMA_FILE,\n watchCodegen,\n type GenerateResult,\n} from \"./codegen\";\n\nconst HELP = `dynamite <command> [options]\n\nCommands:\n codegen Introspect the PocketBomb GraphQL API and write the schema + the gql.tada env\n --url <url> (default $POCKETBOMB_URL)\n --public-key <key> (default $POCKETBOMB_PUBLIC_KEY)\n --schema <file> (default ${DEFAULT_SCHEMA_FILE})\n --env <file> (default ${DEFAULT_ENV_FILE})\n --watch [--interval <ms>]\n --fuse [--seed <module>] start a local server with fuse for the codegen\n dev Start fuse (unless $POCKETBOMB_URL is set), the codegen watcher and the app\n --seed <module> --data-dir <dir>\n --entry <file> run a custom server entry with Node.js (default: the Vite dev server)\n --port <port> the Vite dev server port\n info Print the PocketBomb environment and the generated files status\n`;\n\ntype Io = { log: (line: string) => void; error: (line: string) => void };\n\nconst defaultIo: Io = {\n log: (line) => process.stdout.write(line + \"\\n\"),\n error: (line) => process.stderr.write(line + \"\\n\"),\n};\n\nfunction readPackageVersion(): string {\n try {\n const pkg = JSON.parse(fs.readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\"));\n return pkg.version;\n } catch {\n return \"unknown\";\n }\n}\n\nasync function startFuseFromFlags(\n flags: { seed?: string; \"data-dir\"?: string },\n io: Io,\n): Promise<Fuse> {\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n const fuse = await startFuse({\n seed: flags.seed,\n dataDir: flags[\"data-dir\"],\n log: (line) => io.error(\"[fuse] \" + line),\n });\n Object.assign(process.env, fuse.env);\n io.error(\"[fuse] PocketBomb running on \" + fuse.url);\n return fuse;\n}\n\nfunction describeResult(result: GenerateResult, cwd: string): string {\n return (\n (result.changed ? \"generated \" : \"up to date: \") +\n path.relative(cwd, result.schemaFile) +\n \", \" +\n path.relative(cwd, result.envFile)\n );\n}\n\nasync function runCodegen(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n url: { type: \"string\" },\n \"public-key\": { type: \"string\" },\n schema: { type: \"string\" },\n env: { type: \"string\" },\n watch: { type: \"boolean\" },\n interval: { type: \"string\" },\n fuse: { type: \"boolean\" },\n seed: { type: \"string\" },\n cwd: { type: \"string\" },\n },\n });\n\n const cwd = path.resolve(values.cwd || process.cwd());\n const fuse = values.fuse ? await startFuseFromFlags(values, io) : null;\n\n try {\n const url = values.url || process.env.POCKETBOMB_URL;\n if (!url) {\n io.error(\"dynamite codegen: missing --url (or $POCKETBOMB_URL)\");\n return 2;\n }\n\n const options = {\n url,\n publicKey: values[\"public-key\"] || process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n schemaFile: values.schema,\n envFile: values.env,\n };\n\n if (!values.watch) {\n io.log(describeResult(await codegen(options), cwd));\n return 0;\n }\n\n const stop = watchCodegen({\n ...options,\n interval: values.interval ? parseInt(values.interval, 10) : undefined,\n onChange: (result) => io.log(describeResult(result, cwd)),\n onError: (err) => io.error(\"codegen: \" + String((err as Error)?.message || err)),\n });\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n stop();\n return 0;\n } finally {\n await fuse?.stop();\n }\n}\n\nasync function runDev(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n seed: { type: \"string\" },\n \"data-dir\": { type: \"string\" },\n entry: { type: \"string\" },\n port: { type: \"string\" },\n \"no-codegen\": { type: \"boolean\" },\n },\n });\n\n const cwd = process.cwd();\n const fuse = process.env.POCKETBOMB_URL ? null : await startFuseFromFlags(values, io);\n\n let stopCodegen = () => {};\n if (!values[\"no-codegen\"] && process.env.POCKETBOMB_PUBLIC_KEY) {\n stopCodegen = watchCodegen({\n url: process.env.POCKETBOMB_URL!,\n publicKey: process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n onChange: (result) => io.error(\"[codegen] \" + describeResult(result, cwd)),\n onError: (err) => io.error(\"[codegen] \" + String((err as Error)?.message || err)),\n });\n }\n\n const cleanup = async () => {\n stopCodegen();\n await fuse?.stop();\n };\n\n if (values.entry) {\n const child = spawn(process.execPath, [path.resolve(cwd, values.entry)], {\n stdio: \"inherit\",\n env: { ...process.env, NODE_ENV: process.env.NODE_ENV || \"development\" },\n });\n const forward = (signal: NodeJS.Signals) => child.kill(signal);\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n const code: number = await new Promise((resolve) => child.on(\"exit\", (c) => resolve(c ?? 1)));\n await cleanup();\n return code;\n }\n\n const { createServer } = await import(\"vite\");\n const server = await createServer({\n root: cwd,\n server: values.port ? { port: parseInt(values.port, 10) } : undefined,\n });\n await server.listen();\n server.printUrls();\n\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n await server.close();\n await cleanup();\n return 0;\n}\n\nasync function runInfo(io: Io): Promise<number> {\n const cwd = process.cwd();\n const mask = (v?: string) => (v ? v.substring(0, 6) + \"…\" : \"(not set)\");\n\n io.log(\"pocketbomb-dynamite \" + readPackageVersion());\n io.log(\"node \" + process.version);\n io.log(\"POCKETBOMB_URL \" + (process.env.POCKETBOMB_URL || \"(not set)\"));\n io.log(\"POCKETBOMB_PUBLIC_KEY \" + mask(process.env.POCKETBOMB_PUBLIC_KEY));\n io.log(\"POCKETBOMB_PRIVATE_KEY \" + (process.env.POCKETBOMB_PRIVATE_KEY ? \"(set)\" : \"(not set)\"));\n\n for (const file of [DEFAULT_SCHEMA_FILE, DEFAULT_ENV_FILE]) {\n const full = path.resolve(cwd, file);\n io.log(\n file.padEnd(23) +\n (fs.existsSync(full) ? \"generated \" + fs.statSync(full).mtime.toISOString() : \"missing\"),\n );\n }\n\n try {\n const { resolveFuseBinary } = await import(\"pocketbomb-fuse/node\");\n const resolved = resolveFuseBinary({ log: () => {} });\n io.log(\"fuse binary \" + resolved.bin + \" (\" + resolved.source + \")\");\n } catch (err) {\n io.log(\"fuse binary \" + String((err as Error).message));\n }\n\n return 0;\n}\n\n/**\n * Runs the CLI with the provided arguments (without the node and script paths).\n */\nexport async function run(argv: Array<string>, io: Io = defaultIo): Promise<number> {\n const [command, ...rest] = argv;\n\n switch (command) {\n case \"codegen\":\n return runCodegen(rest, io);\n case \"dev\":\n return runDev(rest, io);\n case \"info\":\n return runInfo(io);\n case \"--version\":\n case \"-v\":\n io.log(readPackageVersion());\n return 0;\n case undefined:\n case \"help\":\n case \"--help\":\n case \"-h\":\n io.log(HELP);\n return 0;\n default:\n io.error(\"Unknown command: \" + command + \"\\n\\n\" + HELP);\n return 2;\n }\n}\n\nexport * from \"./codegen\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["// The `dynamite` CLI (Node.js only): codegen, dev, info.\nimport { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport type { Fuse } from \"pocketbomb-fuse/node\";\nimport { check } from \"./check\";\nimport {\n codegen,\n DEFAULT_ENV_FILE,\n DEFAULT_SCHEMA_FILE,\n watchCodegen,\n type GenerateResult,\n} from \"./codegen\";\n\nconst HELP = `dynamite <command> [options]\n\nCommands:\n codegen Introspect the PocketBomb GraphQL API and write the schema + the gql.tada env\n --url <url> (default $POCKETBOMB_URL)\n --public-key <key> (default $POCKETBOMB_PUBLIC_KEY)\n --schema <file> (default ${DEFAULT_SCHEMA_FILE})\n --env <file> (default ${DEFAULT_ENV_FILE})\n --watch [--interval <ms>]\n --fuse [--seed <module>] start a local server with fuse for the codegen\n dev Start fuse (unless $POCKETBOMB_URL is set), the codegen watcher and the app\n --seed <module> --data-dir <dir>\n --entry <file> run a custom server entry with Node.js (default: the Vite dev server)\n --port <port> the Vite dev server port\n check Validate the graphql() documents of the project against the schema\n --schema <file> (default ${DEFAULT_SCHEMA_FILE})\n info Print the PocketBomb environment and the generated files status\n`;\n\ntype Io = { log: (line: string) => void; error: (line: string) => void };\n\nconst defaultIo: Io = {\n log: (line) => process.stdout.write(line + \"\\n\"),\n error: (line) => process.stderr.write(line + \"\\n\"),\n};\n\nfunction readPackageVersion(): string {\n try {\n const pkg = JSON.parse(fs.readFileSync(new URL(\"../../package.json\", import.meta.url), \"utf8\"));\n return pkg.version;\n } catch {\n return \"unknown\";\n }\n}\n\nasync function startFuseFromFlags(\n flags: { seed?: string; \"data-dir\"?: string },\n io: Io,\n): Promise<Fuse> {\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n const fuse = await startFuse({\n seed: flags.seed,\n dataDir: flags[\"data-dir\"],\n log: (line) => io.error(\"[fuse] \" + line),\n });\n Object.assign(process.env, fuse.env);\n io.error(\"[fuse] PocketBomb running on \" + fuse.url);\n return fuse;\n}\n\nfunction describeResult(result: GenerateResult, cwd: string): string {\n return (\n (result.changed ? \"generated \" : \"up to date: \") +\n path.relative(cwd, result.schemaFile) +\n \", \" +\n path.relative(cwd, result.envFile)\n );\n}\n\nasync function runCodegen(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n url: { type: \"string\" },\n \"public-key\": { type: \"string\" },\n schema: { type: \"string\" },\n env: { type: \"string\" },\n watch: { type: \"boolean\" },\n interval: { type: \"string\" },\n fuse: { type: \"boolean\" },\n seed: { type: \"string\" },\n cwd: { type: \"string\" },\n },\n });\n\n const cwd = path.resolve(values.cwd || process.cwd());\n const fuse = values.fuse ? await startFuseFromFlags(values, io) : null;\n\n try {\n const url = values.url || process.env.POCKETBOMB_URL;\n if (!url) {\n io.error(\"dynamite codegen: missing --url (or $POCKETBOMB_URL)\");\n return 2;\n }\n\n const options = {\n url,\n publicKey: values[\"public-key\"] || process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n schemaFile: values.schema,\n envFile: values.env,\n };\n\n if (!values.watch) {\n io.log(describeResult(await codegen(options), cwd));\n return 0;\n }\n\n const stop = watchCodegen({\n ...options,\n interval: values.interval ? parseInt(values.interval, 10) : undefined,\n onChange: (result) => io.log(describeResult(result, cwd)),\n onError: (err) => io.error(\"codegen: \" + String((err as Error)?.message || err)),\n });\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n stop();\n return 0;\n } finally {\n await fuse?.stop();\n }\n}\n\nasync function runDev(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: {\n seed: { type: \"string\" },\n \"data-dir\": { type: \"string\" },\n entry: { type: \"string\" },\n port: { type: \"string\" },\n \"no-codegen\": { type: \"boolean\" },\n },\n });\n\n const cwd = process.cwd();\n const fuse = process.env.POCKETBOMB_URL ? null : await startFuseFromFlags(values, io);\n\n let stopCodegen = () => {};\n if (!values[\"no-codegen\"] && process.env.POCKETBOMB_PUBLIC_KEY) {\n stopCodegen = watchCodegen({\n url: process.env.POCKETBOMB_URL!,\n publicKey: process.env.POCKETBOMB_PUBLIC_KEY,\n cwd,\n onChange: (result) => io.error(\"[codegen] \" + describeResult(result, cwd)),\n onError: (err) => io.error(\"[codegen] \" + String((err as Error)?.message || err)),\n });\n }\n\n const cleanup = async () => {\n stopCodegen();\n await fuse?.stop();\n };\n\n if (values.entry) {\n const child = spawn(process.execPath, [path.resolve(cwd, values.entry)], {\n stdio: \"inherit\",\n env: { ...process.env, NODE_ENV: process.env.NODE_ENV || \"development\" },\n });\n const forward = (signal: NodeJS.Signals) => child.kill(signal);\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n const code: number = await new Promise((resolve) => child.on(\"exit\", (c) => resolve(c ?? 1)));\n await cleanup();\n return code;\n }\n\n const { createServer } = await import(\"vite\");\n const server = await createServer({\n root: cwd,\n server: values.port ? { port: parseInt(values.port, 10) } : undefined,\n });\n await server.listen();\n server.printUrls();\n\n await new Promise<void>((resolve) => {\n process.once(\"SIGINT\", resolve);\n process.once(\"SIGTERM\", resolve);\n });\n await server.close();\n await cleanup();\n return 0;\n}\n\nasync function runCheck(argv: Array<string>, io: Io): Promise<number> {\n const { values } = parseArgs({\n args: argv,\n options: { schema: { type: \"string\" }, cwd: { type: \"string\" } },\n });\n const cwd = path.resolve(values.cwd || process.cwd());\n\n let result;\n try {\n result = await check({ cwd, schemaFile: values.schema });\n } catch (err) {\n io.error(\"dynamite check: \" + (err as Error).message);\n return 2;\n }\n\n for (const p of result.problems) {\n io.error(`${path.relative(cwd, p.file)}:${p.line}:${p.column} ${p.message}`);\n }\n if (result.problems.length) {\n io.error(\n `dynamite check: ${result.problems.length} problem(s) in ${result.documents} documents`,\n );\n return 1;\n }\n io.log(`dynamite check: ${result.documents} documents OK`);\n return 0;\n}\n\nasync function runInfo(io: Io): Promise<number> {\n const cwd = process.cwd();\n const mask = (v?: string) => (v ? v.substring(0, 6) + \"…\" : \"(not set)\");\n\n io.log(\"pocketbomb-dynamite \" + readPackageVersion());\n io.log(\"node \" + process.version);\n io.log(\"POCKETBOMB_URL \" + (process.env.POCKETBOMB_URL || \"(not set)\"));\n io.log(\"POCKETBOMB_PUBLIC_KEY \" + mask(process.env.POCKETBOMB_PUBLIC_KEY));\n io.log(\"POCKETBOMB_PRIVATE_KEY \" + (process.env.POCKETBOMB_PRIVATE_KEY ? \"(set)\" : \"(not set)\"));\n\n for (const file of [DEFAULT_SCHEMA_FILE, DEFAULT_ENV_FILE]) {\n const full = path.resolve(cwd, file);\n io.log(\n file.padEnd(23) +\n (fs.existsSync(full) ? \"generated \" + fs.statSync(full).mtime.toISOString() : \"missing\"),\n );\n }\n\n try {\n const { resolveFuseBinary } = await import(\"pocketbomb-fuse/node\");\n const resolved = resolveFuseBinary({ log: () => {} });\n io.log(\"fuse binary \" + resolved.bin + \" (\" + resolved.source + \")\");\n } catch (err) {\n io.log(\"fuse binary \" + String((err as Error).message));\n }\n\n return 0;\n}\n\n/**\n * Runs the CLI with the provided arguments (without the node and script paths).\n */\nexport async function run(argv: Array<string>, io: Io = defaultIo): Promise<number> {\n const [command, ...rest] = argv;\n\n switch (command) {\n case \"codegen\":\n return runCodegen(rest, io);\n case \"dev\":\n return runDev(rest, io);\n case \"check\":\n return runCheck(rest, io);\n case \"info\":\n return runInfo(io);\n case \"--version\":\n case \"-v\":\n io.log(readPackageVersion());\n return 0;\n case undefined:\n case \"help\":\n case \"--help\":\n case \"-h\":\n io.log(HELP);\n return 0;\n default:\n io.error(\"Unknown command: \" + command + \"\\n\\n\" + HELP);\n return 2;\n }\n}\n\nexport * from \"./check\";\nexport * from \"./codegen\";\n"],"mappings":";;;;;;;;AAeA,MAAM,OAAO;;;;;;8CAMiC,oBAAoB;8CACpB,iBAAiB;;;;;;;;8CAQjB,oBAAoB;;;AAMlE,MAAM,YAAgB;CACpB,MAAM,SAAS,QAAQ,OAAO,MAAM,OAAO,IAAI;CAC/C,QAAQ,SAAS,QAAQ,OAAO,MAAM,OAAO,IAAI;AACnD;AAEA,SAAS,qBAA6B;CACpC,IAAI;EAEF,OADY,KAAK,MAAM,GAAG,aAAa,IAAI,IAAI,sBAAsB,YAAY,GAAG,GAAG,MAAM,CACpF,CAAC,CAAC;CACb,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,mBACb,OACA,IACe;CACf,MAAM,EAAE,cAAc,MAAM,OAAO;CACnC,MAAM,OAAO,MAAM,UAAU;EAC3B,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,MAAM,SAAS,GAAG,MAAM,YAAY,IAAI;CAC1C,CAAC;CACD,OAAO,OAAO,QAAQ,KAAK,KAAK,GAAG;CACnC,GAAG,MAAM,kCAAkC,KAAK,GAAG;CACnD,OAAO;AACT;AAEA,SAAS,eAAe,QAAwB,KAAqB;CACnE,QACG,OAAO,UAAU,eAAe,kBACjC,KAAK,SAAS,KAAK,OAAO,UAAU,IACpC,OACA,KAAK,SAAS,KAAK,OAAO,OAAO;AAErC;AAEA,eAAe,WAAW,MAAqB,IAAyB;CACtE,MAAM,EAAE,WAAW,UAAU;EAC3B,MAAM;EACN,SAAS;GACP,KAAK,EAAE,MAAM,SAAS;GACtB,cAAc,EAAE,MAAM,SAAS;GAC/B,QAAQ,EAAE,MAAM,SAAS;GACzB,KAAK,EAAE,MAAM,SAAS;GACtB,OAAO,EAAE,MAAM,UAAU;GACzB,UAAU,EAAE,MAAM,SAAS;GAC3B,MAAM,EAAE,MAAM,UAAU;GACxB,MAAM,EAAE,MAAM,SAAS;GACvB,KAAK,EAAE,MAAM,SAAS;EACxB;CACF,CAAC;CAED,MAAM,MAAM,KAAK,QAAQ,OAAO,OAAO,QAAQ,IAAI,CAAC;CACpD,MAAM,OAAO,OAAO,OAAO,MAAM,mBAAmB,QAAQ,EAAE,IAAI;CAElE,IAAI;EACF,MAAM,MAAM,OAAO,OAAO,QAAQ,IAAI;EACtC,IAAI,CAAC,KAAK;GACR,GAAG,MAAM,sDAAsD;GAC/D,OAAO;EACT;EAEA,MAAM,UAAU;GACd;GACA,WAAW,OAAO,iBAAiB,QAAQ,IAAI;GAC/C;GACA,YAAY,OAAO;GACnB,SAAS,OAAO;EAClB;EAEA,IAAI,CAAC,OAAO,OAAO;GACjB,GAAG,IAAI,eAAe,MAAM,QAAQ,OAAO,GAAG,GAAG,CAAC;GAClD,OAAO;EACT;EAEA,MAAM,OAAO,aAAa;GACxB,GAAG;GACH,UAAU,OAAO,WAAW,SAAS,OAAO,UAAU,EAAE,IAAI,KAAA;GAC5D,WAAW,WAAW,GAAG,IAAI,eAAe,QAAQ,GAAG,CAAC;GACxD,UAAU,QAAQ,GAAG,MAAM,cAAc,OAAQ,KAAe,WAAW,GAAG,CAAC;EACjF,CAAC;EACD,MAAM,IAAI,SAAe,YAAY;GACnC,QAAQ,KAAK,UAAU,OAAO;GAC9B,QAAQ,KAAK,WAAW,OAAO;EACjC,CAAC;EACD,KAAK;EACL,OAAO;CACT,UAAU;EACR,MAAM,MAAM,KAAK;CACnB;AACF;AAEA,eAAe,OAAO,MAAqB,IAAyB;CAClE,MAAM,EAAE,WAAW,UAAU;EAC3B,MAAM;EACN,SAAS;GACP,MAAM,EAAE,MAAM,SAAS;GACvB,YAAY,EAAE,MAAM,SAAS;GAC7B,OAAO,EAAE,MAAM,SAAS;GACxB,MAAM,EAAE,MAAM,SAAS;GACvB,cAAc,EAAE,MAAM,UAAU;EAClC;CACF,CAAC;CAED,MAAM,MAAM,QAAQ,IAAI;CACxB,MAAM,OAAO,QAAQ,IAAI,iBAAiB,OAAO,MAAM,mBAAmB,QAAQ,EAAE;CAEpF,IAAI,oBAAoB,CAAC;CACzB,IAAI,CAAC,OAAO,iBAAiB,QAAQ,IAAI,uBACvC,cAAc,aAAa;EACzB,KAAK,QAAQ,IAAI;EACjB,WAAW,QAAQ,IAAI;EACvB;EACA,WAAW,WAAW,GAAG,MAAM,eAAe,eAAe,QAAQ,GAAG,CAAC;EACzE,UAAU,QAAQ,GAAG,MAAM,eAAe,OAAQ,KAAe,WAAW,GAAG,CAAC;CAClF,CAAC;CAGH,MAAM,UAAU,YAAY;EAC1B,YAAY;EACZ,MAAM,MAAM,KAAK;CACnB;CAEA,IAAI,OAAO,OAAO;EAChB,MAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,KAAK,QAAQ,KAAK,OAAO,KAAK,CAAC,GAAG;GACvE,OAAO;GACP,KAAK;IAAE,GAAG,QAAQ;IAAK,UAAU,QAAQ,IAAI,YAAY;GAAc;EACzE,CAAC;EACD,MAAM,WAAW,WAA2B,MAAM,KAAK,MAAM;EAC7D,QAAQ,GAAG,UAAU,OAAO;EAC5B,QAAQ,GAAG,WAAW,OAAO;EAE7B,MAAM,OAAe,MAAM,IAAI,SAAS,YAAY,MAAM,GAAG,SAAS,MAAM,QAAQ,KAAK,CAAC,CAAC,CAAC;EAC5F,MAAM,QAAQ;EACd,OAAO;CACT;CAEA,MAAM,EAAE,iBAAiB,MAAM,OAAO;CACtC,MAAM,SAAS,MAAM,aAAa;EAChC,MAAM;EACN,QAAQ,OAAO,OAAO,EAAE,MAAM,SAAS,OAAO,MAAM,EAAE,EAAE,IAAI,KAAA;CAC9D,CAAC;CACD,MAAM,OAAO,OAAO;CACpB,OAAO,UAAU;CAEjB,MAAM,IAAI,SAAe,YAAY;EACnC,QAAQ,KAAK,UAAU,OAAO;EAC9B,QAAQ,KAAK,WAAW,OAAO;CACjC,CAAC;CACD,MAAM,OAAO,MAAM;CACnB,MAAM,QAAQ;CACd,OAAO;AACT;AAEA,eAAe,SAAS,MAAqB,IAAyB;CACpE,MAAM,EAAE,WAAW,UAAU;EAC3B,MAAM;EACN,SAAS;GAAE,QAAQ,EAAE,MAAM,SAAS;GAAG,KAAK,EAAE,MAAM,SAAS;EAAE;CACjE,CAAC;CACD,MAAM,MAAM,KAAK,QAAQ,OAAO,OAAO,QAAQ,IAAI,CAAC;CAEpD,IAAI;CACJ,IAAI;EACF,SAAS,MAAM,MAAM;GAAE;GAAK,YAAY,OAAO;EAAO,CAAC;CACzD,SAAS,KAAK;EACZ,GAAG,MAAM,qBAAsB,IAAc,OAAO;EACpD,OAAO;CACT;CAEA,KAAK,MAAM,KAAK,OAAO,UACrB,GAAG,MAAM,GAAG,KAAK,SAAS,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,GAAG,EAAE,SAAS;CAE7E,IAAI,OAAO,SAAS,QAAQ;EAC1B,GAAG,MACD,mBAAmB,OAAO,SAAS,OAAO,iBAAiB,OAAO,UAAU,WAC9E;EACA,OAAO;CACT;CACA,GAAG,IAAI,mBAAmB,OAAO,UAAU,cAAc;CACzD,OAAO;AACT;AAEA,eAAe,QAAQ,IAAyB;CAC9C,MAAM,MAAM,QAAQ,IAAI;CACxB,MAAM,QAAQ,MAAgB,IAAI,EAAE,UAAU,GAAG,CAAC,IAAI,MAAM;CAE5D,GAAG,IAAI,yBAAyB,mBAAmB,CAAC;CACpD,GAAG,IAAI,UAAU,QAAQ,OAAO;CAChC,GAAG,IAAI,6BAA6B,QAAQ,IAAI,kBAAkB,YAAY;CAC9E,GAAG,IAAI,4BAA4B,KAAK,QAAQ,IAAI,qBAAqB,CAAC;CAC1E,GAAG,IAAI,6BAA6B,QAAQ,IAAI,yBAAyB,UAAU,YAAY;CAE/F,KAAK,MAAM,QAAQ,CAAC,qBAAqB,gBAAgB,GAAG;EAC1D,MAAM,OAAO,KAAK,QAAQ,KAAK,IAAI;EACnC,GAAG,IACD,KAAK,OAAO,EAAE,KACX,GAAG,WAAW,IAAI,IAAI,eAAe,GAAG,SAAS,IAAI,CAAC,CAAC,MAAM,YAAY,IAAI,UAClF;CACF;CAEA,IAAI;EACF,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAC3C,MAAM,WAAW,kBAAkB,EAAE,WAAW,CAAC,EAAE,CAAC;EACpD,GAAG,IAAI,4BAA4B,SAAS,MAAM,OAAO,SAAS,SAAS,GAAG;CAChF,SAAS,KAAK;EACZ,GAAG,IAAI,4BAA4B,OAAQ,IAAc,OAAO,CAAC;CACnE;CAEA,OAAO;AACT;;;;AAKA,eAAsB,IAAI,MAAqB,KAAS,WAA4B;CAClF,MAAM,CAAC,SAAS,GAAG,QAAQ;CAE3B,QAAQ,SAAR;EACE,KAAK,WACH,OAAO,WAAW,MAAM,EAAE;EAC5B,KAAK,OACH,OAAO,OAAO,MAAM,EAAE;EACxB,KAAK,SACH,OAAO,SAAS,MAAM,EAAE;EAC1B,KAAK,QACH,OAAO,QAAQ,EAAE;EACnB,KAAK;EACL,KAAK;GACH,GAAG,IAAI,mBAAmB,CAAC;GAC3B,OAAO;EACT,KAAK,KAAA;EACL,KAAK;EACL,KAAK;EACL,KAAK;GACH,GAAG,IAAI,IAAI;GACX,OAAO;EACT;GACE,GAAG,MAAM,sBAAsB,UAAU,SAAS,IAAI;GACtD,OAAO;CACX;AACF"}
@@ -101,7 +101,62 @@ function createCustomerAccountClient(options) {
101
101
  function isSafeReturnTo(path) {
102
102
  return path.startsWith("/") && !path.startsWith("//") && !path.startsWith("/\\");
103
103
  }
104
+ /**
105
+ * Returns the login route URL with the provided options (see {@link loginOptionsFromURL}).
106
+ */
107
+ function loginRouteURL(route, options = {}) {
108
+ const params = new URLSearchParams();
109
+ if (options.returnTo !== void 0) params.set("returnTo", options.returnTo);
110
+ if (options.prompt !== void 0) params.set("prompt", options.prompt);
111
+ if (options.maxAge !== void 0) params.set("maxAge", String(options.maxAge));
112
+ if (options.loginHint !== void 0) params.set("loginHint", options.loginHint);
113
+ const query = params.toString();
114
+ return query ? route + (route.includes("?") ? "&" : "?") + query : route;
115
+ }
116
+ /**
117
+ * Returns the login options of a login route request, for `loginRedirect()`:
118
+ *
119
+ * ```ts
120
+ * return context.customer.loginRedirect(loginOptionsFromURL(request.url));
121
+ * ```
122
+ *
123
+ * The URL is attacker controlled (anyone can send a link): an unsafe `returnTo` (see {@link isSafeReturnTo})
124
+ * and an invalid `maxAge` are dropped, and no other option can be set through it.
125
+ */
126
+ function loginOptionsFromURL(url) {
127
+ const params = new URL(url, "http://localhost").searchParams;
128
+ const result = {};
129
+ const returnTo = params.get("returnTo");
130
+ if (returnTo && isSafeReturnTo(returnTo)) result.returnTo = returnTo;
131
+ const prompt = params.get("prompt");
132
+ if (prompt) result.prompt = prompt;
133
+ const maxAge = params.get("maxAge");
134
+ if (maxAge && /^\d+$/.test(maxAge)) result.maxAge = parseInt(maxAge, 10);
135
+ const loginHint = params.get("loginHint");
136
+ if (loginHint) result.loginHint = loginHint;
137
+ return result;
138
+ }
139
+ /**
140
+ * Returns the logout route URL with the provided options (see {@link logoutOptionsFromURL}).
141
+ */
142
+ function logoutRouteURL(route, options = {}) {
143
+ if (options.postLogoutRedirectUri === void 0) return route;
144
+ const query = new URLSearchParams({ postLogoutRedirectUri: options.postLogoutRedirectUri });
145
+ return route + (route.includes("?") ? "&" : "?") + query;
146
+ }
147
+ /**
148
+ * Returns the logout options of a logout route request, for `logoutRedirect()`.
149
+ * The issuer accepts only the registered post logout redirect URIs of the client.
150
+ */
151
+ function logoutOptionsFromURL(url) {
152
+ const uri = new URL(url, "http://localhost").searchParams.get("postLogoutRedirectUri");
153
+ return uri ? { postLogoutRedirectUri: uri } : {};
154
+ }
104
155
  exports.createCustomerAccountClient = createCustomerAccountClient;
105
156
  exports.isSafeReturnTo = isSafeReturnTo;
157
+ exports.loginOptionsFromURL = loginOptionsFromURL;
158
+ exports.loginRouteURL = loginRouteURL;
159
+ exports.logoutOptionsFromURL = logoutOptionsFromURL;
160
+ exports.logoutRouteURL = logoutRouteURL;
106
161
 
107
162
  //# sourceMappingURL=customer-account.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"customer-account.cjs","names":["createAccountClient","sendGraphQL","unwrapGraphQLResponse"],"sources":["../../src/customer-account/customer-account.ts"],"sourcesContent":["import {\n createAccountClient,\n type Account,\n type AccountCallbackResult,\n type AccountConfig,\n type AccountLoginOptions,\n type AccountLogoutOptions,\n type AccountMe,\n type AuthStore,\n type Client,\n type GraphQLResponse,\n} from \"pocketbomb-primer\";\nimport type { RequestContext } from \"../core/request-context\";\nimport {\n executeGraphQL as sendGraphQL,\n unwrapGraphQLResponse,\n type GraphQLRequestOptions,\n type ResultOf,\n type DocumentInput,\n type VariablesOf,\n} from \"../graphql/graphql\";\n\nexport interface CustomerAccountClientOptions extends Omit<AccountConfig, \"session\"> {\n /**\n * The PocketBomb base URL.\n */\n url: string;\n\n /**\n * Optional public API key of the same OAuth2 client (sent with every request).\n */\n publicKey?: string;\n\n /**\n * Optional `Accept-Language` (default to \"en-US\").\n */\n lang?: string;\n\n /**\n * Optional fetch implementation.\n */\n fetch?: typeof fetch;\n\n /**\n * The per-request context of a server rendered app: the customer session is stored in\n * its httpOnly cookie session (auth state + login flow state) and the `*Redirect()`\n * helpers return redirect responses with the session cookies.\n */\n requestContext?: RequestContext;\n\n /**\n * The auth store (browser/mobile; default to the request context session for SSR,\n * a `LocalAuthStore` in browsers and an in-memory store elsewhere).\n */\n authStore?: AuthStore;\n\n /**\n * The login flow state storage (default to the request context session for SSR).\n */\n session?: AccountConfig[\"session\"];\n}\n\nexport interface LoginRedirectOptions extends AccountLoginOptions {\n /**\n * The app path to return to after the sign-in (stored in the login state).\n */\n returnTo?: string;\n}\n\nexport interface CallbackRedirectOptions {\n /**\n * The callback URL (default to the request context request URL).\n */\n url?: string;\n\n /**\n * Where to redirect after a successful sign-in without a `returnTo` (default to \"/\").\n */\n successRedirect?: string;\n}\n\n/**\n * The customer account client (see {@link createCustomerAccountClient}).\n */\nexport interface CustomerAccountClient {\n /**\n * The underlying primer account client (records REST with the customer token, ...).\n */\n readonly client: Client;\n\n /**\n * The primer OAuth2 account (PKCE, refresh rotation, logout).\n */\n readonly account: Account;\n\n /** Browser redirect (or mobile `openAuthSession`) login. */\n login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;\n\n /** Browser redirect (or mobile `openAuthSession`) signup (`prompt=create`). */\n signup(options?: Omit<AccountLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n\n /** Handles the authorization response of the provided (or the current browser) URL. */\n handleCallback(url?: string): Promise<AccountCallbackResult | null>;\n\n /** Revokes the refresh token, clears the session and ends the issuer session. */\n logout(options?: AccountLogoutOptions): Promise<string>;\n\n /** Loosely checks whether the customer has a valid access token. */\n isLoggedIn(): boolean;\n\n /** Returns a valid (refreshed if needed) access token or an empty string. */\n getAccessToken(): Promise<string>;\n\n /** Returns the customer record and permissions (`/api/permissions/me`). */\n me(): Promise<AccountMe>;\n\n /** Returns the OpenID Connect userinfo claims. */\n userinfo(): Promise<{ [key: string]: any }>;\n\n /** Sends an email change request for the signed in customer. */\n requestEmailChange(newEmail: string): Promise<boolean>;\n\n /** Sends a verification email request. */\n requestVerification(email: string): Promise<boolean>;\n\n /** Sends a typed GraphQL document with the customer token. */\n graphql<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<GraphQLResponse<ResultOf<Doc>>>;\n\n /** Like `graphql()` but returns the `data` and throws a `GraphQLError` for the GraphQL errors. */\n query<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<ResultOf<Doc>>;\n\n /** SSR: returns a redirect response to the hosted login page (requires `requestContext`). */\n loginRedirect(options?: LoginRedirectOptions): Promise<Response>;\n\n /** SSR: handles the callback request and redirects to the `returnTo` page (requires `requestContext`). */\n callbackRedirect(options?: CallbackRedirectOptions): Promise<Response>;\n\n /** SSR: signs out and redirects to the issuer end_session page (requires `requestContext`). */\n logoutRedirect(options?: Omit<AccountLogoutOptions, \"redirect\">): Promise<Response>;\n}\n\n/**\n * Creates the customer account client of an auth collection issuer\n * (hosted login/signup with PKCE, refresh rotation, logout, own sessions and consents),\n * similar to the Hydrogen `createCustomerAccountClient`.\n *\n * - SSR: pass the `requestContext` (the session is stored in its httpOnly cookie) and use\n * the `loginRedirect()`, `callbackRedirect()` and `logoutRedirect()` route helpers.\n * - browser: `login()` redirects and `handleCallback()` completes the sign-in.\n * - mobile: pass an `openAuthSession` adapter and an `AsyncAuthStore`.\n */\nexport function createCustomerAccountClient(\n options: CustomerAccountClientOptions,\n): CustomerAccountClient {\n const context = options.requestContext;\n const { requestContext: _, ...rest } = options;\n\n const client = createAccountClient({\n ...rest,\n authStore: options.authStore || context?.session,\n session: options.session || context?.session,\n backgroundRefresh: context ? false : options.backgroundRefresh,\n // the SSR helpers build the redirect responses themselves\n navigate: options.navigate || (context ? () => {} : undefined),\n // concurrent server loaders must not cancel each other\n autoCancellation: context ? false : undefined,\n });\n const account = client.account;\n\n function requireContext(name: string): RequestContext {\n if (!context) {\n throw new Error(name + \"() requires the requestContext option.\");\n }\n return context;\n }\n\n function redirect(location: string, ctx: RequestContext): Response {\n const headers = new Headers({ Location: location });\n ctx.markPersonalized();\n ctx.applyResponseHeaders(headers);\n\n return new Response(null, { status: 302, headers });\n }\n\n function personalized(): void {\n if (context && client.authStore.token) {\n context.markPersonalized();\n }\n }\n\n async function graphql<Doc extends DocumentInput>(\n document: Doc,\n requestOptions: GraphQLRequestOptions<VariablesOf<Doc>> = {},\n ): Promise<GraphQLResponse<ResultOf<Doc>>> {\n personalized();\n return sendGraphQL(client, document, requestOptions);\n }\n\n return {\n client,\n account,\n login: (opts) => account.login(opts),\n signup: (opts) => account.signup(opts),\n handleCallback: (url) => account.handleCallback(url),\n logout: (opts) => account.logout(opts),\n isLoggedIn: () => account.isLoggedIn(),\n getAccessToken: () => account.getAccessToken(),\n me: () => {\n personalized();\n return account.me();\n },\n userinfo: () => {\n personalized();\n return account.userinfo();\n },\n requestEmailChange: (newEmail) =>\n client.collection(account.config.collection).requestEmailChange(newEmail),\n requestVerification: (email) =>\n client.collection(account.config.collection).requestVerification(email),\n graphql,\n async query(document, requestOptions) {\n return unwrapGraphQLResponse(await graphql(document, requestOptions));\n },\n async loginRedirect(opts = {}) {\n const ctx = requireContext(\"loginRedirect\");\n const { returnTo, ...loginOptions } = opts;\n const state =\n returnTo !== undefined ? { ...loginOptions.state, returnTo } : loginOptions.state;\n\n return redirect(await account.authorizeURL({ ...loginOptions, state }), ctx);\n },\n async callbackRedirect(opts = {}) {\n const ctx = requireContext(\"callbackRedirect\");\n const url = opts.url || ctx.request.url;\n if (!url) {\n throw new Error(\"callbackRedirect() requires the callback url (missing request.url).\");\n }\n\n const result = await account.handleCallback(url);\n const returnTo = result?.state?.returnTo;\n\n return redirect(\n typeof returnTo === \"string\" && isSafeReturnTo(returnTo)\n ? returnTo\n : opts.successRedirect || \"/\",\n ctx,\n );\n },\n async logoutRedirect(opts = {}) {\n const ctx = requireContext(\"logoutRedirect\");\n\n return redirect(await account.logout({ ...opts, redirect: true }), ctx);\n },\n };\n}\n\n/**\n * Reports whether the provided return path is a same-origin relative path\n * (prevents open redirects through the login state).\n */\nexport function isSafeReturnTo(path: string): boolean {\n return path.startsWith(\"/\") && !path.startsWith(\"//\") && !path.startsWith(\"/\\\\\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"customer-account.cjs","names":["createAccountClient","sendGraphQL","unwrapGraphQLResponse"],"sources":["../../src/customer-account/customer-account.ts"],"sourcesContent":["import {\n createAccountClient,\n type Account,\n type AccountCallbackResult,\n type AccountConfig,\n type AccountLoginOptions,\n type AccountLogoutOptions,\n type AccountMe,\n type AuthStore,\n type Client,\n type GraphQLResponse,\n} from \"pocketbomb-primer\";\nimport type { RequestContext } from \"../core/request-context\";\nimport {\n executeGraphQL as sendGraphQL,\n unwrapGraphQLResponse,\n type GraphQLRequestOptions,\n type ResultOf,\n type DocumentInput,\n type VariablesOf,\n} from \"../graphql/graphql\";\n\nexport interface CustomerAccountClientOptions extends Omit<AccountConfig, \"session\"> {\n /**\n * The PocketBomb base URL.\n */\n url: string;\n\n /**\n * Optional public API key of the same OAuth2 client (sent with every request).\n */\n publicKey?: string;\n\n /**\n * Optional `Accept-Language` (default to \"en-US\").\n */\n lang?: string;\n\n /**\n * Optional fetch implementation.\n */\n fetch?: typeof fetch;\n\n /**\n * The per-request context of a server rendered app: the customer session is stored in\n * its httpOnly cookie session (auth state + login flow state) and the `*Redirect()`\n * helpers return redirect responses with the session cookies.\n */\n requestContext?: RequestContext;\n\n /**\n * The auth store (browser/mobile; default to the request context session for SSR,\n * a `LocalAuthStore` in browsers and an in-memory store elsewhere).\n */\n authStore?: AuthStore;\n\n /**\n * The login flow state storage (default to the request context session for SSR).\n */\n session?: AccountConfig[\"session\"];\n}\n\nexport interface LoginRedirectOptions extends AccountLoginOptions {\n /**\n * The app path to return to after the sign-in (stored in the login state).\n */\n returnTo?: string;\n}\n\nexport interface CallbackRedirectOptions {\n /**\n * The callback URL (default to the request context request URL).\n */\n url?: string;\n\n /**\n * Where to redirect after a successful sign-in without a `returnTo` (default to \"/\").\n */\n successRedirect?: string;\n}\n\n/**\n * The customer account client (see {@link createCustomerAccountClient}).\n */\nexport interface CustomerAccountClient {\n /**\n * The underlying primer account client (records REST with the customer token, ...).\n */\n readonly client: Client;\n\n /**\n * The primer OAuth2 account (PKCE, refresh rotation, logout).\n */\n readonly account: Account;\n\n /** Browser redirect (or mobile `openAuthSession`) login. */\n login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;\n\n /** Browser redirect (or mobile `openAuthSession`) signup (`prompt=create`). */\n signup(options?: Omit<AccountLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n\n /** Handles the authorization response of the provided (or the current browser) URL. */\n handleCallback(url?: string): Promise<AccountCallbackResult | null>;\n\n /** Revokes the refresh token, clears the session and ends the issuer session. */\n logout(options?: AccountLogoutOptions): Promise<string>;\n\n /** Loosely checks whether the customer has a valid access token. */\n isLoggedIn(): boolean;\n\n /** Returns a valid (refreshed if needed) access token or an empty string. */\n getAccessToken(): Promise<string>;\n\n /** Returns the customer record and permissions (`/api/permissions/me`). */\n me(): Promise<AccountMe>;\n\n /** Returns the OpenID Connect userinfo claims. */\n userinfo(): Promise<{ [key: string]: any }>;\n\n /** Sends an email change request for the signed in customer. */\n requestEmailChange(newEmail: string): Promise<boolean>;\n\n /** Sends a verification email request. */\n requestVerification(email: string): Promise<boolean>;\n\n /** Sends a typed GraphQL document with the customer token. */\n graphql<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<GraphQLResponse<ResultOf<Doc>>>;\n\n /** Like `graphql()` but returns the `data` and throws a `GraphQLError` for the GraphQL errors. */\n query<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<ResultOf<Doc>>;\n\n /** SSR: returns a redirect response to the hosted login page (requires `requestContext`). */\n loginRedirect(options?: LoginRedirectOptions): Promise<Response>;\n\n /** SSR: handles the callback request and redirects to the `returnTo` page (requires `requestContext`). */\n callbackRedirect(options?: CallbackRedirectOptions): Promise<Response>;\n\n /** SSR: signs out and redirects to the issuer end_session page (requires `requestContext`). */\n logoutRedirect(options?: Omit<AccountLogoutOptions, \"redirect\">): Promise<Response>;\n}\n\n/**\n * Creates the customer account client of an auth collection issuer\n * (hosted login/signup with PKCE, refresh rotation, logout, own sessions and consents),\n * similar to the Hydrogen `createCustomerAccountClient`.\n *\n * - SSR: pass the `requestContext` (the session is stored in its httpOnly cookie) and use\n * the `loginRedirect()`, `callbackRedirect()` and `logoutRedirect()` route helpers.\n * - browser: `login()` redirects and `handleCallback()` completes the sign-in.\n * - mobile: pass an `openAuthSession` adapter and an `AsyncAuthStore`.\n */\nexport function createCustomerAccountClient(\n options: CustomerAccountClientOptions,\n): CustomerAccountClient {\n const context = options.requestContext;\n const { requestContext: _, ...rest } = options;\n\n const client = createAccountClient({\n ...rest,\n authStore: options.authStore || context?.session,\n session: options.session || context?.session,\n backgroundRefresh: context ? false : options.backgroundRefresh,\n // the SSR helpers build the redirect responses themselves\n navigate: options.navigate || (context ? () => {} : undefined),\n // concurrent server loaders must not cancel each other\n autoCancellation: context ? false : undefined,\n });\n const account = client.account;\n\n function requireContext(name: string): RequestContext {\n if (!context) {\n throw new Error(name + \"() requires the requestContext option.\");\n }\n return context;\n }\n\n function redirect(location: string, ctx: RequestContext): Response {\n const headers = new Headers({ Location: location });\n ctx.markPersonalized();\n ctx.applyResponseHeaders(headers);\n\n return new Response(null, { status: 302, headers });\n }\n\n function personalized(): void {\n if (context && client.authStore.token) {\n context.markPersonalized();\n }\n }\n\n async function graphql<Doc extends DocumentInput>(\n document: Doc,\n requestOptions: GraphQLRequestOptions<VariablesOf<Doc>> = {},\n ): Promise<GraphQLResponse<ResultOf<Doc>>> {\n personalized();\n return sendGraphQL(client, document, requestOptions);\n }\n\n return {\n client,\n account,\n login: (opts) => account.login(opts),\n signup: (opts) => account.signup(opts),\n handleCallback: (url) => account.handleCallback(url),\n logout: (opts) => account.logout(opts),\n isLoggedIn: () => account.isLoggedIn(),\n getAccessToken: () => account.getAccessToken(),\n me: () => {\n personalized();\n return account.me();\n },\n userinfo: () => {\n personalized();\n return account.userinfo();\n },\n requestEmailChange: (newEmail) =>\n client.collection(account.config.collection).requestEmailChange(newEmail),\n requestVerification: (email) =>\n client.collection(account.config.collection).requestVerification(email),\n graphql,\n async query(document, requestOptions) {\n return unwrapGraphQLResponse(await graphql(document, requestOptions));\n },\n async loginRedirect(opts = {}) {\n const ctx = requireContext(\"loginRedirect\");\n const { returnTo, ...loginOptions } = opts;\n const state =\n returnTo !== undefined ? { ...loginOptions.state, returnTo } : loginOptions.state;\n\n return redirect(await account.authorizeURL({ ...loginOptions, state }), ctx);\n },\n async callbackRedirect(opts = {}) {\n const ctx = requireContext(\"callbackRedirect\");\n const url = opts.url || ctx.request.url;\n if (!url) {\n throw new Error(\"callbackRedirect() requires the callback url (missing request.url).\");\n }\n\n const result = await account.handleCallback(url);\n const returnTo = result?.state?.returnTo;\n\n return redirect(\n typeof returnTo === \"string\" && isSafeReturnTo(returnTo)\n ? returnTo\n : opts.successRedirect || \"/\",\n ctx,\n );\n },\n async logoutRedirect(opts = {}) {\n const ctx = requireContext(\"logoutRedirect\");\n\n return redirect(await account.logout({ ...opts, redirect: true }), ctx);\n },\n };\n}\n\n/**\n * Reports whether the provided return path is a same-origin relative path\n * (prevents open redirects through the login state).\n */\nexport function isSafeReturnTo(path: string): boolean {\n return path.startsWith(\"/\") && !path.startsWith(\"//\") && !path.startsWith(\"/\\\\\");\n}\n\n/**\n * The login options that a client can ask for through the login route URL\n * (`useCustomer().login()` of a server rendered app).\n */\nexport type LoginRouteOptions = Pick<\n LoginRedirectOptions,\n \"returnTo\" | \"prompt\" | \"maxAge\" | \"loginHint\"\n>;\n\n/**\n * Returns the login route URL with the provided options (see {@link loginOptionsFromURL}).\n */\nexport function loginRouteURL(route: string, options: LoginRouteOptions = {}): string {\n const params = new URLSearchParams();\n if (options.returnTo !== undefined) params.set(\"returnTo\", options.returnTo);\n if (options.prompt !== undefined) params.set(\"prompt\", options.prompt);\n if (options.maxAge !== undefined) params.set(\"maxAge\", String(options.maxAge));\n if (options.loginHint !== undefined) params.set(\"loginHint\", options.loginHint);\n\n const query = params.toString();\n return query ? route + (route.includes(\"?\") ? \"&\" : \"?\") + query : route;\n}\n\n/**\n * Returns the login options of a login route request, for `loginRedirect()`:\n *\n * ```ts\n * return context.customer.loginRedirect(loginOptionsFromURL(request.url));\n * ```\n *\n * The URL is attacker controlled (anyone can send a link): an unsafe `returnTo` (see {@link isSafeReturnTo})\n * and an invalid `maxAge` are dropped, and no other option can be set through it.\n */\nexport function loginOptionsFromURL(url: string | URL): LoginRouteOptions {\n const params = new URL(url, \"http://localhost\").searchParams;\n const result: LoginRouteOptions = {};\n\n const returnTo = params.get(\"returnTo\");\n if (returnTo && isSafeReturnTo(returnTo)) result.returnTo = returnTo;\n\n const prompt = params.get(\"prompt\");\n if (prompt) result.prompt = prompt;\n\n const maxAge = params.get(\"maxAge\");\n if (maxAge && /^\\d+$/.test(maxAge)) result.maxAge = parseInt(maxAge, 10);\n\n const loginHint = params.get(\"loginHint\");\n if (loginHint) result.loginHint = loginHint;\n\n return result;\n}\n\n/**\n * The logout option that a client can ask for through the logout route URL.\n */\nexport type LogoutRouteOptions = Pick<AccountLogoutOptions, \"postLogoutRedirectUri\">;\n\n/**\n * Returns the logout route URL with the provided options (see {@link logoutOptionsFromURL}).\n */\nexport function logoutRouteURL(route: string, options: LogoutRouteOptions = {}): string {\n if (options.postLogoutRedirectUri === undefined) {\n return route;\n }\n const query = new URLSearchParams({ postLogoutRedirectUri: options.postLogoutRedirectUri });\n return route + (route.includes(\"?\") ? \"&\" : \"?\") + query;\n}\n\n/**\n * Returns the logout options of a logout route request, for `logoutRedirect()`.\n * The issuer accepts only the registered post logout redirect URIs of the client.\n */\nexport function logoutOptionsFromURL(url: string | URL): LogoutRouteOptions {\n const uri = new URL(url, \"http://localhost\").searchParams.get(\"postLogoutRedirectUri\");\n return uri ? { postLogoutRedirectUri: uri } : {};\n}\n"],"mappings":"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"}
@@ -111,4 +111,37 @@ export declare function createCustomerAccountClient(options: CustomerAccountClie
111
111
  * (prevents open redirects through the login state).
112
112
  */
113
113
  export declare function isSafeReturnTo(path: string): boolean;
114
+ /**
115
+ * The login options that a client can ask for through the login route URL
116
+ * (`useCustomer().login()` of a server rendered app).
117
+ */
118
+ export type LoginRouteOptions = Pick<LoginRedirectOptions, "returnTo" | "prompt" | "maxAge" | "loginHint">;
119
+ /**
120
+ * Returns the login route URL with the provided options (see {@link loginOptionsFromURL}).
121
+ */
122
+ export declare function loginRouteURL(route: string, options?: LoginRouteOptions): string;
123
+ /**
124
+ * Returns the login options of a login route request, for `loginRedirect()`:
125
+ *
126
+ * ```ts
127
+ * return context.customer.loginRedirect(loginOptionsFromURL(request.url));
128
+ * ```
129
+ *
130
+ * The URL is attacker controlled (anyone can send a link): an unsafe `returnTo` (see {@link isSafeReturnTo})
131
+ * and an invalid `maxAge` are dropped, and no other option can be set through it.
132
+ */
133
+ export declare function loginOptionsFromURL(url: string | URL): LoginRouteOptions;
134
+ /**
135
+ * The logout option that a client can ask for through the logout route URL.
136
+ */
137
+ export type LogoutRouteOptions = Pick<AccountLogoutOptions, "postLogoutRedirectUri">;
138
+ /**
139
+ * Returns the logout route URL with the provided options (see {@link logoutOptionsFromURL}).
140
+ */
141
+ export declare function logoutRouteURL(route: string, options?: LogoutRouteOptions): string;
142
+ /**
143
+ * Returns the logout options of a logout route request, for `logoutRedirect()`.
144
+ * The issuer accepts only the registered post logout redirect URIs of the client.
145
+ */
146
+ export declare function logoutOptionsFromURL(url: string | URL): LogoutRouteOptions;
114
147
  //# sourceMappingURL=customer-account.d.cts.map
@@ -111,4 +111,37 @@ export declare function createCustomerAccountClient(options: CustomerAccountClie
111
111
  * (prevents open redirects through the login state).
112
112
  */
113
113
  export declare function isSafeReturnTo(path: string): boolean;
114
+ /**
115
+ * The login options that a client can ask for through the login route URL
116
+ * (`useCustomer().login()` of a server rendered app).
117
+ */
118
+ export type LoginRouteOptions = Pick<LoginRedirectOptions, "returnTo" | "prompt" | "maxAge" | "loginHint">;
119
+ /**
120
+ * Returns the login route URL with the provided options (see {@link loginOptionsFromURL}).
121
+ */
122
+ export declare function loginRouteURL(route: string, options?: LoginRouteOptions): string;
123
+ /**
124
+ * Returns the login options of a login route request, for `loginRedirect()`:
125
+ *
126
+ * ```ts
127
+ * return context.customer.loginRedirect(loginOptionsFromURL(request.url));
128
+ * ```
129
+ *
130
+ * The URL is attacker controlled (anyone can send a link): an unsafe `returnTo` (see {@link isSafeReturnTo})
131
+ * and an invalid `maxAge` are dropped, and no other option can be set through it.
132
+ */
133
+ export declare function loginOptionsFromURL(url: string | URL): LoginRouteOptions;
134
+ /**
135
+ * The logout option that a client can ask for through the logout route URL.
136
+ */
137
+ export type LogoutRouteOptions = Pick<AccountLogoutOptions, "postLogoutRedirectUri">;
138
+ /**
139
+ * Returns the logout route URL with the provided options (see {@link logoutOptionsFromURL}).
140
+ */
141
+ export declare function logoutRouteURL(route: string, options?: LogoutRouteOptions): string;
142
+ /**
143
+ * Returns the logout options of a logout route request, for `logoutRedirect()`.
144
+ * The issuer accepts only the registered post logout redirect URIs of the client.
145
+ */
146
+ export declare function logoutOptionsFromURL(url: string | URL): LogoutRouteOptions;
114
147
  //# sourceMappingURL=customer-account.d.ts.map
@@ -101,6 +101,57 @@ function createCustomerAccountClient(options) {
101
101
  function isSafeReturnTo(path) {
102
102
  return path.startsWith("/") && !path.startsWith("//") && !path.startsWith("/\\");
103
103
  }
104
- export { createCustomerAccountClient, isSafeReturnTo };
104
+ /**
105
+ * Returns the login route URL with the provided options (see {@link loginOptionsFromURL}).
106
+ */
107
+ function loginRouteURL(route, options = {}) {
108
+ const params = new URLSearchParams();
109
+ if (options.returnTo !== void 0) params.set("returnTo", options.returnTo);
110
+ if (options.prompt !== void 0) params.set("prompt", options.prompt);
111
+ if (options.maxAge !== void 0) params.set("maxAge", String(options.maxAge));
112
+ if (options.loginHint !== void 0) params.set("loginHint", options.loginHint);
113
+ const query = params.toString();
114
+ return query ? route + (route.includes("?") ? "&" : "?") + query : route;
115
+ }
116
+ /**
117
+ * Returns the login options of a login route request, for `loginRedirect()`:
118
+ *
119
+ * ```ts
120
+ * return context.customer.loginRedirect(loginOptionsFromURL(request.url));
121
+ * ```
122
+ *
123
+ * The URL is attacker controlled (anyone can send a link): an unsafe `returnTo` (see {@link isSafeReturnTo})
124
+ * and an invalid `maxAge` are dropped, and no other option can be set through it.
125
+ */
126
+ function loginOptionsFromURL(url) {
127
+ const params = new URL(url, "http://localhost").searchParams;
128
+ const result = {};
129
+ const returnTo = params.get("returnTo");
130
+ if (returnTo && isSafeReturnTo(returnTo)) result.returnTo = returnTo;
131
+ const prompt = params.get("prompt");
132
+ if (prompt) result.prompt = prompt;
133
+ const maxAge = params.get("maxAge");
134
+ if (maxAge && /^\d+$/.test(maxAge)) result.maxAge = parseInt(maxAge, 10);
135
+ const loginHint = params.get("loginHint");
136
+ if (loginHint) result.loginHint = loginHint;
137
+ return result;
138
+ }
139
+ /**
140
+ * Returns the logout route URL with the provided options (see {@link logoutOptionsFromURL}).
141
+ */
142
+ function logoutRouteURL(route, options = {}) {
143
+ if (options.postLogoutRedirectUri === void 0) return route;
144
+ const query = new URLSearchParams({ postLogoutRedirectUri: options.postLogoutRedirectUri });
145
+ return route + (route.includes("?") ? "&" : "?") + query;
146
+ }
147
+ /**
148
+ * Returns the logout options of a logout route request, for `logoutRedirect()`.
149
+ * The issuer accepts only the registered post logout redirect URIs of the client.
150
+ */
151
+ function logoutOptionsFromURL(url) {
152
+ const uri = new URL(url, "http://localhost").searchParams.get("postLogoutRedirectUri");
153
+ return uri ? { postLogoutRedirectUri: uri } : {};
154
+ }
155
+ export { createCustomerAccountClient, isSafeReturnTo, loginOptionsFromURL, loginRouteURL, logoutOptionsFromURL, logoutRouteURL };
105
156
 
106
157
  //# sourceMappingURL=customer-account.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"customer-account.js","names":["sendGraphQL"],"sources":["../../src/customer-account/customer-account.ts"],"sourcesContent":["import {\n createAccountClient,\n type Account,\n type AccountCallbackResult,\n type AccountConfig,\n type AccountLoginOptions,\n type AccountLogoutOptions,\n type AccountMe,\n type AuthStore,\n type Client,\n type GraphQLResponse,\n} from \"pocketbomb-primer\";\nimport type { RequestContext } from \"../core/request-context\";\nimport {\n executeGraphQL as sendGraphQL,\n unwrapGraphQLResponse,\n type GraphQLRequestOptions,\n type ResultOf,\n type DocumentInput,\n type VariablesOf,\n} from \"../graphql/graphql\";\n\nexport interface CustomerAccountClientOptions extends Omit<AccountConfig, \"session\"> {\n /**\n * The PocketBomb base URL.\n */\n url: string;\n\n /**\n * Optional public API key of the same OAuth2 client (sent with every request).\n */\n publicKey?: string;\n\n /**\n * Optional `Accept-Language` (default to \"en-US\").\n */\n lang?: string;\n\n /**\n * Optional fetch implementation.\n */\n fetch?: typeof fetch;\n\n /**\n * The per-request context of a server rendered app: the customer session is stored in\n * its httpOnly cookie session (auth state + login flow state) and the `*Redirect()`\n * helpers return redirect responses with the session cookies.\n */\n requestContext?: RequestContext;\n\n /**\n * The auth store (browser/mobile; default to the request context session for SSR,\n * a `LocalAuthStore` in browsers and an in-memory store elsewhere).\n */\n authStore?: AuthStore;\n\n /**\n * The login flow state storage (default to the request context session for SSR).\n */\n session?: AccountConfig[\"session\"];\n}\n\nexport interface LoginRedirectOptions extends AccountLoginOptions {\n /**\n * The app path to return to after the sign-in (stored in the login state).\n */\n returnTo?: string;\n}\n\nexport interface CallbackRedirectOptions {\n /**\n * The callback URL (default to the request context request URL).\n */\n url?: string;\n\n /**\n * Where to redirect after a successful sign-in without a `returnTo` (default to \"/\").\n */\n successRedirect?: string;\n}\n\n/**\n * The customer account client (see {@link createCustomerAccountClient}).\n */\nexport interface CustomerAccountClient {\n /**\n * The underlying primer account client (records REST with the customer token, ...).\n */\n readonly client: Client;\n\n /**\n * The primer OAuth2 account (PKCE, refresh rotation, logout).\n */\n readonly account: Account;\n\n /** Browser redirect (or mobile `openAuthSession`) login. */\n login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;\n\n /** Browser redirect (or mobile `openAuthSession`) signup (`prompt=create`). */\n signup(options?: Omit<AccountLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n\n /** Handles the authorization response of the provided (or the current browser) URL. */\n handleCallback(url?: string): Promise<AccountCallbackResult | null>;\n\n /** Revokes the refresh token, clears the session and ends the issuer session. */\n logout(options?: AccountLogoutOptions): Promise<string>;\n\n /** Loosely checks whether the customer has a valid access token. */\n isLoggedIn(): boolean;\n\n /** Returns a valid (refreshed if needed) access token or an empty string. */\n getAccessToken(): Promise<string>;\n\n /** Returns the customer record and permissions (`/api/permissions/me`). */\n me(): Promise<AccountMe>;\n\n /** Returns the OpenID Connect userinfo claims. */\n userinfo(): Promise<{ [key: string]: any }>;\n\n /** Sends an email change request for the signed in customer. */\n requestEmailChange(newEmail: string): Promise<boolean>;\n\n /** Sends a verification email request. */\n requestVerification(email: string): Promise<boolean>;\n\n /** Sends a typed GraphQL document with the customer token. */\n graphql<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<GraphQLResponse<ResultOf<Doc>>>;\n\n /** Like `graphql()` but returns the `data` and throws a `GraphQLError` for the GraphQL errors. */\n query<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<ResultOf<Doc>>;\n\n /** SSR: returns a redirect response to the hosted login page (requires `requestContext`). */\n loginRedirect(options?: LoginRedirectOptions): Promise<Response>;\n\n /** SSR: handles the callback request and redirects to the `returnTo` page (requires `requestContext`). */\n callbackRedirect(options?: CallbackRedirectOptions): Promise<Response>;\n\n /** SSR: signs out and redirects to the issuer end_session page (requires `requestContext`). */\n logoutRedirect(options?: Omit<AccountLogoutOptions, \"redirect\">): Promise<Response>;\n}\n\n/**\n * Creates the customer account client of an auth collection issuer\n * (hosted login/signup with PKCE, refresh rotation, logout, own sessions and consents),\n * similar to the Hydrogen `createCustomerAccountClient`.\n *\n * - SSR: pass the `requestContext` (the session is stored in its httpOnly cookie) and use\n * the `loginRedirect()`, `callbackRedirect()` and `logoutRedirect()` route helpers.\n * - browser: `login()` redirects and `handleCallback()` completes the sign-in.\n * - mobile: pass an `openAuthSession` adapter and an `AsyncAuthStore`.\n */\nexport function createCustomerAccountClient(\n options: CustomerAccountClientOptions,\n): CustomerAccountClient {\n const context = options.requestContext;\n const { requestContext: _, ...rest } = options;\n\n const client = createAccountClient({\n ...rest,\n authStore: options.authStore || context?.session,\n session: options.session || context?.session,\n backgroundRefresh: context ? false : options.backgroundRefresh,\n // the SSR helpers build the redirect responses themselves\n navigate: options.navigate || (context ? () => {} : undefined),\n // concurrent server loaders must not cancel each other\n autoCancellation: context ? false : undefined,\n });\n const account = client.account;\n\n function requireContext(name: string): RequestContext {\n if (!context) {\n throw new Error(name + \"() requires the requestContext option.\");\n }\n return context;\n }\n\n function redirect(location: string, ctx: RequestContext): Response {\n const headers = new Headers({ Location: location });\n ctx.markPersonalized();\n ctx.applyResponseHeaders(headers);\n\n return new Response(null, { status: 302, headers });\n }\n\n function personalized(): void {\n if (context && client.authStore.token) {\n context.markPersonalized();\n }\n }\n\n async function graphql<Doc extends DocumentInput>(\n document: Doc,\n requestOptions: GraphQLRequestOptions<VariablesOf<Doc>> = {},\n ): Promise<GraphQLResponse<ResultOf<Doc>>> {\n personalized();\n return sendGraphQL(client, document, requestOptions);\n }\n\n return {\n client,\n account,\n login: (opts) => account.login(opts),\n signup: (opts) => account.signup(opts),\n handleCallback: (url) => account.handleCallback(url),\n logout: (opts) => account.logout(opts),\n isLoggedIn: () => account.isLoggedIn(),\n getAccessToken: () => account.getAccessToken(),\n me: () => {\n personalized();\n return account.me();\n },\n userinfo: () => {\n personalized();\n return account.userinfo();\n },\n requestEmailChange: (newEmail) =>\n client.collection(account.config.collection).requestEmailChange(newEmail),\n requestVerification: (email) =>\n client.collection(account.config.collection).requestVerification(email),\n graphql,\n async query(document, requestOptions) {\n return unwrapGraphQLResponse(await graphql(document, requestOptions));\n },\n async loginRedirect(opts = {}) {\n const ctx = requireContext(\"loginRedirect\");\n const { returnTo, ...loginOptions } = opts;\n const state =\n returnTo !== undefined ? { ...loginOptions.state, returnTo } : loginOptions.state;\n\n return redirect(await account.authorizeURL({ ...loginOptions, state }), ctx);\n },\n async callbackRedirect(opts = {}) {\n const ctx = requireContext(\"callbackRedirect\");\n const url = opts.url || ctx.request.url;\n if (!url) {\n throw new Error(\"callbackRedirect() requires the callback url (missing request.url).\");\n }\n\n const result = await account.handleCallback(url);\n const returnTo = result?.state?.returnTo;\n\n return redirect(\n typeof returnTo === \"string\" && isSafeReturnTo(returnTo)\n ? returnTo\n : opts.successRedirect || \"/\",\n ctx,\n );\n },\n async logoutRedirect(opts = {}) {\n const ctx = requireContext(\"logoutRedirect\");\n\n return redirect(await account.logout({ ...opts, redirect: true }), ctx);\n },\n };\n}\n\n/**\n * Reports whether the provided return path is a same-origin relative path\n * (prevents open redirects through the login state).\n */\nexport function isSafeReturnTo(path: string): boolean {\n return path.startsWith(\"/\") && !path.startsWith(\"//\") && !path.startsWith(\"/\\\\\");\n}\n"],"mappings":";;;;;;;;;;;;;AA6JA,SAAgB,4BACd,SACuB;CACvB,MAAM,UAAU,QAAQ;CACxB,MAAM,EAAE,gBAAgB,GAAG,GAAG,SAAS;CAEvC,MAAM,SAAS,oBAAoB;EACjC,GAAG;EACH,WAAW,QAAQ,aAAa,SAAS;EACzC,SAAS,QAAQ,WAAW,SAAS;EACrC,mBAAmB,UAAU,QAAQ,QAAQ;EAE7C,UAAU,QAAQ,aAAa,gBAAgB,CAAC,IAAI,KAAA;EAEpD,kBAAkB,UAAU,QAAQ,KAAA;CACtC,CAAC;CACD,MAAM,UAAU,OAAO;CAEvB,SAAS,eAAe,MAA8B;EACpD,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,OAAO,wCAAwC;EAEjE,OAAO;CACT;CAEA,SAAS,SAAS,UAAkB,KAA+B;EACjE,MAAM,UAAU,IAAI,QAAQ,EAAE,UAAU,SAAS,CAAC;EAClD,IAAI,iBAAiB;EACrB,IAAI,qBAAqB,OAAO;EAEhC,OAAO,IAAI,SAAS,MAAM;GAAE,QAAQ;GAAK;EAAQ,CAAC;CACpD;CAEA,SAAS,eAAqB;EAC5B,IAAI,WAAW,OAAO,UAAU,OAC9B,QAAQ,iBAAiB;CAE7B;CAEA,eAAe,QACb,UACA,iBAA0D,CAAC,GAClB;EACzC,aAAa;EACb,OAAOA,eAAY,QAAQ,UAAU,cAAc;CACrD;CAEA,OAAO;EACL;EACA;EACA,QAAQ,SAAS,QAAQ,MAAM,IAAI;EACnC,SAAS,SAAS,QAAQ,OAAO,IAAI;EACrC,iBAAiB,QAAQ,QAAQ,eAAe,GAAG;EACnD,SAAS,SAAS,QAAQ,OAAO,IAAI;EACrC,kBAAkB,QAAQ,WAAW;EACrC,sBAAsB,QAAQ,eAAe;EAC7C,UAAU;GACR,aAAa;GACb,OAAO,QAAQ,GAAG;EACpB;EACA,gBAAgB;GACd,aAAa;GACb,OAAO,QAAQ,SAAS;EAC1B;EACA,qBAAqB,aACnB,OAAO,WAAW,QAAQ,OAAO,UAAU,CAAC,CAAC,mBAAmB,QAAQ;EAC1E,sBAAsB,UACpB,OAAO,WAAW,QAAQ,OAAO,UAAU,CAAC,CAAC,oBAAoB,KAAK;EACxE;EACA,MAAM,MAAM,UAAU,gBAAgB;GACpC,OAAO,sBAAsB,MAAM,QAAQ,UAAU,cAAc,CAAC;EACtE;EACA,MAAM,cAAc,OAAO,CAAC,GAAG;GAC7B,MAAM,MAAM,eAAe,eAAe;GAC1C,MAAM,EAAE,UAAU,GAAG,iBAAiB;GACtC,MAAM,QACJ,aAAa,KAAA,IAAY;IAAE,GAAG,aAAa;IAAO;GAAS,IAAI,aAAa;GAE9E,OAAO,SAAS,MAAM,QAAQ,aAAa;IAAE,GAAG;IAAc;GAAM,CAAC,GAAG,GAAG;EAC7E;EACA,MAAM,iBAAiB,OAAO,CAAC,GAAG;GAChC,MAAM,MAAM,eAAe,kBAAkB;GAC7C,MAAM,MAAM,KAAK,OAAO,IAAI,QAAQ;GACpC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,qEAAqE;GAIvF,MAAM,YAAW,MADI,QAAQ,eAAe,GAAG,EAAA,EACtB,OAAO;GAEhC,OAAO,SACL,OAAO,aAAa,YAAY,eAAe,QAAQ,IACnD,WACA,KAAK,mBAAmB,KAC5B,GACF;EACF;EACA,MAAM,eAAe,OAAO,CAAC,GAAG;GAC9B,MAAM,MAAM,eAAe,gBAAgB;GAE3C,OAAO,SAAS,MAAM,QAAQ,OAAO;IAAE,GAAG;IAAM,UAAU;GAAK,CAAC,GAAG,GAAG;EACxE;CACF;AACF;;;;;AAMA,SAAgB,eAAe,MAAuB;CACpD,OAAO,KAAK,WAAW,GAAG,KAAK,CAAC,KAAK,WAAW,IAAI,KAAK,CAAC,KAAK,WAAW,KAAK;AACjF"}
1
+ {"version":3,"file":"customer-account.js","names":["sendGraphQL"],"sources":["../../src/customer-account/customer-account.ts"],"sourcesContent":["import {\n createAccountClient,\n type Account,\n type AccountCallbackResult,\n type AccountConfig,\n type AccountLoginOptions,\n type AccountLogoutOptions,\n type AccountMe,\n type AuthStore,\n type Client,\n type GraphQLResponse,\n} from \"pocketbomb-primer\";\nimport type { RequestContext } from \"../core/request-context\";\nimport {\n executeGraphQL as sendGraphQL,\n unwrapGraphQLResponse,\n type GraphQLRequestOptions,\n type ResultOf,\n type DocumentInput,\n type VariablesOf,\n} from \"../graphql/graphql\";\n\nexport interface CustomerAccountClientOptions extends Omit<AccountConfig, \"session\"> {\n /**\n * The PocketBomb base URL.\n */\n url: string;\n\n /**\n * Optional public API key of the same OAuth2 client (sent with every request).\n */\n publicKey?: string;\n\n /**\n * Optional `Accept-Language` (default to \"en-US\").\n */\n lang?: string;\n\n /**\n * Optional fetch implementation.\n */\n fetch?: typeof fetch;\n\n /**\n * The per-request context of a server rendered app: the customer session is stored in\n * its httpOnly cookie session (auth state + login flow state) and the `*Redirect()`\n * helpers return redirect responses with the session cookies.\n */\n requestContext?: RequestContext;\n\n /**\n * The auth store (browser/mobile; default to the request context session for SSR,\n * a `LocalAuthStore` in browsers and an in-memory store elsewhere).\n */\n authStore?: AuthStore;\n\n /**\n * The login flow state storage (default to the request context session for SSR).\n */\n session?: AccountConfig[\"session\"];\n}\n\nexport interface LoginRedirectOptions extends AccountLoginOptions {\n /**\n * The app path to return to after the sign-in (stored in the login state).\n */\n returnTo?: string;\n}\n\nexport interface CallbackRedirectOptions {\n /**\n * The callback URL (default to the request context request URL).\n */\n url?: string;\n\n /**\n * Where to redirect after a successful sign-in without a `returnTo` (default to \"/\").\n */\n successRedirect?: string;\n}\n\n/**\n * The customer account client (see {@link createCustomerAccountClient}).\n */\nexport interface CustomerAccountClient {\n /**\n * The underlying primer account client (records REST with the customer token, ...).\n */\n readonly client: Client;\n\n /**\n * The primer OAuth2 account (PKCE, refresh rotation, logout).\n */\n readonly account: Account;\n\n /** Browser redirect (or mobile `openAuthSession`) login. */\n login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;\n\n /** Browser redirect (or mobile `openAuthSession`) signup (`prompt=create`). */\n signup(options?: Omit<AccountLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n\n /** Handles the authorization response of the provided (or the current browser) URL. */\n handleCallback(url?: string): Promise<AccountCallbackResult | null>;\n\n /** Revokes the refresh token, clears the session and ends the issuer session. */\n logout(options?: AccountLogoutOptions): Promise<string>;\n\n /** Loosely checks whether the customer has a valid access token. */\n isLoggedIn(): boolean;\n\n /** Returns a valid (refreshed if needed) access token or an empty string. */\n getAccessToken(): Promise<string>;\n\n /** Returns the customer record and permissions (`/api/permissions/me`). */\n me(): Promise<AccountMe>;\n\n /** Returns the OpenID Connect userinfo claims. */\n userinfo(): Promise<{ [key: string]: any }>;\n\n /** Sends an email change request for the signed in customer. */\n requestEmailChange(newEmail: string): Promise<boolean>;\n\n /** Sends a verification email request. */\n requestVerification(email: string): Promise<boolean>;\n\n /** Sends a typed GraphQL document with the customer token. */\n graphql<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<GraphQLResponse<ResultOf<Doc>>>;\n\n /** Like `graphql()` but returns the `data` and throws a `GraphQLError` for the GraphQL errors. */\n query<Doc extends DocumentInput>(\n document: Doc,\n options?: GraphQLRequestOptions<VariablesOf<Doc>>,\n ): Promise<ResultOf<Doc>>;\n\n /** SSR: returns a redirect response to the hosted login page (requires `requestContext`). */\n loginRedirect(options?: LoginRedirectOptions): Promise<Response>;\n\n /** SSR: handles the callback request and redirects to the `returnTo` page (requires `requestContext`). */\n callbackRedirect(options?: CallbackRedirectOptions): Promise<Response>;\n\n /** SSR: signs out and redirects to the issuer end_session page (requires `requestContext`). */\n logoutRedirect(options?: Omit<AccountLogoutOptions, \"redirect\">): Promise<Response>;\n}\n\n/**\n * Creates the customer account client of an auth collection issuer\n * (hosted login/signup with PKCE, refresh rotation, logout, own sessions and consents),\n * similar to the Hydrogen `createCustomerAccountClient`.\n *\n * - SSR: pass the `requestContext` (the session is stored in its httpOnly cookie) and use\n * the `loginRedirect()`, `callbackRedirect()` and `logoutRedirect()` route helpers.\n * - browser: `login()` redirects and `handleCallback()` completes the sign-in.\n * - mobile: pass an `openAuthSession` adapter and an `AsyncAuthStore`.\n */\nexport function createCustomerAccountClient(\n options: CustomerAccountClientOptions,\n): CustomerAccountClient {\n const context = options.requestContext;\n const { requestContext: _, ...rest } = options;\n\n const client = createAccountClient({\n ...rest,\n authStore: options.authStore || context?.session,\n session: options.session || context?.session,\n backgroundRefresh: context ? false : options.backgroundRefresh,\n // the SSR helpers build the redirect responses themselves\n navigate: options.navigate || (context ? () => {} : undefined),\n // concurrent server loaders must not cancel each other\n autoCancellation: context ? false : undefined,\n });\n const account = client.account;\n\n function requireContext(name: string): RequestContext {\n if (!context) {\n throw new Error(name + \"() requires the requestContext option.\");\n }\n return context;\n }\n\n function redirect(location: string, ctx: RequestContext): Response {\n const headers = new Headers({ Location: location });\n ctx.markPersonalized();\n ctx.applyResponseHeaders(headers);\n\n return new Response(null, { status: 302, headers });\n }\n\n function personalized(): void {\n if (context && client.authStore.token) {\n context.markPersonalized();\n }\n }\n\n async function graphql<Doc extends DocumentInput>(\n document: Doc,\n requestOptions: GraphQLRequestOptions<VariablesOf<Doc>> = {},\n ): Promise<GraphQLResponse<ResultOf<Doc>>> {\n personalized();\n return sendGraphQL(client, document, requestOptions);\n }\n\n return {\n client,\n account,\n login: (opts) => account.login(opts),\n signup: (opts) => account.signup(opts),\n handleCallback: (url) => account.handleCallback(url),\n logout: (opts) => account.logout(opts),\n isLoggedIn: () => account.isLoggedIn(),\n getAccessToken: () => account.getAccessToken(),\n me: () => {\n personalized();\n return account.me();\n },\n userinfo: () => {\n personalized();\n return account.userinfo();\n },\n requestEmailChange: (newEmail) =>\n client.collection(account.config.collection).requestEmailChange(newEmail),\n requestVerification: (email) =>\n client.collection(account.config.collection).requestVerification(email),\n graphql,\n async query(document, requestOptions) {\n return unwrapGraphQLResponse(await graphql(document, requestOptions));\n },\n async loginRedirect(opts = {}) {\n const ctx = requireContext(\"loginRedirect\");\n const { returnTo, ...loginOptions } = opts;\n const state =\n returnTo !== undefined ? { ...loginOptions.state, returnTo } : loginOptions.state;\n\n return redirect(await account.authorizeURL({ ...loginOptions, state }), ctx);\n },\n async callbackRedirect(opts = {}) {\n const ctx = requireContext(\"callbackRedirect\");\n const url = opts.url || ctx.request.url;\n if (!url) {\n throw new Error(\"callbackRedirect() requires the callback url (missing request.url).\");\n }\n\n const result = await account.handleCallback(url);\n const returnTo = result?.state?.returnTo;\n\n return redirect(\n typeof returnTo === \"string\" && isSafeReturnTo(returnTo)\n ? returnTo\n : opts.successRedirect || \"/\",\n ctx,\n );\n },\n async logoutRedirect(opts = {}) {\n const ctx = requireContext(\"logoutRedirect\");\n\n return redirect(await account.logout({ ...opts, redirect: true }), ctx);\n },\n };\n}\n\n/**\n * Reports whether the provided return path is a same-origin relative path\n * (prevents open redirects through the login state).\n */\nexport function isSafeReturnTo(path: string): boolean {\n return path.startsWith(\"/\") && !path.startsWith(\"//\") && !path.startsWith(\"/\\\\\");\n}\n\n/**\n * The login options that a client can ask for through the login route URL\n * (`useCustomer().login()` of a server rendered app).\n */\nexport type LoginRouteOptions = Pick<\n LoginRedirectOptions,\n \"returnTo\" | \"prompt\" | \"maxAge\" | \"loginHint\"\n>;\n\n/**\n * Returns the login route URL with the provided options (see {@link loginOptionsFromURL}).\n */\nexport function loginRouteURL(route: string, options: LoginRouteOptions = {}): string {\n const params = new URLSearchParams();\n if (options.returnTo !== undefined) params.set(\"returnTo\", options.returnTo);\n if (options.prompt !== undefined) params.set(\"prompt\", options.prompt);\n if (options.maxAge !== undefined) params.set(\"maxAge\", String(options.maxAge));\n if (options.loginHint !== undefined) params.set(\"loginHint\", options.loginHint);\n\n const query = params.toString();\n return query ? route + (route.includes(\"?\") ? \"&\" : \"?\") + query : route;\n}\n\n/**\n * Returns the login options of a login route request, for `loginRedirect()`:\n *\n * ```ts\n * return context.customer.loginRedirect(loginOptionsFromURL(request.url));\n * ```\n *\n * The URL is attacker controlled (anyone can send a link): an unsafe `returnTo` (see {@link isSafeReturnTo})\n * and an invalid `maxAge` are dropped, and no other option can be set through it.\n */\nexport function loginOptionsFromURL(url: string | URL): LoginRouteOptions {\n const params = new URL(url, \"http://localhost\").searchParams;\n const result: LoginRouteOptions = {};\n\n const returnTo = params.get(\"returnTo\");\n if (returnTo && isSafeReturnTo(returnTo)) result.returnTo = returnTo;\n\n const prompt = params.get(\"prompt\");\n if (prompt) result.prompt = prompt;\n\n const maxAge = params.get(\"maxAge\");\n if (maxAge && /^\\d+$/.test(maxAge)) result.maxAge = parseInt(maxAge, 10);\n\n const loginHint = params.get(\"loginHint\");\n if (loginHint) result.loginHint = loginHint;\n\n return result;\n}\n\n/**\n * The logout option that a client can ask for through the logout route URL.\n */\nexport type LogoutRouteOptions = Pick<AccountLogoutOptions, \"postLogoutRedirectUri\">;\n\n/**\n * Returns the logout route URL with the provided options (see {@link logoutOptionsFromURL}).\n */\nexport function logoutRouteURL(route: string, options: LogoutRouteOptions = {}): string {\n if (options.postLogoutRedirectUri === undefined) {\n return route;\n }\n const query = new URLSearchParams({ postLogoutRedirectUri: options.postLogoutRedirectUri });\n return route + (route.includes(\"?\") ? \"&\" : \"?\") + query;\n}\n\n/**\n * Returns the logout options of a logout route request, for `logoutRedirect()`.\n * The issuer accepts only the registered post logout redirect URIs of the client.\n */\nexport function logoutOptionsFromURL(url: string | URL): LogoutRouteOptions {\n const uri = new URL(url, \"http://localhost\").searchParams.get(\"postLogoutRedirectUri\");\n return uri ? { postLogoutRedirectUri: uri } : {};\n}\n"],"mappings":"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"}
@@ -2,3 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_customer_account = require("./customer-account.cjs");
3
3
  exports.createCustomerAccountClient = require_customer_account.createCustomerAccountClient;
4
4
  exports.isSafeReturnTo = require_customer_account.isSafeReturnTo;
5
+ exports.loginOptionsFromURL = require_customer_account.loginOptionsFromURL;
6
+ exports.loginRouteURL = require_customer_account.loginRouteURL;
7
+ exports.logoutOptionsFromURL = require_customer_account.logoutOptionsFromURL;
8
+ exports.logoutRouteURL = require_customer_account.logoutRouteURL;
@@ -1,2 +1,2 @@
1
- import { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, createCustomerAccountClient, isSafeReturnTo } from "./customer-account.cjs";
2
- export { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, createCustomerAccountClient, isSafeReturnTo };
1
+ import { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, LoginRouteOptions, LogoutRouteOptions, createCustomerAccountClient, isSafeReturnTo, loginOptionsFromURL, loginRouteURL, logoutOptionsFromURL, logoutRouteURL } from "./customer-account.cjs";
2
+ export { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, LoginRouteOptions, LogoutRouteOptions, createCustomerAccountClient, isSafeReturnTo, loginOptionsFromURL, loginRouteURL, logoutOptionsFromURL, logoutRouteURL };
@@ -1,2 +1,2 @@
1
- import { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, createCustomerAccountClient, isSafeReturnTo } from "./customer-account.js";
2
- export { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, createCustomerAccountClient, isSafeReturnTo };
1
+ import { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, LoginRouteOptions, LogoutRouteOptions, createCustomerAccountClient, isSafeReturnTo, loginOptionsFromURL, loginRouteURL, logoutOptionsFromURL, logoutRouteURL } from "./customer-account.js";
2
+ export { CallbackRedirectOptions, CustomerAccountClient, CustomerAccountClientOptions, LoginRedirectOptions, LoginRouteOptions, LogoutRouteOptions, createCustomerAccountClient, isSafeReturnTo, loginOptionsFromURL, loginRouteURL, logoutOptionsFromURL, logoutRouteURL };
@@ -1 +1 @@
1
- export { createCustomerAccountClient, isSafeReturnTo } from "./customer-account.js";
1
+ export { createCustomerAccountClient, isSafeReturnTo, loginOptionsFromURL, loginRouteURL, logoutOptionsFromURL, logoutRouteURL } from "./customer-account.js";