@intentsolutionsio/supabase-pack 1.0.0 → 1.0.3
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/LICENSE +1 -1
- package/README.md +73 -47
- package/package.json +4 -4
- package/skills/supabase-advanced-troubleshooting/SKILL.md +404 -200
- package/skills/supabase-advanced-troubleshooting/references/errors.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/evidence-collection-framework.md +34 -0
- package/skills/supabase-advanced-troubleshooting/references/examples.md +11 -0
- package/skills/supabase-advanced-troubleshooting/references/rls-edge-functions-realtime.md +363 -0
- package/skills/supabase-advanced-troubleshooting/references/systematic-isolation.md +56 -0
- package/skills/supabase-advanced-troubleshooting/references/timing-analysis.md +35 -0
- package/skills/supabase-architecture-variants/SKILL.md +395 -216
- package/skills/supabase-architecture-variants/references/errors.md +11 -0
- package/skills/supabase-architecture-variants/references/examples.md +12 -0
- package/skills/supabase-architecture-variants/references/serverless-and-multi-tenant.md +251 -0
- package/skills/supabase-architecture-variants/references/variant-a-monolith-(simple).md +44 -0
- package/skills/supabase-architecture-variants/references/variant-b-service-layer-(moderate).md +72 -0
- package/skills/supabase-architecture-variants/references/variant-c-microservice-(complex).md +81 -0
- package/skills/supabase-auth-storage-realtime-core/SKILL.md +471 -37
- package/skills/supabase-ci-integration/SKILL.md +315 -67
- package/skills/supabase-ci-integration/references/errors.md +10 -0
- package/skills/supabase-ci-integration/references/examples.md +36 -0
- package/skills/supabase-ci-integration/references/implementation.md +54 -0
- package/skills/supabase-common-errors/SKILL.md +320 -62
- package/skills/supabase-common-errors/references/errors.md +53 -0
- package/skills/supabase-common-errors/references/examples.md +23 -0
- package/skills/supabase-cost-tuning/SKILL.md +365 -131
- package/skills/supabase-cost-tuning/references/cost-estimation.md +34 -0
- package/skills/supabase-cost-tuning/references/cost-reduction-strategies.md +40 -0
- package/skills/supabase-cost-tuning/references/errors.md +11 -0
- package/skills/supabase-cost-tuning/references/examples.md +15 -0
- package/skills/supabase-data-handling/SKILL.md +378 -145
- package/skills/supabase-data-handling/references/errors.md +11 -0
- package/skills/supabase-data-handling/references/examples.md +27 -0
- package/skills/supabase-data-handling/references/implementation.md +223 -0
- package/skills/supabase-data-handling/references/retention-and-backup.md +221 -0
- package/skills/supabase-debug-bundle/SKILL.md +267 -73
- package/skills/supabase-debug-bundle/references/errors.md +12 -0
- package/skills/supabase-debug-bundle/references/examples.md +24 -0
- package/skills/supabase-debug-bundle/references/implementation.md +54 -0
- package/skills/supabase-deploy-integration/SKILL.md +258 -147
- package/skills/supabase-deploy-integration/references/errors.md +11 -0
- package/skills/supabase-deploy-integration/references/examples.md +21 -0
- package/skills/supabase-deploy-integration/references/google-cloud-run.md +36 -0
- package/skills/supabase-deploy-integration/references/vercel-deployment.md +35 -0
- package/skills/supabase-enterprise-rbac/SKILL.md +327 -160
- package/skills/supabase-enterprise-rbac/references/api-scoping-and-enforcement.md +255 -0
- package/skills/supabase-enterprise-rbac/references/errors.md +11 -0
- package/skills/supabase-enterprise-rbac/references/examples.md +12 -0
- package/skills/supabase-enterprise-rbac/references/role-implementation.md +33 -0
- package/skills/supabase-enterprise-rbac/references/sso-integration.md +35 -0
- package/skills/supabase-hello-world/SKILL.md +160 -54
- package/skills/supabase-incident-runbook/SKILL.md +453 -131
- package/skills/supabase-incident-runbook/references/errors.md +11 -0
- package/skills/supabase-incident-runbook/references/examples.md +10 -0
- package/skills/supabase-incident-runbook/references/immediate-actions-by-error-type.md +41 -0
- package/skills/supabase-install-auth/SKILL.md +186 -50
- package/skills/supabase-install-auth/references/examples.md +102 -0
- package/skills/supabase-known-pitfalls/SKILL.md +411 -241
- package/skills/supabase-known-pitfalls/references/errors.md +11 -0
- package/skills/supabase-known-pitfalls/references/examples.md +12 -0
- package/skills/supabase-load-scale/SKILL.md +346 -217
- package/skills/supabase-load-scale/references/capacity-planning.md +47 -0
- package/skills/supabase-load-scale/references/errors.md +11 -0
- package/skills/supabase-load-scale/references/examples.md +26 -0
- package/skills/supabase-load-scale/references/load-testing-with-k6.md +59 -0
- package/skills/supabase-load-scale/references/scaling-patterns.md +65 -0
- package/skills/supabase-load-scale/references/table-partitioning.md +263 -0
- package/skills/supabase-local-dev-loop/SKILL.md +272 -73
- package/skills/supabase-local-dev-loop/references/errors.md +11 -0
- package/skills/supabase-local-dev-loop/references/examples.md +21 -0
- package/skills/supabase-local-dev-loop/references/implementation.md +60 -0
- package/skills/supabase-migration-deep-dive/SKILL.md +338 -177
- package/skills/supabase-migration-deep-dive/references/backfill-versioning-rollback.md +258 -0
- package/skills/supabase-migration-deep-dive/references/errors.md +11 -0
- package/skills/supabase-migration-deep-dive/references/examples.md +12 -0
- package/skills/supabase-migration-deep-dive/references/implementation-plan.md +80 -0
- package/skills/supabase-migration-deep-dive/references/pre-migration-assessment.md +39 -0
- package/skills/supabase-multi-env-setup/SKILL.md +393 -152
- package/skills/supabase-multi-env-setup/references/configuration-structure.md +59 -0
- package/skills/supabase-multi-env-setup/references/errors.md +11 -0
- package/skills/supabase-multi-env-setup/references/examples.md +11 -0
- package/skills/supabase-observability/SKILL.md +318 -196
- package/skills/supabase-observability/references/alert-configuration.md +40 -0
- package/skills/supabase-observability/references/errors.md +11 -0
- package/skills/supabase-observability/references/examples.md +13 -0
- package/skills/supabase-observability/references/metrics-collection.md +65 -0
- package/skills/supabase-performance-tuning/SKILL.md +304 -160
- package/skills/supabase-performance-tuning/references/caching-strategy.md +49 -0
- package/skills/supabase-performance-tuning/references/errors.md +11 -0
- package/skills/supabase-performance-tuning/references/examples.md +13 -0
- package/skills/supabase-policy-guardrails/SKILL.md +248 -221
- package/skills/supabase-policy-guardrails/references/ci-cost-security.md +484 -0
- package/skills/supabase-policy-guardrails/references/errors.md +11 -0
- package/skills/supabase-policy-guardrails/references/eslint-rules.md +46 -0
- package/skills/supabase-policy-guardrails/references/examples.md +10 -0
- package/skills/supabase-prod-checklist/SKILL.md +474 -84
- package/skills/supabase-prod-checklist/references/errors.md +63 -0
- package/skills/supabase-prod-checklist/references/examples.md +153 -0
- package/skills/supabase-prod-checklist/references/implementation.md +113 -0
- package/skills/supabase-rate-limits/SKILL.md +311 -98
- package/skills/supabase-rate-limits/references/errors.md +11 -0
- package/skills/supabase-rate-limits/references/examples.md +46 -0
- package/skills/supabase-rate-limits/references/implementation.md +66 -0
- package/skills/supabase-reference-architecture/SKILL.md +249 -182
- package/skills/supabase-reference-architecture/references/errors.md +29 -0
- package/skills/supabase-reference-architecture/references/examples.md +116 -0
- package/skills/supabase-reference-architecture/references/key-components.md +244 -0
- package/skills/supabase-reference-architecture/references/project-structure.md +109 -0
- package/skills/supabase-reliability-patterns/SKILL.md +229 -234
- package/skills/supabase-reliability-patterns/references/circuit-breaker.md +36 -0
- package/skills/supabase-reliability-patterns/references/dead-letter-queue.md +48 -0
- package/skills/supabase-reliability-patterns/references/errors.md +11 -0
- package/skills/supabase-reliability-patterns/references/examples.md +11 -0
- package/skills/supabase-reliability-patterns/references/idempotency-keys.md +36 -0
- package/skills/supabase-reliability-patterns/references/offline-degradation-health-dualwrite.md +489 -0
- package/skills/supabase-schema-from-requirements/SKILL.md +373 -34
- package/skills/supabase-sdk-patterns/SKILL.md +388 -99
- package/skills/supabase-sdk-patterns/references/errors.md +11 -0
- package/skills/supabase-sdk-patterns/references/examples.md +45 -0
- package/skills/supabase-sdk-patterns/references/implementation.md +67 -0
- package/skills/supabase-security-basics/SKILL.md +282 -102
- package/skills/supabase-security-basics/references/errors.md +10 -0
- package/skills/supabase-security-basics/references/examples.md +70 -0
- package/skills/supabase-security-basics/references/implementation.md +39 -0
- package/skills/supabase-upgrade-migration/SKILL.md +248 -66
- package/skills/supabase-upgrade-migration/references/errors.md +10 -0
- package/skills/supabase-upgrade-migration/references/examples.md +51 -0
- package/skills/supabase-upgrade-migration/references/implementation.md +29 -0
- package/skills/supabase-webhooks-events/SKILL.md +412 -138
- package/skills/supabase-webhooks-events/references/errors.md +55 -0
- package/skills/supabase-webhooks-events/references/event-handler-pattern.md +106 -0
- package/skills/supabase-webhooks-events/references/examples.md +133 -0
- package/skills/supabase-webhooks-events/references/signature-verification.md +165 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
### Next.js Production Client Setup
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// lib/supabase/client.ts — browser client (anon key only)
|
|
7
|
+
import { createClient } from '@supabase/supabase-js';
|
|
8
|
+
import type { Database } from '@/types/supabase';
|
|
9
|
+
|
|
10
|
+
export const supabase = createClient<Database>(
|
|
11
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
12
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
13
|
+
);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// lib/supabase/server.ts — server-side admin client
|
|
18
|
+
import { createClient } from '@supabase/supabase-js';
|
|
19
|
+
import type { Database } from '@/types/supabase';
|
|
20
|
+
|
|
21
|
+
export const supabaseAdmin = createClient<Database>(
|
|
22
|
+
process.env.SUPABASE_URL!,
|
|
23
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
|
24
|
+
{ auth: { autoRefreshToken: false, persistSession: false } }
|
|
25
|
+
);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Health Check Endpoint
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// app/api/health/route.ts
|
|
32
|
+
import { createClient } from '@supabase/supabase-js';
|
|
33
|
+
|
|
34
|
+
const supabase = createClient(
|
|
35
|
+
process.env.SUPABASE_URL!,
|
|
36
|
+
process.env.SUPABASE_ANON_KEY!
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export async function GET() {
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
const { data, error } = await supabase
|
|
42
|
+
.from('_health_check')
|
|
43
|
+
.select('id')
|
|
44
|
+
.limit(1);
|
|
45
|
+
|
|
46
|
+
const latency = Date.now() - start;
|
|
47
|
+
|
|
48
|
+
return Response.json({
|
|
49
|
+
status: error ? 'unhealthy' : 'healthy',
|
|
50
|
+
latency_ms: latency,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
supabase_reachable: !error,
|
|
53
|
+
}, { status: error ? 503 : 200 });
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Complete RLS Policy Set
|
|
58
|
+
|
|
59
|
+
```sql
|
|
60
|
+
-- Enable RLS on the posts table
|
|
61
|
+
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
|
|
62
|
+
|
|
63
|
+
-- Public read for published posts
|
|
64
|
+
CREATE POLICY "Public read published" ON public.posts
|
|
65
|
+
FOR SELECT USING (status = 'published');
|
|
66
|
+
|
|
67
|
+
-- Authors read own drafts
|
|
68
|
+
CREATE POLICY "Authors read own drafts" ON public.posts
|
|
69
|
+
FOR SELECT USING (auth.uid() = author_id AND status = 'draft');
|
|
70
|
+
|
|
71
|
+
-- Authors create posts
|
|
72
|
+
CREATE POLICY "Authors create posts" ON public.posts
|
|
73
|
+
FOR INSERT WITH CHECK (auth.uid() = author_id);
|
|
74
|
+
|
|
75
|
+
-- Authors update own posts
|
|
76
|
+
CREATE POLICY "Authors update own posts" ON public.posts
|
|
77
|
+
FOR UPDATE USING (auth.uid() = author_id)
|
|
78
|
+
WITH CHECK (auth.uid() = author_id);
|
|
79
|
+
|
|
80
|
+
-- Authors delete own drafts only
|
|
81
|
+
CREATE POLICY "Authors delete own drafts" ON public.posts
|
|
82
|
+
FOR DELETE USING (auth.uid() = author_id AND status = 'draft');
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Storage Bucket Policy
|
|
86
|
+
|
|
87
|
+
```sql
|
|
88
|
+
-- Allow authenticated users to upload to their own folder in avatars bucket
|
|
89
|
+
CREATE POLICY "Users upload own avatars"
|
|
90
|
+
ON storage.objects
|
|
91
|
+
FOR INSERT
|
|
92
|
+
WITH CHECK (
|
|
93
|
+
bucket_id = 'avatars'
|
|
94
|
+
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
-- Allow public read access to the public-assets bucket
|
|
98
|
+
CREATE POLICY "Public read assets"
|
|
99
|
+
ON storage.objects
|
|
100
|
+
FOR SELECT
|
|
101
|
+
USING (bucket_id = 'public-assets');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Edge Function with Secrets
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// supabase/functions/process-webhook/index.ts
|
|
108
|
+
import { createClient } from '@supabase/supabase-js';
|
|
109
|
+
|
|
110
|
+
Deno.serve(async (req) => {
|
|
111
|
+
const supabase = createClient(
|
|
112
|
+
Deno.env.get('SUPABASE_URL')!,
|
|
113
|
+
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const body = await req.json();
|
|
117
|
+
|
|
118
|
+
const { error } = await supabase
|
|
119
|
+
.from('webhook_events')
|
|
120
|
+
.insert({ payload: body, received_at: new Date().toISOString() });
|
|
121
|
+
|
|
122
|
+
if (error) {
|
|
123
|
+
return new Response(JSON.stringify({ error: error.message }), {
|
|
124
|
+
status: 500,
|
|
125
|
+
headers: { 'Content-Type': 'application/json' },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return new Response(JSON.stringify({ received: true }), {
|
|
130
|
+
headers: { 'Content-Type': 'application/json' },
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Rollback Procedure
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 1. Create a rollback migration for a bad schema change
|
|
139
|
+
npx supabase migration new rollback_bad_change
|
|
140
|
+
# Edit the generated SQL file with reversal statements
|
|
141
|
+
npx supabase db push
|
|
142
|
+
|
|
143
|
+
# 2. For data issues — use Point-in-Time Recovery
|
|
144
|
+
# Dashboard > Database > Backups > PITR
|
|
145
|
+
# Select timestamp before the incident
|
|
146
|
+
|
|
147
|
+
# 3. For application deployment issues
|
|
148
|
+
# Vercel: vercel rollback
|
|
149
|
+
# Netlify: netlify deploy --prod --dir=previous-build
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Implementation Guide
|
|
2
|
+
|
|
3
|
+
## Pre-Deployment Configuration
|
|
4
|
+
|
|
5
|
+
### Step 1: Environment Variable Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Required environment variables for production
|
|
9
|
+
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
10
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # Safe for client-side
|
|
11
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
12
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJ... # Server-side ONLY
|
|
13
|
+
DATABASE_URL=postgresql://postgres.[REF]:[PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres # Pooled
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- [ ] Production API keys stored in deployment platform secrets (Vercel, Netlify, etc.)
|
|
17
|
+
- [ ] Environment variables set in deployment platform (not in `.env` files committed to git)
|
|
18
|
+
- [ ] API key scopes are minimal (least privilege)
|
|
19
|
+
- [ ] `.env` and `.env.local` in `.gitignore`
|
|
20
|
+
|
|
21
|
+
### Step 2: Code Quality Verification
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Verify no hardcoded credentials
|
|
25
|
+
grep -r "eyJ" src/ --include="*.ts" --include="*.tsx" --include="*.js"
|
|
26
|
+
# Should return zero results
|
|
27
|
+
|
|
28
|
+
# Verify service_role key not in client bundle
|
|
29
|
+
grep -r "service_role\|SERVICE_ROLE" src/ --include="*.ts" --include="*.tsx"
|
|
30
|
+
# Should only appear in server-side files (app/api/, lib/supabase/server.ts)
|
|
31
|
+
|
|
32
|
+
# Run test suite
|
|
33
|
+
npm test
|
|
34
|
+
npm run typecheck
|
|
35
|
+
npm run lint
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- [ ] All tests passing
|
|
39
|
+
- [ ] No hardcoded credentials in source code
|
|
40
|
+
- [ ] Error handling covers Supabase error types (see errors.md)
|
|
41
|
+
- [ ] Retry logic with exponential backoff for transient failures
|
|
42
|
+
- [ ] Logging is production-appropriate (no PII, no secrets)
|
|
43
|
+
|
|
44
|
+
### Step 3: Database Configuration
|
|
45
|
+
|
|
46
|
+
```sql
|
|
47
|
+
-- Set statement timeout for authenticated role (prevent runaway queries)
|
|
48
|
+
ALTER ROLE authenticated SET statement_timeout = '10s';
|
|
49
|
+
|
|
50
|
+
-- Enable required extensions
|
|
51
|
+
CREATE EXTENSION IF NOT EXISTS pg_stat_statements; -- Query monitoring
|
|
52
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto; -- Encryption functions
|
|
53
|
+
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- Text search (if needed)
|
|
54
|
+
|
|
55
|
+
-- Verify RLS on all public tables (must return zero rows)
|
|
56
|
+
SELECT schemaname, tablename, rowsecurity
|
|
57
|
+
FROM pg_tables
|
|
58
|
+
WHERE schemaname = 'public' AND rowsecurity = false;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Step 4: Infrastructure Setup
|
|
62
|
+
|
|
63
|
+
- [ ] Health check endpoint deployed (see examples.md)
|
|
64
|
+
- [ ] Monitoring/alerting configured (error rates, latency, pool usage)
|
|
65
|
+
- [ ] Custom domain configured and DNS verified
|
|
66
|
+
- [ ] Network restrictions applied (IP allowlist)
|
|
67
|
+
- [ ] SSL enforcement enabled
|
|
68
|
+
|
|
69
|
+
### Step 5: Migration Verification
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Verify migrations are clean
|
|
73
|
+
npx supabase db reset # Reset local DB and replay all migrations
|
|
74
|
+
npx supabase migration list # Compare local vs remote migration history
|
|
75
|
+
npx supabase db diff --use-migra # Check for schema drift
|
|
76
|
+
|
|
77
|
+
# Apply to production
|
|
78
|
+
npx supabase db push
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- [ ] All schema changes captured in `supabase/migrations/`
|
|
82
|
+
- [ ] Migrations replay cleanly on a fresh database
|
|
83
|
+
- [ ] No schema drift between local and remote
|
|
84
|
+
- [ ] Rollback migration prepared for risky changes
|
|
85
|
+
|
|
86
|
+
### Step 6: Deploy with Verification
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Pre-flight checks
|
|
90
|
+
curl -sf https://status.supabase.com/api/v2/status.json | jq '.status'
|
|
91
|
+
npx supabase db ping --linked
|
|
92
|
+
|
|
93
|
+
# Deploy application
|
|
94
|
+
# (Platform-specific: Vercel, Netlify, etc.)
|
|
95
|
+
|
|
96
|
+
# Post-deploy verification
|
|
97
|
+
curl -sf https://your-app.com/api/health | jq '.'
|
|
98
|
+
# Expected: { "status": "healthy", "latency_ms": <50, ... }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 7: Post-Launch Monitoring (First 24 Hours)
|
|
102
|
+
|
|
103
|
+
| Check | Frequency | Where |
|
|
104
|
+
|-------|-----------|-------|
|
|
105
|
+
| Health check response | Every 5 min | Uptime monitor (UptimeRobot, Better Stack) |
|
|
106
|
+
| API error rate | Hourly | Dashboard > Logs > API |
|
|
107
|
+
| Query performance | Every 4 hours | Dashboard > Database > Performance |
|
|
108
|
+
| Connection pool usage | Every 2 hours | Dashboard > Database > Connections |
|
|
109
|
+
| Auth success rate | Every 4 hours | Dashboard > Auth > Logs |
|
|
110
|
+
| Storage bandwidth | Daily | Dashboard > Storage |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|