Skip to content

Commit 8be6d20

Browse files
committed
Add type generation script and schema idempotency fixes
- Add npm run gen:types script that loads SUPABASE_ACCESS_TOKEN from .env - Fix schema idempotency: SELECT drop_function_overloads to DO/PERFORM - Add migrations section for columns missing from existing databases - Clean up type workarounds now that types are regenerated - Regenerate supabase.ts with configuration_revisions column
1 parent e3d0bf3 commit 8be6d20

File tree

11 files changed

+7651
-32
lines changed

11 files changed

+7651
-32
lines changed

.cursor/rules/database.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ globs:
1212

1313
1. `supabase/schema.sql` ← source of truth, bump version
1414
2. `src/lib/schemaVersion.ts` ← sync EXPECTED_SCHEMA_VERSION
15-
3. `src/types/database.ts` ← regenerate if schema changed
15+
3. `src/types/supabase.ts` ← regenerate with `npm run gen:types`
1616

1717
## Supabase Query Patterns
1818

CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22

33
All notable changes to BluePLM will be documented in this file.
44

5-
## [3.11.1] - 2026-01-22
5+
## [3.11.1] - 2026-01-23
6+
7+
### Added
8+
- **Type generation script**: New `npm run gen:types` command that loads `SUPABASE_ACCESS_TOKEN` from `.env` file and regenerates TypeScript types from the live database
69

710
### Fixed
811
- **Delete from server keeps file read-only**: Fixed issue where deleting a checked-in file from the server while keeping the local copy would leave the file read-only. Local-only files are now correctly made writable after the server deletion
912
- **Sub-assemblies stay read-only after folder checkout**: Fixed issue where sub-assemblies and parts loaded as components of an open assembly would remain read-only in SolidWorks after checking out the folder. The checkout now updates the read-only state for all loaded documents, not just those with visible windows
1013
- **SolidWorks checkout not clearing read-only state**: Fixed issue where checking out an assembly file that's open in SOLIDWORKS would fail to clear the read-only flag, requiring users to close and reopen the file to edit it. The health check before `setDocumentReadOnly` was spawning a new thread that would time out when assemblies with components were open, even though SOLIDWORKS was actually responsive. Removed the overly strict health check since `ExecuteSerialized()` already provides proper retry logic
14+
- **Folder deletion not working for folders with special characters**: Fixed issue where deleting folders with spaces or parentheses in their names (e.g., "New Folder (3)") would appear to succeed but the folder would reappear after refreshing. The server-side soft delete was failing silently due to improper query escaping
15+
- **Schema idempotency**: Fixed `10-source-files.sql` not being fully idempotent - `SELECT drop_function_overloads()` calls were returning result sets that interfered with Supabase SQL Editor execution. Changed to `DO/PERFORM` blocks. Added migrations section for columns that may be missing from existing databases (`configuration_revisions`, `endpoint`, `restic_password_encrypted`, etc.)
16+
17+
### Changed
18+
- **Removed type workarounds**: Cleaned up `as any` type casts for `folders` table, `move_file` RPC, and `create_default_workflow` RPC now that types are regenerated
1119

1220
### Removed
1321
- **Speculative parent assembly warning**: Removed the warning toast "Some files may have parent assemblies still checked out" that appeared when checking in parts or assemblies. This warning was overly aggressive and triggered false positives - it would warn even when the checked-out assemblies had nothing to do with the files being checked in
@@ -33,7 +41,7 @@ All notable changes to BluePLM will be documented in this file.
3341
- **Tab number hover effect**: The inline tab number input now shows the same hover box effect as the item number when hovering over the cell
3442

3543
### Fixed
36-
- **Folder deletion not working for folders with special characters**: Fixed issue where deleting folders with spaces or parentheses in their names (e.g., "New Folder (3)") would appear to succeed but the folder would reappear after refreshing. The server-side soft delete was failing silently due to improper query escaping
44+
3745
- **Serial number preview showing tab number**: Fixed the serial number preview incorrectly showing a sample tab number (e.g., "BR-00001-001") when tabs were enabled, even though generation only produces the base number. Preview now correctly shows just the base number that will be generated
3846
- **BOM extraction hanging for assemblies**: Fixed issue where viewing the Bill of Materials for SolidWorks assemblies would hang for ~30 seconds and fail. The orphaned process watchdog was incorrectly killing a background SolidWorks process spawned by the Document Manager API during reference resolution. The watchdog now pauses during BOM and reference extraction operations
3947
- **BOM items showing empty**: Fixed JSON serialization mismatch where BOM item properties (fileName, filePath, quantity, etc.) were serialized with PascalCase but the frontend expected camelCase, resulting in empty/undefined values

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"api:build": "npx tsc api/server.ts --outDir dist-api --esModuleInterop --module commonjs --target ES2020",
2020
"docs:dev": "cd docs && npm run dev",
2121
"docs:build": "cd docs && npm run build",
22-
"docs:preview": "cd docs && npm run preview"
22+
"docs:preview": "cd docs && npm run preview",
23+
"gen:types": "node scripts/gen-types.js"
2324
},
2425
"dependencies": {
2526
"@fastify/cors": "^10.0.2",

scripts/gen-types.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Generate Supabase TypeScript types
4+
*
5+
* Loads SUPABASE_ACCESS_TOKEN from .env file and runs type generation.
6+
*
7+
* Usage: npm run gen:types
8+
*
9+
* Requires SUPABASE_ACCESS_TOKEN in .env file:
10+
* SUPABASE_ACCESS_TOKEN=your-token-here
11+
*
12+
* Get your token from: https://supabase.com/dashboard/account/tokens
13+
*/
14+
15+
import { execSync } from 'child_process';
16+
import { readFileSync, existsSync } from 'fs';
17+
import { resolve, dirname } from 'path';
18+
import { fileURLToPath } from 'url';
19+
20+
const __dirname = dirname(fileURLToPath(import.meta.url));
21+
const rootDir = resolve(__dirname, '..');
22+
const envPath = resolve(rootDir, '.env');
23+
24+
// Project ID for bluePLM
25+
const PROJECT_ID = 'vvyhpdzqdizvorrhjhvq';
26+
const OUTPUT_PATH = 'src/types/supabase.ts';
27+
28+
// Load .env file manually (simple parser)
29+
function loadEnv() {
30+
if (!existsSync(envPath)) {
31+
console.error('❌ No .env file found at:', envPath);
32+
console.error('\nCreate a .env file with:');
33+
console.error(' SUPABASE_ACCESS_TOKEN=your-token-here');
34+
console.error('\nGet your token from: https://supabase.com/dashboard/account/tokens');
35+
process.exit(1);
36+
}
37+
38+
const content = readFileSync(envPath, 'utf-8');
39+
for (const line of content.split('\n')) {
40+
const trimmed = line.trim();
41+
if (!trimmed || trimmed.startsWith('#')) continue;
42+
43+
const eqIndex = trimmed.indexOf('=');
44+
if (eqIndex === -1) continue;
45+
46+
const key = trimmed.slice(0, eqIndex).trim();
47+
let value = trimmed.slice(eqIndex + 1).trim();
48+
49+
// Remove quotes if present
50+
if ((value.startsWith('"') && value.endsWith('"')) ||
51+
(value.startsWith("'") && value.endsWith("'"))) {
52+
value = value.slice(1, -1);
53+
}
54+
55+
process.env[key] = value;
56+
}
57+
}
58+
59+
// Main
60+
loadEnv();
61+
62+
if (!process.env.SUPABASE_ACCESS_TOKEN) {
63+
console.error('❌ SUPABASE_ACCESS_TOKEN not found in .env file');
64+
console.error('\nAdd this line to your .env file:');
65+
console.error(' SUPABASE_ACCESS_TOKEN=your-token-here');
66+
console.error('\nGet your token from: https://supabase.com/dashboard/account/tokens');
67+
process.exit(1);
68+
}
69+
70+
console.log('🔄 Generating Supabase types...');
71+
console.log(` Project: ${PROJECT_ID}`);
72+
console.log(` Output: ${OUTPUT_PATH}`);
73+
74+
try {
75+
execSync(
76+
`npx supabase gen types typescript --project-id ${PROJECT_ID} > ${OUTPUT_PATH}`,
77+
{
78+
cwd: rootDir,
79+
stdio: 'inherit',
80+
env: {
81+
...process.env,
82+
SUPABASE_ACCESS_TOKEN: process.env.SUPABASE_ACCESS_TOKEN,
83+
},
84+
}
85+
);
86+
console.log('✅ Types generated successfully!');
87+
} catch (error) {
88+
console.error('❌ Failed to generate types');
89+
process.exit(1);
90+
}

src/features/source/workflows/services/workflowService.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,10 @@ export const workflowService = {
5454
* Create a new workflow using the default workflow function
5555
*/
5656
async createDefault(orgId: string, userId: string): Promise<WorkflowServiceResult<string>> {
57-
// Cast to any to call RPC function that may not be in generated types
58-
const { data, error } = await (supabase as unknown as { rpc: (fn: string, args: Record<string, unknown>) => Promise<{ data: unknown; error: { message: string } | null }> })
59-
.rpc('create_default_workflow', {
60-
p_org_id: orgId,
61-
p_created_by: userId
62-
})
57+
const { data, error } = await supabase.rpc('create_default_workflow', {
58+
p_org_id: orgId,
59+
p_created_by: userId
60+
})
6361

6462
return {
6563
data: data as string | null,

src/lib/supabase/files/folders.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77

88
import { getSupabaseClient } from '../client'
99

10-
// Note: The 'folders' table exists in the database but isn't in the generated types yet.
11-
// We use type assertions to work around this until types are regenerated.
12-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13-
type AnySupabaseClient = ReturnType<typeof getSupabaseClient> & { from: (table: string) => any }
14-
1510
// ============================================
1611
// Types
1712
// ============================================
@@ -58,7 +53,7 @@ export async function syncFolder(
5853
userId: string,
5954
folderPath: string
6055
): Promise<{ folder: FolderRecord | null; error: any }> {
61-
const client = getSupabaseClient() as AnySupabaseClient
56+
const client = getSupabaseClient()
6257
const logFn = getLogFn()
6358

6459
// Normalize path: use forward slashes, no leading/trailing slashes
@@ -89,7 +84,7 @@ export async function syncFolder(
8984
* Internal helper to sync a single folder (no parent handling)
9085
*/
9186
async function syncSingleFolder(
92-
client: AnySupabaseClient,
87+
client: ReturnType<typeof getSupabaseClient>,
9388
orgId: string,
9489
vaultId: string,
9590
userId: string,
@@ -162,7 +157,7 @@ async function syncSingleFolder(
162157
export async function getVaultFolders(
163158
vaultId: string
164159
): Promise<{ folders: FolderRecord[]; error?: string }> {
165-
const client = getSupabaseClient() as AnySupabaseClient
160+
const client = getSupabaseClient()
166161
const logFn = getLogFn()
167162

168163
logFn('debug', '[getVaultFolders] Fetching folders', { vaultId })
@@ -205,7 +200,7 @@ export async function updateFolderServerPath(
205200
folderId: string,
206201
newPath: string
207202
): Promise<{ success: boolean; error?: string }> {
208-
const client = getSupabaseClient() as AnySupabaseClient
203+
const client = getSupabaseClient()
209204
const logFn = getLogFn()
210205

211206
// Normalize path
@@ -286,7 +281,7 @@ export async function deleteFolderOnServer(
286281
folderId: string,
287282
userId: string
288283
): Promise<{ success: boolean; error?: string }> {
289-
const client = getSupabaseClient() as AnySupabaseClient
284+
const client = getSupabaseClient()
290285
const logFn = getLogFn()
291286

292287
logFn('debug', '[deleteFolderOnServer] Soft deleting folder', { folderId, userId })
@@ -358,7 +353,7 @@ export async function deleteFolderByPath(
358353
folderPath: string,
359354
userId: string
360355
): Promise<{ success: boolean; error?: string }> {
361-
const client = getSupabaseClient() as AnySupabaseClient
356+
const client = getSupabaseClient()
362357
const logFn = getLogFn()
363358

364359
// Normalize path

src/lib/supabase/files/move.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@ export async function moveFileOnServer(
2727
const client = getSupabaseClient()
2828

2929
// Use atomic RPC to prevent race conditions and ensure proper validation
30-
// Note: Using 'as any' for rpc name since move_file is a new RPC not yet in generated types
31-
const { data, error } = await (client.rpc as any)('move_file', {
30+
const { data, error } = await client.rpc('move_file', {
3231
p_file_id: fileId,
3332
p_user_id: userId,
3433
p_new_file_path: newFilePath,
35-
p_new_file_name: newFileName || null
34+
p_new_file_name: newFileName
3635
})
3736

3837
if (error) {

src/types/database.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
* 5. Custom type unions not defined in the database
1010
*
1111
* NOTE: supabase.ts is auto-generated. To regenerate after schema changes:
12-
* npx supabase gen types typescript --project-id YOUR_PROJECT_ID --schema public > src/types/supabase.ts
12+
* npm run gen:types
13+
*
14+
* Requires SUPABASE_ACCESS_TOKEN in .env file (get from supabase.com/dashboard/account/tokens)
1315
*
1416
* @see ./supabase.ts for auto-generated types (DO NOT EDIT THAT FILE)
1517
*/

0 commit comments

Comments
 (0)