@mr.dj2u/knowledge 0.1.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.
- package/dist/content/checklists/ship-test-loop.md +16 -0
- package/dist/content/checklists/unified-agent-bundle-validation.md +8 -0
- package/dist/content/examples/ship-test-loop.md +13 -0
- package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -0
- package/dist/content/guides/animation-performance.md +30 -0
- package/dist/content/guides/post-create-onboarding.md +113 -0
- package/dist/content/patterns/api/api-routes.md +314 -0
- package/dist/content/patterns/api/error-handling.md +311 -0
- package/dist/content/patterns/database/drizzle-schema.md +280 -0
- package/dist/content/patterns/database/migrations.md +365 -0
- package/dist/content/patterns/database/query-organization.md +537 -0
- package/dist/content/patterns/database/relations.md +450 -0
- package/dist/content/patterns/deployment/build-configuration.md +452 -0
- package/dist/content/patterns/deployment/ci-cd-patterns.md +448 -0
- package/dist/content/patterns/deployment/environment-config.md +380 -0
- package/dist/content/patterns/deployment/hosting-setup.md +425 -0
- package/dist/content/patterns/project/configuration-patterns.md +459 -0
- package/dist/content/patterns/project/documentation-org.md +506 -0
- package/dist/content/patterns/project/folder-structure.md +398 -0
- package/dist/content/patterns/project/library-exports.md +465 -0
- package/dist/content/patterns/project/monorepo-structure.md +500 -0
- package/dist/content/patterns/routing/dynamic-routes.md +221 -0
- package/dist/content/patterns/routing/file-based-routing.md +186 -0
- package/dist/content/patterns/routing/route-groups.md +429 -0
- package/dist/content/patterns/state/persistence-middleware.md +521 -0
- package/dist/content/patterns/state/selector-hooks.md +538 -0
- package/dist/content/patterns/state/store-organization.md +539 -0
- package/dist/content/patterns/state/zustand-patterns.md +348 -0
- package/dist/content/patterns/styling/component-styling.md +468 -0
- package/dist/content/patterns/styling/responsive-patterns.md +398 -0
- package/dist/content/patterns/styling/theme-configuration.md +426 -0
- package/dist/content/patterns/styling/uniwind-setup.md +412 -0
- package/dist/content/prompts/continue-development.md +27 -0
- package/dist/content/prompts/create-expo-super-stack.md +29 -0
- package/dist/content/prompts/fix-seo.md +29 -0
- package/dist/content/prompts/onboard-new-expo-app.md +11 -0
- package/dist/content/prompts/prepare-deploy.md +29 -0
- package/dist/content/prompts/project-research-plan.md +29 -0
- package/dist/content/prompts/review-expo-project.md +29 -0
- package/dist/content/prompts/run-doctor.md +30 -0
- package/dist/content/prompts/ship-test-loop.md +24 -0
- package/dist/content/reference/create-expo-stack-uniwind.md +29 -0
- package/dist/content/reference/doctor-dogfood.md +42 -0
- package/dist/content/reference/mcp-sdk-transport.md +30 -0
- package/dist/content/reference/package-ci-patterns.md +24 -0
- package/dist/content/reference/reference-repo-evacuation.md +31 -0
- package/dist/content/resource-index.json +67 -0
- package/dist/content/rules/app-folder-architecture.md +13 -0
- package/dist/content/rules/env-hygiene.md +9 -0
- package/dist/content/rules/seo-metadata.md +7 -0
- package/dist/content/rules/ssr-safety.md +9 -0
- package/dist/content/skills/api-routes.md +33 -0
- package/dist/content/skills/continue-development.md +31 -0
- package/dist/content/skills/debugging.md +31 -0
- package/dist/content/skills/deployment.md +32 -0
- package/dist/content/skills/dev-server-management.md +31 -0
- package/dist/content/skills/env-vars.md +32 -0
- package/dist/content/skills/expo-router-architecture.md +33 -0
- package/dist/content/skills/expo-ssr-safety.md +32 -0
- package/dist/content/skills/plugin-creation.md +41 -0
- package/dist/content/skills/production-server-patterns.md +31 -0
- package/dist/content/skills/project-onboarding.md +31 -0
- package/dist/content/skills/research-plan-intake.md +31 -0
- package/dist/content/skills/seo-metadata.md +31 -0
- package/dist/content/skills/super-stack-startup.md +31 -0
- package/dist/content/skills/uniwind-theming.md +32 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/index.d.ts +78 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +264 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/prompts/index.d.ts +35 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +270 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Ship-Test Loop Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist when running the PR loop into `test`.
|
|
4
|
+
|
|
5
|
+
1. Doctor CI pass: `mds doctor --ci`.
|
|
6
|
+
2. Stage intentional files only.
|
|
7
|
+
3. Create meaningful commit message.
|
|
8
|
+
4. Push branch.
|
|
9
|
+
5. Open/update PR to `test`.
|
|
10
|
+
6. Wait ~2 minutes before polling checks/comments.
|
|
11
|
+
7. Collect failed checks + review comments.
|
|
12
|
+
8. Fix issues locally.
|
|
13
|
+
9. Rerun Doctor CI.
|
|
14
|
+
10. Push and poll again.
|
|
15
|
+
11. Repeat up to 5 cycles.
|
|
16
|
+
12. Merge only when checks are green and blockers are cleared.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Unified Agent Bundle Validation Checklist
|
|
2
|
+
|
|
3
|
+
Phase 9 validation artifact intent:
|
|
4
|
+
|
|
5
|
+
1. Run Doctor (`--ci`) through the agent.
|
|
6
|
+
2. Fetch one guide via MCP resource (`animation-performance`).
|
|
7
|
+
3. Execute one CLI workflow (`mds continue` or `/ship-test-loop`).
|
|
8
|
+
4. Confirm tools/resources are visible in the target client.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Ship-Test Loop Example
|
|
2
|
+
|
|
3
|
+
Example iteration log for `/ship-test-loop`:
|
|
4
|
+
|
|
5
|
+
- Iteration 1:
|
|
6
|
+
- Failed: `lint` and one requested review change.
|
|
7
|
+
- Fixes: remove dead import, update API guard clause.
|
|
8
|
+
- Result: `lint` green, one test still failing.
|
|
9
|
+
- Iteration 2:
|
|
10
|
+
- Failed: `test` in `packages/cli`.
|
|
11
|
+
- Fixes: update stale skill ID expectation.
|
|
12
|
+
- Result: all checks green.
|
|
13
|
+
- Merge: PR merged into `test`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Unified Agent Bundle Bootstrap Example
|
|
2
|
+
|
|
3
|
+
Phase 9 bootstrap artifact intent:
|
|
4
|
+
|
|
5
|
+
1. Install MCP server config.
|
|
6
|
+
2. Install generated skills/prompts package.
|
|
7
|
+
3. Verify the agent can call Doctor + knowledge tools.
|
|
8
|
+
4. Verify the agent can run one CLI workflow.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Animation Performance Guide
|
|
2
|
+
|
|
3
|
+
Source: Expo blog, "The real cost of React Native animations: benchmarking
|
|
4
|
+
every approach" by App & Flow:
|
|
5
|
+
https://expo.dev/blog/the-real-cost-of-react-native-animations-benchmarking-every-approach
|
|
6
|
+
|
|
7
|
+
## Practical Guidance
|
|
8
|
+
|
|
9
|
+
- Measure animation cost in release builds before changing libraries. Debug
|
|
10
|
+
builds can exaggerate overhead, especially for Reanimated.
|
|
11
|
+
- Library choice matters most for long-running animations, large animated lists,
|
|
12
|
+
low-end Android devices, and screens with many simultaneously animated views.
|
|
13
|
+
- Library choice matters less for short one-shot transitions where animation
|
|
14
|
+
work ends quickly and the component count is low.
|
|
15
|
+
- Prefer native/platform-driven animation paths for large repeated animations
|
|
16
|
+
when the interaction does not require gesture coupling or complex shared
|
|
17
|
+
values.
|
|
18
|
+
- Keep Reanimated for gesture-driven interactions, layout-sensitive motion, and
|
|
19
|
+
complex UI-thread coordination.
|
|
20
|
+
- Animated view count is a budget. A single animated card is different from 100
|
|
21
|
+
animated rows.
|
|
22
|
+
|
|
23
|
+
## Doctor And Onboarding Rules
|
|
24
|
+
|
|
25
|
+
- Warn when route or list files animate many repeated items without an explicit
|
|
26
|
+
performance note.
|
|
27
|
+
- Warn when benchmark claims are based only on debug builds.
|
|
28
|
+
- Ask during onboarding whether the app has long-running, list-heavy, or
|
|
29
|
+
gesture-heavy animation requirements.
|
|
30
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Post-Create Expo Onboarding
|
|
2
|
+
|
|
3
|
+
MDS onboarding runs after `rn-new`, `create-expo-app`, `create-expo-stack`, or
|
|
4
|
+
another generator has created an Expo project. `create-expo-super-stack` wraps
|
|
5
|
+
that same idea by running `create-expo-stack` first and then applying the MDS
|
|
6
|
+
onboarding pass.
|
|
7
|
+
|
|
8
|
+
## Flow
|
|
9
|
+
|
|
10
|
+
1. Detect package manager, Expo SDK, Expo Router, app directory, aliases, and
|
|
11
|
+
styling stack.
|
|
12
|
+
2. Explain project memory first, then ask what the app is for, who it serves,
|
|
13
|
+
primary flows, data needs, and release/deployment intent.
|
|
14
|
+
3. Use friendly Clack prompts with visible defaults, helpful explanations, and
|
|
15
|
+
no blank required answers.
|
|
16
|
+
4. Derive defaults from selected answers instead of asking for comma-separated
|
|
17
|
+
internal keywords.
|
|
18
|
+
5. Offer rich boilerplate by default: project memory docs, exposition pages,
|
|
19
|
+
Uniwind repair for existing apps, Software Mansion core examples, Supabase or
|
|
20
|
+
local data guidance, MCP/Codex/Claude instructions, and CI/release safeguards.
|
|
21
|
+
6. Scaffold only selected pieces.
|
|
22
|
+
7. Add generated cleanup scripts such as `clear-expo-start` and `free-port` (with `kill-port` compatibility alias).
|
|
23
|
+
8. Install added dependencies, run `expo install --fix`, install known missing
|
|
24
|
+
Expo peers such as `expo-font` for `@expo/vector-icons`, then run Doctor.
|
|
25
|
+
|
|
26
|
+
## Agentic Onboarding (MCP)
|
|
27
|
+
|
|
28
|
+
The terminal `mds onboard` command writes the project memory files. The
|
|
29
|
+
agentic version is a conversation with Claude Code, Codex, or Cursor that
|
|
30
|
+
fills those files in collaboratively.
|
|
31
|
+
|
|
32
|
+
Install **once, globally** (user scope, default) so every workspace gets
|
|
33
|
+
the prompts:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mds mcp install --client claude # merges into ~/.claude.json
|
|
37
|
+
mds mcp install --client codex # merges into ~/.codex/config.toml
|
|
38
|
+
mds mcp install --client cursor # merges into ~/.cursor/mcp.json
|
|
39
|
+
mds mcp install --dry-run # preview merge before writing
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Restart the host (or run `claude mcp reload`) and the MDS prompts are
|
|
43
|
+
available from any workspace.
|
|
44
|
+
|
|
45
|
+
Two prompts ship with the server:
|
|
46
|
+
|
|
47
|
+
- `create_expo_super_stack` — invoke from a **parent folder** (e.g.
|
|
48
|
+
`F:\ReactNativeApps`) when the app folder does not exist yet. The
|
|
49
|
+
agent confirms platforms, styling, data start, etc., then runs
|
|
50
|
+
`create-expo-super-stack`, verifies the generated app folder, and
|
|
51
|
+
offers to resolve TodoForContext markers.
|
|
52
|
+
- `onboard_new_expo_app` — invoke from **inside an existing Expo app
|
|
53
|
+
folder** (a freshly generated one or a year-old project). Runs the
|
|
54
|
+
intake → normalize → plan → scaffold flow.
|
|
55
|
+
|
|
56
|
+
After generation, the user-dev should open the generated app folder
|
|
57
|
+
directly in a new agent session and run `mds continue`. That fresh
|
|
58
|
+
app-root session reduces token usage and saves money because future
|
|
59
|
+
searches, reads, and plans are scoped to the app instead of the parent
|
|
60
|
+
folder and old generator conversation.
|
|
61
|
+
|
|
62
|
+
Both prompts enforce or surface a **TodoForContext blocker** before
|
|
63
|
+
implementation work: they scan every `project/` file for the literal
|
|
64
|
+
marker `# TodoForContext(optional):` and ask the user to fill it in or
|
|
65
|
+
delete the marker line.
|
|
66
|
+
|
|
67
|
+
`mds doctor` mirrors this rule with a `todo-for-context markers`
|
|
68
|
+
error so the same blocker stops CI and editor surfaces until the user
|
|
69
|
+
fills the section or deletes the marker line.
|
|
70
|
+
|
|
71
|
+
### What's new in the agentic prompts
|
|
72
|
+
|
|
73
|
+
- **PHASE 0 file intake.** The agent offers to digest an existing
|
|
74
|
+
`project/info.md` and `project/style.md` if the user has them already.
|
|
75
|
+
Questions whose answers are unambiguous in the file are skipped; the
|
|
76
|
+
agent asks for clarification only when the file is silent or unclear.
|
|
77
|
+
A reference template URL is mentioned in the prompt; the agent can
|
|
78
|
+
also inline the template on request.
|
|
79
|
+
- **Android TV alongside Apple TV.** The platform multi-select now
|
|
80
|
+
includes Android TV. Android TV builds from the same Android target
|
|
81
|
+
with leanback config in `app.json`; Apple TV is a separate tvOS build
|
|
82
|
+
target via `react-native-tvos`. Selecting either records the intent
|
|
83
|
+
in project memory.
|
|
84
|
+
- **Embedded flag map.** Both prompts now include the exact
|
|
85
|
+
create-expo-stack and `--mds-*` flag map so the agent does not have
|
|
86
|
+
to grep `node_modules` or scan source to translate answers into a
|
|
87
|
+
command. This was the biggest source of slowness in early test runs.
|
|
88
|
+
- **Credits while waiting.** When generation kicks off, the agent
|
|
89
|
+
prints a recognition note for the upstream teams and individuals
|
|
90
|
+
whose work fills the MDS knowledge base.
|
|
91
|
+
- **Success message surfaced.** After a successful run, the agent
|
|
92
|
+
quotes the generator's `MDS onboarding complete` tail block back
|
|
93
|
+
verbatim so the Mr. DJ thank-you text appears in chat the same way
|
|
94
|
+
it does in a terminal CLI run.
|
|
95
|
+
- **MDS Continue handoff.** The agent runs `mds continue` from the
|
|
96
|
+
generated app folder, then tells the user-dev to open that app folder
|
|
97
|
+
in a fresh agent session to lower token usage and cost.
|
|
98
|
+
|
|
99
|
+
## Defaults
|
|
100
|
+
|
|
101
|
+
- Prefer Uniwind and Tailwind v4 for new projects.
|
|
102
|
+
- Keep route files thin and move business logic into features, services, hooks,
|
|
103
|
+
and utilities.
|
|
104
|
+
- Always add `project/info.md`, `project/todo.md`, `project/style.md`, and
|
|
105
|
+
`project/guidelines.md` when project memory is selected.
|
|
106
|
+
- Normalize existing `project/info.md` and `project/style.md` into canonical
|
|
107
|
+
sections while preserving unknown content under Imported Notes.
|
|
108
|
+
- Add `project/intake-agent.md` when context is thin or imported notes need a
|
|
109
|
+
follow-up agent conversation.
|
|
110
|
+
- Keep `project/style.md` visual-only; put technical and agent rules in
|
|
111
|
+
`project/guidelines.md`.
|
|
112
|
+
- Treat monorepo-aware scaffolding as a future step after the single-app MVP is
|
|
113
|
+
stable.
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# API Routes with Catch-All Pattern
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Expo Router API routes use the `+api.ts` pattern to create server-side endpoints directly alongside client routes. The `[...segments]+api.ts` syntax creates catch-all routes that capture all path segments, enabling flexible API endpoint structures with full control over HTTP methods, CORS, and request handling.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
**Use API routes** for:
|
|
10
|
+
- ✅ Backend endpoints served from same domain as frontend
|
|
11
|
+
- ✅ Proxy servers that forward requests upstream
|
|
12
|
+
- ✅ Server-side validation and data transformation
|
|
13
|
+
- ✅ Protected endpoints with authentication
|
|
14
|
+
- ✅ Catch-all routes for dynamic path handling
|
|
15
|
+
|
|
16
|
+
## Code Example
|
|
17
|
+
|
|
18
|
+
### Basic API Route (Single Endpoint)
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// File: app/api/quantum-backend/[...segments]+api.ts
|
|
22
|
+
import { ExpoRequest, ExpoResponse } from 'expo-server-rendering';
|
|
23
|
+
|
|
24
|
+
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
|
|
25
|
+
|
|
26
|
+
const ALLOWED_ORIGINS = [
|
|
27
|
+
'http://localhost:3000',
|
|
28
|
+
'http://localhost:8081',
|
|
29
|
+
'http://127.0.0.1:3000',
|
|
30
|
+
'http://127.0.0.1:8081',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function normalizeOrigin(request: ExpoRequest): string | null {
|
|
34
|
+
const origin = request.headers.get('origin');
|
|
35
|
+
if (!origin) return null;
|
|
36
|
+
|
|
37
|
+
return ALLOWED_ORIGINS.includes(origin) ? origin : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function GET(request: ExpoRequest) {
|
|
41
|
+
const allowedOrigin = normalizeOrigin(request);
|
|
42
|
+
|
|
43
|
+
if (request.method === 'OPTIONS') {
|
|
44
|
+
return new ExpoResponse(null, {
|
|
45
|
+
status: 204,
|
|
46
|
+
headers: allowedOrigin
|
|
47
|
+
? { 'Access-Control-Allow-Origin': allowedOrigin }
|
|
48
|
+
: {},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle GET request
|
|
53
|
+
return new ExpoResponse(JSON.stringify({ message: 'GET OK' }), {
|
|
54
|
+
status: 200,
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
...(allowedOrigin && {
|
|
58
|
+
'Access-Control-Allow-Origin': allowedOrigin,
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function POST(request: ExpoRequest) {
|
|
65
|
+
const allowedOrigin = normalizeOrigin(request);
|
|
66
|
+
const body = await request.json();
|
|
67
|
+
|
|
68
|
+
// Process request
|
|
69
|
+
const result = {
|
|
70
|
+
success: true,
|
|
71
|
+
data: body,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return new ExpoResponse(JSON.stringify(result), {
|
|
75
|
+
status: 200,
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
...(allowedOrigin && {
|
|
79
|
+
'Access-Control-Allow-Origin': allowedOrigin,
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**From:** DJsPortfolio/src/app/api/quantum-backend/[...segments]+api.ts
|
|
87
|
+
|
|
88
|
+
### Catch-All Route with Path Normalization
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// File: app/public-facing/api/quantum/v1/[...segments]+api.ts
|
|
92
|
+
import { ExpoRequest, ExpoResponse } from 'expo-server-rendering';
|
|
93
|
+
|
|
94
|
+
const DISALLOWED_PREFIXES = ['/v1/keys', '/v1/ibm/profiles'];
|
|
95
|
+
const DEFAULT_UPSTREAM_BASE_URL = 'http://127.0.0.1:8000/v1';
|
|
96
|
+
|
|
97
|
+
type ProxyMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
98
|
+
|
|
99
|
+
function normalizeUpstreamBaseUrl(): string {
|
|
100
|
+
const raw = process.env.EXPO_PUBLIC_QUANTUM_API_BASE_URL?.trim();
|
|
101
|
+
const base = raw || DEFAULT_UPSTREAM_BASE_URL;
|
|
102
|
+
|
|
103
|
+
// Remove trailing slash
|
|
104
|
+
const trimmed = base.replace(/\/+$/, '');
|
|
105
|
+
|
|
106
|
+
// Ensure /v1 suffix
|
|
107
|
+
return trimmed.endsWith('/v1') ? trimmed : `${trimmed}/v1`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeOperationPath(pathname: string): string {
|
|
111
|
+
// Remove leading/trailing slashes, lowercase
|
|
112
|
+
return `/${pathname.toLowerCase().trim().replace(/^\/+|\/+$/g, '')}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function POST(request: ExpoRequest) {
|
|
116
|
+
try {
|
|
117
|
+
// Parse segments from URL
|
|
118
|
+
const url = new URL(request.url);
|
|
119
|
+
const pathname = url.pathname;
|
|
120
|
+
|
|
121
|
+
// Extract segments after route prefix
|
|
122
|
+
const segments = pathname
|
|
123
|
+
.replace('/public-facing/api/quantum/v1', '')
|
|
124
|
+
.split('/')
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
|
|
127
|
+
const operationPath = '/' + segments.join('/');
|
|
128
|
+
|
|
129
|
+
// Validate disallowed paths
|
|
130
|
+
for (const prefix of DISALLOWED_PREFIXES) {
|
|
131
|
+
if (operationPath.startsWith(prefix)) {
|
|
132
|
+
return new ExpoResponse(
|
|
133
|
+
JSON.stringify({ error: 'Path not allowed' }),
|
|
134
|
+
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Forward to upstream
|
|
140
|
+
const upstreamUrl = normalizeUpstreamBaseUrl() + operationPath;
|
|
141
|
+
const upstreamRequest = new Request(upstreamUrl, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: request.headers,
|
|
144
|
+
body: await request.text(),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const response = await fetch(upstreamRequest);
|
|
148
|
+
return new ExpoResponse(await response.text(), {
|
|
149
|
+
status: response.status,
|
|
150
|
+
headers: Object.fromEntries(response.headers),
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return new ExpoResponse(
|
|
154
|
+
JSON.stringify({ error: 'Internal Server Error' }),
|
|
155
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**From:** DJsPortfolio/src/app/public-facing/api/quantum/v1/[...segments]+api.ts (lines 1-50)
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
### Enable API Routes in app.json
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"expo": {
|
|
170
|
+
"plugins": [
|
|
171
|
+
[
|
|
172
|
+
"expo-router",
|
|
173
|
+
{
|
|
174
|
+
"apiRoutes": true,
|
|
175
|
+
"origin": false
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Environment Variables for API Base URLs
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# .env or .env.production
|
|
187
|
+
EXPO_PUBLIC_QUANTUM_API_BASE_URL=http://127.0.0.1:8000/v1
|
|
188
|
+
QUANTUM_UPSTREAM_API_SECRET=your-secret-key
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### TypeScript Setup
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { ExpoRequest, ExpoResponse } from 'expo-server-rendering';
|
|
195
|
+
|
|
196
|
+
export type { ExpoRequest, ExpoResponse };
|
|
197
|
+
|
|
198
|
+
// Type for allowed HTTP methods
|
|
199
|
+
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Best Practices
|
|
203
|
+
|
|
204
|
+
### ✅ DO
|
|
205
|
+
|
|
206
|
+
1. **Always validate CORS origin** before responding
|
|
207
|
+
```typescript
|
|
208
|
+
const ALLOWED_ORIGINS = ['http://localhost:3000', 'https://app.example.com'];
|
|
209
|
+
|
|
210
|
+
function validateOrigin(origin: string | null): boolean {
|
|
211
|
+
return !!origin && ALLOWED_ORIGINS.includes(origin);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
2. **Normalize paths and trim trailing slashes**
|
|
216
|
+
```typescript
|
|
217
|
+
function normalizePath(path: string): string {
|
|
218
|
+
return path.replace(/\/+$/, '').toLowerCase();
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
3. **Use explicit status codes**
|
|
223
|
+
```typescript
|
|
224
|
+
// ✅ GOOD
|
|
225
|
+
return new ExpoResponse(JSON.stringify(error), {
|
|
226
|
+
status: 400,
|
|
227
|
+
headers: { 'Content-Type': 'application/json' },
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
4. **Handle disallowed paths explicitly**
|
|
232
|
+
```typescript
|
|
233
|
+
const DISALLOWED_PREFIXES = ['/keys', '/credentials'];
|
|
234
|
+
|
|
235
|
+
for (const prefix of DISALLOWED_PREFIXES) {
|
|
236
|
+
if (operationPath.startsWith(prefix)) {
|
|
237
|
+
return new ExpoResponse(
|
|
238
|
+
JSON.stringify({ error: 'Forbidden' }),
|
|
239
|
+
{ status: 403 }
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
5. **Support OPTIONS preflight requests**
|
|
246
|
+
```typescript
|
|
247
|
+
if (request.method === 'OPTIONS') {
|
|
248
|
+
return new ExpoResponse(null, {
|
|
249
|
+
status: 204,
|
|
250
|
+
headers: corsHeaders,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### ❌ DON'T
|
|
256
|
+
|
|
257
|
+
1. **Don't skip CORS validation**
|
|
258
|
+
```typescript
|
|
259
|
+
// ❌ BAD - allows any origin
|
|
260
|
+
headers: { 'Access-Control-Allow-Origin': '*' }
|
|
261
|
+
|
|
262
|
+
// ✅ GOOD - restrict to known origins
|
|
263
|
+
if (ALLOWED_ORIGINS.includes(origin)) {
|
|
264
|
+
headers: { 'Access-Control-Allow-Origin': origin }
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
2. **Don't expose sensitive environment variables**
|
|
269
|
+
```typescript
|
|
270
|
+
// ❌ BAD
|
|
271
|
+
headers: process.env.API_SECRET
|
|
272
|
+
|
|
273
|
+
// ✅ GOOD - only use server-side secrets
|
|
274
|
+
const secret = process.env.API_SECRET; // Used in headers, not exposed
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
3. **Don't pass unchecked path segments to upstream**
|
|
278
|
+
```typescript
|
|
279
|
+
// ❌ BAD - could be exploited
|
|
280
|
+
const upstreamUrl = baseUrl + segments;
|
|
281
|
+
|
|
282
|
+
// ✅ GOOD - validate and normalize
|
|
283
|
+
const validatedPath = normalizePath(segments);
|
|
284
|
+
const upstreamUrl = baseUrl + validatedPath;
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
4. **Don't forget to handle errors**
|
|
288
|
+
```typescript
|
|
289
|
+
// ❌ BAD - uncaught errors crash request
|
|
290
|
+
const data = await request.json();
|
|
291
|
+
|
|
292
|
+
// ✅ GOOD - catch and return error response
|
|
293
|
+
try {
|
|
294
|
+
const data = await request.json();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
return new ExpoResponse(
|
|
297
|
+
JSON.stringify({ error: 'Invalid JSON' }),
|
|
298
|
+
{ status: 400 }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Related Patterns
|
|
304
|
+
|
|
305
|
+
- [File-Based Routing](./file-based-routing.md) — Route file structure
|
|
306
|
+
- [Error Handling](../api/error-handling.md) — Custom error classes
|
|
307
|
+
- [CORS Configuration](../api/cors-configuration.md) — Origin validation
|
|
308
|
+
- [Health Endpoints](../api/health-endpoints.md) — Health check patterns
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
*Pattern extracted from production repositories: DJsPortfolio, PokePages*
|
|
313
|
+
*Files: DJsPortfolio/src\app\api\quantum-backend\[...segments]+api.ts*
|
|
314
|
+
*Lines 1-40 of catch-all route pattern with path normalization*
|