From 43477aceae0d0ddd3c60eca7cf045e9674969465 Mon Sep 17 00:00:00 2001 From: Richard Ward Date: Tue, 30 Dec 2025 21:37:27 +0000 Subject: [PATCH] docs: schema,migrations,scripts (2 files) --- deployment/scripts/apply-schema-migrations.sh | 90 +++++++++++ docs/packages/schema-migrations.md | 153 ++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 deployment/scripts/apply-schema-migrations.sh create mode 100644 docs/packages/schema-migrations.md diff --git a/deployment/scripts/apply-schema-migrations.sh b/deployment/scripts/apply-schema-migrations.sh new file mode 100644 index 000000000..ff8ee43a4 --- /dev/null +++ b/deployment/scripts/apply-schema-migrations.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Docker entrypoint hook for schema migrations +# Called during container startup to apply approved migrations + +set -e + +REGISTRY_FILE="/app/prisma/schema-registry.json" +GENERATED_PRISMA="/app/prisma/generated-from-packages.prisma" + +echo "πŸ” Checking for pending schema migrations..." + +# Check if registry exists +if [ ! -f "$REGISTRY_FILE" ]; then + echo "βœ“ No schema registry found - skipping" + exit 0 +fi + +# Check for approved migrations +APPROVED_COUNT=$(cat "$REGISTRY_FILE" | python3 -c " +import json, sys +data = json.load(sys.stdin) +count = sum(1 for m in data.get('migrationQueue', []) if m['status'] == 'approved') +print(count) +") + +if [ "$APPROVED_COUNT" -eq 0 ]; then + echo "βœ“ No approved migrations pending" + exit 0 +fi + +echo "⚠️ Found $APPROVED_COUNT approved migration(s) to apply" + +# Generate Prisma schema fragment +echo "πŸ“ Generating Prisma schema..." +npx ts-node /app/tools/codegen/schema-cli.ts generate + +# Check if generated file exists +if [ ! -f "$GENERATED_PRISMA" ]; then + echo "❌ Failed to generate Prisma schema" + exit 1 +fi + +echo "πŸ“‹ Generated schema fragment:" +cat "$GENERATED_PRISMA" + +# Apply migrations +echo "" +echo "πŸš€ Running Prisma migrate..." +npx prisma migrate dev --name "package-schema-$(date +%Y%m%d%H%M%S)" --skip-generate + +if [ $? -eq 0 ]; then + echo "βœ“ Migrations applied successfully" + + # Mark migrations as applied in registry + python3 -c " +import json + +with open('$REGISTRY_FILE', 'r') as f: + data = json.load(f) + +import time +for m in data.get('migrationQueue', []): + if m['status'] == 'approved': + m['status'] = 'applied' + m['appliedAt'] = int(time.time() * 1000) + # Update entity registry + data['entities'][m['entityName']] = { + 'checksum': m['newChecksum'], + 'version': '1.0', + 'ownerPackage': m['packageId'], + 'prismaModel': m['prismaPreview'], + 'appliedAt': m['appliedAt'] + } + +with open('$REGISTRY_FILE', 'w') as f: + json.dump(data, f, indent=2) + +print('βœ“ Registry updated') +" + + # Regenerate Prisma client + echo "πŸ”§ Regenerating Prisma client..." + npx prisma generate + + echo "" + echo "βœ… Schema migrations complete!" +else + echo "❌ Migration failed!" + exit 1 +fi diff --git a/docs/packages/schema-migrations.md b/docs/packages/schema-migrations.md new file mode 100644 index 000000000..d233847e3 --- /dev/null +++ b/docs/packages/schema-migrations.md @@ -0,0 +1,153 @@ +# Package Schema Migration System + +## Overview + +Packages can define their own database schemas in `seed/schema/entities.yaml`. The system: + +1. **Validates** schemas across packages to prevent conflicts +2. **Queues** changes for admin approval +3. **Generates** Prisma schema fragments +4. **Applies** migrations on container restart + +## Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Package with │────▢│ npm run │────▢│ Admin reviews β”‚ +β”‚ schema/ β”‚ β”‚ schema:scan β”‚ β”‚ pending queue β”‚ +β”‚ entities.yaml β”‚ β”‚ β”‚ β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Prisma migrate │◀────│ Container │◀────│ Admin approves β”‚ +β”‚ applied β”‚ β”‚ restart β”‚ β”‚ migrations β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Package Schema Format + +Create `packages/{name}/seed/schema/entities.yaml`: + +```yaml +entities: + - name: MyEntity + version: "1.0" + description: "Description of entity" + + fields: + id: + type: cuid + primary: true + generated: true + + tenantId: + type: string + required: true + index: true + + name: + type: string + required: true + maxLength: 100 + + data: + type: string + nullable: true + description: "JSON: additional data" + + createdAt: + type: bigint + required: true + + indexes: + - fields: [tenantId, name] + unique: true + + relations: + - name: tenant + type: belongsTo + entity: Tenant + field: tenantId + onDelete: Cascade + + acl: + create: [user] + read: [public] + update: [self, admin] + delete: [admin] +``` + +## Field Types + +| Type | Prisma | Notes | +|------|--------|-------| +| `string` | `String` | Text fields | +| `int` | `Int` | 32-bit integer | +| `bigint` | `BigInt` | 64-bit integer (timestamps) | +| `float` | `Float` | Decimal numbers | +| `boolean` | `Boolean` | True/false | +| `cuid` | `String` | CUID identifier | +| `uuid` | `String` | UUID identifier | +| `json` | `Json` | JSON data | + +## CLI Commands + +```bash +# Scan all packages for schema changes +npm run schema:scan + +# List pending migrations +npm run schema:list + +# Approve migrations +npm run schema:approve all +npm run schema:approve + +# Generate Prisma fragment +npm run schema:generate + +# Preview package schema as Prisma +npm run schema:preview audit_log + +# Check status +npm run schema:status +``` + +## Checksum Validation + +The system computes checksums from schema structure (not descriptions). If two packages define the same entity: + +- **Same checksum**: Compatible, no conflict +- **Different checksum**: Error - packages must coordinate + +## Metadata.json Integration + +Add schema export to `metadata.json`: + +```json +{ + "packageId": "my_package", + "schema": { + "entities": ["MyEntity", "MyOtherEntity"], + "path": "schema/entities.yaml" + } +} +``` + +## Docker Integration + +The container startup script (`apply-schema-migrations.sh`) automatically: + +1. Checks for approved migrations +2. Generates Prisma schema fragment +3. Runs `prisma migrate dev` +4. Updates registry to mark as applied +5. Regenerates Prisma client + +## Security + +- Only **admin** can approve migrations +- Schema changes require container restart +- Checksums prevent unauthorized modifications +- ACL rules define entity-level permissions