@effect/sql-pg 4.0.0-beta.7 → 4.0.0-beta.71

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/src/PgMigrator.ts CHANGED
@@ -1,94 +1,148 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * PostgreSQL migration support for Effect SQL applications.
3
+ *
4
+ * This module adapts the shared SQL migrator to PostgreSQL. It re-exports the
5
+ * common migration loaders and errors, then provides {@link run} and
6
+ * {@link layer} helpers that execute pending migrations with the current
7
+ * `SqlClient` and `PgClient`.
8
+ *
9
+ * **Mental model**
10
+ *
11
+ * Migrations are numbered operations loaded from files, records, or bundler
12
+ * glob results. The migrator ensures the migrations table exists, reads the
13
+ * latest recorded id, and runs only migrations with a greater id. PostgreSQL
14
+ * runs use the configured `PgClient` connection details for both migration SQL
15
+ * and optional schema dumps.
16
+ *
17
+ * **Common tasks**
18
+ *
19
+ * - Run migrations explicitly with {@link run} during startup or deployment
20
+ * - Add migrations to a layer graph with {@link layer} so dependent services
21
+ * are acquired after the schema is prepared
22
+ * - Reuse the shared loaders such as `fromGlob`, `fromRecord`, and
23
+ * `fromFileSystem`
24
+ * - Enable `schemaDirectory` to write a portable schema snapshot after a
25
+ * successful migration run
26
+ *
27
+ * **Gotchas**
28
+ *
29
+ * - The default migrations table is `effect_sql_migrations`; use `table` when a
30
+ * database needs a different name
31
+ * - Only migrations with an id greater than the latest recorded id are run, so
32
+ * editing an older migration does not make it run again
33
+ * - Schema dumps shell out to `pg_dump`, so `pg_dump` must be on `PATH` and the
34
+ * layer must provide child process, filesystem, and path services
35
+ * - Generated dumps intentionally omit comments, session settings, ownership,
36
+ * and privilege statements to keep snapshots portable
37
+ *
38
+ * @since 4.0.0
3
39
  */
4
- import type * as Effect from "effect/Effect"
40
+ import * as Effect from "effect/Effect"
41
+ import * as FileSystem from "effect/FileSystem"
5
42
  import * as Layer from "effect/Layer"
43
+ import * as Path from "effect/Path"
44
+ import * as Redacted from "effect/Redacted"
45
+ import * as ChildProcess from "effect/unstable/process/ChildProcess"
46
+ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
6
47
  import * as Migrator from "effect/unstable/sql/Migrator"
7
- import type * as Client from "effect/unstable/sql/SqlClient"
48
+ import type { SqlClient } from "effect/unstable/sql/SqlClient"
8
49
  import type { SqlError } from "effect/unstable/sql/SqlError"
50
+ import { PgClient } from "./PgClient.ts"
9
51
 
10
52
  /**
11
- * @since 1.0.0
53
+ * @since 4.0.0
12
54
  */
13
55
  export * from "effect/unstable/sql/Migrator"
14
56
 
15
57
  /**
16
- * @category constructor
17
- * @since 1.0.0
58
+ * Runs PostgreSQL SQL migrations using the configured clients. Schema dumps use `pg_dump` and require child process, filesystem, and path services.
59
+ *
60
+ * @category constructors
61
+ * @since 4.0.0
18
62
  */
19
63
  export const run: <R2 = never>(
20
64
  options: Migrator.MigratorOptions<R2>
21
65
  ) => Effect.Effect<
22
66
  ReadonlyArray<readonly [id: number, name: string]>,
23
67
  Migrator.MigrationError | SqlError,
24
- Client.SqlClient | R2
68
+ | SqlClient
69
+ | PgClient
70
+ | ChildProcessSpawner.ChildProcessSpawner
71
+ | FileSystem.FileSystem
72
+ | Path.Path
73
+ | R2
25
74
  > = Migrator.make({
26
- // TODO: Wait for Command module
27
- // dumpSchema(path, table) {
28
- // const pgDump = (args: Array<string>) =>
29
- // Effect.gen(function*() {
30
- // const sql = yield* PgClient
31
- // const dump = yield* pipe(
32
- // Command.make("pg_dump", ...args, "--no-owner", "--no-privileges"),
33
- // Command.env({
34
- // PATH: (globalThis as any).process?.env.PATH,
35
- // PGHOST: sql.config.host,
36
- // PGPORT: sql.config.port?.toString(),
37
- // PGUSER: sql.config.username,
38
- // PGPASSWORD: sql.config.password
39
- // ? Redacted.value(sql.config.password)
40
- // : undefined,
41
- // PGDATABASE: sql.config.database,
42
- // PGSSLMODE: sql.config.ssl ? "require" : "prefer"
43
- // }),
44
- // Command.string
45
- // )
46
- //
47
- // return dump.replace(/^--.*$/gm, "")
48
- // .replace(/^SET .*$/gm, "")
49
- // .replace(/^SELECT pg_catalog\..*$/gm, "")
50
- // .replace(/\n{2,}/gm, "\n\n")
51
- // .trim()
52
- // }).pipe(
53
- // Effect.mapError((error) => new Migrator.MigrationError({ kind: "Failed", message: error.message }))
54
- // )
55
- //
56
- // const pgDumpSchema = pgDump(["--schema-only"])
57
- //
58
- // const pgDumpMigrations = pgDump([
59
- // "--column-inserts",
60
- // "--data-only",
61
- // `--table=${table}`
62
- // ])
63
- //
64
- // const pgDumpAll = Effect.map(
65
- // Effect.all([pgDumpSchema, pgDumpMigrations], { concurrency: 2 }),
66
- // ([schema, migrations]) => schema + "\n\n" + migrations
67
- // )
68
- //
69
- // const pgDumpFile = (path: string) =>
70
- // Effect.gen(function*() {
71
- // const fs = yield* FileSystem
72
- // const path_ = yield* Path
73
- // const dump = yield* pgDumpAll
74
- // yield* fs.makeDirectory(path_.dirname(path), { recursive: true })
75
- // yield* fs.writeFileString(path, dump)
76
- // }).pipe(
77
- // Effect.mapError((error) => new Migrator.MigrationError({ kind: "Failed", message: error.message }))
78
- // )
79
- //
80
- // return pgDumpFile(path)
81
- // }
75
+ dumpSchema(path, table) {
76
+ const pgDump = (args: Array<string>) =>
77
+ Effect.gen(function*() {
78
+ const sql = yield* PgClient
79
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
80
+ const dump = yield* ChildProcess.make("pg_dump", [...args, "--no-owner", "--no-privileges"], {
81
+ env: {
82
+ PATH: (globalThis as any).process?.env.PATH,
83
+ PGHOST: sql.config.host,
84
+ PGPORT: sql.config.port?.toString(),
85
+ PGUSER: sql.config.username,
86
+ PGPASSWORD: sql.config.password
87
+ ? Redacted.value(sql.config.password)
88
+ : undefined,
89
+ PGDATABASE: sql.config.database,
90
+ PGSSLMODE: sql.config.ssl ? "require" : "prefer"
91
+ }
92
+ }).pipe(spawner.string)
93
+
94
+ return dump.replace(/^--.*$/gm, "")
95
+ .replace(/^SET .*$/gm, "")
96
+ .replace(/^SELECT pg_catalog\..*$/gm, "")
97
+ .replace(/\n{2,}/gm, "\n\n")
98
+ .trim();
99
+ }).pipe(
100
+ Effect.mapError((error) => new Migrator.MigrationError({ kind: "Failed", message: error.message }))
101
+ )
102
+
103
+ const pgDumpSchema = pgDump(["--schema-only"])
104
+
105
+ const pgDumpMigrations = pgDump([
106
+ "--column-inserts",
107
+ "--data-only",
108
+ `--table=${table}`
109
+ ])
110
+
111
+ const pgDumpAll = Effect.map(
112
+ Effect.all([pgDumpSchema, pgDumpMigrations], { concurrency: 2 }),
113
+ ([schema, migrations]) => schema + "\n\n" + migrations
114
+ )
115
+
116
+ const pgDumpFile = (path: string) =>
117
+ Effect.gen(function*() {
118
+ const fs = yield* FileSystem.FileSystem
119
+ const path_ = yield* Path.Path
120
+ const dump = yield* pgDumpAll
121
+ yield* fs.makeDirectory(path_.dirname(path), { recursive: true })
122
+ yield* fs.writeFileString(path, dump)
123
+ }).pipe(
124
+ Effect.mapError((error) => new Migrator.MigrationError({ kind: "Failed", message: error.message }))
125
+ )
126
+
127
+ return pgDumpFile(path)
128
+ }
82
129
  })
83
130
 
84
131
  /**
132
+ * Creates a layer that runs PostgreSQL migrations during layer construction, including `pg_dump`-based schema dump support when requested.
133
+ *
85
134
  * @category layers
86
- * @since 1.0.0
135
+ * @since 4.0.0
87
136
  */
88
137
  export const layer = <R>(
89
138
  options: Migrator.MigratorOptions<R>
90
139
  ): Layer.Layer<
91
140
  never,
92
141
  Migrator.MigrationError | SqlError,
93
- Client.SqlClient | R
142
+ | SqlClient
143
+ | PgClient
144
+ | ChildProcessSpawner.ChildProcessSpawner
145
+ | FileSystem.FileSystem
146
+ | Path.Path
147
+ | R
94
148
  > => Layer.effectDiscard(run(options))
package/src/index.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * @since 4.0.0
3
3
  */
4
4
 
5
5
  // @barrel: Auto-generated exports. Do not edit manually.
6
6
 
7
7
  /**
8
- * @since 1.0.0
8
+ * @since 4.0.0
9
9
  */
10
10
  export * as PgClient from "./PgClient.ts"
11
11
 
12
12
  /**
13
- * @since 1.0.0
13
+ * @since 4.0.0
14
14
  */
15
15
  export * as PgMigrator from "./PgMigrator.ts"