@polymorphism-tech/morph-spec 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CLAUDE.md +108 -946
  2. package/bin/morph-spec.js +284 -9
  3. package/bin/task-manager.cjs +102 -14
  4. package/bin/validate.js +4 -4
  5. package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
  6. package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
  7. package/docs/next-generation/EXECUTION-FLOW.md +274 -0
  8. package/docs/next-generation/META-PROMPTS.md +235 -0
  9. package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
  10. package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
  11. package/package.json +5 -5
  12. package/src/commands/agents/agents-fuse.js +97 -0
  13. package/src/commands/agents/micro-agent.js +112 -0
  14. package/src/commands/agents/spawn-team.js +69 -4
  15. package/src/commands/agents/squad-template.js +146 -0
  16. package/src/commands/analytics/analytics.js +176 -0
  17. package/src/commands/context/context-prime.js +63 -0
  18. package/src/commands/context/core-four.js +54 -0
  19. package/src/commands/mcp/mcp.js +102 -0
  20. package/src/commands/project/detect-agents.js +32 -2
  21. package/src/commands/project/detect.js +11 -1
  22. package/src/commands/project/doctor.js +573 -356
  23. package/src/commands/project/init.js +9 -2
  24. package/src/commands/project/update.js +13 -3
  25. package/src/commands/state/advance-phase.js +448 -416
  26. package/src/commands/state/state.js +14 -12
  27. package/src/commands/tasks/task.js +1 -1
  28. package/src/commands/templates/template-render.js +80 -1
  29. package/src/commands/threads/thread-template.js +103 -0
  30. package/src/commands/threads/threads.js +261 -0
  31. package/src/commands/trust/trust.js +205 -0
  32. package/src/{orchestrator.js → core/orchestrator.js} +8 -8
  33. package/src/core/state/state-manager.js +37 -17
  34. package/src/core/workflows/workflow-detector.js +114 -3
  35. package/src/lib/agents/micro-agent-factory.js +161 -0
  36. package/src/lib/analytics/analytics-engine.js +345 -0
  37. package/src/lib/checkpoints/checkpoint-hooks.js +298 -258
  38. package/src/lib/context/context-bundler.js +240 -0
  39. package/src/lib/context/context-optimizer.js +212 -0
  40. package/src/lib/context/context-tracker.js +273 -0
  41. package/src/lib/context/core-four-tracker.js +201 -0
  42. package/src/lib/context/mcp-optimizer.js +200 -0
  43. package/src/lib/detectors/index.js +1 -1
  44. package/src/lib/detectors/standards-generator.js +77 -17
  45. package/src/lib/detectors/structure-detector.js +67 -39
  46. package/src/lib/execution/fusion-executor.js +304 -0
  47. package/src/lib/execution/parallel-executor.js +270 -0
  48. package/src/lib/generators/context-generator.js +3 -3
  49. package/src/lib/generators/recap-generator.js +32 -12
  50. package/src/lib/hooks/hook-executor.js +169 -0
  51. package/src/lib/hooks/stop-hook-executor.js +286 -0
  52. package/src/lib/hops/hop-composer.js +221 -0
  53. package/src/lib/threads/thread-coordinator.js +238 -0
  54. package/src/lib/threads/thread-manager.js +317 -0
  55. package/src/lib/tracking/artifact-trail.js +202 -0
  56. package/src/lib/trust/trust-manager.js +269 -0
  57. package/src/lib/validators/design-system/design-system-validator.js +2 -2
  58. package/src/lib/validators/validation-runner.js +14 -30
  59. package/src/utils/hooks-installer.js +69 -0
  60. package/stacks/blazor-azure/.morph/config/agents.json +72 -3
  61. package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
  62. package/docs/llm-interaction-config.md +0 -735
  63. package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
  64. package/src/commands/utils/migrate-state.js +0 -158
  65. package/src/commands/utils/upgrade.js +0 -346
  66. package/src/lib/validators/architecture-validator.js +0 -60
  67. package/src/lib/validators/content-validator.js +0 -164
  68. package/src/lib/validators/package-validator.js +0 -61
  69. package/src/lib/validators/ui-contrast-validator.js +0 -44
  70. package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
  71. package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
  72. package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
  73. package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
  74. package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
  75. package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
  76. package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
  77. package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
  78. package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
  79. package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
  80. package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
  81. package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
  82. package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
  83. package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
  84. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
  85. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
  86. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
  87. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
  88. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
  89. package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
  90. package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
  91. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
  92. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
  93. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
  94. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
  95. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
  96. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
  97. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
  98. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
  99. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
  100. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
  101. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
  102. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
  103. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
  104. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
  105. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
  106. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
  107. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
  108. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
  109. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
  110. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
  111. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
  112. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
  113. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
  114. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
  115. package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
  116. package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
  117. package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
  118. package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
  119. package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
  120. package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
  121. package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
  122. package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
  123. package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
  124. package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
  125. package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
  126. package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
  127. package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
  128. package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
  129. package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
  130. package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
  131. package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
  132. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
  133. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
  134. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
  135. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
  136. /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
  137. /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
  138. /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
  139. /package/docs/{v3.0 → next-generation}/README.md +0 -0
  140. /package/docs/{v3.0 → next-generation}/ROADMAP.md +0 -0
@@ -1,244 +0,0 @@
1
- # .NET + Supabase Backend
2
-
3
- > **Layer:** 2 | **Load:** on-keyword | **Keywords:** dotnet, supabase, npgsql, dapper, jwt, minimal-api, postgresql
4
-
5
- ## Identity
6
-
7
- Backend specialist for .NET Minimal API with Supabase (PostgreSQL). Uses Npgsql for connections, Dapper for data access with records, JWT middleware for Supabase Auth token validation, and typed Minimal API endpoints. No EF Core -- direct SQL with Dapper for maximum control over Supabase's PostgreSQL.
8
-
9
- ## Domains
10
-
11
- - data-access
12
- - auth-middleware
13
- - api-endpoints
14
-
15
- ## Standards
16
-
17
- - coding.md -- C# naming, sealed classes, CancellationToken, Result pattern
18
- - architecture.md -- Layer boundaries (API -> Application -> Domain)
19
-
20
- ## Patterns
21
-
22
- ### Npgsql Connection Setup
23
-
24
- ```csharp
25
- // Program.cs
26
- builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
27
- {
28
- var connectionString = builder.Configuration.GetConnectionString("Supabase")
29
- ?? throw new InvalidOperationException("Supabase connection string not configured");
30
- var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
31
- dataSourceBuilder.UseVector(); // pgvector support
32
- return dataSourceBuilder.Build();
33
- });
34
-
35
- // appsettings.json
36
- {
37
- "ConnectionStrings": {
38
- "Supabase": "Host=db.xxx.supabase.co;Port=5432;Database=postgres;Username=postgres;Password=${DB_PASSWORD};SSL Mode=Require;Trust Server Certificate=true"
39
- }
40
- }
41
- ```
42
-
43
- ### Dapper Queries with Records
44
-
45
- ```csharp
46
- namespace MyApp.Infrastructure.Repositories;
47
-
48
- public sealed class DocumentRepository(NpgsqlDataSource dataSource) : IDocumentRepository
49
- {
50
- public async Task<DocumentDto?> GetByIdAsync(Guid id, CancellationToken ct = default)
51
- {
52
- await using var connection = await dataSource.OpenConnectionAsync(ct);
53
- return await connection.QueryFirstOrDefaultAsync<DocumentDto>(
54
- """
55
- SELECT id, title, content, user_id AS UserId, created_at AS CreatedAt
56
- FROM documents
57
- WHERE id = @Id
58
- """,
59
- new { Id = id });
60
- }
61
-
62
- public async Task<PagedResult<DocumentDto>> GetPagedAsync(
63
- PaginationQuery query,
64
- Guid userId,
65
- CancellationToken ct = default)
66
- {
67
- await using var connection = await dataSource.OpenConnectionAsync(ct);
68
-
69
- var count = await connection.ExecuteScalarAsync<int>(
70
- "SELECT count(*) FROM documents WHERE user_id = @UserId",
71
- new { UserId = userId });
72
-
73
- var items = await connection.QueryAsync<DocumentDto>(
74
- """
75
- SELECT id, title, content, user_id AS UserId, created_at AS CreatedAt
76
- FROM documents
77
- WHERE user_id = @UserId
78
- ORDER BY created_at DESC
79
- LIMIT @PageSize OFFSET @Offset
80
- """,
81
- new { UserId = userId, query.PageSize, Offset = (query.Page - 1) * query.PageSize });
82
-
83
- return new PagedResult<DocumentDto>(items.ToList(), count, query.Page, query.PageSize);
84
- }
85
-
86
- public async Task<Guid> CreateAsync(CreateDocumentRequest request, Guid userId, CancellationToken ct = default)
87
- {
88
- await using var connection = await dataSource.OpenConnectionAsync(ct);
89
- return await connection.ExecuteScalarAsync<Guid>(
90
- """
91
- INSERT INTO documents (title, content, user_id)
92
- VALUES (@Title, @Content, @UserId)
93
- RETURNING id
94
- """,
95
- new { request.Title, request.Content, UserId = userId });
96
- }
97
- }
98
- ```
99
-
100
- ### JWT Middleware Setup (Supabase Auth)
101
-
102
- ```csharp
103
- // Program.cs
104
- builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
105
- .AddJwtBearer(options =>
106
- {
107
- var supabaseUrl = builder.Configuration["Supabase:Url"]
108
- ?? throw new InvalidOperationException("Supabase:Url not configured");
109
- var jwtSecret = builder.Configuration["Supabase:JwtSecret"]
110
- ?? throw new InvalidOperationException("Supabase:JwtSecret not configured");
111
-
112
- options.TokenValidationParameters = new TokenValidationParameters
113
- {
114
- ValidateIssuer = true,
115
- ValidIssuer = $"{supabaseUrl}/auth/v1",
116
- ValidateAudience = true,
117
- ValidAudience = "authenticated",
118
- ValidateIssuerSigningKey = true,
119
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)),
120
- ValidateLifetime = true,
121
- ClockSkew = TimeSpan.FromSeconds(30)
122
- };
123
- });
124
-
125
- builder.Services.AddAuthorization();
126
- app.UseAuthentication();
127
- app.UseAuthorization();
128
- ```
129
-
130
- ### User ID Extraction
131
-
132
- ```csharp
133
- // Extension method for ClaimsPrincipal
134
- public static class ClaimsPrincipalExtensions
135
- {
136
- public static Guid GetUserId(this ClaimsPrincipal user)
137
- {
138
- var sub = user.FindFirstValue(ClaimTypes.NameIdentifier)
139
- ?? throw new UnauthorizedAccessException("User ID claim not found");
140
- return Guid.Parse(sub);
141
- }
142
- }
143
- ```
144
-
145
- ### Minimal API Endpoints
146
-
147
- ```csharp
148
- namespace MyApp.Api.Endpoints;
149
-
150
- public static class DocumentEndpoints
151
- {
152
- public static void MapDocumentEndpoints(this WebApplication app)
153
- {
154
- var group = app.MapGroup("/api/documents")
155
- .WithTags("Documents")
156
- .RequireAuthorization();
157
-
158
- group.MapGet("/", GetAllAsync);
159
- group.MapGet("/{id:guid}", GetByIdAsync);
160
- group.MapPost("/", CreateAsync);
161
- group.MapPut("/{id:guid}", UpdateAsync);
162
- group.MapDelete("/{id:guid}", DeleteAsync);
163
- }
164
-
165
- private static async Task<IResult> GetAllAsync(
166
- [AsParameters] PaginationQuery query,
167
- ClaimsPrincipal user,
168
- IDocumentService service,
169
- CancellationToken ct)
170
- {
171
- var userId = user.GetUserId();
172
- var result = await service.GetPagedAsync(query, userId, ct);
173
- return Results.Ok(result);
174
- }
175
-
176
- private static async Task<IResult> CreateAsync(
177
- CreateDocumentRequest request,
178
- ClaimsPrincipal user,
179
- IDocumentService service,
180
- CancellationToken ct)
181
- {
182
- var userId = user.GetUserId();
183
- var result = await service.CreateAsync(request, userId, ct);
184
- return result.IsSuccess
185
- ? Results.Created($"/api/documents/{result.Value.Id}", result.Value)
186
- : Results.BadRequest(result.Error);
187
- }
188
- }
189
- ```
190
-
191
- ### Supabase Client Wrapper (for Storage/Auth Admin)
192
-
193
- ```csharp
194
- namespace MyApp.Infrastructure.Supabase;
195
-
196
- public sealed class SupabaseAdmin(HttpClient httpClient, IOptions<SupabaseOptions> options)
197
- {
198
- private readonly string _serviceKey = options.Value.ServiceRoleKey;
199
-
200
- public async Task<string> UploadFileAsync(
201
- string bucket,
202
- string path,
203
- Stream content,
204
- string contentType,
205
- CancellationToken ct = default)
206
- {
207
- using var request = new HttpRequestMessage(HttpMethod.Post,
208
- $"{options.Value.Url}/storage/v1/object/{bucket}/{path}");
209
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _serviceKey);
210
- request.Headers.Add("apikey", _serviceKey);
211
- request.Content = new StreamContent(content);
212
- request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
213
-
214
- var response = await httpClient.SendAsync(request, ct);
215
- response.EnsureSuccessStatusCode();
216
-
217
- var result = await response.Content.ReadFromJsonAsync<StorageUploadResult>(ct);
218
- return $"{options.Value.Url}/storage/v1/object/public/{bucket}/{result?.Key}";
219
- }
220
- }
221
-
222
- public sealed record SupabaseOptions
223
- {
224
- public required string Url { get; init; }
225
- public required string AnonKey { get; init; }
226
- public required string ServiceRoleKey { get; init; }
227
- public required string JwtSecret { get; init; }
228
- }
229
- ```
230
-
231
- ## Checklist
232
-
233
- - [ ] NpgsqlDataSource registered as Singleton (connection pooling)
234
- - [ ] Dapper queries use parameterized SQL (never string interpolation)
235
- - [ ] JWT middleware validates issuer, audience, signing key, and lifetime
236
- - [ ] service_role key stored in environment variables (not appsettings)
237
- - [ ] All async methods accept CancellationToken
238
- - [ ] All DTOs are records (not classes)
239
- - [ ] Endpoints use RequireAuthorization()
240
- - [ ] User ID extracted from JWT claims (not request body)
241
-
242
- ---
243
-
244
- *MORPH-SPEC by Polymorphism Tech*
@@ -1,335 +0,0 @@
1
- # Next.js + Supabase Frontend
2
-
3
- > **Layer:** 2 | **Load:** on-keyword | **Keywords:** nextjs, supabase, ssr, auth, react-query, shadcn, tailwind, middleware
4
-
5
- ## Identity
6
-
7
- Frontend specialist for Next.js App Router with Supabase. Implements @supabase/ssr for server and browser clients, auth middleware for route protection, React Query for data fetching with Supabase, shadcn/ui components with Tailwind CSS, and Realtime subscriptions.
8
-
9
- ## Domains
10
-
11
- - auth-ui
12
- - data-fetching
13
- - realtime
14
- - components
15
-
16
- ## Standards
17
-
18
- - css-naming.md -- Tailwind utility class ordering conventions
19
- - architecture.md -- Server vs Client component boundaries
20
-
21
- ## Patterns
22
-
23
- ### @supabase/ssr Client Setup
24
-
25
- ```typescript
26
- // lib/supabase/client.ts (Browser client)
27
- import { createBrowserClient } from "@supabase/ssr";
28
- import type { Database } from "@/types/database";
29
-
30
- export function createClient() {
31
- return createBrowserClient<Database>(
32
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
33
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
34
- );
35
- }
36
- ```
37
-
38
- ```typescript
39
- // lib/supabase/server.ts (Server client)
40
- import { createServerClient } from "@supabase/ssr";
41
- import { cookies } from "next/headers";
42
- import type { Database } from "@/types/database";
43
-
44
- export async function createServerSupabaseClient() {
45
- const cookieStore = await cookies();
46
-
47
- return createServerClient<Database>(
48
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
49
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
50
- {
51
- cookies: {
52
- getAll() {
53
- return cookieStore.getAll();
54
- },
55
- setAll(cookiesToSet) {
56
- try {
57
- cookiesToSet.forEach(({ name, value, options }) =>
58
- cookieStore.set(name, value, options)
59
- );
60
- } catch {
61
- // The `setAll` method is called from a Server Component
62
- // if middleware refreshes the session. Ignore in that context.
63
- }
64
- },
65
- },
66
- }
67
- );
68
- }
69
- ```
70
-
71
- ### Auth Middleware
72
-
73
- ```typescript
74
- // middleware.ts
75
- import { createServerClient } from "@supabase/ssr";
76
- import { NextResponse, type NextRequest } from "next/server";
77
-
78
- const publicRoutes = ["/", "/login", "/signup", "/auth/callback"];
79
-
80
- export async function middleware(request: NextRequest) {
81
- let response = NextResponse.next({ request });
82
-
83
- const supabase = createServerClient(
84
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
85
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
86
- {
87
- cookies: {
88
- getAll() {
89
- return request.cookies.getAll();
90
- },
91
- setAll(cookiesToSet) {
92
- cookiesToSet.forEach(({ name, value }) =>
93
- request.cookies.set(name, value)
94
- );
95
- response = NextResponse.next({ request });
96
- cookiesToSet.forEach(({ name, value, options }) =>
97
- response.cookies.set(name, value, options)
98
- );
99
- },
100
- },
101
- }
102
- );
103
-
104
- const {
105
- data: { user },
106
- } = await supabase.auth.getUser();
107
-
108
- const isPublicRoute = publicRoutes.some((route) =>
109
- request.nextUrl.pathname.startsWith(route)
110
- );
111
-
112
- if (!user && !isPublicRoute) {
113
- const url = request.nextUrl.clone();
114
- url.pathname = "/login";
115
- url.searchParams.set("redirectTo", request.nextUrl.pathname);
116
- return NextResponse.redirect(url);
117
- }
118
-
119
- return response;
120
- }
121
-
122
- export const config = {
123
- matcher: ["/((?!_next/static|_next/image|favicon.ico|api/health).*)"],
124
- };
125
- ```
126
-
127
- ### Auth Callback Route
128
-
129
- ```typescript
130
- // app/auth/callback/route.ts
131
- import { createServerSupabaseClient } from "@/lib/supabase/server";
132
- import { NextResponse } from "next/server";
133
-
134
- export async function GET(request: Request) {
135
- const { searchParams, origin } = new URL(request.url);
136
- const code = searchParams.get("code");
137
- const next = searchParams.get("next") ?? "/dashboard";
138
-
139
- if (code) {
140
- const supabase = await createServerSupabaseClient();
141
- const { error } = await supabase.auth.exchangeCodeForSession(code);
142
- if (!error) {
143
- return NextResponse.redirect(`${origin}${next}`);
144
- }
145
- }
146
-
147
- return NextResponse.redirect(`${origin}/login?error=auth_failed`);
148
- }
149
- ```
150
-
151
- ### React Query + Supabase
152
-
153
- ```typescript
154
- // hooks/use-documents.ts
155
- "use client";
156
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
157
- import { createClient } from "@/lib/supabase/client";
158
- import type { Document, CreateDocumentInput } from "@/types/database";
159
-
160
- const supabase = createClient();
161
-
162
- export function useDocuments(page = 1, pageSize = 10) {
163
- return useQuery({
164
- queryKey: ["documents", page, pageSize],
165
- queryFn: async () => {
166
- const from = (page - 1) * pageSize;
167
- const to = from + pageSize - 1;
168
-
169
- const { data, error, count } = await supabase
170
- .from("documents")
171
- .select("*", { count: "exact" })
172
- .order("created_at", { ascending: false })
173
- .range(from, to);
174
-
175
- if (error) throw error;
176
- return { items: data as Document[], totalCount: count ?? 0, page, pageSize };
177
- },
178
- });
179
- }
180
-
181
- export function useCreateDocument() {
182
- const queryClient = useQueryClient();
183
-
184
- return useMutation({
185
- mutationFn: async (input: CreateDocumentInput) => {
186
- const { data, error } = await supabase
187
- .from("documents")
188
- .insert(input)
189
- .select()
190
- .single();
191
-
192
- if (error) throw error;
193
- return data as Document;
194
- },
195
- onSuccess: () => {
196
- queryClient.invalidateQueries({ queryKey: ["documents"] });
197
- },
198
- });
199
- }
200
- ```
201
-
202
- ### Form with react-hook-form + zod
203
-
204
- ```typescript
205
- // components/document-form.tsx
206
- "use client";
207
- import { useForm } from "react-hook-form";
208
- import { zodResolver } from "@hookform/resolvers/zod";
209
- import { z } from "zod";
210
- import { Button } from "@/components/ui/button";
211
- import { Input } from "@/components/ui/input";
212
- import { Textarea } from "@/components/ui/textarea";
213
- import { useCreateDocument } from "@/hooks/use-documents";
214
-
215
- const schema = z.object({
216
- title: z.string().min(1, "Title is required").max(200),
217
- content: z.string().min(1, "Content is required"),
218
- });
219
-
220
- type FormData = z.infer<typeof schema>;
221
-
222
- export function DocumentForm({ onSuccess }: { onSuccess?: () => void }) {
223
- const { register, handleSubmit, formState: { errors }, reset } = useForm<FormData>({
224
- resolver: zodResolver(schema),
225
- });
226
-
227
- const createDocument = useCreateDocument();
228
-
229
- const onSubmit = async (data: FormData) => {
230
- await createDocument.mutateAsync(data);
231
- reset();
232
- onSuccess?.();
233
- };
234
-
235
- return (
236
- <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
237
- <div>
238
- <Input placeholder="Document title" {...register("title")} />
239
- {errors.title && <p className="text-sm text-destructive mt-1">{errors.title.message}</p>}
240
- </div>
241
- <div>
242
- <Textarea placeholder="Content..." rows={6} {...register("content")} />
243
- {errors.content && <p className="text-sm text-destructive mt-1">{errors.content.message}</p>}
244
- </div>
245
- <Button type="submit" disabled={createDocument.isPending}>
246
- {createDocument.isPending ? "Creating..." : "Create Document"}
247
- </Button>
248
- </form>
249
- );
250
- }
251
- ```
252
-
253
- ### Realtime Subscription Hook
254
-
255
- ```typescript
256
- // hooks/use-realtime.ts
257
- "use client";
258
- import { useEffect } from "react";
259
- import { useQueryClient } from "@tanstack/react-query";
260
- import { createClient } from "@/lib/supabase/client";
261
- import type { RealtimePostgresChangesPayload } from "@supabase/supabase-js";
262
-
263
- export function useRealtimeSubscription<T extends Record<string, unknown>>(
264
- table: string,
265
- queryKey: string[]
266
- ) {
267
- const queryClient = useQueryClient();
268
- const supabase = createClient();
269
-
270
- useEffect(() => {
271
- const channel = supabase
272
- .channel(`${table}-changes`)
273
- .on<T>(
274
- "postgres_changes",
275
- { event: "*", schema: "public", table },
276
- (payload: RealtimePostgresChangesPayload<T>) => {
277
- queryClient.invalidateQueries({ queryKey });
278
- }
279
- )
280
- .subscribe();
281
-
282
- return () => {
283
- supabase.removeChannel(channel);
284
- };
285
- }, [table, queryKey, queryClient, supabase]);
286
- }
287
-
288
- // Usage in component:
289
- // useRealtimeSubscription<Document>("documents", ["documents"]);
290
- ```
291
-
292
- ### Server Component Data Fetching
293
-
294
- ```typescript
295
- // app/dashboard/page.tsx
296
- import { createServerSupabaseClient } from "@/lib/supabase/server";
297
- import { DocumentList } from "@/components/document-list";
298
-
299
- export default async function DashboardPage() {
300
- const supabase = await createServerSupabaseClient();
301
- const { data: user } = await supabase.auth.getUser();
302
-
303
- const { data: documents } = await supabase
304
- .from("documents")
305
- .select("*")
306
- .order("created_at", { ascending: false })
307
- .limit(20);
308
-
309
- return (
310
- <main className="container mx-auto py-8">
311
- <h1 className="text-2xl font-bold mb-6">
312
- Welcome, {user.user?.email}
313
- </h1>
314
- <DocumentList initialDocuments={documents ?? []} />
315
- </main>
316
- );
317
- }
318
- ```
319
-
320
- ## Checklist
321
-
322
- - [ ] @supabase/ssr used (not @supabase/auth-helpers-nextjs)
323
- - [ ] Server and browser clients in separate files
324
- - [ ] Middleware refreshes session on every request
325
- - [ ] Public routes excluded from auth check
326
- - [ ] Auth callback route handles code exchange
327
- - [ ] React Query configured with QueryClientProvider
328
- - [ ] Forms validated with zod schemas
329
- - [ ] Realtime subscriptions clean up on unmount
330
- - [ ] Server Components fetch data directly (no useEffect)
331
- - [ ] Environment variables: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY
332
-
333
- ---
334
-
335
- *MORPH-SPEC by Polymorphism Tech*