@palbase/backend 3.0.0 → 4.0.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/dist/{chunk-B7EUJP5W.js → chunk-EG7TTYHY.js} +113 -3
- package/dist/chunk-EG7TTYHY.js.map +1 -0
- package/dist/{chunk-PHAFZGHN.js → chunk-WUQO76NW.js} +26 -19
- package/dist/chunk-WUQO76NW.js.map +1 -0
- package/dist/db/index.cjs +117 -2
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +11 -1
- package/dist/{endpoint-DJ98tQd6.d.cts → endpoint-2d_DpASt.d.cts} +92 -56
- package/dist/{endpoint-DJ98tQd6.d.ts → endpoint-2d_DpASt.d.ts} +92 -56
- package/dist/{index-CXUs9iTQ.d.ts → index-DZW9CjiY.d.ts} +210 -41
- package/dist/{index-CZAwpQE1.d.cts → index-DzRFS3Tl.d.cts} +210 -41
- package/dist/index.cjs +371 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -215
- package/dist/index.d.ts +88 -215
- package/dist/index.js +217 -21
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +34 -19
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +10 -2
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +11 -11
- package/docs/database.md +40 -0
- package/docs/endpoints.md +98 -92
- package/docs/errors.md +37 -30
- package/docs/getting-started.md +24 -20
- package/docs/llms-full.txt +401 -235
- package/docs/routing.md +39 -45
- package/docs/schema.md +134 -23
- package/docs/services.md +14 -10
- package/package.json +2 -2
- package/dist/chunk-B7EUJP5W.js.map +0 -1
- package/dist/chunk-PHAFZGHN.js.map +0 -1
|
@@ -1,10 +1,115 @@
|
|
|
1
|
+
// src/db/policy.ts
|
|
2
|
+
var PolicyBuilder = class {
|
|
3
|
+
_def;
|
|
4
|
+
constructor(name) {
|
|
5
|
+
this._def = {
|
|
6
|
+
name,
|
|
7
|
+
command: "all",
|
|
8
|
+
roles: ["authenticated"],
|
|
9
|
+
using: null,
|
|
10
|
+
withCheck: null,
|
|
11
|
+
permissive: true
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Restrict the policy to a single SQL command (default `"all"`). */
|
|
15
|
+
for(command) {
|
|
16
|
+
this._def.command = command;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Set the DB roles the policy applies to (the `TO` clause), replacing any
|
|
21
|
+
* previously-set roles. Call with no arguments to target PUBLIC (all roles).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* policy("p").to("authenticated")
|
|
25
|
+
* policy("p").to("authenticated", "service_role")
|
|
26
|
+
* policy("p").to() // PUBLIC
|
|
27
|
+
*/
|
|
28
|
+
to(...roles) {
|
|
29
|
+
this._def.roles = roles;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
/** Set the `USING (...)` row-visibility expression (raw SQL). */
|
|
33
|
+
using(sqlExpr) {
|
|
34
|
+
this._def.using = sqlExpr;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
/** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
|
|
38
|
+
withCheck(sqlExpr) {
|
|
39
|
+
this._def.withCheck = sqlExpr;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
/** Set the policy mode: `"permissive"` (default, OR-combined) or
|
|
43
|
+
* `"restrictive"` (AND-combined). */
|
|
44
|
+
as(mode) {
|
|
45
|
+
this._def.permissive = mode === "permissive";
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function policy(name) {
|
|
50
|
+
return new PolicyBuilder(name);
|
|
51
|
+
}
|
|
52
|
+
|
|
1
53
|
// src/db/schema.ts
|
|
54
|
+
function toPolicyDef(p) {
|
|
55
|
+
return p instanceof PolicyBuilder ? p._def : p;
|
|
56
|
+
}
|
|
2
57
|
function defineSchema(input) {
|
|
3
58
|
const tables = {};
|
|
4
59
|
for (const name of Object.keys(input.tables)) {
|
|
5
|
-
|
|
60
|
+
const table = input.tables[name];
|
|
61
|
+
if (table === void 0) continue;
|
|
62
|
+
const policies = (table.policies ?? []).map(toPolicyDef);
|
|
63
|
+
const rls = table.rls === true || policies.length > 0;
|
|
64
|
+
tables[name] = {
|
|
65
|
+
name,
|
|
66
|
+
columns: table.columns,
|
|
67
|
+
rls,
|
|
68
|
+
policies
|
|
69
|
+
};
|
|
6
70
|
}
|
|
7
|
-
|
|
71
|
+
const extensions = [...new Set(input.extensions ?? [])];
|
|
72
|
+
return { tables, extensions };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/db/extensions.ts
|
|
76
|
+
var PALBASE_EXTENSIONS = [
|
|
77
|
+
// Search & text
|
|
78
|
+
"vector",
|
|
79
|
+
// pgvector: AI embeddings + vector similarity search (semantic search / RAG).
|
|
80
|
+
// NB: the Postgres extension is named "vector", not "pgvector" — declare "vector".
|
|
81
|
+
"pg_trgm",
|
|
82
|
+
// trigram fuzzy / typo-tolerant text search
|
|
83
|
+
"unaccent",
|
|
84
|
+
// accent-insensitive text search
|
|
85
|
+
"citext",
|
|
86
|
+
// case-insensitive text type
|
|
87
|
+
// Geospatial / location
|
|
88
|
+
"postgis",
|
|
89
|
+
// geospatial types + queries (maps, "near me")
|
|
90
|
+
"cube",
|
|
91
|
+
// multi-dimensional cubes (dependency of earthdistance)
|
|
92
|
+
"earthdistance",
|
|
93
|
+
// great-circle distance (needs cube)
|
|
94
|
+
// Data types & structures
|
|
95
|
+
"hstore",
|
|
96
|
+
// key/value pairs in a single column
|
|
97
|
+
"ltree",
|
|
98
|
+
// hierarchical tree-structured labels
|
|
99
|
+
// Scheduling
|
|
100
|
+
"pg_cron",
|
|
101
|
+
// schedule jobs inside the database
|
|
102
|
+
// Crypto / ids (also installed by default; listable for explicitness)
|
|
103
|
+
"pgcrypto",
|
|
104
|
+
// cryptographic functions (hashing, encryption)
|
|
105
|
+
"uuid-ossp"
|
|
106
|
+
// UUID generation functions
|
|
107
|
+
];
|
|
108
|
+
var EXTENSION_DEPENDENCIES = {
|
|
109
|
+
earthdistance: ["cube"]
|
|
110
|
+
};
|
|
111
|
+
function isPalbaseExtension(name) {
|
|
112
|
+
return PALBASE_EXTENSIONS.includes(name);
|
|
8
113
|
}
|
|
9
114
|
|
|
10
115
|
// src/db/columns.ts
|
|
@@ -112,7 +217,12 @@ function makeTypedDB(schema, raw) {
|
|
|
112
217
|
}
|
|
113
218
|
|
|
114
219
|
export {
|
|
220
|
+
PolicyBuilder,
|
|
221
|
+
policy,
|
|
115
222
|
defineSchema,
|
|
223
|
+
PALBASE_EXTENSIONS,
|
|
224
|
+
EXTENSION_DEPENDENCIES,
|
|
225
|
+
isPalbaseExtension,
|
|
116
226
|
uuid,
|
|
117
227
|
text,
|
|
118
228
|
integer,
|
|
@@ -122,4 +232,4 @@ export {
|
|
|
122
232
|
enumType,
|
|
123
233
|
makeTypedDB
|
|
124
234
|
};
|
|
125
|
-
//# sourceMappingURL=chunk-
|
|
235
|
+
//# sourceMappingURL=chunk-EG7TTYHY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/db/policy.ts","../src/db/schema.ts","../src/db/extensions.ts","../src/db/columns.ts","../src/db/typed-db.ts"],"sourcesContent":["/**\n * policy.ts — the RLS policy authoring DSL.\n *\n * `policy(name)` returns a fluent builder that mirrors the `ColumnBuilder`\n * style in columns.ts: each chainable method mutates the underlying\n * definition and returns the builder so calls compose. The terminal value is\n * a plain {@link PolicyDef} — the exact JSON shape the runtime's\n * `schema_extract.js` reads off the bundled module and the Go side parses into\n * `PolicyJSON` (CONTRACT-POLICY).\n *\n * @example\n * import { policy } from \"@palbase/backend\";\n *\n * policy(\"owner_select\")\n * .for(\"select\")\n * .to(\"authenticated\")\n * .using(\"owner = (select auth.uid())\");\n */\n\n/** The SQL command a policy applies to. `\"all\"` covers SELECT/INSERT/UPDATE/DELETE. */\nexport type PolicyCommand = \"all\" | \"select\" | \"insert\" | \"update\" | \"delete\";\n\n/** Whether a policy is permissive (OR-combined, the default) or restrictive\n * (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */\nexport type PolicyMode = \"permissive\" | \"restrictive\";\n\n/**\n * The compiled, serializable policy definition — the EXACT shape consumed by\n * `schema_extract.js` → Go `PolicyJSON` (CONTRACT-POLICY).\n *\n * - `roles`: the DB roles this policy applies to (`TO` clause). An empty array\n * means the policy applies to PUBLIC (all roles) — the Postgres default.\n * - `using`: the `USING (...)` row-visibility expression, or `null` when none.\n * - `withCheck`: the `WITH CHECK (...)` write-validation expression, or `null`.\n * - `permissive`: `true` for `AS PERMISSIVE` (default), `false` for restrictive.\n */\nexport interface PolicyDef {\n name: string;\n command: PolicyCommand;\n roles: string[];\n using: string | null;\n withCheck: string | null;\n permissive: boolean;\n}\n\n/**\n * Fluent RLS policy builder.\n *\n * Defaults (documented, applied at construction):\n * - `command`: `\"all\"` — applies to every SQL command unless `.for(...)` narrows it.\n * - `roles`: `[\"authenticated\"]` — the common case is \"rule applies to signed-in\n * users\". Call `.to(...)` to override; pass `.to()` with no roles (or never\n * call it after a reset) to target PUBLIC.\n * - `using` / `withCheck`: `null` — no row filter / write check until set.\n * - `permissive`: `true` — `AS PERMISSIVE` (policies OR together).\n *\n * Each method mutates `_def` in place and returns `this`, so the chain is a\n * single builder instance (no per-call allocation, like a tagged-template\n * compile target). The terminal `PolicyDef` is read directly off `_def` by\n * `schema_extract.js`.\n */\nexport class PolicyBuilder {\n readonly _def: PolicyDef;\n\n constructor(name: string) {\n this._def = {\n name,\n command: \"all\",\n roles: [\"authenticated\"],\n using: null,\n withCheck: null,\n permissive: true,\n };\n }\n\n /** Restrict the policy to a single SQL command (default `\"all\"`). */\n for(command: PolicyCommand): this {\n this._def.command = command;\n return this;\n }\n\n /**\n * Set the DB roles the policy applies to (the `TO` clause), replacing any\n * previously-set roles. Call with no arguments to target PUBLIC (all roles).\n *\n * @example\n * policy(\"p\").to(\"authenticated\")\n * policy(\"p\").to(\"authenticated\", \"service_role\")\n * policy(\"p\").to() // PUBLIC\n */\n to(...roles: string[]): this {\n this._def.roles = roles;\n return this;\n }\n\n /** Set the `USING (...)` row-visibility expression (raw SQL). */\n using(sqlExpr: string): this {\n this._def.using = sqlExpr;\n return this;\n }\n\n /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */\n withCheck(sqlExpr: string): this {\n this._def.withCheck = sqlExpr;\n return this;\n }\n\n /** Set the policy mode: `\"permissive\"` (default, OR-combined) or\n * `\"restrictive\"` (AND-combined). */\n as(mode: PolicyMode): this {\n this._def.permissive = mode === \"permissive\";\n return this;\n }\n}\n\n/**\n * Start authoring an RLS policy. Returns a {@link PolicyBuilder}; the resulting\n * `PolicyBuilder` is accepted directly in a table's `policies: [...]` array\n * (its `_def` is read at schema-extract time).\n *\n * @param name The policy name. Palbase reconciliation keys policies by\n * `(table, name)`, so names must be unique per table.\n */\nexport function policy(name: string): PolicyBuilder {\n return new PolicyBuilder(name);\n}\n","import type { ColumnBuilder } from \"./columns.js\";\nimport { PolicyBuilder } from \"./policy.js\";\nimport type { PolicyDef } from \"./policy.js\";\nimport type { PalbaseExtension } from \"./extensions.js\";\n\n/**\n * A map of column builders keyed by column name — the value you write under\n * the `columns` key of `defineSchema({ tables: { <name>: { columns } } })`.\n *\n * The default `Record<string, ColumnBuilder>` keeps bare references compiling\n * without a type argument.\n */\nexport type ColumnMap = Record<string, ColumnBuilder>;\n\n/**\n * The author-facing value written under each table key:\n * `{ columns, rls?, policies? }`.\n *\n * - `columns`: the column map (required).\n * - `rls`: enable + FORCE row-level security on this table. Implied when\n * `policies` is non-empty; set it explicitly to enable RLS with no policies\n * yet (deny-all — useful only as an intermediate step).\n * - `policies`: the RLS policies for this table, authored with `policy(name)`.\n * Each entry may be a {@link PolicyBuilder} (the normal `policy(...)` chain)\n * or a raw {@link PolicyDef} object.\n *\n * The `C` type parameter preserves the precise per-column phantom types so the\n * typed `Database.tables.*` surface keeps inferring insert/row shapes.\n */\nexport interface TableInput<C extends ColumnMap = ColumnMap> {\n columns: C;\n rls?: boolean;\n policies?: (PolicyBuilder | PolicyDef)[];\n}\n\n/**\n * A table definition — the runtime value the Go runtime's `schema_extract.js`\n * reads. It keys tables by `tableDef.name`, reads `tableDef.columns` for the\n * column DDL, and `tableDef.rls` + `tableDef.policies` for RLS.\n *\n * `defineSchema` derives `name` from the object key, so authors never repeat\n * the table name. `rls`/`policies` are always present after normalization\n * (defaulted to `false`/`[]`).\n *\n * The `C` type parameter preserves the precise per-column phantom types so that\n * downstream mapped types (InsertShape, RowShape) can discriminate on them.\n */\nexport interface TableDef<C extends ColumnMap = ColumnMap> {\n name: string;\n columns: C;\n rls: boolean;\n policies: PolicyDef[];\n}\n\n/**\n * A schema definition containing multiple tables, keyed by table name.\n *\n * The `T` type parameter preserves the exact `TableDef<...>` type for each\n * table so that `SchemaDef[\"tables\"][\"rooms\"]` resolves to the precise\n * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.\n */\nexport interface SchemaDef<\n T extends Record<string, TableDef> = Record<string, TableDef>,\n> {\n tables: T;\n /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */\n extensions: PalbaseExtension[];\n}\n\n/** The author-facing input to `defineSchema` — a `tables` map whose keys are\n * the table names and whose values are `{ columns, rls?, policies? }`, plus an\n * optional `extensions` allowlist. */\nexport interface SchemaInput<\n T extends Record<string, TableInput> = Record<string, TableInput>,\n> {\n tables: T;\n /**\n * Postgres extensions to enable for this project, e.g. `[\"vector\"]`.\n * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA\n * extensions) with the privileged deploy connection. The type is an\n * allowlist union, so unsupported names fail typecheck.\n */\n extensions?: PalbaseExtension[];\n}\n\n/** Map the author's `{ tables: { <name>: { columns } } }` input to the\n * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the\n * per-table column map `T[K][\"columns\"]` so column-level inference survives. */\ntype TablesFromInput<T extends Record<string, TableInput>> = {\n [K in keyof T]: TableDef<T[K][\"columns\"]>;\n};\n\n/** Normalize a single `policies` entry into a plain `PolicyDef` (read off a\n * `PolicyBuilder._def`, or passed through when already a `PolicyDef`). */\nfunction toPolicyDef(p: PolicyBuilder | PolicyDef): PolicyDef {\n return p instanceof PolicyBuilder ? p._def : p;\n}\n\n/**\n * Define a schema. The table NAME comes from the object key. Each table value\n * is `{ columns, rls?, policies? }`:\n *\n * export default defineSchema({\n * tables: {\n * todos: {\n * columns: {\n * id: uuid().primaryKey().defaultRandom(),\n * owner: text().notNull(),\n * title: text().notNull(),\n * },\n * rls: true,\n * policies: [\n * policy(\"owner_all\").for(\"all\").to(\"authenticated\")\n * .using(\"owner = (select auth.uid())\")\n * .withCheck(\"owner = (select auth.uid())\"),\n * ],\n * },\n * },\n * });\n *\n * The returned value is\n * `{ tables: { todos: { name, columns, rls, policies } } }` — the exact shape\n * the runtime schema extractor parses. Per-column phantom types are preserved\n * so `Database.tables.todos.insert({...})` stays typed.\n *\n * RLS normalization: `rls` defaults to `false`, `policies` to `[]`. When\n * `policies` is non-empty, `rls` is forced on (ENABLE + FORCE) regardless of\n * the declared `rls` flag — a table with policies must have RLS enabled or the\n * policies would be inert.\n */\nexport function defineSchema<T extends Record<string, TableInput>>(\n input: SchemaInput<T>,\n): SchemaDef<TablesFromInput<T>> {\n const tables = {} as TablesFromInput<T>;\n for (const name of Object.keys(input.tables) as (keyof T)[]) {\n const table = input.tables[name];\n // `noUncheckedIndexedAccess` widens the index access to `… | undefined`,\n // but `name` comes straight from `Object.keys(input.tables)`, so the entry\n // always exists. Guard to narrow without a cast.\n if (table === undefined) continue;\n const policies = (table.policies ?? []).map(toPolicyDef);\n // A table with policies must have RLS enabled, otherwise the policies are\n // inert. Enabling RLS without policies (deny-all) is allowed explicitly.\n const rls = table.rls === true || policies.length > 0;\n tables[name] = {\n name: name as string,\n columns: table.columns,\n rls,\n policies,\n };\n }\n // Dedupe + normalize extensions (order-independent; deploy resolves deps).\n const extensions = [...new Set(input.extensions ?? [])];\n return { tables, extensions };\n}\n","/**\n * Postgres extensions a Palbase project can enable from its schema.\n *\n * Extensions are config-as-code: declare them in `defineSchema({ extensions })`\n * and the deploy installs them (CREATE EXTENSION … SCHEMA extensions) using the\n * deploy path's privileged connection. They are NOT toggled live from Studio —\n * CREATE EXTENSION requires a superuser role that only the deploy path holds.\n *\n * The list is an allowlist (a string-literal union) so editors autocomplete the\n * supported names and a typo fails typecheck. It is intentionally extensible:\n * add a name here (+ confirm the base image ships it) to support more.\n */\nexport const PALBASE_EXTENSIONS = [\n // Search & text\n \"vector\", // pgvector: AI embeddings + vector similarity search (semantic search / RAG).\n // NB: the Postgres extension is named \"vector\", not \"pgvector\" — declare \"vector\".\n \"pg_trgm\", // trigram fuzzy / typo-tolerant text search\n \"unaccent\", // accent-insensitive text search\n \"citext\", // case-insensitive text type\n // Geospatial / location\n \"postgis\", // geospatial types + queries (maps, \"near me\")\n \"cube\", // multi-dimensional cubes (dependency of earthdistance)\n \"earthdistance\", // great-circle distance (needs cube)\n // Data types & structures\n \"hstore\", // key/value pairs in a single column\n \"ltree\", // hierarchical tree-structured labels\n // Scheduling\n \"pg_cron\", // schedule jobs inside the database\n // Crypto / ids (also installed by default; listable for explicitness)\n \"pgcrypto\", // cryptographic functions (hashing, encryption)\n \"uuid-ossp\", // UUID generation functions\n] as const;\n\n/** A Postgres extension supported by Palbase (allowlist union). */\nexport type PalbaseExtension = (typeof PALBASE_EXTENSIONS)[number];\n\n/**\n * Extensions that depend on another extension. The deploy installs\n * dependencies first; declaring `earthdistance` without `cube` still works\n * because the deploy resolves the order, but listing both is clearer.\n */\nexport const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseExtension[]>> = {\n earthdistance: [\"cube\"],\n};\n\n/** Runtime guard: is `name` a supported Palbase extension? */\nexport function isPalbaseExtension(name: string): name is PalbaseExtension {\n return (PALBASE_EXTENSIONS as readonly string[]).includes(name);\n}\n","/** On delete action for foreign key references. */\nexport type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';\n\n/** Column type identifiers. */\nexport type ColumnType = 'uuid' | 'text' | 'integer' | 'boolean' | 'timestamp' | 'jsonb' | 'enum';\n\n/** Base column definition shared by all column types. */\nexport interface ColumnDef {\n type: ColumnType;\n nullable: boolean;\n primaryKey: boolean;\n defaultValue?: unknown;\n defaultRandom?: boolean;\n defaultNow?: boolean;\n references?: { table: string; column: string };\n onDeleteAction?: OnDeleteAction;\n enumName?: string;\n enumValues?: string[];\n}\n\n// Phantom brand symbols — never have runtime values; exist only to force\n// TypeScript's structural type system to distinguish ColumnBuilder instances\n// with different type-param combinations. Without these, TS sees all\n// ColumnBuilder<K,...> as structurally identical and the first branch of\n// ColValue matches everything.\ndeclare const __colKind: unique symbol;\ndeclare const __colNullable: unique symbol;\ndeclare const __colHasDefault: unique symbol;\ndeclare const __colEnumValues: unique symbol;\n\n/**\n * Fluent column builder with phantom type params:\n * K — ColumnType literal (e.g. \"text\", \"integer\")\n * N — boolean: true when nullable() has been called last (false = NOT NULL)\n * D — boolean: true when a default has been set\n * E — enum value union (never for non-enum columns)\n *\n * All four params have defaults so bare `ColumnBuilder` (no args) still\n * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.\n *\n * The four `declare readonly` brand fields carry the phantom types into the\n * structural shape so that conditional types like ColValue<C> can discriminate\n * on K without requiring runtime values on those fields.\n */\nexport class ColumnBuilder<\n K extends ColumnType = ColumnType,\n N extends boolean = boolean,\n D extends boolean = boolean,\n E = unknown,\n> {\n // These fields exist only in the type layer (declared, never initialised at\n // runtime — TypeScript allows declared class members without an initializer\n // in strict mode as long as they're never read at runtime).\n declare readonly [__colKind]: K;\n declare readonly [__colNullable]: N;\n declare readonly [__colHasDefault]: D;\n declare readonly [__colEnumValues]: E;\n\n readonly _def: ColumnDef;\n\n constructor(type: K, existingDef?: ColumnDef) {\n this._def = existingDef ?? {\n type,\n nullable: false,\n primaryKey: false,\n };\n }\n\n /** Mark this column as the primary key. */\n primaryKey(): ColumnBuilder<K, N, D, E> {\n this._def.primaryKey = true;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Mark this column as NOT NULL (default). */\n notNull(): ColumnBuilder<K, false, D, E> {\n this._def.nullable = false;\n return new ColumnBuilder<K, false, D, E>(this._def.type as K, this._def);\n }\n\n /** Allow NULL values. */\n nullable(): ColumnBuilder<K, true, D, E> {\n this._def.nullable = true;\n return new ColumnBuilder<K, true, D, E>(this._def.type as K, this._def);\n }\n\n /** Set a default value. */\n default(value: unknown): ColumnBuilder<K, N, true, E> {\n this._def.defaultValue = value;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** UUID: generate a random default (gen_random_uuid()). */\n defaultRandom(): ColumnBuilder<K, N, true, E> {\n this._def.defaultRandom = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Timestamp: default to now(). */\n defaultNow(): ColumnBuilder<K, N, true, E> {\n this._def.defaultNow = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Add a foreign key reference. */\n references(table: string, column: string): ColumnBuilder<K, N, D, E> {\n this._def.references = { table, column };\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Set the ON DELETE action for a foreign key reference. */\n onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E> {\n this._def.onDeleteAction = action;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type extractors — imported by Task 2 to derive insert/row shapes.\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts the TypeScript value type for a column, respecting nullability.\n * - \"uuid\" | \"text\" | \"timestamp\" → string (or string | null when N = true)\n * - \"integer\" → number\n * - \"boolean\" → boolean\n * - \"jsonb\" → unknown (opaque JSON)\n * - \"enum\" → E (the union of literal values)\n */\nexport type ColValue<C> =\n C extends ColumnBuilder<'uuid' | 'text' | 'timestamp', infer N, infer _D, infer _E>\n ? N extends true\n ? string | null\n : string\n : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E>\n ? N extends true\n ? number | null\n : number\n : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E>\n ? N extends true\n ? boolean | null\n : boolean\n : C extends ColumnBuilder<'jsonb', infer _N, infer _D, infer _E>\n ? unknown\n : C extends ColumnBuilder<'enum', infer N, infer _D, infer E>\n ? N extends true\n ? E | null\n : E\n : never;\n\n/**\n * True when a column is optional on INSERT:\n * - nullable columns (N = true) — the DB allows NULL so the field may be omitted\n * - columns with a default (D = true) — the DB fills in the value when absent\n */\nexport type ColIsOptionalOnInsert<C> =\n C extends ColumnBuilder<infer _K, true, infer _D, infer _E>\n ? true\n : C extends ColumnBuilder<infer _K, infer _N, true, infer _E>\n ? true\n : false;\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\n/** Create a UUID column. */\nexport function uuid(): ColumnBuilder<'uuid', false, false, never> {\n return new ColumnBuilder('uuid');\n}\n\n/** Create a TEXT column. */\nexport function text(): ColumnBuilder<'text', false, false, never> {\n return new ColumnBuilder('text');\n}\n\n/** Create an INTEGER column. */\nexport function integer(): ColumnBuilder<'integer', false, false, never> {\n return new ColumnBuilder('integer');\n}\n\n/** Create a BOOLEAN column. */\nexport function boolean(): ColumnBuilder<'boolean', false, false, never> {\n return new ColumnBuilder('boolean');\n}\n\n/** Create a TIMESTAMP column. */\nexport function timestamp(): ColumnBuilder<'timestamp', false, false, never> {\n return new ColumnBuilder('timestamp');\n}\n\n/** Create a JSONB column. */\nexport function jsonb(): ColumnBuilder<'jsonb', false, false, never> {\n return new ColumnBuilder('jsonb');\n}\n\n/**\n * Create an ENUM column.\n * @param name The PostgreSQL enum type name (used in DDL).\n * @param values A readonly tuple of valid string values — kept `const` so the\n * union `V[number]` is as narrow as possible.\n */\nexport function enumType<const V extends readonly string[]>(\n name: string,\n values: V,\n): ColumnBuilder<'enum', false, false, V[number]> {\n const builder = new ColumnBuilder<'enum', false, false, V[number]>('enum');\n builder._def.enumName = name;\n builder._def.enumValues = [...values];\n return builder;\n}\n","/**\n * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.\n *\n * Derives INSERT and full-row TypeScript types from a `defineSchema()` result\n * and wraps the untyped runtime `DBClient` with a typed facade.\n *\n * No value-any. No `as unknown as X`. The two narrow `as` casts in\n * `makeTypedTable` are safe because:\n * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to\n * typed values; all value types are subsets of `unknown`, so the cast is\n * structurally sound.\n * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,\n * unknown>` which is the erased form of the typed row; we're narrowing back\n * to the precise shape that the schema declared.\n * Both casts are narrowing only (not widening) and correctness is guaranteed\n * by the schema the caller provides.\n */\n\nimport type { ColValue, ColIsOptionalOnInsert, ColumnBuilder } from \"./columns.js\";\nimport type { TableDef, SchemaDef } from \"./schema.js\";\nimport type { Tables, TableTypes } from \"./env.js\";\nimport type { DBClient, TxClient } from \"../endpoint.js\";\n\n// ---------------------------------------------------------------------------\n// Key discriminators — split a column map into required vs optional keys.\n// ---------------------------------------------------------------------------\n\n/** Keys of C whose columns are required on INSERT (not nullable, no default). */\ntype RequiredKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;\n}[keyof C];\n\n/** Keys of C whose columns are optional on INSERT (nullable or has a default). */\ntype OptionalKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;\n}[keyof C];\n\n// ---------------------------------------------------------------------------\n// Public shape types — exported so callers can reference them directly.\n// ---------------------------------------------------------------------------\n\n/**\n * The TypeScript type for an INSERT payload for table `T`.\n * - Required: columns that are NOT NULL and have no DB-level default.\n * - Optional: columns that are nullable or carry a default.\n *\n * When all columns are optional, `RequiredKeys<C>` resolves to `never` and\n * the first part becomes `{}`, which is a neutral element for `&`.\n */\nexport type InsertShape<T extends TableDef> = {\n [K in RequiredKeys<T[\"columns\"]>]: ColValue<T[\"columns\"][K]>;\n} & {\n [K in OptionalKeys<T[\"columns\"]>]?: ColValue<T[\"columns\"][K]>;\n};\n\n/**\n * The TypeScript type for a full row returned by the DB for table `T`.\n * Every column is present; nullable columns resolve to `T | null`.\n */\nexport type RowShape<T extends TableDef> = {\n [K in keyof T[\"columns\"]]: ColValue<T[\"columns\"][K]>;\n};\n\n// ---------------------------------------------------------------------------\n// TypedTable + TypedDB interfaces.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor that mirrors the runtime DBClient surface. */\nexport interface TypedTable<T extends TableDef> {\n insert(data: InsertShape<T>): Promise<RowShape<T>>;\n update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T>>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<RowShape<T> | null>;\n findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;\n}\n\n/** A typed DB facade covering all tables declared in schema `S`. */\nexport interface TypedDB<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n transaction<T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T>;\n}\n\n/** Transaction-scoped typed facade: same typed tables, no nested transaction. */\nexport interface TypedTx<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Runtime factory.\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a typed table accessor that delegates every call to `raw` using the\n * runtime table name string. Two narrow `as` casts bridge the mapped-type\n * shapes to/from `Record<string, unknown>` — see module-level doc comment.\n *\n * The `raw` param is typed `TxClient` (the op surface shared by `DBClient` and\n * the transaction-scoped client) because this only ever calls the five\n * insert/update/delete/findById/findMany ops — never `transaction`. This lets\n * the same factory wrap both the top-level db and a tx without any cast.\n */\nfunction makeTypedTable<T extends TableDef<Record<string, ColumnBuilder>>>(\n name: string,\n raw: TxClient,\n): TypedTable<T> {\n return {\n insert: (data: InsertShape<T>) =>\n raw.insert(name, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n update: (id: string, data: Partial<InsertShape<T>>) =>\n raw.update(name, id, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n delete: (id: string) => raw.delete(name, id),\n\n findById: (id: string) =>\n raw.findById(name, id) as Promise<RowShape<T> | null>,\n\n findMany: (query?: Partial<RowShape<T>>) =>\n raw.findMany(name, query as Record<string, unknown> | undefined) as Promise<RowShape<T>[]>,\n };\n}\n\n/**\n * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from\n * the provided schema. No behavior change — all calls delegate to `raw` with\n * the table name as a plain string.\n *\n * `buildTables` is the reusable factory that wraps any op-bearing client\n * (`TxClient` — the surface shared by `DBClient` and the transaction-scoped\n * client) into the typed tables map. It is used both for the top-level db\n * (wrapping `raw`) and inside `transaction`, where it wraps the raw `TxClient`\n * the runtime yields so the callback sees the same typed `.tables` API.\n *\n * `transaction` delegates straight to `raw.transaction`; the two narrow\n * `as TypedTx<S>` / `as TypedDB<S>` casts are single structural narrowings\n * from the dynamically-built tables object to the precise mapped type (TS\n * cannot infer through `Object.keys` iteration) — see module-level doc comment.\n */\nexport function makeTypedDB<S extends SchemaDef>(\n schema: S,\n raw: DBClient,\n): TypedDB<S> {\n function buildTables(client: TxClient): Record<string, TypedTable<TableDef>> {\n const tables = {} as Record<string, TypedTable<TableDef>>;\n for (const key of Object.keys(schema.tables)) {\n const tableDef = schema.tables[key];\n if (tableDef !== undefined) {\n tables[key] = makeTypedTable(tableDef.name, client);\n }\n }\n return tables;\n }\n\n const result = {\n tables: buildTables(raw),\n transaction: <T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T> =>\n raw.transaction((rawTx) => fn({ tables: buildTables(rawTx) } as TypedTx<S>)),\n };\n\n // Narrow cast: `result.tables` is structurally identical to\n // TypedDB<S>[\"tables\"] — each key maps to a TypedTable for the matching\n // TableDef. TS cannot infer the mapped-type result through Object.keys\n // iteration, so a single `as` bridges the gap.\n return result as TypedDB<S>;\n}\n\n// ---------------------------------------------------------------------------\n// Env-augmentation-driven typed surface — the typed-by-default `Database`.\n//\n// These types read the globally-augmented `Tables` interface from\n// `@palbase/backend/env` (filled by the generated `palbase-env.d.ts`). They\n// back `Database.tables.<name>` so handler code is typed with no import and no\n// generic (C5). They DELIBERATELY do not reference `ColumnBuilder` — the env\n// `Tables` interface carries flat `row`/`insert` object types.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor derived from one env `Tables` entry's flat shapes. */\nexport interface EnvTypedTable<T extends TableTypes> {\n insert(data: T[\"insert\"]): Promise<T[\"row\"]>;\n update(id: string, data: Partial<T[\"insert\"]>): Promise<T[\"row\"]>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<T[\"row\"] | null>;\n findMany(query?: Partial<T[\"row\"]>): Promise<T[\"row\"][]>;\n}\n\n/** The `tables` map exposed on `Database`/`tx`, keyed by the env `Tables`\n * interface. When no schema is declared `Tables` is empty, so `tables` is an\n * empty object — accessing `.tables.foo` is then a compile error (no member). */\nexport type EnvTables = {\n [K in keyof Tables]: EnvTypedTable<Tables[K]>;\n};\n\n/** Transaction-scoped typed facade for the env-augmented surface: same typed\n * tables, no nested transaction. */\nexport interface EnvTypedTx {\n tables: EnvTables;\n}\n\n/**\n * The RLS-bypass sibling returned by `Database.asService()`. Same typed surface\n * as {@link EnvTypedDatabase} — `tables`, the raw string ops, and a typed\n * `transaction` — but it does NOT re-expose `asService` (no double-bypass).\n * Every op it performs runs as the `service_role` (BYPASSRLS).\n */\nexport interface EnvServiceDatabase extends Omit<DBClient, \"transaction\" | \"asService\"> {\n tables: EnvTables;\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;\n}\n\n/**\n * The typed-by-default Database surface: the raw string-keyed `DBClient` ops\n * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`,\n * a `transaction` whose callback receives the typed tables, and `asService()`\n * for the explicit RLS-bypass sibling.\n *\n * `transaction` is declared here (overriding `DBClient[\"transaction\"]`) so the\n * `tx` the callback receives carries the typed `.tables` API. `asService` is\n * re-typed to return the typed {@link EnvServiceDatabase} sibling.\n */\nexport interface EnvTypedDatabase extends Omit<DBClient, \"transaction\" | \"asService\"> {\n tables: EnvTables;\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;\n /**\n * Return a sibling that bypasses RLS by running as the `service_role`. Use\n * sparingly and explicitly — the default `Database.*` path is RLS-enforced.\n *\n * @example\n * const all = await Database.asService().tables.todos.findMany({});\n * const rows = await Database.asService().query(\"SELECT * FROM todos\");\n */\n asService(): EnvServiceDatabase;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -48,27 +48,34 @@ function makeTablesAccessor(ops) {
|
|
|
48
48
|
return tablesProxy;
|
|
49
49
|
}
|
|
50
50
|
var rawDatabase = makeServiceProxy("Database");
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
tables: makeTablesAccessor(() => rawDatabase),
|
|
51
|
+
function makeTypedSurface(raw) {
|
|
52
|
+
const ops = {
|
|
53
|
+
query: (sql, params) => raw.query(sql, params),
|
|
54
|
+
insert: (table, data) => raw.insert(table, data),
|
|
55
|
+
update: (table, id, data) => raw.update(table, id, data),
|
|
56
|
+
delete: (table, id) => raw.delete(table, id),
|
|
57
|
+
findById: (table, id) => raw.findById(table, id),
|
|
58
|
+
findMany: (table, query) => raw.findMany(table, query)
|
|
59
|
+
};
|
|
60
|
+
return Object.assign(ops, {
|
|
61
|
+
tables: makeTablesAccessor(() => raw),
|
|
65
62
|
transaction(fn) {
|
|
66
|
-
return
|
|
67
|
-
(rawTx) => fn({ tables: makeTablesAccessor(() => rawTx) })
|
|
68
|
-
);
|
|
63
|
+
return raw.transaction((rawTx) => fn({ tables: makeTablesAccessor(() => rawTx) }));
|
|
69
64
|
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
var Database = Object.assign(makeTypedSurface(rawDatabase), {
|
|
68
|
+
/**
|
|
69
|
+
* Lazily resolve the runtime's service-role sibling on each call. We do NOT
|
|
70
|
+
* cache it: `rawDatabase.asService()` reads the CURRENT request scope through
|
|
71
|
+
* the runtime proxy, and the per-request runtime injects a service client
|
|
72
|
+
* bound to that request's identity headers — caching would leak one request's
|
|
73
|
+
* sibling into another concurrent request.
|
|
74
|
+
*/
|
|
75
|
+
asService() {
|
|
76
|
+
return makeTypedSurface(rawDatabase.asService());
|
|
70
77
|
}
|
|
71
|
-
);
|
|
78
|
+
});
|
|
72
79
|
var Documents = makeServiceProxy("Documents");
|
|
73
80
|
var Storage = makeServiceProxy("Storage");
|
|
74
81
|
var Cache = makeServiceProxy("Cache");
|
|
@@ -91,4 +98,4 @@ export {
|
|
|
91
98
|
Notifications,
|
|
92
99
|
Flags
|
|
93
100
|
};
|
|
94
|
-
//# sourceMappingURL=chunk-
|
|
101
|
+
//# sourceMappingURL=chunk-WUQO76NW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * runtime.ts — request-scoped service singletons.\n *\n * The backend SDK no longer threads a `ctx` god-object through every handler.\n * Instead, controller methods import PascalCase service singletons directly:\n *\n * import { Controller, Post, Body, Database } from \"@palbase/backend\";\n *\n * \\@Controller(\"/todos\")\n * export default class TodosController {\n * \\@Post(\"\") create(\\@Body(CreateTodoBody) body: CreateTodoBody): unknown {\n * return Database.insert(\"todos\", { title: body.title });\n * }\n * }\n *\n * The singletons are thin Proxies. Every property access forwards to the live\n * client for the CURRENT request scope, resolved through {@link __getRuntime}.\n *\n * # Request-scope resolution (persistent app-server)\n *\n * The runtime is a long-running Node process that serves many concurrent\n * requests on one event loop (NOT a fresh subprocess per request). A single\n * module-global slot would let one in-flight request's services bleed into\n * another's. So the services are carried in an {@link AsyncLocalStorage} store\n * ({@link __requestALS}) that the runtime sets per request with\n * {@link __runWithRuntime}; every async continuation of that request reads its\n * own store. `__getRuntime` reads the ALS store first; the module-global slot\n * (set by {@link __setRuntime}) is only a fallback for callers that run OUTSIDE\n * an ALS scope (dev-server, unit tests, the legacy single-shot path). Because\n * each `br-<ref>` pod is single-tenant, there is no cross-tenant leakage; the\n * ALS store is what prevents cross-REQUEST leakage within the shared process.\n *\n * The seam that makes `import { Database } from \"@palbase/backend\"` resolve to\n * the runtime-injected client: `@palbase/backend` is marked esbuild-EXTERNAL\n * when the tenant bundle is built, and the package is installed globally in the\n * pod (NODE_PATH=/usr/local/lib/node_modules). So worker.js's\n * `require('@palbase/backend')` and the bundle's `import` resolve to ONE shared\n * module instance — the ALS store and `__setRuntime` slot on that instance are\n * visible to the singletons the bundle imported.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type {\n DBClient,\n DBOps,\n TxClient,\n CacheClient,\n QueueClient,\n Logger,\n PalbaseDocsClient,\n} from \"./endpoint.js\";\nimport type {\n PalbaseStorageClient,\n PalbaseNotificationsClient,\n PalbaseFlagsClient,\n} from \"./clients.js\";\nimport type {\n EnvTypedDatabase,\n EnvServiceDatabase,\n EnvTypedTx,\n EnvTables,\n} from \"./db/typed-db.js\";\n\n/** The set of live clients the runtime injects per request scope.\n *\n * EXCLUDED on purpose: Realtime, Functions, CMS, Links, Analytics, Auth. They\n * are not exposed as backend handler singletons (auth lives on the client SDK;\n * the rest are out of scope for backend endpoints). */\nexport interface RuntimeServices {\n Database: DBClient;\n Documents: PalbaseDocsClient;\n Storage: PalbaseStorageClient;\n Cache: CacheClient;\n Queue: QueueClient;\n Log: Logger;\n Notifications: PalbaseNotificationsClient;\n Flags: PalbaseFlagsClient;\n}\n\n/**\n * Per-request store. The persistent runtime runs each request inside\n * {@link __runWithRuntime}, so every async continuation of that request reads\n * its OWN `runtime` (and any other request-scoped fields the runtime adds).\n *\n * Exported with a `__` prefix so the runtime (worker.js) shares the SAME ALS\n * instance across the one module instance — two ALS instances would silently\n * not see each other's stores. NOT part of the public author-facing API.\n */\nexport const __requestALS = new AsyncLocalStorage<{ runtime: RuntimeServices }>();\n\n/** Process-global fallback slot. Used only OUTSIDE an ALS scope (dev-server,\n * unit tests, legacy single-shot worker). Inside the persistent server every\n * request runs in {@link __requestALS}, which takes precedence. */\nlet runtime: RuntimeServices | null = null;\n\n/** Install the live clients in the process-global fallback slot.\n *\n * Persistent-server requests should use {@link __runWithRuntime} instead; this\n * remains for dev-server / tests / the legacy single-shot path that run without\n * an ALS scope. NOT part of the public author-facing API. */\nexport function __setRuntime(services: RuntimeServices): void {\n runtime = services;\n}\n\n/** Run `fn` with `services` bound as the request-scoped runtime.\n *\n * The persistent worker calls this once per request so concurrent requests\n * never share a services slot. NOT part of the public author-facing API. */\nexport function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T {\n return __requestALS.run({ runtime: services }, fn);\n}\n\n/** Read the live clients, throwing if accessed outside a request scope.\n *\n * Resolves the ALS store first (persistent server, per-request), then the\n * process-global fallback (dev-server / tests). NOT part of the public\n * author-facing API — used by the runtime and the singleton Proxies. */\nexport function __getRuntime(): RuntimeServices {\n const scoped = __requestALS.getStore();\n if (scoped) return scoped.runtime;\n if (runtime === null) {\n throw new Error(\n \"Palbase services accessed outside a request scope. The Database/Documents/… \" +\n \"singletons are only available inside an endpoint handler (or after the \" +\n \"runtime has called __runWithRuntime / __setRuntime).\",\n );\n }\n return runtime;\n}\n\n/**\n * Build a Proxy singleton that forwards every property access to the live\n * client named `key` on the current runtime.\n *\n * The single `as RuntimeServices[K]` is the only contained cast in the surface:\n * `Reflect.get` on a typed object returns `unknown` for a `string | symbol`\n * key, but `prop` is constrained to keys of the client interface at the call\n * sites (the exported singletons are typed below), so the forward is sound.\n */\nfunction makeServiceProxy<K extends keyof RuntimeServices>(key: K): RuntimeServices[K] {\n const handler: ProxyHandler<RuntimeServices[K]> = {\n get(_target, prop, receiver) {\n const client = __getRuntime()[key];\n const value = Reflect.get(client as object, prop, receiver) as unknown;\n // Bind methods to their owning client so `this` stays correct when the\n // author destructures or calls `Database.query(...)`.\n return typeof value === \"function\" ? value.bind(client) : value;\n },\n };\n // The Proxy target is irrelevant (all access goes through `get`); the cast\n // names the surface type the singleton presents to authors.\n return new Proxy({} as RuntimeServices[K], handler);\n}\n\n/**\n * Build the `.tables` accessor for an op-bearing client (the top-level\n * `Database` or a transaction-scoped `tx`). Each `tables.<name>` access\n * returns a small object that forwards the five CRUD ops to the underlying\n * client using `name` as the string table identifier. The shapes are typed\n * against the generated `palbase-env.d.ts` (`EnvTables`); at runtime they are\n * plain string-keyed calls, so no schema value is needed here.\n *\n * Returns `EnvTables` — TS cannot infer the mapped type through the Proxy, so\n * a single structural narrowing names the surface (the proxy returns a\n * correctly-shaped accessor for whatever string member is read).\n */\nfunction makeTablesAccessor(ops: () => TxClient): EnvTables {\n const tablesProxy = new Proxy(\n {},\n {\n get(_t, prop: string | symbol) {\n if (typeof prop !== \"string\") return undefined;\n const name = prop;\n return {\n insert: (data: Record<string, unknown>) => ops().insert(name, data),\n update: (id: string, data: Record<string, unknown>) => ops().update(name, id, data),\n delete: (id: string) => ops().delete(name, id),\n findById: (id: string) => ops().findById(name, id),\n findMany: (query?: Record<string, unknown>) => ops().findMany(name, query),\n };\n },\n },\n );\n return tablesProxy as EnvTables;\n}\n\n/** The raw string-keyed `DBClient` for the current request scope. */\nconst rawDatabase: DBClient = makeServiceProxy(\"Database\");\n\n/**\n * Wrap a raw `DBClient` into the typed `{ ...ops, tables, transaction }`\n * surface. The five string ops forward straight through; `tables` is the\n * env-typed accessor; `transaction` yields typed tables. Reused for both the\n * default (RLS-enforced) `Database` and the `asService()` sibling — each is\n * fed its own raw client (the default proxy vs `rawDatabase.asService()`).\n *\n * The `satisfies` pins the op surface so a missing/renamed op is a compile\n * error; the assembled object carries `tables`/`transaction` alongside.\n */\nfunction makeTypedSurface(raw: Omit<DBClient, \"asService\">): EnvServiceDatabase {\n const ops = {\n query: (sql: string, params?: unknown[]) => raw.query(sql, params),\n insert: (table: string, data: Record<string, unknown>) => raw.insert(table, data),\n update: (table: string, id: string, data: Record<string, unknown>) =>\n raw.update(table, id, data),\n delete: (table: string, id: string) => raw.delete(table, id),\n findById: (table: string, id: string) => raw.findById(table, id),\n findMany: (table: string, query?: Record<string, unknown>) => raw.findMany(table, query),\n } satisfies DBOps;\n return Object.assign(ops, {\n tables: makeTablesAccessor(() => raw),\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T> {\n return raw.transaction((rawTx) => fn({ tables: makeTablesAccessor(() => rawTx) }));\n },\n });\n}\n\n/**\n * The project's own Postgres (pgx, schema `env_<envId>`).\n *\n * Typed by default: `Database.tables.<name>.insert({...})` is typed against\n * the project's generated `palbase-env.d.ts` with NO import and NO generic.\n * The raw string ops (`query`/`insert`/`update`/`delete`/`findById`/`findMany`)\n * are also available for dynamic table names and read-only SQL.\n *\n * RLS is enforced by default (the runtime runs each op as `authenticated` with\n * the verified user's claims). To bypass RLS, call `Database.asService()` —\n * explicit and greppable — which runs as the `service_role` (BYPASSRLS).\n *\n * @example\n * import { Database } from \"@palbase/backend\";\n *\n * const todo = await Database.tables.todos.insert({ title: req.input.title });\n * todo.id; // string ✓\n * const rows = await Database.query(\"SELECT id FROM todos WHERE done = $1\", [false]);\n * const all = await Database.asService().tables.todos.findMany({}); // RLS bypass\n */\nexport const Database: EnvTypedDatabase = Object.assign(makeTypedSurface(rawDatabase), {\n /**\n * Lazily resolve the runtime's service-role sibling on each call. We do NOT\n * cache it: `rawDatabase.asService()` reads the CURRENT request scope through\n * the runtime proxy, and the per-request runtime injects a service client\n * bound to that request's identity headers — caching would leak one request's\n * sibling into another concurrent request.\n */\n asService(): EnvServiceDatabase {\n return makeTypedSurface(rawDatabase.asService());\n },\n});\n\n/** Firestore-like document client (PalDocs). */\nexport const Documents: PalbaseDocsClient = makeServiceProxy(\"Documents\");\n\n/** Object storage client (buckets, signed URLs). */\nexport const Storage: PalbaseStorageClient = makeServiceProxy(\"Storage\");\n\n/** JSON-typed cache (get/set/incr/getOrSet). */\nexport const Cache: CacheClient = makeServiceProxy(\"Cache\");\n\n/** Background job queue. */\nexport const Queue: QueueClient = makeServiceProxy(\"Queue\");\n\n/** Structured logger. */\nexport const Log: Logger = makeServiceProxy(\"Log\");\n\n/** Push / email / SMS / in-app notifications. */\nexport const Notifications: PalbaseNotificationsClient = makeServiceProxy(\"Notifications\");\n\n/** Feature flags. */\nexport const Flags: PalbaseFlagsClient = makeServiceProxy(\"Flags\");\n"],"mappings":"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","names":[]}
|
package/dist/db/index.cjs
CHANGED
|
@@ -20,25 +20,135 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/db/index.ts
|
|
21
21
|
var db_exports = {};
|
|
22
22
|
__export(db_exports, {
|
|
23
|
+
EXTENSION_DEPENDENCIES: () => EXTENSION_DEPENDENCIES,
|
|
24
|
+
PALBASE_EXTENSIONS: () => PALBASE_EXTENSIONS,
|
|
25
|
+
PolicyBuilder: () => PolicyBuilder,
|
|
23
26
|
boolean: () => boolean,
|
|
24
27
|
defineSchema: () => defineSchema,
|
|
25
28
|
enumType: () => enumType,
|
|
26
29
|
integer: () => integer,
|
|
30
|
+
isPalbaseExtension: () => isPalbaseExtension,
|
|
27
31
|
jsonb: () => jsonb,
|
|
28
32
|
makeTypedDB: () => makeTypedDB,
|
|
33
|
+
policy: () => policy,
|
|
29
34
|
text: () => text,
|
|
30
35
|
timestamp: () => timestamp,
|
|
31
36
|
uuid: () => uuid
|
|
32
37
|
});
|
|
33
38
|
module.exports = __toCommonJS(db_exports);
|
|
34
39
|
|
|
40
|
+
// src/db/policy.ts
|
|
41
|
+
var PolicyBuilder = class {
|
|
42
|
+
_def;
|
|
43
|
+
constructor(name) {
|
|
44
|
+
this._def = {
|
|
45
|
+
name,
|
|
46
|
+
command: "all",
|
|
47
|
+
roles: ["authenticated"],
|
|
48
|
+
using: null,
|
|
49
|
+
withCheck: null,
|
|
50
|
+
permissive: true
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Restrict the policy to a single SQL command (default `"all"`). */
|
|
54
|
+
for(command) {
|
|
55
|
+
this._def.command = command;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Set the DB roles the policy applies to (the `TO` clause), replacing any
|
|
60
|
+
* previously-set roles. Call with no arguments to target PUBLIC (all roles).
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* policy("p").to("authenticated")
|
|
64
|
+
* policy("p").to("authenticated", "service_role")
|
|
65
|
+
* policy("p").to() // PUBLIC
|
|
66
|
+
*/
|
|
67
|
+
to(...roles) {
|
|
68
|
+
this._def.roles = roles;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
/** Set the `USING (...)` row-visibility expression (raw SQL). */
|
|
72
|
+
using(sqlExpr) {
|
|
73
|
+
this._def.using = sqlExpr;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
/** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
|
|
77
|
+
withCheck(sqlExpr) {
|
|
78
|
+
this._def.withCheck = sqlExpr;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
/** Set the policy mode: `"permissive"` (default, OR-combined) or
|
|
82
|
+
* `"restrictive"` (AND-combined). */
|
|
83
|
+
as(mode) {
|
|
84
|
+
this._def.permissive = mode === "permissive";
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
function policy(name) {
|
|
89
|
+
return new PolicyBuilder(name);
|
|
90
|
+
}
|
|
91
|
+
|
|
35
92
|
// src/db/schema.ts
|
|
93
|
+
function toPolicyDef(p) {
|
|
94
|
+
return p instanceof PolicyBuilder ? p._def : p;
|
|
95
|
+
}
|
|
36
96
|
function defineSchema(input) {
|
|
37
97
|
const tables = {};
|
|
38
98
|
for (const name of Object.keys(input.tables)) {
|
|
39
|
-
|
|
99
|
+
const table = input.tables[name];
|
|
100
|
+
if (table === void 0) continue;
|
|
101
|
+
const policies = (table.policies ?? []).map(toPolicyDef);
|
|
102
|
+
const rls = table.rls === true || policies.length > 0;
|
|
103
|
+
tables[name] = {
|
|
104
|
+
name,
|
|
105
|
+
columns: table.columns,
|
|
106
|
+
rls,
|
|
107
|
+
policies
|
|
108
|
+
};
|
|
40
109
|
}
|
|
41
|
-
|
|
110
|
+
const extensions = [...new Set(input.extensions ?? [])];
|
|
111
|
+
return { tables, extensions };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/db/extensions.ts
|
|
115
|
+
var PALBASE_EXTENSIONS = [
|
|
116
|
+
// Search & text
|
|
117
|
+
"vector",
|
|
118
|
+
// pgvector: AI embeddings + vector similarity search (semantic search / RAG).
|
|
119
|
+
// NB: the Postgres extension is named "vector", not "pgvector" — declare "vector".
|
|
120
|
+
"pg_trgm",
|
|
121
|
+
// trigram fuzzy / typo-tolerant text search
|
|
122
|
+
"unaccent",
|
|
123
|
+
// accent-insensitive text search
|
|
124
|
+
"citext",
|
|
125
|
+
// case-insensitive text type
|
|
126
|
+
// Geospatial / location
|
|
127
|
+
"postgis",
|
|
128
|
+
// geospatial types + queries (maps, "near me")
|
|
129
|
+
"cube",
|
|
130
|
+
// multi-dimensional cubes (dependency of earthdistance)
|
|
131
|
+
"earthdistance",
|
|
132
|
+
// great-circle distance (needs cube)
|
|
133
|
+
// Data types & structures
|
|
134
|
+
"hstore",
|
|
135
|
+
// key/value pairs in a single column
|
|
136
|
+
"ltree",
|
|
137
|
+
// hierarchical tree-structured labels
|
|
138
|
+
// Scheduling
|
|
139
|
+
"pg_cron",
|
|
140
|
+
// schedule jobs inside the database
|
|
141
|
+
// Crypto / ids (also installed by default; listable for explicitness)
|
|
142
|
+
"pgcrypto",
|
|
143
|
+
// cryptographic functions (hashing, encryption)
|
|
144
|
+
"uuid-ossp"
|
|
145
|
+
// UUID generation functions
|
|
146
|
+
];
|
|
147
|
+
var EXTENSION_DEPENDENCIES = {
|
|
148
|
+
earthdistance: ["cube"]
|
|
149
|
+
};
|
|
150
|
+
function isPalbaseExtension(name) {
|
|
151
|
+
return PALBASE_EXTENSIONS.includes(name);
|
|
42
152
|
}
|
|
43
153
|
|
|
44
154
|
// src/db/columns.ts
|
|
@@ -146,12 +256,17 @@ function makeTypedDB(schema, raw) {
|
|
|
146
256
|
}
|
|
147
257
|
// Annotate the CommonJS export names for ESM import in node:
|
|
148
258
|
0 && (module.exports = {
|
|
259
|
+
EXTENSION_DEPENDENCIES,
|
|
260
|
+
PALBASE_EXTENSIONS,
|
|
261
|
+
PolicyBuilder,
|
|
149
262
|
boolean,
|
|
150
263
|
defineSchema,
|
|
151
264
|
enumType,
|
|
152
265
|
integer,
|
|
266
|
+
isPalbaseExtension,
|
|
153
267
|
jsonb,
|
|
154
268
|
makeTypedDB,
|
|
269
|
+
policy,
|
|
155
270
|
text,
|
|
156
271
|
timestamp,
|
|
157
272
|
uuid
|
package/dist/db/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/db/index.ts","../../src/db/schema.ts","../../src/db/columns.ts","../../src/db/typed-db.ts"],"sourcesContent":["export { defineSchema } from \"./schema.js\";\nexport type { TableDef, SchemaDef, ColumnMap, SchemaInput } from \"./schema.js\";\nexport {\n uuid,\n text,\n integer,\n boolean,\n timestamp,\n jsonb,\n enumType,\n} from \"./columns.js\";\nexport type { ColumnDef, ColumnType, OnDeleteAction, ColumnBuilder } from \"./columns.js\";\nexport { makeTypedDB } from \"./typed-db.js\";\nexport type { TypedDB, TypedTx, TypedTable, InsertShape, RowShape } from \"./typed-db.js\";\n","import type { ColumnBuilder } from \"./columns.js\";\n\n/**\n * A map of column builders keyed by column name — the value you write under\n * each key of `defineSchema({ tables: { <name>: <columns> } })`.\n *\n * The default `Record<string, ColumnBuilder>` keeps bare references compiling\n * without a type argument.\n */\nexport type ColumnMap = Record<string, ColumnBuilder>;\n\n/**\n * A table definition with its name and columns.\n *\n * The runtime value shape — `{ name, columns }` — is the contract the Go\n * runtime's `schema_extract.js` reads (it keys tables by `tableDef.name`).\n * `defineSchema` derives `name` from the object key, so authors never repeat\n * the table name.\n *\n * The `C` type parameter preserves the precise per-column phantom types so\n * that downstream mapped types (InsertShape, RowShape) can discriminate on\n * them.\n */\nexport interface TableDef<C extends ColumnMap = ColumnMap> {\n name: string;\n columns: C;\n}\n\n/**\n * A schema definition containing multiple tables, keyed by table name.\n *\n * The `T` type parameter preserves the exact `TableDef<...>` type for each\n * table so that `SchemaDef[\"tables\"][\"rooms\"]` resolves to the precise\n * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.\n */\nexport interface SchemaDef<\n T extends Record<string, TableDef> = Record<string, TableDef>,\n> {\n tables: T;\n}\n\n/** The author-facing input to `defineSchema` — a `tables` map whose keys are\n * the table names and whose values are the column maps. */\nexport interface SchemaInput<T extends Record<string, ColumnMap> = Record<string, ColumnMap>> {\n tables: T;\n}\n\n/** Map the author's `{ tables: { <name>: <columns> } }` input to the\n * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape. */\ntype TablesFromInput<T extends Record<string, ColumnMap>> = {\n [K in keyof T]: TableDef<T[K]>;\n};\n\n/**\n * Define a schema. The table NAME comes from the object key — there is one\n * canonical form:\n *\n * export default defineSchema({\n * tables: {\n * todos: {\n * id: uuid().primaryKey().defaultRandom(),\n * title: text().notNull(),\n * },\n * },\n * });\n *\n * The returned value is `{ tables: { todos: { name: \"todos\", columns: {...} } } }`\n * — the exact shape the runtime schema extractor parses. Per-column phantom\n * types are preserved so `Database.tables.todos.insert({...})` is typed.\n *\n * @example\n * import { defineSchema, uuid, text, timestamp } from \"@palbase/backend\";\n *\n * export default defineSchema({\n * tables: {\n * todos: {\n * id: uuid().primaryKey().defaultRandom(),\n * title: text().notNull(),\n * done: boolean().default(false),\n * created_at: timestamp().defaultNow(),\n * },\n * },\n * });\n */\nexport function defineSchema<T extends Record<string, ColumnMap>>(\n input: SchemaInput<T>,\n): SchemaDef<TablesFromInput<T>> {\n const tables = {} as TablesFromInput<T>;\n for (const name of Object.keys(input.tables) as (keyof T)[]) {\n tables[name] = { name: name as string, columns: input.tables[name] };\n }\n return { tables };\n}\n","/** On delete action for foreign key references. */\nexport type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';\n\n/** Column type identifiers. */\nexport type ColumnType = 'uuid' | 'text' | 'integer' | 'boolean' | 'timestamp' | 'jsonb' | 'enum';\n\n/** Base column definition shared by all column types. */\nexport interface ColumnDef {\n type: ColumnType;\n nullable: boolean;\n primaryKey: boolean;\n defaultValue?: unknown;\n defaultRandom?: boolean;\n defaultNow?: boolean;\n references?: { table: string; column: string };\n onDeleteAction?: OnDeleteAction;\n enumName?: string;\n enumValues?: string[];\n}\n\n// Phantom brand symbols — never have runtime values; exist only to force\n// TypeScript's structural type system to distinguish ColumnBuilder instances\n// with different type-param combinations. Without these, TS sees all\n// ColumnBuilder<K,...> as structurally identical and the first branch of\n// ColValue matches everything.\ndeclare const __colKind: unique symbol;\ndeclare const __colNullable: unique symbol;\ndeclare const __colHasDefault: unique symbol;\ndeclare const __colEnumValues: unique symbol;\n\n/**\n * Fluent column builder with phantom type params:\n * K — ColumnType literal (e.g. \"text\", \"integer\")\n * N — boolean: true when nullable() has been called last (false = NOT NULL)\n * D — boolean: true when a default has been set\n * E — enum value union (never for non-enum columns)\n *\n * All four params have defaults so bare `ColumnBuilder` (no args) still\n * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.\n *\n * The four `declare readonly` brand fields carry the phantom types into the\n * structural shape so that conditional types like ColValue<C> can discriminate\n * on K without requiring runtime values on those fields.\n */\nexport class ColumnBuilder<\n K extends ColumnType = ColumnType,\n N extends boolean = boolean,\n D extends boolean = boolean,\n E = unknown,\n> {\n // These fields exist only in the type layer (declared, never initialised at\n // runtime — TypeScript allows declared class members without an initializer\n // in strict mode as long as they're never read at runtime).\n declare readonly [__colKind]: K;\n declare readonly [__colNullable]: N;\n declare readonly [__colHasDefault]: D;\n declare readonly [__colEnumValues]: E;\n\n readonly _def: ColumnDef;\n\n constructor(type: K, existingDef?: ColumnDef) {\n this._def = existingDef ?? {\n type,\n nullable: false,\n primaryKey: false,\n };\n }\n\n /** Mark this column as the primary key. */\n primaryKey(): ColumnBuilder<K, N, D, E> {\n this._def.primaryKey = true;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Mark this column as NOT NULL (default). */\n notNull(): ColumnBuilder<K, false, D, E> {\n this._def.nullable = false;\n return new ColumnBuilder<K, false, D, E>(this._def.type as K, this._def);\n }\n\n /** Allow NULL values. */\n nullable(): ColumnBuilder<K, true, D, E> {\n this._def.nullable = true;\n return new ColumnBuilder<K, true, D, E>(this._def.type as K, this._def);\n }\n\n /** Set a default value. */\n default(value: unknown): ColumnBuilder<K, N, true, E> {\n this._def.defaultValue = value;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** UUID: generate a random default (gen_random_uuid()). */\n defaultRandom(): ColumnBuilder<K, N, true, E> {\n this._def.defaultRandom = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Timestamp: default to now(). */\n defaultNow(): ColumnBuilder<K, N, true, E> {\n this._def.defaultNow = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Add a foreign key reference. */\n references(table: string, column: string): ColumnBuilder<K, N, D, E> {\n this._def.references = { table, column };\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Set the ON DELETE action for a foreign key reference. */\n onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E> {\n this._def.onDeleteAction = action;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type extractors — imported by Task 2 to derive insert/row shapes.\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts the TypeScript value type for a column, respecting nullability.\n * - \"uuid\" | \"text\" | \"timestamp\" → string (or string | null when N = true)\n * - \"integer\" → number\n * - \"boolean\" → boolean\n * - \"jsonb\" → unknown (opaque JSON)\n * - \"enum\" → E (the union of literal values)\n */\nexport type ColValue<C> =\n C extends ColumnBuilder<'uuid' | 'text' | 'timestamp', infer N, infer _D, infer _E>\n ? N extends true\n ? string | null\n : string\n : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E>\n ? N extends true\n ? number | null\n : number\n : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E>\n ? N extends true\n ? boolean | null\n : boolean\n : C extends ColumnBuilder<'jsonb', infer _N, infer _D, infer _E>\n ? unknown\n : C extends ColumnBuilder<'enum', infer N, infer _D, infer E>\n ? N extends true\n ? E | null\n : E\n : never;\n\n/**\n * True when a column is optional on INSERT:\n * - nullable columns (N = true) — the DB allows NULL so the field may be omitted\n * - columns with a default (D = true) — the DB fills in the value when absent\n */\nexport type ColIsOptionalOnInsert<C> =\n C extends ColumnBuilder<infer _K, true, infer _D, infer _E>\n ? true\n : C extends ColumnBuilder<infer _K, infer _N, true, infer _E>\n ? true\n : false;\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\n/** Create a UUID column. */\nexport function uuid(): ColumnBuilder<'uuid', false, false, never> {\n return new ColumnBuilder('uuid');\n}\n\n/** Create a TEXT column. */\nexport function text(): ColumnBuilder<'text', false, false, never> {\n return new ColumnBuilder('text');\n}\n\n/** Create an INTEGER column. */\nexport function integer(): ColumnBuilder<'integer', false, false, never> {\n return new ColumnBuilder('integer');\n}\n\n/** Create a BOOLEAN column. */\nexport function boolean(): ColumnBuilder<'boolean', false, false, never> {\n return new ColumnBuilder('boolean');\n}\n\n/** Create a TIMESTAMP column. */\nexport function timestamp(): ColumnBuilder<'timestamp', false, false, never> {\n return new ColumnBuilder('timestamp');\n}\n\n/** Create a JSONB column. */\nexport function jsonb(): ColumnBuilder<'jsonb', false, false, never> {\n return new ColumnBuilder('jsonb');\n}\n\n/**\n * Create an ENUM column.\n * @param name The PostgreSQL enum type name (used in DDL).\n * @param values A readonly tuple of valid string values — kept `const` so the\n * union `V[number]` is as narrow as possible.\n */\nexport function enumType<const V extends readonly string[]>(\n name: string,\n values: V,\n): ColumnBuilder<'enum', false, false, V[number]> {\n const builder = new ColumnBuilder<'enum', false, false, V[number]>('enum');\n builder._def.enumName = name;\n builder._def.enumValues = [...values];\n return builder;\n}\n","/**\n * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.\n *\n * Derives INSERT and full-row TypeScript types from a `defineSchema()` result\n * and wraps the untyped runtime `DBClient` with a typed facade.\n *\n * No value-any. No `as unknown as X`. The two narrow `as` casts in\n * `makeTypedTable` are safe because:\n * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to\n * typed values; all value types are subsets of `unknown`, so the cast is\n * structurally sound.\n * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,\n * unknown>` which is the erased form of the typed row; we're narrowing back\n * to the precise shape that the schema declared.\n * Both casts are narrowing only (not widening) and correctness is guaranteed\n * by the schema the caller provides.\n */\n\nimport type { ColValue, ColIsOptionalOnInsert, ColumnBuilder } from \"./columns.js\";\nimport type { TableDef, SchemaDef } from \"./schema.js\";\nimport type { Tables, TableTypes } from \"./env.js\";\nimport type { DBClient, TxClient } from \"../endpoint.js\";\n\n// ---------------------------------------------------------------------------\n// Key discriminators — split a column map into required vs optional keys.\n// ---------------------------------------------------------------------------\n\n/** Keys of C whose columns are required on INSERT (not nullable, no default). */\ntype RequiredKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;\n}[keyof C];\n\n/** Keys of C whose columns are optional on INSERT (nullable or has a default). */\ntype OptionalKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;\n}[keyof C];\n\n// ---------------------------------------------------------------------------\n// Public shape types — exported so callers can reference them directly.\n// ---------------------------------------------------------------------------\n\n/**\n * The TypeScript type for an INSERT payload for table `T`.\n * - Required: columns that are NOT NULL and have no DB-level default.\n * - Optional: columns that are nullable or carry a default.\n *\n * When all columns are optional, `RequiredKeys<C>` resolves to `never` and\n * the first part becomes `{}`, which is a neutral element for `&`.\n */\nexport type InsertShape<T extends TableDef> = {\n [K in RequiredKeys<T[\"columns\"]>]: ColValue<T[\"columns\"][K]>;\n} & {\n [K in OptionalKeys<T[\"columns\"]>]?: ColValue<T[\"columns\"][K]>;\n};\n\n/**\n * The TypeScript type for a full row returned by the DB for table `T`.\n * Every column is present; nullable columns resolve to `T | null`.\n */\nexport type RowShape<T extends TableDef> = {\n [K in keyof T[\"columns\"]]: ColValue<T[\"columns\"][K]>;\n};\n\n// ---------------------------------------------------------------------------\n// TypedTable + TypedDB interfaces.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor that mirrors the runtime DBClient surface. */\nexport interface TypedTable<T extends TableDef> {\n insert(data: InsertShape<T>): Promise<RowShape<T>>;\n update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T>>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<RowShape<T> | null>;\n findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;\n}\n\n/** A typed DB facade covering all tables declared in schema `S`. */\nexport interface TypedDB<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n transaction<T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T>;\n}\n\n/** Transaction-scoped typed facade: same typed tables, no nested transaction. */\nexport interface TypedTx<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Runtime factory.\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a typed table accessor that delegates every call to `raw` using the\n * runtime table name string. Two narrow `as` casts bridge the mapped-type\n * shapes to/from `Record<string, unknown>` — see module-level doc comment.\n *\n * The `raw` param is typed `TxClient` (the op surface shared by `DBClient` and\n * the transaction-scoped client) because this only ever calls the five\n * insert/update/delete/findById/findMany ops — never `transaction`. This lets\n * the same factory wrap both the top-level db and a tx without any cast.\n */\nfunction makeTypedTable<T extends TableDef<Record<string, ColumnBuilder>>>(\n name: string,\n raw: TxClient,\n): TypedTable<T> {\n return {\n insert: (data: InsertShape<T>) =>\n raw.insert(name, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n update: (id: string, data: Partial<InsertShape<T>>) =>\n raw.update(name, id, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n delete: (id: string) => raw.delete(name, id),\n\n findById: (id: string) =>\n raw.findById(name, id) as Promise<RowShape<T> | null>,\n\n findMany: (query?: Partial<RowShape<T>>) =>\n raw.findMany(name, query as Record<string, unknown> | undefined) as Promise<RowShape<T>[]>,\n };\n}\n\n/**\n * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from\n * the provided schema. No behavior change — all calls delegate to `raw` with\n * the table name as a plain string.\n *\n * `buildTables` is the reusable factory that wraps any op-bearing client\n * (`TxClient` — the surface shared by `DBClient` and the transaction-scoped\n * client) into the typed tables map. It is used both for the top-level db\n * (wrapping `raw`) and inside `transaction`, where it wraps the raw `TxClient`\n * the runtime yields so the callback sees the same typed `.tables` API.\n *\n * `transaction` delegates straight to `raw.transaction`; the two narrow\n * `as TypedTx<S>` / `as TypedDB<S>` casts are single structural narrowings\n * from the dynamically-built tables object to the precise mapped type (TS\n * cannot infer through `Object.keys` iteration) — see module-level doc comment.\n */\nexport function makeTypedDB<S extends SchemaDef>(\n schema: S,\n raw: DBClient,\n): TypedDB<S> {\n function buildTables(client: TxClient): Record<string, TypedTable<TableDef>> {\n const tables = {} as Record<string, TypedTable<TableDef>>;\n for (const key of Object.keys(schema.tables)) {\n const tableDef = schema.tables[key];\n if (tableDef !== undefined) {\n tables[key] = makeTypedTable(tableDef.name, client);\n }\n }\n return tables;\n }\n\n const result = {\n tables: buildTables(raw),\n transaction: <T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T> =>\n raw.transaction((rawTx) => fn({ tables: buildTables(rawTx) } as TypedTx<S>)),\n };\n\n // Narrow cast: `result.tables` is structurally identical to\n // TypedDB<S>[\"tables\"] — each key maps to a TypedTable for the matching\n // TableDef. TS cannot infer the mapped-type result through Object.keys\n // iteration, so a single `as` bridges the gap.\n return result as TypedDB<S>;\n}\n\n// ---------------------------------------------------------------------------\n// Env-augmentation-driven typed surface — the typed-by-default `Database`.\n//\n// These types read the globally-augmented `Tables` interface from\n// `@palbase/backend/env` (filled by the generated `palbase-env.d.ts`). They\n// back `Database.tables.<name>` so handler code is typed with no import and no\n// generic (C5). They DELIBERATELY do not reference `ColumnBuilder` — the env\n// `Tables` interface carries flat `row`/`insert` object types.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor derived from one env `Tables` entry's flat shapes. */\nexport interface EnvTypedTable<T extends TableTypes> {\n insert(data: T[\"insert\"]): Promise<T[\"row\"]>;\n update(id: string, data: Partial<T[\"insert\"]>): Promise<T[\"row\"]>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<T[\"row\"] | null>;\n findMany(query?: Partial<T[\"row\"]>): Promise<T[\"row\"][]>;\n}\n\n/** The `tables` map exposed on `Database`/`tx`, keyed by the env `Tables`\n * interface. When no schema is declared `Tables` is empty, so `tables` is an\n * empty object — accessing `.tables.foo` is then a compile error (no member). */\nexport type EnvTables = {\n [K in keyof Tables]: EnvTypedTable<Tables[K]>;\n};\n\n/** Transaction-scoped typed facade for the env-augmented surface: same typed\n * tables, no nested transaction. */\nexport interface EnvTypedTx {\n tables: EnvTables;\n}\n\n/**\n * The typed-by-default Database surface: the raw string-keyed `DBClient` ops\n * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`\n * and a `transaction` whose callback receives the typed tables.\n *\n * `transaction` is declared here (overriding `DBClient[\"transaction\"]`) so the\n * `tx` the callback receives carries the typed `.tables` API.\n */\nexport interface EnvTypedDatabase extends Omit<DBClient, \"transaction\"> {\n tables: EnvTables;\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/db/index.ts","../../src/db/policy.ts","../../src/db/schema.ts","../../src/db/extensions.ts","../../src/db/columns.ts","../../src/db/typed-db.ts"],"sourcesContent":["export { defineSchema } from \"./schema.js\";\nexport type { TableDef, TableInput, SchemaDef, ColumnMap, SchemaInput } from \"./schema.js\";\nexport { policy, PolicyBuilder } from \"./policy.js\";\nexport type { PolicyDef, PolicyCommand, PolicyMode } from \"./policy.js\";\nexport { PALBASE_EXTENSIONS, EXTENSION_DEPENDENCIES, isPalbaseExtension } from \"./extensions.js\";\nexport type { PalbaseExtension } from \"./extensions.js\";\nexport {\n uuid,\n text,\n integer,\n boolean,\n timestamp,\n jsonb,\n enumType,\n} from \"./columns.js\";\nexport type { ColumnDef, ColumnType, OnDeleteAction, ColumnBuilder } from \"./columns.js\";\nexport { makeTypedDB } from \"./typed-db.js\";\nexport type {\n TypedDB,\n TypedTx,\n TypedTable,\n InsertShape,\n RowShape,\n EnvTypedDatabase,\n EnvServiceDatabase,\n} from \"./typed-db.js\";\n","/**\n * policy.ts — the RLS policy authoring DSL.\n *\n * `policy(name)` returns a fluent builder that mirrors the `ColumnBuilder`\n * style in columns.ts: each chainable method mutates the underlying\n * definition and returns the builder so calls compose. The terminal value is\n * a plain {@link PolicyDef} — the exact JSON shape the runtime's\n * `schema_extract.js` reads off the bundled module and the Go side parses into\n * `PolicyJSON` (CONTRACT-POLICY).\n *\n * @example\n * import { policy } from \"@palbase/backend\";\n *\n * policy(\"owner_select\")\n * .for(\"select\")\n * .to(\"authenticated\")\n * .using(\"owner = (select auth.uid())\");\n */\n\n/** The SQL command a policy applies to. `\"all\"` covers SELECT/INSERT/UPDATE/DELETE. */\nexport type PolicyCommand = \"all\" | \"select\" | \"insert\" | \"update\" | \"delete\";\n\n/** Whether a policy is permissive (OR-combined, the default) or restrictive\n * (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */\nexport type PolicyMode = \"permissive\" | \"restrictive\";\n\n/**\n * The compiled, serializable policy definition — the EXACT shape consumed by\n * `schema_extract.js` → Go `PolicyJSON` (CONTRACT-POLICY).\n *\n * - `roles`: the DB roles this policy applies to (`TO` clause). An empty array\n * means the policy applies to PUBLIC (all roles) — the Postgres default.\n * - `using`: the `USING (...)` row-visibility expression, or `null` when none.\n * - `withCheck`: the `WITH CHECK (...)` write-validation expression, or `null`.\n * - `permissive`: `true` for `AS PERMISSIVE` (default), `false` for restrictive.\n */\nexport interface PolicyDef {\n name: string;\n command: PolicyCommand;\n roles: string[];\n using: string | null;\n withCheck: string | null;\n permissive: boolean;\n}\n\n/**\n * Fluent RLS policy builder.\n *\n * Defaults (documented, applied at construction):\n * - `command`: `\"all\"` — applies to every SQL command unless `.for(...)` narrows it.\n * - `roles`: `[\"authenticated\"]` — the common case is \"rule applies to signed-in\n * users\". Call `.to(...)` to override; pass `.to()` with no roles (or never\n * call it after a reset) to target PUBLIC.\n * - `using` / `withCheck`: `null` — no row filter / write check until set.\n * - `permissive`: `true` — `AS PERMISSIVE` (policies OR together).\n *\n * Each method mutates `_def` in place and returns `this`, so the chain is a\n * single builder instance (no per-call allocation, like a tagged-template\n * compile target). The terminal `PolicyDef` is read directly off `_def` by\n * `schema_extract.js`.\n */\nexport class PolicyBuilder {\n readonly _def: PolicyDef;\n\n constructor(name: string) {\n this._def = {\n name,\n command: \"all\",\n roles: [\"authenticated\"],\n using: null,\n withCheck: null,\n permissive: true,\n };\n }\n\n /** Restrict the policy to a single SQL command (default `\"all\"`). */\n for(command: PolicyCommand): this {\n this._def.command = command;\n return this;\n }\n\n /**\n * Set the DB roles the policy applies to (the `TO` clause), replacing any\n * previously-set roles. Call with no arguments to target PUBLIC (all roles).\n *\n * @example\n * policy(\"p\").to(\"authenticated\")\n * policy(\"p\").to(\"authenticated\", \"service_role\")\n * policy(\"p\").to() // PUBLIC\n */\n to(...roles: string[]): this {\n this._def.roles = roles;\n return this;\n }\n\n /** Set the `USING (...)` row-visibility expression (raw SQL). */\n using(sqlExpr: string): this {\n this._def.using = sqlExpr;\n return this;\n }\n\n /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */\n withCheck(sqlExpr: string): this {\n this._def.withCheck = sqlExpr;\n return this;\n }\n\n /** Set the policy mode: `\"permissive\"` (default, OR-combined) or\n * `\"restrictive\"` (AND-combined). */\n as(mode: PolicyMode): this {\n this._def.permissive = mode === \"permissive\";\n return this;\n }\n}\n\n/**\n * Start authoring an RLS policy. Returns a {@link PolicyBuilder}; the resulting\n * `PolicyBuilder` is accepted directly in a table's `policies: [...]` array\n * (its `_def` is read at schema-extract time).\n *\n * @param name The policy name. Palbase reconciliation keys policies by\n * `(table, name)`, so names must be unique per table.\n */\nexport function policy(name: string): PolicyBuilder {\n return new PolicyBuilder(name);\n}\n","import type { ColumnBuilder } from \"./columns.js\";\nimport { PolicyBuilder } from \"./policy.js\";\nimport type { PolicyDef } from \"./policy.js\";\nimport type { PalbaseExtension } from \"./extensions.js\";\n\n/**\n * A map of column builders keyed by column name — the value you write under\n * the `columns` key of `defineSchema({ tables: { <name>: { columns } } })`.\n *\n * The default `Record<string, ColumnBuilder>` keeps bare references compiling\n * without a type argument.\n */\nexport type ColumnMap = Record<string, ColumnBuilder>;\n\n/**\n * The author-facing value written under each table key:\n * `{ columns, rls?, policies? }`.\n *\n * - `columns`: the column map (required).\n * - `rls`: enable + FORCE row-level security on this table. Implied when\n * `policies` is non-empty; set it explicitly to enable RLS with no policies\n * yet (deny-all — useful only as an intermediate step).\n * - `policies`: the RLS policies for this table, authored with `policy(name)`.\n * Each entry may be a {@link PolicyBuilder} (the normal `policy(...)` chain)\n * or a raw {@link PolicyDef} object.\n *\n * The `C` type parameter preserves the precise per-column phantom types so the\n * typed `Database.tables.*` surface keeps inferring insert/row shapes.\n */\nexport interface TableInput<C extends ColumnMap = ColumnMap> {\n columns: C;\n rls?: boolean;\n policies?: (PolicyBuilder | PolicyDef)[];\n}\n\n/**\n * A table definition — the runtime value the Go runtime's `schema_extract.js`\n * reads. It keys tables by `tableDef.name`, reads `tableDef.columns` for the\n * column DDL, and `tableDef.rls` + `tableDef.policies` for RLS.\n *\n * `defineSchema` derives `name` from the object key, so authors never repeat\n * the table name. `rls`/`policies` are always present after normalization\n * (defaulted to `false`/`[]`).\n *\n * The `C` type parameter preserves the precise per-column phantom types so that\n * downstream mapped types (InsertShape, RowShape) can discriminate on them.\n */\nexport interface TableDef<C extends ColumnMap = ColumnMap> {\n name: string;\n columns: C;\n rls: boolean;\n policies: PolicyDef[];\n}\n\n/**\n * A schema definition containing multiple tables, keyed by table name.\n *\n * The `T` type parameter preserves the exact `TableDef<...>` type for each\n * table so that `SchemaDef[\"tables\"][\"rooms\"]` resolves to the precise\n * `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.\n */\nexport interface SchemaDef<\n T extends Record<string, TableDef> = Record<string, TableDef>,\n> {\n tables: T;\n /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */\n extensions: PalbaseExtension[];\n}\n\n/** The author-facing input to `defineSchema` — a `tables` map whose keys are\n * the table names and whose values are `{ columns, rls?, policies? }`, plus an\n * optional `extensions` allowlist. */\nexport interface SchemaInput<\n T extends Record<string, TableInput> = Record<string, TableInput>,\n> {\n tables: T;\n /**\n * Postgres extensions to enable for this project, e.g. `[\"vector\"]`.\n * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA\n * extensions) with the privileged deploy connection. The type is an\n * allowlist union, so unsupported names fail typecheck.\n */\n extensions?: PalbaseExtension[];\n}\n\n/** Map the author's `{ tables: { <name>: { columns } } }` input to the\n * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the\n * per-table column map `T[K][\"columns\"]` so column-level inference survives. */\ntype TablesFromInput<T extends Record<string, TableInput>> = {\n [K in keyof T]: TableDef<T[K][\"columns\"]>;\n};\n\n/** Normalize a single `policies` entry into a plain `PolicyDef` (read off a\n * `PolicyBuilder._def`, or passed through when already a `PolicyDef`). */\nfunction toPolicyDef(p: PolicyBuilder | PolicyDef): PolicyDef {\n return p instanceof PolicyBuilder ? p._def : p;\n}\n\n/**\n * Define a schema. The table NAME comes from the object key. Each table value\n * is `{ columns, rls?, policies? }`:\n *\n * export default defineSchema({\n * tables: {\n * todos: {\n * columns: {\n * id: uuid().primaryKey().defaultRandom(),\n * owner: text().notNull(),\n * title: text().notNull(),\n * },\n * rls: true,\n * policies: [\n * policy(\"owner_all\").for(\"all\").to(\"authenticated\")\n * .using(\"owner = (select auth.uid())\")\n * .withCheck(\"owner = (select auth.uid())\"),\n * ],\n * },\n * },\n * });\n *\n * The returned value is\n * `{ tables: { todos: { name, columns, rls, policies } } }` — the exact shape\n * the runtime schema extractor parses. Per-column phantom types are preserved\n * so `Database.tables.todos.insert({...})` stays typed.\n *\n * RLS normalization: `rls` defaults to `false`, `policies` to `[]`. When\n * `policies` is non-empty, `rls` is forced on (ENABLE + FORCE) regardless of\n * the declared `rls` flag — a table with policies must have RLS enabled or the\n * policies would be inert.\n */\nexport function defineSchema<T extends Record<string, TableInput>>(\n input: SchemaInput<T>,\n): SchemaDef<TablesFromInput<T>> {\n const tables = {} as TablesFromInput<T>;\n for (const name of Object.keys(input.tables) as (keyof T)[]) {\n const table = input.tables[name];\n // `noUncheckedIndexedAccess` widens the index access to `… | undefined`,\n // but `name` comes straight from `Object.keys(input.tables)`, so the entry\n // always exists. Guard to narrow without a cast.\n if (table === undefined) continue;\n const policies = (table.policies ?? []).map(toPolicyDef);\n // A table with policies must have RLS enabled, otherwise the policies are\n // inert. Enabling RLS without policies (deny-all) is allowed explicitly.\n const rls = table.rls === true || policies.length > 0;\n tables[name] = {\n name: name as string,\n columns: table.columns,\n rls,\n policies,\n };\n }\n // Dedupe + normalize extensions (order-independent; deploy resolves deps).\n const extensions = [...new Set(input.extensions ?? [])];\n return { tables, extensions };\n}\n","/**\n * Postgres extensions a Palbase project can enable from its schema.\n *\n * Extensions are config-as-code: declare them in `defineSchema({ extensions })`\n * and the deploy installs them (CREATE EXTENSION … SCHEMA extensions) using the\n * deploy path's privileged connection. They are NOT toggled live from Studio —\n * CREATE EXTENSION requires a superuser role that only the deploy path holds.\n *\n * The list is an allowlist (a string-literal union) so editors autocomplete the\n * supported names and a typo fails typecheck. It is intentionally extensible:\n * add a name here (+ confirm the base image ships it) to support more.\n */\nexport const PALBASE_EXTENSIONS = [\n // Search & text\n \"vector\", // pgvector: AI embeddings + vector similarity search (semantic search / RAG).\n // NB: the Postgres extension is named \"vector\", not \"pgvector\" — declare \"vector\".\n \"pg_trgm\", // trigram fuzzy / typo-tolerant text search\n \"unaccent\", // accent-insensitive text search\n \"citext\", // case-insensitive text type\n // Geospatial / location\n \"postgis\", // geospatial types + queries (maps, \"near me\")\n \"cube\", // multi-dimensional cubes (dependency of earthdistance)\n \"earthdistance\", // great-circle distance (needs cube)\n // Data types & structures\n \"hstore\", // key/value pairs in a single column\n \"ltree\", // hierarchical tree-structured labels\n // Scheduling\n \"pg_cron\", // schedule jobs inside the database\n // Crypto / ids (also installed by default; listable for explicitness)\n \"pgcrypto\", // cryptographic functions (hashing, encryption)\n \"uuid-ossp\", // UUID generation functions\n] as const;\n\n/** A Postgres extension supported by Palbase (allowlist union). */\nexport type PalbaseExtension = (typeof PALBASE_EXTENSIONS)[number];\n\n/**\n * Extensions that depend on another extension. The deploy installs\n * dependencies first; declaring `earthdistance` without `cube` still works\n * because the deploy resolves the order, but listing both is clearer.\n */\nexport const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseExtension[]>> = {\n earthdistance: [\"cube\"],\n};\n\n/** Runtime guard: is `name` a supported Palbase extension? */\nexport function isPalbaseExtension(name: string): name is PalbaseExtension {\n return (PALBASE_EXTENSIONS as readonly string[]).includes(name);\n}\n","/** On delete action for foreign key references. */\nexport type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';\n\n/** Column type identifiers. */\nexport type ColumnType = 'uuid' | 'text' | 'integer' | 'boolean' | 'timestamp' | 'jsonb' | 'enum';\n\n/** Base column definition shared by all column types. */\nexport interface ColumnDef {\n type: ColumnType;\n nullable: boolean;\n primaryKey: boolean;\n defaultValue?: unknown;\n defaultRandom?: boolean;\n defaultNow?: boolean;\n references?: { table: string; column: string };\n onDeleteAction?: OnDeleteAction;\n enumName?: string;\n enumValues?: string[];\n}\n\n// Phantom brand symbols — never have runtime values; exist only to force\n// TypeScript's structural type system to distinguish ColumnBuilder instances\n// with different type-param combinations. Without these, TS sees all\n// ColumnBuilder<K,...> as structurally identical and the first branch of\n// ColValue matches everything.\ndeclare const __colKind: unique symbol;\ndeclare const __colNullable: unique symbol;\ndeclare const __colHasDefault: unique symbol;\ndeclare const __colEnumValues: unique symbol;\n\n/**\n * Fluent column builder with phantom type params:\n * K — ColumnType literal (e.g. \"text\", \"integer\")\n * N — boolean: true when nullable() has been called last (false = NOT NULL)\n * D — boolean: true when a default has been set\n * E — enum value union (never for non-enum columns)\n *\n * All four params have defaults so bare `ColumnBuilder` (no args) still\n * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.\n *\n * The four `declare readonly` brand fields carry the phantom types into the\n * structural shape so that conditional types like ColValue<C> can discriminate\n * on K without requiring runtime values on those fields.\n */\nexport class ColumnBuilder<\n K extends ColumnType = ColumnType,\n N extends boolean = boolean,\n D extends boolean = boolean,\n E = unknown,\n> {\n // These fields exist only in the type layer (declared, never initialised at\n // runtime — TypeScript allows declared class members without an initializer\n // in strict mode as long as they're never read at runtime).\n declare readonly [__colKind]: K;\n declare readonly [__colNullable]: N;\n declare readonly [__colHasDefault]: D;\n declare readonly [__colEnumValues]: E;\n\n readonly _def: ColumnDef;\n\n constructor(type: K, existingDef?: ColumnDef) {\n this._def = existingDef ?? {\n type,\n nullable: false,\n primaryKey: false,\n };\n }\n\n /** Mark this column as the primary key. */\n primaryKey(): ColumnBuilder<K, N, D, E> {\n this._def.primaryKey = true;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Mark this column as NOT NULL (default). */\n notNull(): ColumnBuilder<K, false, D, E> {\n this._def.nullable = false;\n return new ColumnBuilder<K, false, D, E>(this._def.type as K, this._def);\n }\n\n /** Allow NULL values. */\n nullable(): ColumnBuilder<K, true, D, E> {\n this._def.nullable = true;\n return new ColumnBuilder<K, true, D, E>(this._def.type as K, this._def);\n }\n\n /** Set a default value. */\n default(value: unknown): ColumnBuilder<K, N, true, E> {\n this._def.defaultValue = value;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** UUID: generate a random default (gen_random_uuid()). */\n defaultRandom(): ColumnBuilder<K, N, true, E> {\n this._def.defaultRandom = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Timestamp: default to now(). */\n defaultNow(): ColumnBuilder<K, N, true, E> {\n this._def.defaultNow = true;\n return new ColumnBuilder<K, N, true, E>(this._def.type as K, this._def);\n }\n\n /** Add a foreign key reference. */\n references(table: string, column: string): ColumnBuilder<K, N, D, E> {\n this._def.references = { table, column };\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n\n /** Set the ON DELETE action for a foreign key reference. */\n onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E> {\n this._def.onDeleteAction = action;\n return new ColumnBuilder<K, N, D, E>(this._def.type as K, this._def);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type extractors — imported by Task 2 to derive insert/row shapes.\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts the TypeScript value type for a column, respecting nullability.\n * - \"uuid\" | \"text\" | \"timestamp\" → string (or string | null when N = true)\n * - \"integer\" → number\n * - \"boolean\" → boolean\n * - \"jsonb\" → unknown (opaque JSON)\n * - \"enum\" → E (the union of literal values)\n */\nexport type ColValue<C> =\n C extends ColumnBuilder<'uuid' | 'text' | 'timestamp', infer N, infer _D, infer _E>\n ? N extends true\n ? string | null\n : string\n : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E>\n ? N extends true\n ? number | null\n : number\n : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E>\n ? N extends true\n ? boolean | null\n : boolean\n : C extends ColumnBuilder<'jsonb', infer _N, infer _D, infer _E>\n ? unknown\n : C extends ColumnBuilder<'enum', infer N, infer _D, infer E>\n ? N extends true\n ? E | null\n : E\n : never;\n\n/**\n * True when a column is optional on INSERT:\n * - nullable columns (N = true) — the DB allows NULL so the field may be omitted\n * - columns with a default (D = true) — the DB fills in the value when absent\n */\nexport type ColIsOptionalOnInsert<C> =\n C extends ColumnBuilder<infer _K, true, infer _D, infer _E>\n ? true\n : C extends ColumnBuilder<infer _K, infer _N, true, infer _E>\n ? true\n : false;\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\n/** Create a UUID column. */\nexport function uuid(): ColumnBuilder<'uuid', false, false, never> {\n return new ColumnBuilder('uuid');\n}\n\n/** Create a TEXT column. */\nexport function text(): ColumnBuilder<'text', false, false, never> {\n return new ColumnBuilder('text');\n}\n\n/** Create an INTEGER column. */\nexport function integer(): ColumnBuilder<'integer', false, false, never> {\n return new ColumnBuilder('integer');\n}\n\n/** Create a BOOLEAN column. */\nexport function boolean(): ColumnBuilder<'boolean', false, false, never> {\n return new ColumnBuilder('boolean');\n}\n\n/** Create a TIMESTAMP column. */\nexport function timestamp(): ColumnBuilder<'timestamp', false, false, never> {\n return new ColumnBuilder('timestamp');\n}\n\n/** Create a JSONB column. */\nexport function jsonb(): ColumnBuilder<'jsonb', false, false, never> {\n return new ColumnBuilder('jsonb');\n}\n\n/**\n * Create an ENUM column.\n * @param name The PostgreSQL enum type name (used in DDL).\n * @param values A readonly tuple of valid string values — kept `const` so the\n * union `V[number]` is as narrow as possible.\n */\nexport function enumType<const V extends readonly string[]>(\n name: string,\n values: V,\n): ColumnBuilder<'enum', false, false, V[number]> {\n const builder = new ColumnBuilder<'enum', false, false, V[number]>('enum');\n builder._def.enumName = name;\n builder._def.enumValues = [...values];\n return builder;\n}\n","/**\n * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.\n *\n * Derives INSERT and full-row TypeScript types from a `defineSchema()` result\n * and wraps the untyped runtime `DBClient` with a typed facade.\n *\n * No value-any. No `as unknown as X`. The two narrow `as` casts in\n * `makeTypedTable` are safe because:\n * - `data as Record<string, unknown>`: InsertShape<T> maps string keys to\n * typed values; all value types are subsets of `unknown`, so the cast is\n * structurally sound.\n * - `result as RowShape<T>`: The runtime DBClient returns `Record<string,\n * unknown>` which is the erased form of the typed row; we're narrowing back\n * to the precise shape that the schema declared.\n * Both casts are narrowing only (not widening) and correctness is guaranteed\n * by the schema the caller provides.\n */\n\nimport type { ColValue, ColIsOptionalOnInsert, ColumnBuilder } from \"./columns.js\";\nimport type { TableDef, SchemaDef } from \"./schema.js\";\nimport type { Tables, TableTypes } from \"./env.js\";\nimport type { DBClient, TxClient } from \"../endpoint.js\";\n\n// ---------------------------------------------------------------------------\n// Key discriminators — split a column map into required vs optional keys.\n// ---------------------------------------------------------------------------\n\n/** Keys of C whose columns are required on INSERT (not nullable, no default). */\ntype RequiredKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;\n}[keyof C];\n\n/** Keys of C whose columns are optional on INSERT (nullable or has a default). */\ntype OptionalKeys<C> = {\n [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;\n}[keyof C];\n\n// ---------------------------------------------------------------------------\n// Public shape types — exported so callers can reference them directly.\n// ---------------------------------------------------------------------------\n\n/**\n * The TypeScript type for an INSERT payload for table `T`.\n * - Required: columns that are NOT NULL and have no DB-level default.\n * - Optional: columns that are nullable or carry a default.\n *\n * When all columns are optional, `RequiredKeys<C>` resolves to `never` and\n * the first part becomes `{}`, which is a neutral element for `&`.\n */\nexport type InsertShape<T extends TableDef> = {\n [K in RequiredKeys<T[\"columns\"]>]: ColValue<T[\"columns\"][K]>;\n} & {\n [K in OptionalKeys<T[\"columns\"]>]?: ColValue<T[\"columns\"][K]>;\n};\n\n/**\n * The TypeScript type for a full row returned by the DB for table `T`.\n * Every column is present; nullable columns resolve to `T | null`.\n */\nexport type RowShape<T extends TableDef> = {\n [K in keyof T[\"columns\"]]: ColValue<T[\"columns\"][K]>;\n};\n\n// ---------------------------------------------------------------------------\n// TypedTable + TypedDB interfaces.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor that mirrors the runtime DBClient surface. */\nexport interface TypedTable<T extends TableDef> {\n insert(data: InsertShape<T>): Promise<RowShape<T>>;\n update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T>>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<RowShape<T> | null>;\n findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;\n}\n\n/** A typed DB facade covering all tables declared in schema `S`. */\nexport interface TypedDB<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n transaction<T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T>;\n}\n\n/** Transaction-scoped typed facade: same typed tables, no nested transaction. */\nexport interface TypedTx<S extends SchemaDef> {\n tables: {\n [K in keyof S[\"tables\"]]: TypedTable<S[\"tables\"][K]>;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Runtime factory.\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a typed table accessor that delegates every call to `raw` using the\n * runtime table name string. Two narrow `as` casts bridge the mapped-type\n * shapes to/from `Record<string, unknown>` — see module-level doc comment.\n *\n * The `raw` param is typed `TxClient` (the op surface shared by `DBClient` and\n * the transaction-scoped client) because this only ever calls the five\n * insert/update/delete/findById/findMany ops — never `transaction`. This lets\n * the same factory wrap both the top-level db and a tx without any cast.\n */\nfunction makeTypedTable<T extends TableDef<Record<string, ColumnBuilder>>>(\n name: string,\n raw: TxClient,\n): TypedTable<T> {\n return {\n insert: (data: InsertShape<T>) =>\n raw.insert(name, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n update: (id: string, data: Partial<InsertShape<T>>) =>\n raw.update(name, id, data as Record<string, unknown>) as Promise<RowShape<T>>,\n\n delete: (id: string) => raw.delete(name, id),\n\n findById: (id: string) =>\n raw.findById(name, id) as Promise<RowShape<T> | null>,\n\n findMany: (query?: Partial<RowShape<T>>) =>\n raw.findMany(name, query as Record<string, unknown> | undefined) as Promise<RowShape<T>[]>,\n };\n}\n\n/**\n * Wraps a raw `DBClient` with the type-safe `TypedDB<S>` facade derived from\n * the provided schema. No behavior change — all calls delegate to `raw` with\n * the table name as a plain string.\n *\n * `buildTables` is the reusable factory that wraps any op-bearing client\n * (`TxClient` — the surface shared by `DBClient` and the transaction-scoped\n * client) into the typed tables map. It is used both for the top-level db\n * (wrapping `raw`) and inside `transaction`, where it wraps the raw `TxClient`\n * the runtime yields so the callback sees the same typed `.tables` API.\n *\n * `transaction` delegates straight to `raw.transaction`; the two narrow\n * `as TypedTx<S>` / `as TypedDB<S>` casts are single structural narrowings\n * from the dynamically-built tables object to the precise mapped type (TS\n * cannot infer through `Object.keys` iteration) — see module-level doc comment.\n */\nexport function makeTypedDB<S extends SchemaDef>(\n schema: S,\n raw: DBClient,\n): TypedDB<S> {\n function buildTables(client: TxClient): Record<string, TypedTable<TableDef>> {\n const tables = {} as Record<string, TypedTable<TableDef>>;\n for (const key of Object.keys(schema.tables)) {\n const tableDef = schema.tables[key];\n if (tableDef !== undefined) {\n tables[key] = makeTypedTable(tableDef.name, client);\n }\n }\n return tables;\n }\n\n const result = {\n tables: buildTables(raw),\n transaction: <T>(fn: (tx: TypedTx<S>) => Promise<T>): Promise<T> =>\n raw.transaction((rawTx) => fn({ tables: buildTables(rawTx) } as TypedTx<S>)),\n };\n\n // Narrow cast: `result.tables` is structurally identical to\n // TypedDB<S>[\"tables\"] — each key maps to a TypedTable for the matching\n // TableDef. TS cannot infer the mapped-type result through Object.keys\n // iteration, so a single `as` bridges the gap.\n return result as TypedDB<S>;\n}\n\n// ---------------------------------------------------------------------------\n// Env-augmentation-driven typed surface — the typed-by-default `Database`.\n//\n// These types read the globally-augmented `Tables` interface from\n// `@palbase/backend/env` (filled by the generated `palbase-env.d.ts`). They\n// back `Database.tables.<name>` so handler code is typed with no import and no\n// generic (C5). They DELIBERATELY do not reference `ColumnBuilder` — the env\n// `Tables` interface carries flat `row`/`insert` object types.\n// ---------------------------------------------------------------------------\n\n/** A typed table accessor derived from one env `Tables` entry's flat shapes. */\nexport interface EnvTypedTable<T extends TableTypes> {\n insert(data: T[\"insert\"]): Promise<T[\"row\"]>;\n update(id: string, data: Partial<T[\"insert\"]>): Promise<T[\"row\"]>;\n delete(id: string): Promise<void>;\n findById(id: string): Promise<T[\"row\"] | null>;\n findMany(query?: Partial<T[\"row\"]>): Promise<T[\"row\"][]>;\n}\n\n/** The `tables` map exposed on `Database`/`tx`, keyed by the env `Tables`\n * interface. When no schema is declared `Tables` is empty, so `tables` is an\n * empty object — accessing `.tables.foo` is then a compile error (no member). */\nexport type EnvTables = {\n [K in keyof Tables]: EnvTypedTable<Tables[K]>;\n};\n\n/** Transaction-scoped typed facade for the env-augmented surface: same typed\n * tables, no nested transaction. */\nexport interface EnvTypedTx {\n tables: EnvTables;\n}\n\n/**\n * The RLS-bypass sibling returned by `Database.asService()`. Same typed surface\n * as {@link EnvTypedDatabase} — `tables`, the raw string ops, and a typed\n * `transaction` — but it does NOT re-expose `asService` (no double-bypass).\n * Every op it performs runs as the `service_role` (BYPASSRLS).\n */\nexport interface EnvServiceDatabase extends Omit<DBClient, \"transaction\" | \"asService\"> {\n tables: EnvTables;\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;\n}\n\n/**\n * The typed-by-default Database surface: the raw string-keyed `DBClient` ops\n * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`,\n * a `transaction` whose callback receives the typed tables, and `asService()`\n * for the explicit RLS-bypass sibling.\n *\n * `transaction` is declared here (overriding `DBClient[\"transaction\"]`) so the\n * `tx` the callback receives carries the typed `.tables` API. `asService` is\n * re-typed to return the typed {@link EnvServiceDatabase} sibling.\n */\nexport interface EnvTypedDatabase extends Omit<DBClient, \"transaction\" | \"asService\"> {\n tables: EnvTables;\n transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;\n /**\n * Return a sibling that bypasses RLS by running as the `service_role`. Use\n * sparingly and explicitly — the default `Database.*` path is RLS-enforced.\n *\n * @example\n * const all = await Database.asService().tables.todos.findMany({});\n * const rows = await Database.asService().query(\"SELECT * FROM todos\");\n */\n asService(): EnvServiceDatabase;\n}\n"],"mappings":"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","names":[]}
|
package/dist/db/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, I as InsertShape, O as OnDeleteAction, R as RowShape, S as SchemaDef,
|
|
1
|
+
export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RowShape, S as SchemaDef, n as SchemaInput, T as TableDef, o as TableInput, p as TypedDB, q as TypedTable, r as TypedTx, s as boolean, t as defineSchema, u as enumType, v as integer, w as isPalbaseExtension, x as jsonb, y as makeTypedDB, z as policy, A as text, B as timestamp, D as uuid } from '../index-DzRFS3Tl.cjs';
|
|
2
2
|
import './env.cjs';
|
|
3
|
-
import '../endpoint-
|
|
3
|
+
import '../endpoint-2d_DpASt.cjs';
|
|
4
4
|
import 'zod';
|