@polymorphism-tech/morph-spec 4.2.0 → 4.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 (132) hide show
  1. package/bin/morph-spec.js +283 -8
  2. package/bin/validate.js +4 -4
  3. package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
  4. package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
  5. package/docs/next-generation/EXECUTION-FLOW.md +274 -0
  6. package/docs/next-generation/META-PROMPTS.md +235 -0
  7. package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
  8. package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
  9. package/package.json +5 -5
  10. package/src/commands/agents/agents-fuse.js +96 -0
  11. package/src/commands/agents/micro-agent.js +112 -0
  12. package/src/commands/agents/spawn-team.js +69 -4
  13. package/src/commands/agents/squad-template.js +146 -0
  14. package/src/commands/analytics/analytics.js +176 -0
  15. package/src/commands/context/context-prime.js +63 -0
  16. package/src/commands/context/core-four.js +54 -0
  17. package/src/commands/mcp/mcp.js +102 -0
  18. package/src/commands/project/detect-agents.js +1 -1
  19. package/src/commands/project/doctor.js +573 -356
  20. package/src/commands/project/init.js +1 -1
  21. package/src/commands/project/update.js +1 -1
  22. package/src/commands/state/advance-phase.js +433 -416
  23. package/src/commands/templates/template-render.js +80 -1
  24. package/src/commands/threads/thread-template.js +103 -0
  25. package/src/commands/threads/threads.js +261 -0
  26. package/src/commands/trust/trust.js +205 -0
  27. package/src/{orchestrator.js → core/orchestrator.js} +8 -8
  28. package/src/core/state/state-manager.js +18 -2
  29. package/src/core/workflows/workflow-detector.js +100 -2
  30. package/src/lib/agents/micro-agent-factory.js +161 -0
  31. package/src/lib/analytics/analytics-engine.js +345 -0
  32. package/src/lib/checkpoints/checkpoint-hooks.js +293 -258
  33. package/src/lib/context/context-bundler.js +240 -0
  34. package/src/lib/context/context-optimizer.js +212 -0
  35. package/src/lib/context/context-tracker.js +273 -0
  36. package/src/lib/context/core-four-tracker.js +201 -0
  37. package/src/lib/context/mcp-optimizer.js +200 -0
  38. package/src/lib/execution/fusion-executor.js +304 -0
  39. package/src/lib/execution/parallel-executor.js +270 -0
  40. package/src/lib/generators/context-generator.js +3 -3
  41. package/src/lib/generators/recap-generator.js +2 -2
  42. package/src/lib/hooks/hook-executor.js +169 -0
  43. package/src/lib/hooks/stop-hook-executor.js +286 -0
  44. package/src/lib/hops/hop-composer.js +221 -0
  45. package/src/lib/threads/thread-coordinator.js +238 -0
  46. package/src/lib/threads/thread-manager.js +317 -0
  47. package/src/lib/tracking/artifact-trail.js +202 -0
  48. package/src/lib/trust/trust-manager.js +269 -0
  49. package/src/lib/validators/design-system/design-system-validator.js +2 -2
  50. package/src/lib/validators/validation-runner.js +6 -6
  51. package/stacks/blazor-azure/.morph/config/agents.json +72 -3
  52. package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
  53. package/CLAUDE.md +0 -993
  54. package/docs/llm-interaction-config.md +0 -735
  55. package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
  56. package/src/commands/utils/migrate-state.js +0 -158
  57. package/src/commands/utils/upgrade.js +0 -346
  58. package/src/lib/validators/architecture-validator.js +0 -60
  59. package/src/lib/validators/content-validator.js +0 -164
  60. package/src/lib/validators/package-validator.js +0 -61
  61. package/src/lib/validators/ui-contrast-validator.js +0 -44
  62. package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
  63. package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
  64. package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
  65. package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
  66. package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
  67. package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
  68. package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
  69. package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
  70. package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
  71. package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
  72. package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
  73. package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
  74. package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
  75. package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
  76. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
  77. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
  78. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
  79. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
  80. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
  81. package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
  82. package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
  83. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
  84. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
  85. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
  86. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
  87. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
  88. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
  89. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
  90. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
  91. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
  92. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
  93. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
  94. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
  95. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
  96. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
  97. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
  98. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
  99. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
  100. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
  101. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
  102. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
  103. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
  104. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
  105. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
  106. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
  107. package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
  108. package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
  109. package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
  110. package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
  111. package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
  112. package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
  113. package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
  114. package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
  115. package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
  116. package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
  117. package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
  118. package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
  119. package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
  120. package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
  121. package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
  122. package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
  123. package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
  124. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
  125. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
  126. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
  127. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
  128. /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
  129. /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
  130. /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
  131. /package/docs/{v3.0 → next-generation}/README.md +0 -0
  132. /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*