@porulle/db 0.1.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/README.md +62 -0
- package/dist/column.d.ts +57 -0
- package/dist/column.d.ts.map +1 -0
- package/dist/column.js +41 -0
- package/dist/column.js.map +1 -0
- package/dist/define-table.d.ts +52 -0
- package/dist/define-table.d.ts.map +1 -0
- package/dist/define-table.js +156 -0
- package/dist/define-table.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
- package/src/column.ts +93 -0
- package/src/define-table.test.ts +79 -0
- package/src/define-table.ts +174 -0
- package/src/index.ts +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @porulle/db
|
|
2
|
+
|
|
3
|
+
The database surface plugins use instead of importing Drizzle directly.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { defineTable, column, eq, and, sql } from "@porulle/db";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Why this exists
|
|
10
|
+
|
|
11
|
+
If every plugin imported from `drizzle-orm` directly, the framework couldn't:
|
|
12
|
+
- Inject `organizationId` and `id` columns automatically
|
|
13
|
+
- Add the standard `createdAt` / `updatedAt` timestamps
|
|
14
|
+
- Generate the per-org index + composite unique constraints
|
|
15
|
+
- Hot-swap the underlying ORM if we ever needed to (today: Drizzle on PostgreSQL — tomorrow could be different)
|
|
16
|
+
|
|
17
|
+
`@porulle/db` is the thin contract that decouples plugins from the ORM. Drizzle stays as an implementation detail.
|
|
18
|
+
|
|
19
|
+
## API
|
|
20
|
+
|
|
21
|
+
### `defineTable(name, columns)`
|
|
22
|
+
|
|
23
|
+
Top-level table. Auto-injects: `id` (UUID PK), `organizationId` (text NOT NULL with org index), `createdAt` and `updatedAt` (timestamps).
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { defineTable, column } from "@porulle/db";
|
|
27
|
+
|
|
28
|
+
export const giftCards = defineTable("gift_cards", {
|
|
29
|
+
code: column.text({ unique: true }),
|
|
30
|
+
balance: column.integer(),
|
|
31
|
+
status: column.text({ enum: ["active", "disabled"], default: "active" }),
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `column.*` builders
|
|
36
|
+
|
|
37
|
+
`column.text({ unique?, optional?, enum?, default? })`
|
|
38
|
+
`column.integer({ optional?, default? })`
|
|
39
|
+
`column.boolean({ default? })`
|
|
40
|
+
`column.timestamp({ optional? })`
|
|
41
|
+
`column.jsonb<T>({ optional?, default? })`
|
|
42
|
+
`column.uuid({ optional?, default? })` — for FKs to other org-scoped tables (child tables skip the auto-org column)
|
|
43
|
+
|
|
44
|
+
### Re-exported Drizzle operators
|
|
45
|
+
|
|
46
|
+
For query construction without importing `drizzle-orm` directly:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
eq, ne, gt, gte, lt, lte, and, or, not, like, ilike, notLike,
|
|
50
|
+
inArray, notInArray, isNull, isNotNull, between, sql, desc, asc
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Conventions
|
|
54
|
+
|
|
55
|
+
- Every top-level table is org-scoped via the auto-injected `organizationId`. Cross-tenant queries are a closed surface — see [`SECURITY.md`](../../SECURITY.md).
|
|
56
|
+
- Child tables (FK to a parent that already carries `organizationId`) get `id` and `createdAt` only. The parent's org column protects the child rows.
|
|
57
|
+
- Drizzle hash drift across workspace copies has bitten us before — `@porulle/db` re-exports the operators so plugins inherit a single drizzle version.
|
|
58
|
+
|
|
59
|
+
## See also
|
|
60
|
+
|
|
61
|
+
- [Plugin Contract](https://github.com/asyncdotengineering/porulle/blob/main/apps/docs/src/content/docs/extending/plugin-contract.mdx) — Drizzle-first rule + org-scoping requirement
|
|
62
|
+
- `packages/core/src/kernel/database/` — how the kernel composes the schema
|
package/dist/column.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column definition helpers for defineTable.
|
|
3
|
+
*
|
|
4
|
+
* These produce config objects that defineTable maps to Drizzle column builders.
|
|
5
|
+
* Plugin developers use these instead of importing from drizzle-orm/pg-core directly.
|
|
6
|
+
*
|
|
7
|
+
* import { defineTable, column } from "@porulle/db";
|
|
8
|
+
*
|
|
9
|
+
* export const giftCards = defineTable("gift_cards", {
|
|
10
|
+
* code: column.text({ unique: true }),
|
|
11
|
+
* balance: column.integer(),
|
|
12
|
+
* status: column.text({ enum: ["active", "disabled"], default: "active" }),
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
export interface TextColumnDef {
|
|
16
|
+
readonly _type: "text";
|
|
17
|
+
unique?: boolean;
|
|
18
|
+
optional?: boolean;
|
|
19
|
+
enum?: readonly string[];
|
|
20
|
+
default?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface IntegerColumnDef {
|
|
23
|
+
readonly _type: "integer";
|
|
24
|
+
unique?: boolean;
|
|
25
|
+
optional?: boolean;
|
|
26
|
+
default?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface BooleanColumnDef {
|
|
29
|
+
readonly _type: "boolean";
|
|
30
|
+
optional?: boolean;
|
|
31
|
+
default?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface UuidColumnDef {
|
|
34
|
+
readonly _type: "uuid";
|
|
35
|
+
optional?: boolean;
|
|
36
|
+
references?: unknown;
|
|
37
|
+
}
|
|
38
|
+
export interface TimestampColumnDef {
|
|
39
|
+
readonly _type: "timestamp";
|
|
40
|
+
optional?: boolean;
|
|
41
|
+
default?: "now";
|
|
42
|
+
}
|
|
43
|
+
export interface JsonColumnDef {
|
|
44
|
+
readonly _type: "json";
|
|
45
|
+
optional?: boolean;
|
|
46
|
+
default?: unknown;
|
|
47
|
+
}
|
|
48
|
+
export type ColumnDef = TextColumnDef | IntegerColumnDef | BooleanColumnDef | UuidColumnDef | TimestampColumnDef | JsonColumnDef;
|
|
49
|
+
export declare const column: {
|
|
50
|
+
text: (opts?: Omit<TextColumnDef, "_type">) => TextColumnDef;
|
|
51
|
+
integer: (opts?: Omit<IntegerColumnDef, "_type">) => IntegerColumnDef;
|
|
52
|
+
boolean: (opts?: Omit<BooleanColumnDef, "_type">) => BooleanColumnDef;
|
|
53
|
+
uuid: (opts?: Omit<UuidColumnDef, "_type">) => UuidColumnDef;
|
|
54
|
+
timestamp: (opts?: Omit<TimestampColumnDef, "_type">) => TimestampColumnDef;
|
|
55
|
+
json: (opts?: Omit<JsonColumnDef, "_type">) => JsonColumnDef;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=column.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column.d.ts","sourceRoot":"","sources":["../src/column.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,SAAS,GACjB,aAAa,GACb,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,kBAAkB,GAClB,aAAa,CAAC;AAElB,eAAO,MAAM,MAAM;kBACH,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,aAAa;qBAKzC,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,KAAG,gBAAgB;qBAKlD,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,KAAG,gBAAgB;kBAKrD,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,aAAa;uBAKvC,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC,KAAG,kBAAkB;kBAK3D,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,aAAa;CAI3D,CAAC"}
|
package/dist/column.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column definition helpers for defineTable.
|
|
3
|
+
*
|
|
4
|
+
* These produce config objects that defineTable maps to Drizzle column builders.
|
|
5
|
+
* Plugin developers use these instead of importing from drizzle-orm/pg-core directly.
|
|
6
|
+
*
|
|
7
|
+
* import { defineTable, column } from "@porulle/db";
|
|
8
|
+
*
|
|
9
|
+
* export const giftCards = defineTable("gift_cards", {
|
|
10
|
+
* code: column.text({ unique: true }),
|
|
11
|
+
* balance: column.integer(),
|
|
12
|
+
* status: column.text({ enum: ["active", "disabled"], default: "active" }),
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
export const column = {
|
|
16
|
+
text: (opts) => ({
|
|
17
|
+
_type: "text",
|
|
18
|
+
...opts,
|
|
19
|
+
}),
|
|
20
|
+
integer: (opts) => ({
|
|
21
|
+
_type: "integer",
|
|
22
|
+
...opts,
|
|
23
|
+
}),
|
|
24
|
+
boolean: (opts) => ({
|
|
25
|
+
_type: "boolean",
|
|
26
|
+
...opts,
|
|
27
|
+
}),
|
|
28
|
+
uuid: (opts) => ({
|
|
29
|
+
_type: "uuid",
|
|
30
|
+
...opts,
|
|
31
|
+
}),
|
|
32
|
+
timestamp: (opts) => ({
|
|
33
|
+
_type: "timestamp",
|
|
34
|
+
...opts,
|
|
35
|
+
}),
|
|
36
|
+
json: (opts) => ({
|
|
37
|
+
_type: "json",
|
|
38
|
+
...opts,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=column.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column.js","sourceRoot":"","sources":["../src/column.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiDH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,CAAC,IAAmC,EAAiB,EAAE,CAAC,CAAC;QAC7D,KAAK,EAAE,MAAe;QACtB,GAAG,IAAI;KACR,CAAC;IAEF,OAAO,EAAE,CAAC,IAAsC,EAAoB,EAAE,CAAC,CAAC;QACtE,KAAK,EAAE,SAAkB;QACzB,GAAG,IAAI;KACR,CAAC;IAEF,OAAO,EAAE,CAAC,IAAsC,EAAoB,EAAE,CAAC,CAAC;QACtE,KAAK,EAAE,SAAkB;QACzB,GAAG,IAAI;KACR,CAAC;IAEF,IAAI,EAAE,CAAC,IAAmC,EAAiB,EAAE,CAAC,CAAC;QAC7D,KAAK,EAAE,MAAe;QACtB,GAAG,IAAI;KACR,CAAC;IAEF,SAAS,EAAE,CAAC,IAAwC,EAAsB,EAAE,CAAC,CAAC;QAC5E,KAAK,EAAE,WAAoB;QAC3B,GAAG,IAAI;KACR,CAAC;IAEF,IAAI,EAAE,CAAC,IAAmC,EAAiB,EAAE,CAAC,CAAC;QAC7D,KAAK,EAAE,MAAe;QACtB,GAAG,IAAI;KACR,CAAC;CACH,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineTable — table definition wrapper for UnifiedCommerce plugins.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Drizzle's pgTable with auto-injected fields:
|
|
5
|
+
* - id (UUID primary key)
|
|
6
|
+
* - organizationId (text, NOT NULL) — on top-level tables only
|
|
7
|
+
* - createdAt, updatedAt (timestamps)
|
|
8
|
+
* - Org index + composite unique constraints
|
|
9
|
+
*
|
|
10
|
+
* Child tables (FK to org-scoped parent) get id + createdAt only.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* import { defineTable, column } from "@porulle/db";
|
|
14
|
+
*
|
|
15
|
+
* export const giftCards = defineTable("gift_cards", {
|
|
16
|
+
* code: column.text({ unique: true }),
|
|
17
|
+
* balance: column.integer(),
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
import type { ColumnDef } from "./column.js";
|
|
21
|
+
/**
|
|
22
|
+
* Define a database table with auto-injected UC fields.
|
|
23
|
+
*/
|
|
24
|
+
export declare function defineTable(name: string, columnDefs: Record<string, ColumnDef>, extraConfig?: (table: Record<string, any>) => Record<string, any>): import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
25
|
+
name: string;
|
|
26
|
+
schema: undefined;
|
|
27
|
+
columns: {
|
|
28
|
+
[x: string]: import("drizzle-orm/pg-core").PgColumn<{
|
|
29
|
+
name: any;
|
|
30
|
+
tableName: string;
|
|
31
|
+
dataType: any;
|
|
32
|
+
columnType: any;
|
|
33
|
+
data: any;
|
|
34
|
+
driverParam: any;
|
|
35
|
+
notNull: false;
|
|
36
|
+
hasDefault: false;
|
|
37
|
+
isPrimaryKey: false;
|
|
38
|
+
isAutoincrement: false;
|
|
39
|
+
hasRuntimeDefault: false;
|
|
40
|
+
enumValues: any;
|
|
41
|
+
baseColumn: never;
|
|
42
|
+
identity: undefined;
|
|
43
|
+
generated: undefined;
|
|
44
|
+
}, {}, {
|
|
45
|
+
[x: string]: any;
|
|
46
|
+
[x: number]: any;
|
|
47
|
+
[x: symbol]: any;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
dialect: "pg";
|
|
51
|
+
}>;
|
|
52
|
+
//# sourceMappingURL=define-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-table.d.ts","sourceRoot":"","sources":["../src/define-table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAcH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAgE7C;;GAEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAErC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsElE"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineTable — table definition wrapper for UnifiedCommerce plugins.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Drizzle's pgTable with auto-injected fields:
|
|
5
|
+
* - id (UUID primary key)
|
|
6
|
+
* - organizationId (text, NOT NULL) — on top-level tables only
|
|
7
|
+
* - createdAt, updatedAt (timestamps)
|
|
8
|
+
* - Org index + composite unique constraints
|
|
9
|
+
*
|
|
10
|
+
* Child tables (FK to org-scoped parent) get id + createdAt only.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* import { defineTable, column } from "@porulle/db";
|
|
14
|
+
*
|
|
15
|
+
* export const giftCards = defineTable("gift_cards", {
|
|
16
|
+
* code: column.text({ unique: true }),
|
|
17
|
+
* balance: column.integer(),
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
import { pgTable, uuid, text, integer, boolean, timestamp, jsonb, index, uniqueIndex, } from "drizzle-orm/pg-core";
|
|
21
|
+
import { getTableColumns } from "drizzle-orm";
|
|
22
|
+
function hasOrgColumn(table) {
|
|
23
|
+
if (!table || typeof table !== "object")
|
|
24
|
+
return false;
|
|
25
|
+
try {
|
|
26
|
+
const cols = getTableColumns(table);
|
|
27
|
+
return "organizationId" in cols;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function toSnake(name) {
|
|
34
|
+
return name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
35
|
+
}
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
function mapColumn(name, def) {
|
|
38
|
+
const sn = toSnake(name);
|
|
39
|
+
switch (def._type) {
|
|
40
|
+
case "text": {
|
|
41
|
+
let c = def.enum ? text(sn, { enum: def.enum }) : text(sn);
|
|
42
|
+
if (def.default !== undefined)
|
|
43
|
+
c = c.default(def.default);
|
|
44
|
+
if (!def.optional)
|
|
45
|
+
c = c.notNull();
|
|
46
|
+
return c;
|
|
47
|
+
}
|
|
48
|
+
case "integer": {
|
|
49
|
+
let c = integer(sn);
|
|
50
|
+
if (def.default !== undefined)
|
|
51
|
+
c = c.default(def.default);
|
|
52
|
+
if (!def.optional)
|
|
53
|
+
c = c.notNull();
|
|
54
|
+
return c;
|
|
55
|
+
}
|
|
56
|
+
case "boolean": {
|
|
57
|
+
let c = boolean(sn);
|
|
58
|
+
if (def.default !== undefined)
|
|
59
|
+
c = c.default(def.default);
|
|
60
|
+
if (!def.optional)
|
|
61
|
+
c = c.notNull();
|
|
62
|
+
return c;
|
|
63
|
+
}
|
|
64
|
+
case "uuid": {
|
|
65
|
+
let c = uuid(sn);
|
|
66
|
+
if (def.references) {
|
|
67
|
+
const refCols = getTableColumns(def.references);
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
c = c.references(() => refCols.id, { onDelete: "cascade" });
|
|
70
|
+
}
|
|
71
|
+
if (!def.optional)
|
|
72
|
+
c = c.notNull();
|
|
73
|
+
return c;
|
|
74
|
+
}
|
|
75
|
+
case "timestamp": {
|
|
76
|
+
let c = timestamp(sn, { withTimezone: true });
|
|
77
|
+
if (def.default === "now")
|
|
78
|
+
c = c.defaultNow();
|
|
79
|
+
if (!def.optional)
|
|
80
|
+
c = c.notNull();
|
|
81
|
+
return c;
|
|
82
|
+
}
|
|
83
|
+
case "json": {
|
|
84
|
+
let c = jsonb(sn);
|
|
85
|
+
if (def.default !== undefined)
|
|
86
|
+
c = c.default(def.default);
|
|
87
|
+
if (!def.optional)
|
|
88
|
+
c = c.notNull();
|
|
89
|
+
return c;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Define a database table with auto-injected UC fields.
|
|
95
|
+
*/
|
|
96
|
+
export function defineTable(name, columnDefs,
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
extraConfig) {
|
|
99
|
+
// Detect child table: any UUID column references an org-scoped parent?
|
|
100
|
+
let isChild = false;
|
|
101
|
+
for (const def of Object.values(columnDefs)) {
|
|
102
|
+
if (def._type === "uuid" && def.references && hasOrgColumn(def.references)) {
|
|
103
|
+
isChild = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Map user columns to Drizzle builders
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
|
+
const cols = {};
|
|
110
|
+
const uniqueCols = [];
|
|
111
|
+
for (const [colName, def] of Object.entries(columnDefs)) {
|
|
112
|
+
cols[colName] = mapColumn(colName, def);
|
|
113
|
+
if ("unique" in def && def.unique) {
|
|
114
|
+
uniqueCols.push(colName);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Build full column set with auto-injected fields
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
const allColumns = {
|
|
120
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
121
|
+
};
|
|
122
|
+
if (!isChild) {
|
|
123
|
+
allColumns.organizationId = text("organization_id").notNull();
|
|
124
|
+
}
|
|
125
|
+
Object.assign(allColumns, cols);
|
|
126
|
+
allColumns.createdAt = timestamp("created_at", { withTimezone: true })
|
|
127
|
+
.defaultNow()
|
|
128
|
+
.notNull();
|
|
129
|
+
if (!isChild) {
|
|
130
|
+
allColumns.updatedAt = timestamp("updated_at", { withTimezone: true })
|
|
131
|
+
.defaultNow()
|
|
132
|
+
.notNull();
|
|
133
|
+
}
|
|
134
|
+
// Create pgTable with indexes
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
const table = pgTable(name, allColumns, (t) => {
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
|
+
const indexes = {};
|
|
139
|
+
if (!isChild) {
|
|
140
|
+
indexes.orgIdx = index(`idx_${name}_org`).on(t.organizationId);
|
|
141
|
+
for (const colName of uniqueCols) {
|
|
142
|
+
const sn = toSnake(colName);
|
|
143
|
+
indexes[`${colName}Unique`] = uniqueIndex(`${name}_org_${sn}_unique`)
|
|
144
|
+
.on(t.organizationId, t[colName]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (extraConfig)
|
|
148
|
+
Object.assign(indexes, extraConfig(t));
|
|
149
|
+
return indexes;
|
|
150
|
+
});
|
|
151
|
+
// Mark for scoped DB proxy
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
table.__ucOrgScoped = !isChild;
|
|
154
|
+
return table;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=define-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-table.js","sourceRoot":"","sources":["../src/define-table.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @porulle/db — single import for all database work.
|
|
3
|
+
*
|
|
4
|
+
* Plugin developers import everything from here instead of drizzle-orm directly.
|
|
5
|
+
* Drizzle is an implementation detail — this package controls the surface.
|
|
6
|
+
*
|
|
7
|
+
* import { defineTable, column, eq, and, desc, sql } from "@porulle/db";
|
|
8
|
+
*/
|
|
9
|
+
export { defineTable } from "./define-table.js";
|
|
10
|
+
export { column } from "./column.js";
|
|
11
|
+
export type { ColumnDef } from "./column.js";
|
|
12
|
+
export { eq, ne, gt, gte, lt, lte, and, or, not, like, ilike, notLike, inArray, notInArray, between, notBetween, isNull, isNotNull, exists, notExists, sql, asc, desc, count, sum, avg, min, max, countDistinct, sumDistinct, avgDistinct, getTableColumns, } from "drizzle-orm";
|
|
13
|
+
export { pgTable, index, uniqueIndex, check, } from "drizzle-orm/pg-core";
|
|
14
|
+
export type { PgTable, PgDatabase, PgQueryResultHKT, } from "drizzle-orm/pg-core";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EACL,EAAE,EACF,EAAE,EACF,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,GAAG,EACH,EAAE,EACF,GAAG,EACH,IAAI,EACJ,KAAK,EACL,OAAO,EACP,OAAO,EACP,UAAU,EACV,OAAO,EACP,UAAU,EACV,MAAM,EACN,SAAS,EACT,MAAM,EACN,SAAS,EACT,GAAG,EACH,GAAG,EACH,IAAI,EACJ,KAAK,EACL,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,aAAa,EACb,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,GACN,MAAM,qBAAqB,CAAC;AAG7B,YAAY,EACV,OAAO,EACP,UAAU,EACV,gBAAgB,GACjB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @porulle/db — single import for all database work.
|
|
3
|
+
*
|
|
4
|
+
* Plugin developers import everything from here instead of drizzle-orm directly.
|
|
5
|
+
* Drizzle is an implementation detail — this package controls the surface.
|
|
6
|
+
*
|
|
7
|
+
* import { defineTable, column, eq, and, desc, sql } from "@porulle/db";
|
|
8
|
+
*/
|
|
9
|
+
// ─── UC Abstractions ──────────────────────────────────────────────────
|
|
10
|
+
export { defineTable } from "./define-table.js";
|
|
11
|
+
export { column } from "./column.js";
|
|
12
|
+
// ─── Drizzle Query Operators (re-exported) ────────────────────────────
|
|
13
|
+
export { eq, ne, gt, gte, lt, lte, and, or, not, like, ilike, notLike, inArray, notInArray, between, notBetween, isNull, isNotNull, exists, notExists, sql, asc, desc, count, sum, avg, min, max, countDistinct, sumDistinct, avgDistinct, getTableColumns, } from "drizzle-orm";
|
|
14
|
+
// ─── Drizzle PG-Specific (re-exported) ───────────────────────────────
|
|
15
|
+
export { pgTable, index, uniqueIndex, check, } from "drizzle-orm/pg-core";
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yEAAyE;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,yEAAyE;AACzE,OAAO,EACL,EAAE,EACF,EAAE,EACF,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,GAAG,EACH,EAAE,EACF,GAAG,EACH,IAAI,EACJ,KAAK,EACL,OAAO,EACP,OAAO,EACP,UAAU,EACV,OAAO,EACP,UAAU,EACV,MAAM,EACN,SAAS,EACT,MAAM,EACN,SAAS,EACT,GAAG,EACH,GAAG,EACH,IAAI,EACJ,KAAK,EACL,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,aAAa,EACb,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,wEAAwE;AACxE,OAAO,EACL,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,GACN,MAAM,qBAAqB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@porulle/db",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"bun": "./src/index.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./src/index.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"check-types": "tsc --noEmit",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"backfill-order-seq": "bun run ./scripts/backfill-order-seq.ts",
|
|
17
|
+
"backfill-inventory-org-id": "bun run ./scripts/backfill-inventory-org-id.ts",
|
|
18
|
+
"build": "rm -rf dist tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@porulle/core": "workspace:*",
|
|
22
|
+
"drizzle-orm": "^0.45.1",
|
|
23
|
+
"postgres": "^3.4.7"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@repo/typescript-config": "*",
|
|
27
|
+
"@types/node": "^24.5.2",
|
|
28
|
+
"typescript": "5.9.2",
|
|
29
|
+
"vitest": "^3.2.4"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"src",
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"description": "The database surface plugins use instead of importing Drizzle directly.",
|
|
40
|
+
"homepage": "https://porulle-docs.vercel.app",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/asyncdotengineering/porulle/issues"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/asyncdotengineering/porulle.git",
|
|
47
|
+
"directory": "packages/db"
|
|
48
|
+
},
|
|
49
|
+
"author": "Porulle contributors"
|
|
50
|
+
}
|
package/src/column.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column definition helpers for defineTable.
|
|
3
|
+
*
|
|
4
|
+
* These produce config objects that defineTable maps to Drizzle column builders.
|
|
5
|
+
* Plugin developers use these instead of importing from drizzle-orm/pg-core directly.
|
|
6
|
+
*
|
|
7
|
+
* import { defineTable, column } from "@porulle/db";
|
|
8
|
+
*
|
|
9
|
+
* export const giftCards = defineTable("gift_cards", {
|
|
10
|
+
* code: column.text({ unique: true }),
|
|
11
|
+
* balance: column.integer(),
|
|
12
|
+
* status: column.text({ enum: ["active", "disabled"], default: "active" }),
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface TextColumnDef {
|
|
17
|
+
readonly _type: "text";
|
|
18
|
+
unique?: boolean;
|
|
19
|
+
optional?: boolean;
|
|
20
|
+
enum?: readonly string[];
|
|
21
|
+
default?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IntegerColumnDef {
|
|
25
|
+
readonly _type: "integer";
|
|
26
|
+
unique?: boolean;
|
|
27
|
+
optional?: boolean;
|
|
28
|
+
default?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BooleanColumnDef {
|
|
32
|
+
readonly _type: "boolean";
|
|
33
|
+
optional?: boolean;
|
|
34
|
+
default?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UuidColumnDef {
|
|
38
|
+
readonly _type: "uuid";
|
|
39
|
+
optional?: boolean;
|
|
40
|
+
references?: unknown; // PgTable reference for FK detection
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TimestampColumnDef {
|
|
44
|
+
readonly _type: "timestamp";
|
|
45
|
+
optional?: boolean;
|
|
46
|
+
default?: "now";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface JsonColumnDef {
|
|
50
|
+
readonly _type: "json";
|
|
51
|
+
optional?: boolean;
|
|
52
|
+
default?: unknown;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ColumnDef =
|
|
56
|
+
| TextColumnDef
|
|
57
|
+
| IntegerColumnDef
|
|
58
|
+
| BooleanColumnDef
|
|
59
|
+
| UuidColumnDef
|
|
60
|
+
| TimestampColumnDef
|
|
61
|
+
| JsonColumnDef;
|
|
62
|
+
|
|
63
|
+
export const column = {
|
|
64
|
+
text: (opts?: Omit<TextColumnDef, "_type">): TextColumnDef => ({
|
|
65
|
+
_type: "text" as const,
|
|
66
|
+
...opts,
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
integer: (opts?: Omit<IntegerColumnDef, "_type">): IntegerColumnDef => ({
|
|
70
|
+
_type: "integer" as const,
|
|
71
|
+
...opts,
|
|
72
|
+
}),
|
|
73
|
+
|
|
74
|
+
boolean: (opts?: Omit<BooleanColumnDef, "_type">): BooleanColumnDef => ({
|
|
75
|
+
_type: "boolean" as const,
|
|
76
|
+
...opts,
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
uuid: (opts?: Omit<UuidColumnDef, "_type">): UuidColumnDef => ({
|
|
80
|
+
_type: "uuid" as const,
|
|
81
|
+
...opts,
|
|
82
|
+
}),
|
|
83
|
+
|
|
84
|
+
timestamp: (opts?: Omit<TimestampColumnDef, "_type">): TimestampColumnDef => ({
|
|
85
|
+
_type: "timestamp" as const,
|
|
86
|
+
...opts,
|
|
87
|
+
}),
|
|
88
|
+
|
|
89
|
+
json: (opts?: Omit<JsonColumnDef, "_type">): JsonColumnDef => ({
|
|
90
|
+
_type: "json" as const,
|
|
91
|
+
...opts,
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { getTableColumns } from "drizzle-orm";
|
|
3
|
+
import { defineTable } from "./define-table.js";
|
|
4
|
+
import { column } from "./column.js";
|
|
5
|
+
|
|
6
|
+
describe("defineTable", () => {
|
|
7
|
+
it("auto-injects id, organizationId, createdAt, updatedAt on top-level tables", () => {
|
|
8
|
+
const table = defineTable("test_products", {
|
|
9
|
+
name: column.text(),
|
|
10
|
+
price: column.integer(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const cols = getTableColumns(table);
|
|
14
|
+
expect(cols).toHaveProperty("id");
|
|
15
|
+
expect(cols).toHaveProperty("organizationId");
|
|
16
|
+
expect(cols).toHaveProperty("createdAt");
|
|
17
|
+
expect(cols).toHaveProperty("updatedAt");
|
|
18
|
+
expect(cols).toHaveProperty("name");
|
|
19
|
+
expect(cols).toHaveProperty("price");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("marks top-level tables as __ucOrgScoped", () => {
|
|
23
|
+
const table = defineTable("test_items", {
|
|
24
|
+
title: column.text(),
|
|
25
|
+
});
|
|
26
|
+
expect((table as Record<string, unknown>).__ucOrgScoped).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("detects child tables (FK to org-scoped parent) and skips organizationId", () => {
|
|
30
|
+
const parent = defineTable("test_parent", {
|
|
31
|
+
slug: column.text({ unique: true }),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const child = defineTable("test_child", {
|
|
35
|
+
parentId: column.uuid({ references: parent }),
|
|
36
|
+
note: column.text({ optional: true }),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const parentCols = getTableColumns(parent);
|
|
40
|
+
const childCols = getTableColumns(child);
|
|
41
|
+
|
|
42
|
+
// Parent has organizationId
|
|
43
|
+
expect(parentCols).toHaveProperty("organizationId");
|
|
44
|
+
// Child does NOT
|
|
45
|
+
expect(childCols).not.toHaveProperty("organizationId");
|
|
46
|
+
// Child has id + createdAt but no updatedAt
|
|
47
|
+
expect(childCols).toHaveProperty("id");
|
|
48
|
+
expect(childCols).toHaveProperty("createdAt");
|
|
49
|
+
expect(childCols).not.toHaveProperty("updatedAt");
|
|
50
|
+
// Child is not org-scoped
|
|
51
|
+
expect((child as Record<string, unknown>).__ucOrgScoped).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("supports all column types", () => {
|
|
55
|
+
const table = defineTable("test_all_types", {
|
|
56
|
+
name: column.text(),
|
|
57
|
+
count: column.integer({ default: 0 }),
|
|
58
|
+
active: column.boolean({ default: true }),
|
|
59
|
+
data: column.json({ default: {} }),
|
|
60
|
+
happenedAt: column.timestamp({ optional: true }),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const cols = getTableColumns(table);
|
|
64
|
+
expect(cols).toHaveProperty("name");
|
|
65
|
+
expect(cols).toHaveProperty("count");
|
|
66
|
+
expect(cols).toHaveProperty("active");
|
|
67
|
+
expect(cols).toHaveProperty("data");
|
|
68
|
+
expect(cols).toHaveProperty("happenedAt");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("supports enum columns", () => {
|
|
72
|
+
const table = defineTable("test_enum", {
|
|
73
|
+
status: column.text({ enum: ["active", "disabled", "exhausted"], default: "active" }),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const cols = getTableColumns(table);
|
|
77
|
+
expect(cols).toHaveProperty("status");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineTable — table definition wrapper for UnifiedCommerce plugins.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Drizzle's pgTable with auto-injected fields:
|
|
5
|
+
* - id (UUID primary key)
|
|
6
|
+
* - organizationId (text, NOT NULL) — on top-level tables only
|
|
7
|
+
* - createdAt, updatedAt (timestamps)
|
|
8
|
+
* - Org index + composite unique constraints
|
|
9
|
+
*
|
|
10
|
+
* Child tables (FK to org-scoped parent) get id + createdAt only.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* import { defineTable, column } from "@porulle/db";
|
|
14
|
+
*
|
|
15
|
+
* export const giftCards = defineTable("gift_cards", {
|
|
16
|
+
* code: column.text({ unique: true }),
|
|
17
|
+
* balance: column.integer(),
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
pgTable,
|
|
23
|
+
uuid,
|
|
24
|
+
text,
|
|
25
|
+
integer,
|
|
26
|
+
boolean,
|
|
27
|
+
timestamp,
|
|
28
|
+
jsonb,
|
|
29
|
+
index,
|
|
30
|
+
uniqueIndex,
|
|
31
|
+
} from "drizzle-orm/pg-core";
|
|
32
|
+
import { getTableColumns } from "drizzle-orm";
|
|
33
|
+
import type { ColumnDef } from "./column.js";
|
|
34
|
+
|
|
35
|
+
function hasOrgColumn(table: unknown): boolean {
|
|
36
|
+
if (!table || typeof table !== "object") return false;
|
|
37
|
+
try {
|
|
38
|
+
const cols = getTableColumns(table as Parameters<typeof getTableColumns>[0]);
|
|
39
|
+
return "organizationId" in cols;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toSnake(name: string): string {
|
|
46
|
+
return name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
function mapColumn(name: string, def: ColumnDef): any {
|
|
51
|
+
const sn = toSnake(name);
|
|
52
|
+
|
|
53
|
+
switch (def._type) {
|
|
54
|
+
case "text": {
|
|
55
|
+
let c = def.enum ? text(sn, { enum: def.enum as [string, ...string[]] }) : text(sn);
|
|
56
|
+
if (def.default !== undefined) c = c.default(def.default) as typeof c;
|
|
57
|
+
if (!def.optional) c = c.notNull() as typeof c;
|
|
58
|
+
return c;
|
|
59
|
+
}
|
|
60
|
+
case "integer": {
|
|
61
|
+
let c = integer(sn);
|
|
62
|
+
if (def.default !== undefined) c = c.default(def.default) as typeof c;
|
|
63
|
+
if (!def.optional) c = c.notNull() as typeof c;
|
|
64
|
+
return c;
|
|
65
|
+
}
|
|
66
|
+
case "boolean": {
|
|
67
|
+
let c = boolean(sn);
|
|
68
|
+
if (def.default !== undefined) c = c.default(def.default) as typeof c;
|
|
69
|
+
if (!def.optional) c = c.notNull() as typeof c;
|
|
70
|
+
return c;
|
|
71
|
+
}
|
|
72
|
+
case "uuid": {
|
|
73
|
+
let c = uuid(sn);
|
|
74
|
+
if (def.references) {
|
|
75
|
+
const refCols = getTableColumns(def.references as Parameters<typeof getTableColumns>[0]);
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
c = c.references(() => (refCols as any).id, { onDelete: "cascade" }) as typeof c;
|
|
78
|
+
}
|
|
79
|
+
if (!def.optional) c = c.notNull() as typeof c;
|
|
80
|
+
return c;
|
|
81
|
+
}
|
|
82
|
+
case "timestamp": {
|
|
83
|
+
let c = timestamp(sn, { withTimezone: true });
|
|
84
|
+
if (def.default === "now") c = c.defaultNow() as typeof c;
|
|
85
|
+
if (!def.optional) c = c.notNull() as typeof c;
|
|
86
|
+
return c;
|
|
87
|
+
}
|
|
88
|
+
case "json": {
|
|
89
|
+
let c = jsonb(sn);
|
|
90
|
+
if (def.default !== undefined) c = c.default(def.default) as typeof c;
|
|
91
|
+
if (!def.optional) c = c.notNull() as typeof c;
|
|
92
|
+
return c;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Define a database table with auto-injected UC fields.
|
|
99
|
+
*/
|
|
100
|
+
export function defineTable(
|
|
101
|
+
name: string,
|
|
102
|
+
columnDefs: Record<string, ColumnDef>,
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
extraConfig?: (table: Record<string, any>) => Record<string, any>,
|
|
105
|
+
) {
|
|
106
|
+
// Detect child table: any UUID column references an org-scoped parent?
|
|
107
|
+
let isChild = false;
|
|
108
|
+
for (const def of Object.values(columnDefs)) {
|
|
109
|
+
if (def._type === "uuid" && def.references && hasOrgColumn(def.references)) {
|
|
110
|
+
isChild = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Map user columns to Drizzle builders
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
const cols: Record<string, any> = {};
|
|
118
|
+
const uniqueCols: string[] = [];
|
|
119
|
+
|
|
120
|
+
for (const [colName, def] of Object.entries(columnDefs)) {
|
|
121
|
+
cols[colName] = mapColumn(colName, def);
|
|
122
|
+
if ("unique" in def && def.unique) {
|
|
123
|
+
uniqueCols.push(colName);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Build full column set with auto-injected fields
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
const allColumns: Record<string, any> = {
|
|
130
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if (!isChild) {
|
|
134
|
+
allColumns.organizationId = text("organization_id").notNull();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Object.assign(allColumns, cols);
|
|
138
|
+
|
|
139
|
+
allColumns.createdAt = timestamp("created_at", { withTimezone: true })
|
|
140
|
+
.defaultNow()
|
|
141
|
+
.notNull();
|
|
142
|
+
|
|
143
|
+
if (!isChild) {
|
|
144
|
+
allColumns.updatedAt = timestamp("updated_at", { withTimezone: true })
|
|
145
|
+
.defaultNow()
|
|
146
|
+
.notNull();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Create pgTable with indexes
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
151
|
+
const table = pgTable(name, allColumns as any, (t: any) => {
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
const indexes: Record<string, any> = {};
|
|
154
|
+
|
|
155
|
+
if (!isChild) {
|
|
156
|
+
indexes.orgIdx = index(`idx_${name}_org`).on(t.organizationId);
|
|
157
|
+
|
|
158
|
+
for (const colName of uniqueCols) {
|
|
159
|
+
const sn = toSnake(colName);
|
|
160
|
+
indexes[`${colName}Unique`] = uniqueIndex(`${name}_org_${sn}_unique`)
|
|
161
|
+
.on(t.organizationId, t[colName]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (extraConfig) Object.assign(indexes, extraConfig(t));
|
|
166
|
+
return indexes;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Mark for scoped DB proxy
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
(table as any).__ucOrgScoped = !isChild;
|
|
172
|
+
|
|
173
|
+
return table;
|
|
174
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @porulle/db — single import for all database work.
|
|
3
|
+
*
|
|
4
|
+
* Plugin developers import everything from here instead of drizzle-orm directly.
|
|
5
|
+
* Drizzle is an implementation detail — this package controls the surface.
|
|
6
|
+
*
|
|
7
|
+
* import { defineTable, column, eq, and, desc, sql } from "@porulle/db";
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ─── UC Abstractions ──────────────────────────────────────────────────
|
|
11
|
+
export { defineTable } from "./define-table.js";
|
|
12
|
+
export { column } from "./column.js";
|
|
13
|
+
export type { ColumnDef } from "./column.js";
|
|
14
|
+
|
|
15
|
+
// ─── Drizzle Query Operators (re-exported) ────────────────────────────
|
|
16
|
+
export {
|
|
17
|
+
eq,
|
|
18
|
+
ne,
|
|
19
|
+
gt,
|
|
20
|
+
gte,
|
|
21
|
+
lt,
|
|
22
|
+
lte,
|
|
23
|
+
and,
|
|
24
|
+
or,
|
|
25
|
+
not,
|
|
26
|
+
like,
|
|
27
|
+
ilike,
|
|
28
|
+
notLike,
|
|
29
|
+
inArray,
|
|
30
|
+
notInArray,
|
|
31
|
+
between,
|
|
32
|
+
notBetween,
|
|
33
|
+
isNull,
|
|
34
|
+
isNotNull,
|
|
35
|
+
exists,
|
|
36
|
+
notExists,
|
|
37
|
+
sql,
|
|
38
|
+
asc,
|
|
39
|
+
desc,
|
|
40
|
+
count,
|
|
41
|
+
sum,
|
|
42
|
+
avg,
|
|
43
|
+
min,
|
|
44
|
+
max,
|
|
45
|
+
countDistinct,
|
|
46
|
+
sumDistinct,
|
|
47
|
+
avgDistinct,
|
|
48
|
+
getTableColumns,
|
|
49
|
+
} from "drizzle-orm";
|
|
50
|
+
|
|
51
|
+
// ─── Drizzle PG-Specific (re-exported) ───────────────────────────────
|
|
52
|
+
export {
|
|
53
|
+
pgTable,
|
|
54
|
+
index,
|
|
55
|
+
uniqueIndex,
|
|
56
|
+
check,
|
|
57
|
+
} from "drizzle-orm/pg-core";
|
|
58
|
+
|
|
59
|
+
// ─── Drizzle Types (re-exported for advanced use) ─────────────────────
|
|
60
|
+
export type {
|
|
61
|
+
PgTable,
|
|
62
|
+
PgDatabase,
|
|
63
|
+
PgQueryResultHKT,
|
|
64
|
+
} from "drizzle-orm/pg-core";
|