@homeflare/alchemy 0.23.0 → 0.25.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.
Files changed (177) hide show
  1. package/README.md +25 -1
  2. package/dist/litellm/client.d.ts +76 -0
  3. package/dist/litellm/client.d.ts.map +1 -0
  4. package/dist/litellm/credentials.d.ts +48 -0
  5. package/dist/litellm/credentials.d.ts.map +1 -0
  6. package/dist/litellm/fake-litellm.d.ts +36 -0
  7. package/dist/litellm/fake-litellm.d.ts.map +1 -0
  8. package/dist/litellm/fake-stack.d.ts +30 -0
  9. package/dist/litellm/fake-stack.d.ts.map +1 -0
  10. package/dist/litellm/generated/pass-through.d.ts +71 -0
  11. package/dist/litellm/generated/pass-through.d.ts.map +1 -0
  12. package/dist/litellm/index.d.ts +13 -0
  13. package/dist/litellm/index.d.ts.map +1 -0
  14. package/dist/litellm/index.js +270 -0
  15. package/dist/litellm/index.js.map +14 -0
  16. package/dist/litellm/pass-through-endpoint.d.ts +94 -0
  17. package/dist/litellm/pass-through-endpoint.d.ts.map +1 -0
  18. package/dist/litellm/pass-through-form.d.ts +40 -0
  19. package/dist/litellm/pass-through-form.d.ts.map +1 -0
  20. package/dist/litellm/providers.d.ts +16 -0
  21. package/dist/litellm/providers.d.ts.map +1 -0
  22. package/dist/paperless/client.d.ts +37 -0
  23. package/dist/paperless/client.d.ts.map +1 -0
  24. package/dist/paperless/constraint-guard.d.ts +19 -0
  25. package/dist/paperless/constraint-guard.d.ts.map +1 -0
  26. package/dist/paperless/constraints.d.ts +50 -0
  27. package/dist/paperless/constraints.d.ts.map +1 -0
  28. package/dist/paperless/credentials.d.ts +40 -0
  29. package/dist/paperless/credentials.d.ts.map +1 -0
  30. package/dist/paperless/custom-field.d.ts +75 -0
  31. package/dist/paperless/custom-field.d.ts.map +1 -0
  32. package/dist/paperless/document-type.d.ts +30 -0
  33. package/dist/paperless/document-type.d.ts.map +1 -0
  34. package/dist/paperless/errors.d.ts +49 -0
  35. package/dist/paperless/errors.d.ts.map +1 -0
  36. package/dist/paperless/fake-paperless.d.ts +39 -0
  37. package/dist/paperless/fake-paperless.d.ts.map +1 -0
  38. package/dist/paperless/generated/constraints/index.d.ts +13 -0
  39. package/dist/paperless/generated/constraints/index.d.ts.map +1 -0
  40. package/dist/paperless/generated/constraints/paperless-custom_fields.d.ts +21 -0
  41. package/dist/paperless/generated/constraints/paperless-custom_fields.d.ts.map +1 -0
  42. package/dist/paperless/generated/constraints/paperless-document_types.d.ts +21 -0
  43. package/dist/paperless/generated/constraints/paperless-document_types.d.ts.map +1 -0
  44. package/dist/paperless/generated/constraints/paperless-storage_paths.d.ts +21 -0
  45. package/dist/paperless/generated/constraints/paperless-storage_paths.d.ts.map +1 -0
  46. package/dist/paperless/generated/constraints/paperless-tags.d.ts +21 -0
  47. package/dist/paperless/generated/constraints/paperless-tags.d.ts.map +1 -0
  48. package/dist/paperless/generated/types/custom-fields.d.ts +23 -0
  49. package/dist/paperless/generated/types/custom-fields.d.ts.map +1 -0
  50. package/dist/paperless/generated/types/document-types.d.ts +39 -0
  51. package/dist/paperless/generated/types/document-types.d.ts.map +1 -0
  52. package/dist/paperless/generated/types/index.d.ts +10 -0
  53. package/dist/paperless/generated/types/index.d.ts.map +1 -0
  54. package/dist/paperless/generated/types/storage-paths.d.ts +31 -0
  55. package/dist/paperless/generated/types/storage-paths.d.ts.map +1 -0
  56. package/dist/paperless/generated/types/tags.d.ts +37 -0
  57. package/dist/paperless/generated/types/tags.d.ts.map +1 -0
  58. package/dist/paperless/index.d.ts +19 -0
  59. package/dist/paperless/index.d.ts.map +1 -0
  60. package/dist/paperless/index.js +524 -0
  61. package/dist/paperless/index.js.map +26 -0
  62. package/dist/paperless/matching-locate.d.ts +33 -0
  63. package/dist/paperless/matching-locate.d.ts.map +1 -0
  64. package/dist/paperless/matching-types.d.ts +60 -0
  65. package/dist/paperless/matching-types.d.ts.map +1 -0
  66. package/dist/paperless/matching.d.ts +61 -0
  67. package/dist/paperless/matching.d.ts.map +1 -0
  68. package/dist/paperless/providers.d.ts +15 -0
  69. package/dist/paperless/providers.d.ts.map +1 -0
  70. package/dist/paperless/storage-path.d.ts +32 -0
  71. package/dist/paperless/storage-path.d.ts.map +1 -0
  72. package/dist/paperless/tag.d.ts +47 -0
  73. package/dist/paperless/tag.d.ts.map +1 -0
  74. package/dist/postgres/connection.d.ts +50 -0
  75. package/dist/postgres/connection.d.ts.map +1 -0
  76. package/dist/postgres/database-attrs.d.ts +79 -0
  77. package/dist/postgres/database-attrs.d.ts.map +1 -0
  78. package/dist/postgres/database-sql.d.ts +73 -0
  79. package/dist/postgres/database-sql.d.ts.map +1 -0
  80. package/dist/postgres/database.d.ts +63 -0
  81. package/dist/postgres/database.d.ts.map +1 -0
  82. package/dist/postgres/errors.d.ts +86 -0
  83. package/dist/postgres/errors.d.ts.map +1 -0
  84. package/dist/postgres/fake-sql.d.ts +22 -0
  85. package/dist/postgres/fake-sql.d.ts.map +1 -0
  86. package/dist/postgres/index.d.ts +16 -0
  87. package/dist/postgres/index.d.ts.map +1 -0
  88. package/dist/postgres/index.js +238 -0
  89. package/dist/postgres/index.js.map +15 -0
  90. package/dist/postgres/providers.d.ts +14 -0
  91. package/dist/postgres/providers.d.ts.map +1 -0
  92. package/dist/proxmox/index.js.map +2 -2
  93. package/dist/proxmox/zfs-pool.d.ts +10 -2
  94. package/dist/proxmox/zfs-pool.d.ts.map +1 -1
  95. package/dist/release/archive.d.ts +6 -2
  96. package/dist/release/archive.d.ts.map +1 -1
  97. package/dist/release/binary-form.d.ts +14 -9
  98. package/dist/release/binary-form.d.ts.map +1 -1
  99. package/dist/release/binary-pins.d.ts +17 -0
  100. package/dist/release/binary-pins.d.ts.map +1 -0
  101. package/dist/release/catalog.d.ts.map +1 -1
  102. package/dist/release/index.js +90 -40
  103. package/dist/release/index.js.map +8 -7
  104. package/dist/release/tar.d.ts +9 -2
  105. package/dist/release/tar.d.ts.map +1 -1
  106. package/docs/litellm.md +110 -0
  107. package/docs/paperless.md +115 -0
  108. package/docs/peers.md +8 -1
  109. package/docs/postgres.md +125 -0
  110. package/docs/release-binary-catalogs.md +40 -4
  111. package/docs/release-binary-refusals.md +22 -8
  112. package/docs/release-binary-upstream.md +33 -15
  113. package/package.json +14 -1
  114. package/src/litellm/client.ts +195 -0
  115. package/src/litellm/credentials.ts +73 -0
  116. package/src/litellm/fake-litellm.ts +114 -0
  117. package/src/litellm/fake-stack.ts +100 -0
  118. package/src/litellm/generated/pass-through.ts +73 -0
  119. package/src/litellm/index.ts +42 -0
  120. package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +66 -0
  121. package/src/litellm/pass-through-endpoint-replace.test.ts +84 -0
  122. package/src/litellm/pass-through-endpoint.test.ts +208 -0
  123. package/src/litellm/pass-through-endpoint.ts +258 -0
  124. package/src/litellm/pass-through-form.test.ts +128 -0
  125. package/src/litellm/pass-through-form.ts +136 -0
  126. package/src/litellm/providers.ts +19 -0
  127. package/src/paperless/client.ts +88 -0
  128. package/src/paperless/constraint-guard.ts +50 -0
  129. package/src/paperless/constraints.test.ts +74 -0
  130. package/src/paperless/constraints.ts +111 -0
  131. package/src/paperless/credentials.ts +69 -0
  132. package/src/paperless/custom-field.test.ts +74 -0
  133. package/src/paperless/custom-field.ts +95 -0
  134. package/src/paperless/document-type.ts +74 -0
  135. package/src/paperless/errors.ts +62 -0
  136. package/src/paperless/fake-paperless.ts +156 -0
  137. package/src/paperless/generated/constraints/index.ts +23 -0
  138. package/src/paperless/generated/constraints/paperless-custom_fields.ts +29 -0
  139. package/src/paperless/generated/constraints/paperless-document_types.ts +32 -0
  140. package/src/paperless/generated/constraints/paperless-storage_paths.ts +34 -0
  141. package/src/paperless/generated/constraints/paperless-tags.ts +34 -0
  142. package/src/paperless/generated/types/custom-fields.ts +23 -0
  143. package/src/paperless/generated/types/document-types.ts +30 -0
  144. package/src/paperless/generated/types/index.ts +9 -0
  145. package/src/paperless/generated/types/storage-paths.ts +31 -0
  146. package/src/paperless/generated/types/tags.ts +37 -0
  147. package/src/paperless/index.ts +56 -0
  148. package/src/paperless/matching-identity.test.ts +194 -0
  149. package/src/paperless/matching-locate.ts +104 -0
  150. package/src/paperless/matching-types.ts +61 -0
  151. package/src/paperless/matching.test.ts +128 -0
  152. package/src/paperless/matching.ts +239 -0
  153. package/src/paperless/providers.ts +27 -0
  154. package/src/paperless/storage-path.ts +78 -0
  155. package/src/paperless/tag.ts +91 -0
  156. package/src/postgres/connection.ts +62 -0
  157. package/src/postgres/database-attrs.ts +87 -0
  158. package/src/postgres/database-bigint-serialization.test.ts +140 -0
  159. package/src/postgres/database-lifecycle.test.ts +113 -0
  160. package/src/postgres/database-provenance.test.ts +118 -0
  161. package/src/postgres/database-refusals.test.ts +135 -0
  162. package/src/postgres/database-sql.ts +174 -0
  163. package/src/postgres/database.ts +193 -0
  164. package/src/postgres/errors.ts +131 -0
  165. package/src/postgres/fake-sql.ts +123 -0
  166. package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +26 -0
  167. package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +67 -0
  168. package/src/postgres/index.ts +30 -0
  169. package/src/postgres/providers.ts +16 -0
  170. package/src/proxmox/zfs-pool.ts +10 -2
  171. package/src/release/archive.ts +10 -4
  172. package/src/release/binary-form.ts +24 -76
  173. package/src/release/binary-pins.ts +95 -0
  174. package/src/release/binary-root.test.ts +122 -0
  175. package/src/release/catalog.ts +10 -2
  176. package/src/release/tar-root.test.ts +114 -0
  177. package/src/release/tar.ts +78 -12
@@ -0,0 +1,174 @@
1
+ /**
2
+ * The four statements `Postgres.Database` ever issues, and the quoting each one needs.
3
+ *
4
+ * ⛔ `CREATE DATABASE` TAKES NO BIND PARAMETERS FOR ANY OF ITS VALUES, INCLUDING `OWNER`.
5
+ * Measured at `gram.y@REL_18_6`: `CreatedbStmt: CREATE DATABASE name opt_with
6
+ * createdb_opt_list`, and every `createdb_opt_item` value is `NumericOnly`,
7
+ * `opt_boolean_or_string` or the bare keyword `DEFAULT` — none of those productions reaches
8
+ * `PARAM` (`$n`). `sql\`…\`` cannot help here; every free-form value in this statement is
9
+ * escaped by hand, once, in this file, and the assembled text runs through `.unsafe()`.
10
+ * ⚠️ THE DATABASE NAME AND EFFECT'S OWN `sql(value)` IDENTIFIER HELPER DO NOT MIX. `name` is a
11
+ * `ColId` (`gram.y@REL_18_6` line 17327), a single token — but `Statement.defaultEscape('"')`
12
+ * (what `sql(value)` compiles to) also rewrites every `.` in the value to `"."`, because it is
13
+ * built for a *qualified* name like `schema.table`. A database named `"my.app"` would come out
14
+ * as `"my"."app"` — two tokens where `CreatedbStmt` parses exactly one — a syntax error the
15
+ * engine would blame on this provider. `quoteIdent` below is the single-token form: it quotes
16
+ * and doubles embedded `"`, and nothing else.
17
+ * ★ EVERY OTHER `WITH` VALUE (`OWNER`, `TABLESPACE`, `ENCODING`, `LOCALE_PROVIDER`,
18
+ * `LC_COLLATE`, `LC_CTYPE`) IS A STRING LITERAL, NOT AN IDENTIFIER. Measured at the same tag:
19
+ * `createdb_opt_item`'s string branch is `opt_boolean_or_string → NonReservedWord_or_Sconst →
20
+ * NonReservedWord | Sconst`. A locale like `en_US.UTF-8` would hit the same dot-splitting
21
+ * `quoteIdent` avoids if it were identifier-quoted; a plain `Sconst` (`quoteStringLiteral`) has
22
+ * no such rule. This rests on `standard_conforming_strings = on` (the PG18 default, and
23
+ * measured live 2026-09-23 — `docs/postgres.md#measured`); the escaper only ever doubles `'`
24
+ * and never emits a backslash, so it never depends on which way that setting points.
25
+ */
26
+ import type { SqlError } from 'effect/unstable/sql/SqlError';
27
+ import * as Effect from 'effect/Effect';
28
+ import type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';
29
+
30
+ /** What every function in this file needs from a client — real (`PgClient.PgClient`) or fake
31
+ * (`fake-sql.ts`). Deliberately smaller than `SqlClient`: nothing here ever needs the tagged
32
+ * template, transactions or streaming, because every statement is either bound params through
33
+ * `.unsafe(text, params)` or a hand-escaped literal through `.unsafe(text)`. */
34
+ export interface PgExecutor {
35
+ readonly unsafe: <A extends object>(
36
+ sql: string,
37
+ params?: ReadonlyArray<unknown>,
38
+ ) => Effect.Effect<ReadonlyArray<A>, SqlError>;
39
+ }
40
+
41
+ /** Single-token identifier quoting: wrap in `"`, double any embedded `"`. No dot-splitting —
42
+ * see the file header. Used for the database name and, since a role or tablespace name is a
43
+ * `NonReservedWord` too, for `OWNER` and `TABLESPACE`. */
44
+ export const quoteIdent = (value: string): string => `"${value.replace(/"/g, '""')}"`;
45
+
46
+ /** SQL string-literal quoting: wrap in `'`, double any embedded `'`. Used for every `WITH`
47
+ * value that is locale or encoding text, never for an identifier (see the file header). */
48
+ export const quoteStringLiteral = (value: string): string => `'${value.replace(/'/g, "''")}'`;
49
+
50
+ const withOptions = (props: PostgresDatabaseProps): string => {
51
+ const parts: string[] = [`OWNER ${quoteIdent(props.owner)}`];
52
+ if (props.encoding !== undefined) parts.push(`ENCODING ${quoteStringLiteral(props.encoding)}`);
53
+ if (props.localeProvider !== undefined) {
54
+ parts.push(`LOCALE_PROVIDER ${quoteStringLiteral(props.localeProvider)}`);
55
+ }
56
+ if (props.lcCollate !== undefined)
57
+ parts.push(`LC_COLLATE ${quoteStringLiteral(props.lcCollate)}`);
58
+ if (props.lcCtype !== undefined) parts.push(`LC_CTYPE ${quoteStringLiteral(props.lcCtype)}`);
59
+ if (props.tablespace !== undefined) parts.push(`TABLESPACE ${quoteIdent(props.tablespace)}`);
60
+ if (props.allowConnections !== undefined) {
61
+ parts.push(`ALLOW_CONNECTIONS ${props.allowConnections ? 'true' : 'false'}`);
62
+ }
63
+ if (props.connectionLimit !== undefined) {
64
+ parts.push(`CONNECTION LIMIT ${String(Math.trunc(props.connectionLimit))}`);
65
+ }
66
+ if (props.isTemplate !== undefined)
67
+ parts.push(`IS_TEMPLATE ${props.isTemplate ? 'true' : 'false'}`);
68
+ return parts.join(' ');
69
+ };
70
+
71
+ /**
72
+ * Build the exact `CREATE DATABASE` text — a pure function, unit-tested on its own with no
73
+ * client at all. `CONNECTION LIMIT` and the two booleans are never string-escaped: an integer is
74
+ * `Math.trunc`ed and stringified, and a boolean is one of exactly two literal words this file
75
+ * writes, so no declared value reaches the statement through string interpolation unescaped.
76
+ */
77
+ export const buildCreateDatabaseSql = (props: PostgresDatabaseProps): string =>
78
+ `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props)}`;
79
+
80
+ /** `42P04` (`duplicate_database`) is a race: something else created the name between our read
81
+ * and our create. Measured at `internal/sqlError.ts@effect/sql-pg 4.0.0-rc.115`: every SQLSTATE
82
+ * starting `42` — this one included — classifies as `SqlSyntaxError`, the same tag a genuine
83
+ * syntax error would raise; there is no more specific tag to `catchTag` on. So this reads the
84
+ * raw code PostgreSQL sent, carried on `reason.cause.code` (`PgConnection.ts#errorFromFields`,
85
+ * `Object.assign(new Error(...), fields)`), rather than trusting the tag alone. */
86
+ export const isDuplicateDatabaseRace = (error: SqlError): boolean => {
87
+ if (error.reason._tag !== 'SqlSyntaxError') return false;
88
+ const cause = error.reason.cause;
89
+ return (
90
+ typeof cause === 'object' && cause !== null && (cause as { code?: unknown }).code === '42P04'
91
+ );
92
+ };
93
+
94
+ const ROLE_EXISTS_SQL = 'SELECT 1 AS present FROM pg_roles WHERE rolname = $1';
95
+
96
+ /** `OWNER` is checked against `pg_roles` before any `CREATE`, so a missing role is
97
+ * `PostgresDatabaseOwnerMissing` at reconcile time rather than the server's own
98
+ * `ERROR: role "…" does not exist` surfacing as an unclassified statement failure. */
99
+ export const roleExists = (pg: PgExecutor, role: string): Effect.Effect<boolean, SqlError> =>
100
+ Effect.map(
101
+ pg.unsafe<{ readonly present: number }>(ROLE_EXISTS_SQL, [role]),
102
+ (rows) => rows.length > 0,
103
+ );
104
+
105
+ /** `datlocprovider` is Postgres's internal 1-byte `"char"` type, which this client decodes as
106
+ * raw bytes rather than text (measured 2026-09-23 against the live socket — `docs/postgres.md`);
107
+ * the `CASE` maps it to the same three words `pg_collation.h@REL_18_6#collprovider_name` returns
108
+ * for the same codes, so the row never crosses the wire as anything but ordinary `text`.
109
+ *
110
+ * ⛔ `oid` AND `datconnlimit` ARE READ AT THEIR NATIVE CATALOG WIDTH — NEVER CAST TO `::int8`.
111
+ * `pg_database.h@REL_18_6` types them `Oid oid` (4 bytes, builtin OID 26) and
112
+ * `int32 datconnlimit` (4 bytes, builtin OID 23); `@effect/sql-pg`'s codec table
113
+ * (`PgTypes.ts@4.0.0-rc.115`) decodes both of those as a plain JS `number`
114
+ * (`readUint32`/`readInt32`), matching `PostgresDatabaseAttributes`'s declared `number`
115
+ * fields. Casting either to `::int8` changes the WIRE type the server sends, so the same
116
+ * codec table decodes it as a JS `bigint` instead (`OID.int8`'s codec calls
117
+ * `DataView.getBigInt64`) — and alchemy's `encodeState`
118
+ * (`node_modules/alchemy/lib/State/StateEncoding.js`) has no `bigint` branch, so
119
+ * `JSON.stringify`ing the persisted state throws `TypeError: Do not know how to serialize a
120
+ * BigInt` on every successful reconcile, and a live `bigint` compared against a declared
121
+ * `number` in `firstDrift` below is never `===`, so a matching `connectionLimit` reads as
122
+ * permanent drift. Measured 2026-09-23; regression test:
123
+ * `database-bigint-serialization.test.ts`. */
124
+ const SELECT_DATABASE_SQL = `SELECT
125
+ d.oid AS oid,
126
+ d.datname AS name,
127
+ pg_get_userbyid(d.datdba) AS owner,
128
+ pg_encoding_to_char(d.encoding) AS encoding,
129
+ CASE d.datlocprovider
130
+ WHEN 'b' THEN 'builtin' WHEN 'i' THEN 'icu' WHEN 'c' THEN 'libc'
131
+ ELSE 'libc'
132
+ END AS "localeProvider",
133
+ d.datcollate AS collate,
134
+ d.datctype AS ctype,
135
+ d.datallowconn AS "allowConnections",
136
+ d.datconnlimit AS "connectionLimit",
137
+ d.datistemplate AS "isTemplate",
138
+ t.spcname AS tablespace
139
+ FROM pg_database d
140
+ JOIN pg_tablespace t ON t.oid = d.dattablespace
141
+ WHERE d.datname = $1`;
142
+
143
+ /** The read: one bound `SELECT` on `pg_database`, joined to the two functions that turn its raw
144
+ * owner and encoding columns into names (`docs/postgres.md#mapping`). `undefined` when the row
145
+ * is absent — ownership branding happens one layer up, in `database.ts`, not here. */
146
+ export const selectDatabase = (
147
+ pg: PgExecutor,
148
+ name: string,
149
+ ): Effect.Effect<PostgresDatabaseAttributes | undefined, SqlError> =>
150
+ Effect.map(pg.unsafe<PostgresDatabaseAttributes>(SELECT_DATABASE_SQL, [name]), (rows) => rows[0]);
151
+
152
+ /** Compare every declared, asserted prop against the live row. Returns the name of the first
153
+ * prop that drifted, or `undefined` when every declared prop matches. `name` and `owner` are
154
+ * compared by their callers (identity and the create-time assertion), never here. */
155
+ export const firstDrift = (
156
+ props: PostgresDatabaseProps,
157
+ live: PostgresDatabaseAttributes,
158
+ ): { readonly prop: string; readonly declared: unknown; readonly live: unknown } | undefined => {
159
+ const checks: ReadonlyArray<readonly [string, unknown, unknown]> = [
160
+ ['encoding', props.encoding, live.encoding],
161
+ ['localeProvider', props.localeProvider, live.localeProvider],
162
+ ['lcCollate', props.lcCollate, live.collate],
163
+ ['lcCtype', props.lcCtype, live.ctype],
164
+ ['allowConnections', props.allowConnections, live.allowConnections],
165
+ ['connectionLimit', props.connectionLimit, live.connectionLimit],
166
+ ['isTemplate', props.isTemplate, live.isTemplate],
167
+ ['tablespace', props.tablespace, live.tablespace],
168
+ ];
169
+ for (const [prop, declared, liveValue] of checks) {
170
+ if (declared !== undefined && declared !== liveValue)
171
+ return { prop, declared, live: liveValue };
172
+ }
173
+ return undefined;
174
+ };
@@ -0,0 +1,193 @@
1
+ /**
2
+ * `Postgres.Database` — create-and-assert over a self-hosted PostgreSQL 18 cluster.
3
+ *
4
+ * ⛔ THIS IS NOT AN ALTER-CAPABLE RESOURCE. Every optional prop is asserted ONCE, at create, and
5
+ * compared against the live row on every later plan; a mismatch is a typed
6
+ * `PostgresDatabaseDrift` refusal, never an `ALTER DATABASE`. `docs/postgres.md` says why:
7
+ * several of these props (`LC_COLLATE`, `LC_CTYPE`, `LOCALE_PROVIDER`) cannot be altered on a
8
+ * live database at all without `ALTER … REFRESH COLLATION VERSION` semantics this family does
9
+ * not implement, and the rest are left alone on principle rather than by accident.
10
+ * ⛔ `delete` NEVER DROPS. See `errors.ts#PostgresDatabaseDropRefused` and
11
+ * `defaultRemovalPolicy: 'retain'` below — two independent reasons the engine never issues
12
+ * `DROP DATABASE` through this provider.
13
+ * ★ A DATABASE CARRIES NO OWNERSHIP MARK (H1). `read` always answers `Unowned` for a match, the
14
+ * same rule `Cloudflare.Snippets.Snippet` uses for a marker-less API — so every one of the
15
+ * mini's 19 already-live databases needs `adopt(true)` in the stack that declares it.
16
+ */
17
+ import { Resource } from 'alchemy';
18
+ import { Unowned } from 'alchemy/AdoptPolicy';
19
+ import { isResolved } from 'alchemy/Diff';
20
+ import type { Input } from 'alchemy/Input';
21
+ import * as Provider from 'alchemy/Provider';
22
+ import * as Effect from 'effect/Effect';
23
+ import type { SqlError } from 'effect/unstable/sql/SqlError';
24
+ import type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';
25
+ import { nameByteRefusal } from './database-attrs.ts';
26
+ import {
27
+ buildCreateDatabaseSql,
28
+ firstDrift,
29
+ isDuplicateDatabaseRace,
30
+ roleExists,
31
+ selectDatabase,
32
+ } from './database-sql.ts';
33
+ import { type PostgresConnection, withPg } from './connection.ts';
34
+ import type { PgExecutor } from './database-sql.ts';
35
+ import {
36
+ PostgresDatabaseCreateVanished,
37
+ PostgresDatabaseDrift,
38
+ PostgresDatabaseDropRefused,
39
+ PostgresDatabaseNameRefused,
40
+ PostgresDatabaseOwnerMissing,
41
+ PostgresDatabaseRenameRefused,
42
+ } from './errors.ts';
43
+
44
+ export interface PostgresDatabase extends Resource<
45
+ 'Postgres.Database',
46
+ PostgresDatabaseProps,
47
+ PostgresDatabaseAttributes,
48
+ never,
49
+ PostgresConnection
50
+ > {}
51
+
52
+ export const PostgresDatabase = Resource<PostgresDatabase>('Postgres.Database', {
53
+ defaultRemovalPolicy: 'retain',
54
+ });
55
+
56
+ /** Mirrors `alchemy/Resource`'s own `isResourceOfType`: a resource constructor is a callable
57
+ * `Object.assign`ed function, not a plain object, so `typeof` must accept both — a bare
58
+ * `'object'` check (measured live in `scripts/smoke.ts`: it rejects `PostgresDatabase` itself,
59
+ * matching only an instance) is the regression this comment exists to prevent. */
60
+ export const isPostgresDatabase = (value: unknown): value is PostgresDatabase =>
61
+ (typeof value === 'object' || typeof value === 'function') &&
62
+ value !== null &&
63
+ (value as { Type?: unknown }).Type === 'Postgres.Database';
64
+
65
+ /** Every asserted-prop mismatch against the LIVE row, owner included — reconcile's own
66
+ * authoritative check, independent of what `diff` compared at plan time (S10: cloud state is
67
+ * authoritative). */
68
+ const liveDrift = (props: PostgresDatabaseProps, live: PostgresDatabaseAttributes) => {
69
+ if (props.owner !== live.owner) {
70
+ return { prop: 'owner', declared: props.owner, live: live.owner };
71
+ }
72
+ return firstDrift(props, live);
73
+ };
74
+
75
+ /**
76
+ * The core of `reconcile`, against any {@link PgExecutor} — the real pooled client through
77
+ * `withPg`, or `fake-sql.ts`'s recording fake in tests. Kept apart from the provider wiring
78
+ * below so a lifecycle test drives this exact function, not a re-implementation of it.
79
+ */
80
+ export const reconcileWithClient = (
81
+ pg: PgExecutor,
82
+ props: PostgresDatabaseProps,
83
+ ): Effect.Effect<
84
+ PostgresDatabaseAttributes,
85
+ PostgresDatabaseOwnerMissing | PostgresDatabaseDrift | PostgresDatabaseCreateVanished | SqlError
86
+ > =>
87
+ Effect.gen(function* () {
88
+ const observed = yield* selectDatabase(pg, props.name);
89
+ if (observed === undefined) {
90
+ const ownerPresent = yield* roleExists(pg, props.owner);
91
+ if (!ownerPresent) {
92
+ return yield* Effect.fail(
93
+ new PostgresDatabaseOwnerMissing({ database: props.name, owner: props.owner }),
94
+ );
95
+ }
96
+ yield* pg.unsafe(buildCreateDatabaseSql(props)).pipe(
97
+ Effect.asVoid,
98
+ Effect.catchTag('SqlError', (error) =>
99
+ isDuplicateDatabaseRace(error) ? Effect.succeed(undefined) : Effect.fail(error),
100
+ ),
101
+ );
102
+ // Never trust the write's own report (S10): re-read what the server actually stored.
103
+ const created = yield* selectDatabase(pg, props.name);
104
+ if (created === undefined) {
105
+ return yield* Effect.fail(new PostgresDatabaseCreateVanished({ database: props.name }));
106
+ }
107
+ return created;
108
+ }
109
+ const drift = liveDrift(props, observed);
110
+ if (drift !== undefined) {
111
+ return yield* Effect.fail(
112
+ new PostgresDatabaseDrift({
113
+ database: props.name,
114
+ prop: drift.prop,
115
+ declared: drift.declared,
116
+ live: drift.live,
117
+ }),
118
+ );
119
+ }
120
+ // Unchanged: the row already read is the authoritative answer. No write, no second round trip.
121
+ return observed;
122
+ });
123
+
124
+ /** The core of `read`: one bound `SELECT`, no ownership branding — the caller (the provider's
125
+ * `read` below, or a test) decides whether to wrap a match in `Unowned`. */
126
+ export const readWithClient = (pg: PgExecutor, name: string) => selectDatabase(pg, name);
127
+
128
+ /**
129
+ * The core of `diff`, entirely offline: `news` (declared) against `output` (last-applied
130
+ * attributes) — never a live query (that authoritative check is `reconcileWithClient`'s job, per
131
+ * S10). A rename is a plan-time refusal; over-long names are too, so a bad name never even reaches
132
+ * `reconcile`; every other change answers `update`, which `reconcileWithClient` then either
133
+ * applies (create) or refuses (drift on an already-live database).
134
+ */
135
+ export const diffPostgresDatabase = (
136
+ news: Input<PostgresDatabaseProps>,
137
+ output: PostgresDatabaseAttributes | undefined,
138
+ ) =>
139
+ Effect.gen(function* () {
140
+ if (output === undefined || !isResolved(news)) return undefined;
141
+ if (news.name !== output.name) {
142
+ return yield* Effect.fail(
143
+ new PostgresDatabaseRenameRefused({ from: output.name, to: news.name }),
144
+ );
145
+ }
146
+ const nameRefusal = nameByteRefusal(news.name);
147
+ if (nameRefusal !== undefined) {
148
+ return yield* Effect.fail(
149
+ new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }),
150
+ );
151
+ }
152
+ const changed = news.owner !== output.owner || firstDrift(news, output) !== undefined;
153
+ return changed ? ({ action: 'update' } as const) : ({ action: 'noop' } as const);
154
+ });
155
+
156
+ /**
157
+ * The five lifecycle handlers, wired to `withPg` for anything that touches a client. Exported
158
+ * on its own — not only through {@link PostgresDatabaseProvider}'s `Layer` — so a test can call
159
+ * `postgresDatabaseHandlers.delete(...)` directly, against the real implementation, without
160
+ * standing up the engine's provider machinery.
161
+ */
162
+ export const postgresDatabaseHandlers = PostgresDatabase.Provider.of({
163
+ // ⚠️ EMPTY, NOT A LIVE SWEEP. Enumerating `pg_database` would list every database on the
164
+ // cluster, most of which nothing declared — the same reasoning `NetboxPrefix` documents
165
+ // for its own marker-less `list`. Adoption for this family is one declaration at a time.
166
+ list: () => Effect.succeed([]),
167
+ nuke: { skip: true },
168
+
169
+ read: ({ olds, output }) =>
170
+ Effect.gen(function* () {
171
+ const name = output?.name ?? olds.name;
172
+ const live = yield* withPg((pg) => readWithClient(pg, name));
173
+ return live === undefined ? undefined : Unowned(live);
174
+ }),
175
+
176
+ diff: ({ news, output }) => diffPostgresDatabase(news, output),
177
+
178
+ reconcile: ({ news }) =>
179
+ Effect.gen(function* () {
180
+ const nameRefusal = nameByteRefusal(news.name);
181
+ if (nameRefusal !== undefined) {
182
+ return yield* Effect.fail(
183
+ new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }),
184
+ );
185
+ }
186
+ return yield* withPg((pg) => reconcileWithClient(pg, news));
187
+ }),
188
+
189
+ delete: ({ olds }) => Effect.fail(new PostgresDatabaseDropRefused({ database: olds.name })),
190
+ });
191
+
192
+ export const PostgresDatabaseProvider = () =>
193
+ Provider.succeed(PostgresDatabase, postgresDatabaseHandlers);
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `Postgres.Database`'s failures as typed tags (S21).
3
+ *
4
+ * ⛔ NONE OF THESE IS A STATUS-CODE OR MESSAGE MATCH. Every one is raised by this family's own
5
+ * code from a fact it already checked (a byte count, a live catalog row, a role lookup) — never
6
+ * from sniffing a driver error's text. The one live-SQLSTATE check this family makes —
7
+ * `isDuplicateDatabaseRace` in `database-sql.ts`, for the `42P04` create race — reads the raw
8
+ * code `@effect/sql-pg` classified onto `SqlSyntaxError.cause.code` (measured in
9
+ * `node_modules/@effect/sql-pg/src/internal/sqlError.ts#classifySqlState`: every `42xxx` code,
10
+ * `42P04` included, becomes `SqlSyntaxError` — there is no more specific tag for it), and it
11
+ * never produces one of these tags: a race converges instead of failing.
12
+ */
13
+ import * as Data from 'effect/Data';
14
+
15
+ /**
16
+ * A declared name would be silently truncated by the server (`NAMEDATALEN` 64, so 63 UTF-8
17
+ * bytes is the last one that survives whole) — refused at plan, before any statement runs.
18
+ *
19
+ * ⛔ WHY THIS IS A PLAN-TIME REFUSAL AND NOT A SERVER ERROR. `truncate_identifier`
20
+ * (`scansup.c@REL_18_6` line 93) clips a long name to 63 bytes and raises only a `NOTICE`
21
+ * (`errcode(ERRCODE_NAME_TOO_LONG)`), never an error. A create would succeed against the
22
+ * TRUNCATED name, so the next plan's read (`WHERE datname = $1`, the name exactly as
23
+ * declared) would find nothing and try to create it again — forever. See
24
+ * `doc/postgres.md#name`.
25
+ */
26
+ export class PostgresDatabaseNameRefused extends Data.TaggedError('PostgresDatabaseNameRefused')<{
27
+ readonly name: string;
28
+ readonly byteLength: number;
29
+ readonly limit: number;
30
+ }> {
31
+ override get message(): string {
32
+ return (
33
+ `Postgres.Database "${this.name}": ${String(this.byteLength)} UTF-8 bytes, over the ` +
34
+ `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` +
35
+ 'accept the CREATE and silently truncate the name with only a NOTICE, so this plan refuses ' +
36
+ 'it instead of creating a database no later read will ever find again.'
37
+ );
38
+ }
39
+ }
40
+
41
+ /** A logical id's `name` changed. A database's name is its identity; there is no `ALTER … RENAME`
42
+ * in this family's vocabulary (see `docs/postgres.md`), and `diff` never turns this into a
43
+ * `replace` — a replace here is `DROP DATABASE` before `CREATE DATABASE`, on data. */
44
+ export class PostgresDatabaseRenameRefused extends Data.TaggedError(
45
+ 'PostgresDatabaseRenameRefused',
46
+ )<{
47
+ readonly from: string;
48
+ readonly to: string;
49
+ }> {
50
+ override get message(): string {
51
+ return (
52
+ `Postgres.Database: the declared name changed from "${this.from}" to "${this.to}". A ` +
53
+ 'rename is refused at plan — this provider never replaces a database (that is DROP then ' +
54
+ 'CREATE) and never runs ALTER … RENAME. Declare a new logical id for the new name, and ' +
55
+ 'remove the old one once its data has moved.'
56
+ );
57
+ }
58
+ }
59
+
60
+ /** `OWNER` names a role `pg_roles` does not have, checked before the `CREATE DATABASE` that
61
+ * would otherwise fail on it. Postgres itself refuses the same case with `ERROR: role "…" does
62
+ * not exist` (`createdb()`, `dbcommands.c`) — this tag exists so the refusal is typed and the
63
+ * check runs before any statement, not caught from the driver's text. */
64
+ export class PostgresDatabaseOwnerMissing extends Data.TaggedError('PostgresDatabaseOwnerMissing')<{
65
+ readonly database: string;
66
+ readonly owner: string;
67
+ }> {
68
+ override get message(): string {
69
+ return `Postgres.Database "${this.database}": OWNER "${this.owner}" is not a role in pg_roles.`;
70
+ }
71
+ }
72
+
73
+ /** A live, asserted property no longer matches the declaration. `reconcile` never issues an
74
+ * `ALTER DATABASE` to fix this — see `docs/postgres.md` for why each asserted prop stops at
75
+ * create. */
76
+ export class PostgresDatabaseDrift extends Data.TaggedError('PostgresDatabaseDrift')<{
77
+ readonly database: string;
78
+ readonly prop: string;
79
+ readonly declared: unknown;
80
+ readonly live: unknown;
81
+ }> {
82
+ override get message(): string {
83
+ return (
84
+ `Postgres.Database "${this.database}": ${this.prop} is declared as ` +
85
+ `${JSON.stringify(this.declared)} but the live database has ${JSON.stringify(this.live)}. ` +
86
+ 'This provider never issues ALTER DATABASE — update the declaration to match the live ' +
87
+ 'value, or change it by hand and re-plan.'
88
+ );
89
+ }
90
+ }
91
+
92
+ /** `delete` never drops a database. `DROP DATABASE` is destructive and irreversible on data
93
+ * this family did not create from nothing (most declared databases here are adopted, not
94
+ * created); the decision to remove one stays a human act on the host. */
95
+ export class PostgresDatabaseDropRefused extends Data.TaggedError('PostgresDatabaseDropRefused')<{
96
+ readonly database: string;
97
+ }> {
98
+ override get message(): string {
99
+ return (
100
+ `Postgres.Database "${this.database}": delete is refused. This provider never issues DROP ` +
101
+ 'DATABASE. Drop it by hand over the trust socket if you mean to remove it, then remove the ' +
102
+ 'declaration.'
103
+ );
104
+ }
105
+ }
106
+
107
+ /** `CREATE DATABASE` raised no error, but the immediate re-read (S10: never trust the write's
108
+ * own report) still finds nothing. Measured to have no real trigger in a correctly speaking
109
+ * client, but S20 forbids `Effect.die` here regardless — an invariant violation is a typed
110
+ * failure the caller can see and retry, never a defect that crashes the whole engine. */
111
+ export class PostgresDatabaseCreateVanished extends Data.TaggedError(
112
+ 'PostgresDatabaseCreateVanished',
113
+ )<{
114
+ readonly database: string;
115
+ }> {
116
+ override get message(): string {
117
+ return (
118
+ `Postgres.Database "${this.database}": CREATE DATABASE raised no error, but the database ` +
119
+ 'is still absent on the immediate re-read. Re-plan — this is not a state this provider ' +
120
+ 'expects to reach.'
121
+ );
122
+ }
123
+ }
124
+
125
+ export type PostgresDatabaseError =
126
+ | PostgresDatabaseNameRefused
127
+ | PostgresDatabaseRenameRefused
128
+ | PostgresDatabaseOwnerMissing
129
+ | PostgresDatabaseDrift
130
+ | PostgresDatabaseDropRefused
131
+ | PostgresDatabaseCreateVanished;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * A recording fake `PgExecutor` (S28: "Tests use `bun:test`… a test never trusts the deploy's
3
+ * own report" — here read back from the fake's own catalog, not from the statement list) for
4
+ * every lifecycle test in this family. No socket, no `@effect/sql-pg` client — it is small
5
+ * enough to re-derive the one thing this provider ever asks of a real one: three statement
6
+ * shapes, matched by text, against an in-memory `pg_roles` set and `pg_database` map.
7
+ *
8
+ * ⛔ IT PARSES ITS OWN OUTPUT, NOT SQL IN GENERAL. `parseCreate` below understands exactly the
9
+ * text `buildCreateDatabaseSql` (`database-sql.ts`) produces — quoted with `quoteIdent` /
10
+ * `quoteStringLiteral` — because that is the only `CREATE DATABASE` this family ever issues. A
11
+ * general SQL parser would hide a quoting bug instead of tripping over it.
12
+ */
13
+ import * as Effect from 'effect/Effect';
14
+ import { SqlError, SqlSyntaxError } from 'effect/unstable/sql/SqlError';
15
+ import type { PostgresDatabaseAttributes } from './database-attrs.ts';
16
+ import type { PgExecutor } from './database-sql.ts';
17
+
18
+ export interface RecordedStatement {
19
+ readonly text: string;
20
+ readonly params: ReadonlyArray<unknown>;
21
+ }
22
+
23
+ export interface FakeSql extends PgExecutor {
24
+ readonly statements: ReadonlyArray<RecordedStatement>;
25
+ /** Mutable on purpose: a test seeds a row a "competing" statement would have produced (the
26
+ * `42P04` race case) before the fake ever sees it. */
27
+ readonly databases: Map<string, PostgresDatabaseAttributes>;
28
+ }
29
+
30
+ export interface FakeSqlOptions {
31
+ readonly roles?: ReadonlyArray<string>;
32
+ readonly databases?: ReadonlyArray<PostgresDatabaseAttributes>;
33
+ /** Fail the NEXT `CREATE DATABASE` with SQLSTATE `42P04` (duplicate_database), once, the way a
34
+ * concurrent creator racing this reconcile would — classified exactly as
35
+ * `@effect/sql-pg`'s own driver classifies it (`database-sql.ts`'s header). */
36
+ readonly raceNextCreate?: boolean;
37
+ }
38
+
39
+ const unquoteIdent = (raw: string): string => raw.replace(/""/g, '"');
40
+ const unquoteLiteral = (raw: string): string => raw.replace(/''/g, "'");
41
+
42
+ /** Pull every field back out of exactly the text `buildCreateDatabaseSql` writes. */
43
+ const parseCreate = (text: string): PostgresDatabaseAttributes => {
44
+ const name = /^CREATE DATABASE "((?:[^"]|"")*)" WITH /.exec(text);
45
+ const owner = /OWNER "((?:[^"]|"")*)"/.exec(text);
46
+ const encoding = /ENCODING '((?:[^']|'')*)'/.exec(text);
47
+ const localeProvider = /LOCALE_PROVIDER '((?:[^']|'')*)'/.exec(text);
48
+ const lcCollate = /LC_COLLATE '((?:[^']|'')*)'/.exec(text);
49
+ const lcCtype = /LC_CTYPE '((?:[^']|'')*)'/.exec(text);
50
+ const tablespace = /TABLESPACE "((?:[^"]|"")*)"/.exec(text);
51
+ const allowConnections = /ALLOW_CONNECTIONS (true|false)/.exec(text);
52
+ const connectionLimit = /CONNECTION LIMIT (-?\d+)/.exec(text);
53
+ const isTemplate = /IS_TEMPLATE (true|false)/.exec(text);
54
+ if (name === null || owner === null) {
55
+ throw new Error(`fake-sql: could not parse a generated CREATE DATABASE statement: ${text}`);
56
+ }
57
+ return {
58
+ name: unquoteIdent(name[1] as string),
59
+ oid: 0,
60
+ owner: unquoteIdent(owner[1] as string),
61
+ encoding: encoding === null ? 'UTF8' : unquoteLiteral(encoding[1] as string),
62
+ localeProvider:
63
+ localeProvider === null ? 'libc' : (unquoteLiteral(localeProvider[1] as string) as 'libc'),
64
+ collate: lcCollate === null ? 'C' : unquoteLiteral(lcCollate[1] as string),
65
+ ctype: lcCtype === null ? 'C' : unquoteLiteral(lcCtype[1] as string),
66
+ tablespace: tablespace === null ? 'pg_default' : unquoteIdent(tablespace[1] as string),
67
+ allowConnections: allowConnections === null ? true : allowConnections[1] === 'true',
68
+ connectionLimit: connectionLimit === null ? -1 : Number(connectionLimit[1]),
69
+ isTemplate: isTemplate === null ? false : isTemplate[1] === 'true',
70
+ };
71
+ };
72
+
73
+ export const makeFakeSql = (options: FakeSqlOptions = {}): FakeSql => {
74
+ const statements: RecordedStatement[] = [];
75
+ const roles = new Set(options.roles ?? []);
76
+ const databases = new Map(options.databases?.map((d) => [d.name, d] as const) ?? []);
77
+ let raceRemaining = options.raceNextCreate === true ? 1 : 0;
78
+ let oidCounter = 20000;
79
+
80
+ const unsafe = <A extends object>(
81
+ text: string,
82
+ params: ReadonlyArray<unknown> = [],
83
+ ): Effect.Effect<ReadonlyArray<A>, SqlError> =>
84
+ Effect.suspend(() => {
85
+ statements.push({ text, params });
86
+
87
+ if (text.startsWith('SELECT 1 AS present FROM pg_roles')) {
88
+ const role = params[0] as string;
89
+ return Effect.succeed(
90
+ (roles.has(role) ? [{ present: 1 }] : []) as unknown as ReadonlyArray<A>,
91
+ );
92
+ }
93
+
94
+ if (text.includes('FROM pg_database')) {
95
+ const name = params[0] as string;
96
+ const row = databases.get(name);
97
+ return Effect.succeed((row === undefined ? [] : [row]) as unknown as ReadonlyArray<A>);
98
+ }
99
+
100
+ if (text.startsWith('CREATE DATABASE')) {
101
+ if (raceRemaining > 0) {
102
+ raceRemaining -= 1;
103
+ return Effect.fail(
104
+ new SqlError({
105
+ reason: new SqlSyntaxError({
106
+ cause: Object.assign(new Error('database "x" already exists'), { code: '42P04' }),
107
+ message: 'duplicate_database (fake race)',
108
+ operation: 'CREATE DATABASE',
109
+ }),
110
+ }),
111
+ );
112
+ }
113
+ const row = { ...parseCreate(text), oid: oidCounter };
114
+ oidCounter += 1;
115
+ databases.set(row.name, row);
116
+ return Effect.succeed([] as unknown as ReadonlyArray<A>);
117
+ }
118
+
119
+ throw new Error(`fake-sql: unrecognised statement: ${text}`);
120
+ });
121
+
122
+ return { unsafe, statements, databases };
123
+ };
@@ -0,0 +1,26 @@
1
+ # path: doc/src/sgml/ref/create_database.sgml
2
+ # blob: 4da8aeebb50a29e89bd9f68a8deac4b1a3ec7b18
3
+ # tag: REL_18_6 (commit 724edf9bde9d356724ad384a2e196edc3c9f80f7)
4
+ # fetched: 2026-09-23 via gh api repos/postgres/postgres/contents/...?ref=724edf9bde9d356724ad384a2e196edc3c9f80f7
5
+ # excerpt: the <synopsis> block only (lines 23-42 of the full file), cut per house doc-size convention
6
+ #
7
+ <synopsis>
8
+ CREATE DATABASE <replaceable class="parameter">name</replaceable>
9
+ [ WITH ] [ OWNER [=] <replaceable class="parameter">user_name</replaceable> ]
10
+ [ TEMPLATE [=] <replaceable class="parameter">template</replaceable> ]
11
+ [ ENCODING [=] <replaceable class="parameter">encoding</replaceable> ]
12
+ [ STRATEGY [=] <replaceable class="parameter">strategy</replaceable> ]
13
+ [ LOCALE [=] <replaceable class="parameter">locale</replaceable> ]
14
+ [ LC_COLLATE [=] <replaceable class="parameter">lc_collate</replaceable> ]
15
+ [ LC_CTYPE [=] <replaceable class="parameter">lc_ctype</replaceable> ]
16
+ [ BUILTIN_LOCALE [=] <replaceable class="parameter">builtin_locale</replaceable> ]
17
+ [ ICU_LOCALE [=] <replaceable class="parameter">icu_locale</replaceable> ]
18
+ [ ICU_RULES [=] <replaceable class="parameter">icu_rules</replaceable> ]
19
+ [ LOCALE_PROVIDER [=] <replaceable class="parameter">locale_provider</replaceable> ]
20
+ [ COLLATION_VERSION = <replaceable>collation_version</replaceable> ]
21
+ [ TABLESPACE [=] <replaceable class="parameter">tablespace_name</replaceable> ]
22
+ [ ALLOW_CONNECTIONS [=] <replaceable class="parameter">allowconn</replaceable> ]
23
+ [ CONNECTION LIMIT [=] <replaceable class="parameter">connlimit</replaceable> ]
24
+ [ IS_TEMPLATE [=] <replaceable class="parameter">istemplate</replaceable> ]
25
+ [ OID [=] <replaceable class="parameter">oid</replaceable> ]
26
+ </synopsis>