@plutocms/supabase 0.3.0 → 0.4.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.
- package/CHANGELOG.md +16 -0
- package/FEATURES.md +2 -2
- package/app/components/navbar/NavbarAdminProvider.vue +4 -0
- package/app/composables/auth.ts +11 -9
- package/app/composables/migrations.ts +16 -3
- package/app/pages/admin/migrations.vue +32 -8
- package/app/pages/admin/setup.vue +2 -3
- package/db/migrations/002_admin_hardening.sql +107 -0
- package/modules/pluto-migrations.ts +167 -32
- package/nuxt.config.ts +3 -1
- package/package.json +2 -2
- package/server/api/migrations/run.post.ts +59 -77
- package/server/api/migrations/status.get.ts +3 -5
- package/server/api/settings/update.post.ts +34 -6
- package/server/api/setup/create.post.ts +64 -74
- package/server/api/users/[id].get.ts +3 -0
- package/server/api/users/index.get.ts +3 -0
- package/server/plugins/migrations.ts +35 -38
- package/server/utils/ledger.ts +124 -0
- package/server/utils/migrations.ts +0 -0
- package/server/utils/pending-migrations.ts +0 -0
- package/server/utils/sql.ts +86 -11
- package/shared/types/migrations.d.ts +28 -0
- package/shared/types/runtime-config.d.ts +10 -8
- package/shared/types/supabase.ts +7 -12
- /package/{public/schema.sql → db/migrations/001_baseline.sql} +0 -0
package/server/utils/sql.ts
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Matches a dollar-quote delimiter: bare (`$$`) or tagged (`$tag$`).
|
|
3
|
+
* The `y` (sticky) flag anchors the match to `lastIndex`, so it only
|
|
4
|
+
* matches when the delimiter starts at the exact position checked —
|
|
5
|
+
* it never matches later in the string.
|
|
6
|
+
*/
|
|
7
|
+
const dollarTagPattern = /\$(?:[a-z_]\w*)?\$/iy
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns the dollar-quote delimiter starting at `sql[i]`, or `null` when
|
|
11
|
+
* there is none. Used both for a bare `$$` and a named `$tag$` delimiter.
|
|
12
|
+
* Also correctly rejects a positional parameter like `$1`, since a tag
|
|
13
|
+
* name can't start with a digit.
|
|
14
|
+
*/
|
|
15
|
+
function matchDollarTag(sql: string, i: number): string | null {
|
|
16
|
+
if (sql[i] !== '$') {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
dollarTagPattern.lastIndex = i
|
|
20
|
+
const match = dollarTagPattern.exec(sql)
|
|
21
|
+
return match ? match[0] : null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Splits a SQL string into individual statements.
|
|
26
|
+
*
|
|
27
|
+
* Recognizes the SQL syntax that can hide a statement-terminating `;`:
|
|
28
|
+
* - Dollar-quoted strings, both bare (`$$...$$`) and tagged (`$tag$...$tag$`).
|
|
29
|
+
* - Single-quoted string literals, with `''` as an escaped quote.
|
|
30
|
+
* - Double-quoted identifiers, with `""` as an escaped quote.
|
|
31
|
+
* - Line comments (`-- ...`).
|
|
32
|
+
* - Block comments (`/* ... *\/`), including nested block comments.
|
|
4
33
|
*/
|
|
5
34
|
export function splitStatements(sql: string): string[] {
|
|
6
35
|
const statements: string[] = []
|
|
@@ -8,15 +37,36 @@ export function splitStatements(sql: string): string[] {
|
|
|
8
37
|
let i = 0
|
|
9
38
|
|
|
10
39
|
while (i < sql.length) {
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
40
|
+
// Dollar-quoted strings: bare `$$` or a named `$tag$`.
|
|
41
|
+
const tag = matchDollarTag(sql, i)
|
|
42
|
+
if (tag) {
|
|
43
|
+
current += tag
|
|
44
|
+
i += tag.length
|
|
45
|
+
const end = sql.indexOf(tag, i)
|
|
46
|
+
if (end === -1) {
|
|
47
|
+
// Unterminated dollar quote — take the rest of the string as-is.
|
|
48
|
+
current += sql.slice(i)
|
|
49
|
+
i = sql.length
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
current += sql.slice(i, end + tag.length)
|
|
53
|
+
i = end + tag.length
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Double-quoted identifiers (handle "" escapes).
|
|
58
|
+
if (sql[i] === '"') {
|
|
59
|
+
current += sql[i]
|
|
60
|
+
i++
|
|
16
61
|
while (i < sql.length) {
|
|
17
|
-
if (sql[i] === '
|
|
18
|
-
current += '
|
|
62
|
+
if (sql[i] === '"' && sql[i + 1] === '"') {
|
|
63
|
+
current += '""'
|
|
19
64
|
i += 2
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
if (sql[i] === '"') {
|
|
68
|
+
current += sql[i]
|
|
69
|
+
i++
|
|
20
70
|
break
|
|
21
71
|
}
|
|
22
72
|
current += sql[i]
|
|
@@ -25,7 +75,7 @@ export function splitStatements(sql: string): string[] {
|
|
|
25
75
|
continue
|
|
26
76
|
}
|
|
27
77
|
|
|
28
|
-
//
|
|
78
|
+
// Single-quoted strings (handle '' escapes).
|
|
29
79
|
if (sql[i] === `'`) {
|
|
30
80
|
current += sql[i]
|
|
31
81
|
i++
|
|
@@ -47,7 +97,32 @@ export function splitStatements(sql: string): string[] {
|
|
|
47
97
|
continue
|
|
48
98
|
}
|
|
49
99
|
|
|
50
|
-
//
|
|
100
|
+
// Block comments, including nested block comments (Postgres allows
|
|
101
|
+
// `/* outer /* inner */ still outer */`).
|
|
102
|
+
if (sql[i] === '/' && sql[i + 1] === '*') {
|
|
103
|
+
current += '/*'
|
|
104
|
+
i += 2
|
|
105
|
+
let depth = 1
|
|
106
|
+
while (i < sql.length && depth > 0) {
|
|
107
|
+
if (sql[i] === '/' && sql[i + 1] === '*') {
|
|
108
|
+
current += '/*'
|
|
109
|
+
i += 2
|
|
110
|
+
depth++
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
if (sql[i] === '*' && sql[i + 1] === '/') {
|
|
114
|
+
current += '*/'
|
|
115
|
+
i += 2
|
|
116
|
+
depth--
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
current += sql[i]
|
|
120
|
+
i++
|
|
121
|
+
}
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Single-line comments
|
|
51
126
|
if (sql[i] === '-' && sql[i + 1] === '-') {
|
|
52
127
|
while (i < sql.length && sql[i] !== '\n') {
|
|
53
128
|
current += sql[i]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single discovered migration file for one layer.
|
|
3
|
+
*
|
|
4
|
+
* `modules/pluto-migrations.ts` discovers these at build time, either from
|
|
5
|
+
* a layer's `db/migrations/` directory, or as a single legacy
|
|
6
|
+
* `public/schema.sql` / `public/schema.<name>.sql` file. `server/utils/
|
|
7
|
+
* migrations.ts` applies them at runtime.
|
|
8
|
+
*
|
|
9
|
+
* Lives under `shared/` so both the Nuxt module (which runs under
|
|
10
|
+
* `nuxt/kit`, at build time, outside the app/server TS projects) and
|
|
11
|
+
* server code can import the same type.
|
|
12
|
+
*/
|
|
13
|
+
export interface PlutoMigrationFile {
|
|
14
|
+
/** File name, for example `001_baseline.sql` or `002_admin_hardening.sql`. */
|
|
15
|
+
name: string
|
|
16
|
+
/** Raw SQL content of the file. */
|
|
17
|
+
sql: string
|
|
18
|
+
/** First 16 hex characters of the SHA-256 digest of `sql`. */
|
|
19
|
+
checksum: string
|
|
20
|
+
/**
|
|
21
|
+
* True when the file's first non-blank line matches
|
|
22
|
+
* `-- pluto:no-transaction`. Such a file runs statement-by-statement,
|
|
23
|
+
* with no surrounding transaction — use this only when a statement
|
|
24
|
+
* cannot run inside a transaction (for example `create index
|
|
25
|
+
* concurrently`).
|
|
26
|
+
*/
|
|
27
|
+
noTransaction: boolean
|
|
28
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import type { PlutoMigrationFile } from './migrations'
|
|
2
|
+
|
|
1
3
|
// Nuxt infers runtimeConfig types from the actual merged value at build
|
|
2
|
-
// time, which narrows `
|
|
3
|
-
// to be discovered in a given project (e.g. `{ "supabase-shop":
|
|
4
|
-
// instead of the general `Record<string,
|
|
5
|
-
// nuxt.config.ts. This augmentation
|
|
6
|
-
// Lives under shared/ so it's picked up
|
|
7
|
-
// TS projects alike (layer-root .d.ts
|
|
8
|
-
// shared project).
|
|
4
|
+
// time, which narrows `plutoLayerMigrations` to whatever layer keys happen
|
|
5
|
+
// to be discovered in a given project (e.g. `{ "supabase-shop":
|
|
6
|
+
// PlutoMigrationFile[] }`) instead of the general `Record<string,
|
|
7
|
+
// PlutoMigrationFile[]>` declared in nuxt.config.ts. This augmentation
|
|
8
|
+
// pins the intended, stable shape. Lives under shared/ so it's picked up
|
|
9
|
+
// by the app, server, and shared TS projects alike (layer-root .d.ts
|
|
10
|
+
// files are only included by the shared project).
|
|
9
11
|
declare module '@nuxt/schema' {
|
|
10
12
|
interface RuntimeConfig {
|
|
11
13
|
plutoRootDir: string
|
|
12
|
-
|
|
14
|
+
plutoLayerMigrations: Record<string, PlutoMigrationFile[]>
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
package/shared/types/supabase.ts
CHANGED
|
@@ -39,7 +39,6 @@ export type Database = {
|
|
|
39
39
|
id: number
|
|
40
40
|
mime_type: string | null
|
|
41
41
|
name: string | null
|
|
42
|
-
product_id: number | null
|
|
43
42
|
size: number | null
|
|
44
43
|
storage_path: string | null
|
|
45
44
|
}
|
|
@@ -49,7 +48,6 @@ export type Database = {
|
|
|
49
48
|
id?: number
|
|
50
49
|
mime_type?: string | null
|
|
51
50
|
name?: string | null
|
|
52
|
-
product_id?: number | null
|
|
53
51
|
size?: number | null
|
|
54
52
|
storage_path?: string | null
|
|
55
53
|
}
|
|
@@ -59,35 +57,32 @@ export type Database = {
|
|
|
59
57
|
id?: number
|
|
60
58
|
mime_type?: string | null
|
|
61
59
|
name?: string | null
|
|
62
|
-
product_id?: number | null
|
|
63
60
|
size?: number | null
|
|
64
61
|
storage_path?: string | null
|
|
65
62
|
}
|
|
66
|
-
Relationships: [
|
|
67
|
-
{
|
|
68
|
-
foreignKeyName: "media_product_id_fkey"
|
|
69
|
-
columns: ["product_id"]
|
|
70
|
-
isOneToOne: false
|
|
71
|
-
referencedRelation: "products"
|
|
72
|
-
referencedColumns: ["id"]
|
|
73
|
-
},
|
|
74
|
-
]
|
|
63
|
+
Relationships: []
|
|
75
64
|
}
|
|
76
65
|
pluto_migrations: {
|
|
77
66
|
Row: {
|
|
78
67
|
applied_at: string
|
|
68
|
+
checksum: string | null
|
|
79
69
|
id: number
|
|
80
70
|
layer_name: string
|
|
71
|
+
migration_name: string
|
|
81
72
|
}
|
|
82
73
|
Insert: {
|
|
83
74
|
applied_at?: string
|
|
75
|
+
checksum?: string | null
|
|
84
76
|
id?: number
|
|
85
77
|
layer_name: string
|
|
78
|
+
migration_name: string
|
|
86
79
|
}
|
|
87
80
|
Update: {
|
|
88
81
|
applied_at?: string
|
|
82
|
+
checksum?: string | null
|
|
89
83
|
id?: number
|
|
90
84
|
layer_name?: string
|
|
85
|
+
migration_name?: string
|
|
91
86
|
}
|
|
92
87
|
Relationships: []
|
|
93
88
|
}
|
|
File without changes
|