@porulle/db 0.8.0 → 0.10.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/column.d.ts +9 -2
- package/dist/column.d.ts.map +1 -1
- package/dist/column.js +6 -0
- package/dist/column.js.map +1 -1
- package/dist/define-table.d.ts +6 -0
- package/dist/define-table.d.ts.map +1 -1
- package/dist/define-table.js +20 -4
- package/dist/define-table.js.map +1 -1
- package/package.json +2 -2
- package/src/column.ts +10 -2
- package/src/define-table.test.ts +53 -0
- package/src/define-table.ts +19 -4
package/dist/column.d.ts
CHANGED
|
@@ -7,21 +7,28 @@
|
|
|
7
7
|
* import { defineTable, column } from "@porulle/db";
|
|
8
8
|
*
|
|
9
9
|
* export const giftCards = defineTable("gift_cards", {
|
|
10
|
+
* // Per-org unique (default): UNIQUE (organization_id, code)
|
|
10
11
|
* code: column.text({ unique: true }),
|
|
12
|
+
* // Globally unique across all tenants: UNIQUE (public_code)
|
|
13
|
+
* publicCode: column.text({ unique: "global" }),
|
|
11
14
|
* balance: column.integer(),
|
|
12
15
|
* status: column.text({ enum: ["active", "disabled"], default: "active" }),
|
|
13
16
|
* });
|
|
17
|
+
*
|
|
18
|
+
* Child tables (FK to org-scoped parent) have no organizationId; unique: true
|
|
19
|
+
* creates UNIQUE (col) on the child column.
|
|
14
20
|
*/
|
|
21
|
+
export type ColumnUnique = boolean | "global";
|
|
15
22
|
export interface TextColumnDef {
|
|
16
23
|
readonly _type: "text";
|
|
17
|
-
unique?:
|
|
24
|
+
unique?: ColumnUnique;
|
|
18
25
|
optional?: boolean;
|
|
19
26
|
enum?: readonly string[];
|
|
20
27
|
default?: string;
|
|
21
28
|
}
|
|
22
29
|
export interface IntegerColumnDef {
|
|
23
30
|
readonly _type: "integer";
|
|
24
|
-
unique?:
|
|
31
|
+
unique?: ColumnUnique;
|
|
25
32
|
optional?: boolean;
|
|
26
33
|
default?: number;
|
|
27
34
|
}
|
package/dist/column.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column.d.ts","sourceRoot":"","sources":["../src/column.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"column.d.ts","sourceRoot":"","sources":["../src/column.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,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,YAAY,CAAC;IACtB,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
CHANGED
|
@@ -7,10 +7,16 @@
|
|
|
7
7
|
* import { defineTable, column } from "@porulle/db";
|
|
8
8
|
*
|
|
9
9
|
* export const giftCards = defineTable("gift_cards", {
|
|
10
|
+
* // Per-org unique (default): UNIQUE (organization_id, code)
|
|
10
11
|
* code: column.text({ unique: true }),
|
|
12
|
+
* // Globally unique across all tenants: UNIQUE (public_code)
|
|
13
|
+
* publicCode: column.text({ unique: "global" }),
|
|
11
14
|
* balance: column.integer(),
|
|
12
15
|
* status: column.text({ enum: ["active", "disabled"], default: "active" }),
|
|
13
16
|
* });
|
|
17
|
+
*
|
|
18
|
+
* Child tables (FK to org-scoped parent) have no organizationId; unique: true
|
|
19
|
+
* creates UNIQUE (col) on the child column.
|
|
14
20
|
*/
|
|
15
21
|
export const column = {
|
|
16
22
|
text: (opts) => ({
|
package/dist/column.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column.js","sourceRoot":"","sources":["../src/column.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"column.js","sourceRoot":"","sources":["../src/column.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAmDH,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"}
|
package/dist/define-table.d.ts
CHANGED
|
@@ -13,9 +13,15 @@
|
|
|
13
13
|
* import { defineTable, column } from "@porulle/db";
|
|
14
14
|
*
|
|
15
15
|
* export const giftCards = defineTable("gift_cards", {
|
|
16
|
+
* // Per-org unique (default): UNIQUE (organization_id, code)
|
|
16
17
|
* code: column.text({ unique: true }),
|
|
18
|
+
* // Globally unique: UNIQUE (public_code)
|
|
19
|
+
* publicCode: column.text({ unique: "global" }),
|
|
17
20
|
* balance: column.integer(),
|
|
18
21
|
* });
|
|
22
|
+
*
|
|
23
|
+
* Child tables (FK to org-scoped parent) have no organizationId; unique: true
|
|
24
|
+
* creates UNIQUE (col) on the child column.
|
|
19
25
|
*/
|
|
20
26
|
import type { ColumnDef } from "./column.js";
|
|
21
27
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-table.d.ts","sourceRoot":"","sources":["../src/define-table.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"define-table.d.ts","sourceRoot":"","sources":["../src/define-table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+ElE"}
|
package/dist/define-table.js
CHANGED
|
@@ -13,9 +13,15 @@
|
|
|
13
13
|
* import { defineTable, column } from "@porulle/db";
|
|
14
14
|
*
|
|
15
15
|
* export const giftCards = defineTable("gift_cards", {
|
|
16
|
+
* // Per-org unique (default): UNIQUE (organization_id, code)
|
|
16
17
|
* code: column.text({ unique: true }),
|
|
18
|
+
* // Globally unique: UNIQUE (public_code)
|
|
19
|
+
* publicCode: column.text({ unique: "global" }),
|
|
17
20
|
* balance: column.integer(),
|
|
18
21
|
* });
|
|
22
|
+
*
|
|
23
|
+
* Child tables (FK to org-scoped parent) have no organizationId; unique: true
|
|
24
|
+
* creates UNIQUE (col) on the child column.
|
|
19
25
|
*/
|
|
20
26
|
import { pgTable, uuid, text, integer, boolean, timestamp, jsonb, index, uniqueIndex, } from "drizzle-orm/pg-core";
|
|
21
27
|
import { getTableColumns } from "drizzle-orm";
|
|
@@ -107,11 +113,17 @@ extraConfig) {
|
|
|
107
113
|
// Map user columns to Drizzle builders
|
|
108
114
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
115
|
const cols = {};
|
|
110
|
-
const
|
|
116
|
+
const perOrgUniqueCols = [];
|
|
117
|
+
const globalUniqueCols = [];
|
|
111
118
|
for (const [colName, def] of Object.entries(columnDefs)) {
|
|
112
119
|
cols[colName] = mapColumn(colName, def);
|
|
113
|
-
if ("unique" in def
|
|
114
|
-
|
|
120
|
+
if (!("unique" in def) || !def.unique)
|
|
121
|
+
continue;
|
|
122
|
+
if (def.unique === "global" || isChild) {
|
|
123
|
+
globalUniqueCols.push(colName);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
perOrgUniqueCols.push(colName);
|
|
115
127
|
}
|
|
116
128
|
}
|
|
117
129
|
// Build full column set with auto-injected fields
|
|
@@ -138,12 +150,16 @@ extraConfig) {
|
|
|
138
150
|
const indexes = {};
|
|
139
151
|
if (!isChild) {
|
|
140
152
|
indexes.orgIdx = index(`idx_${name}_org`).on(t.organizationId);
|
|
141
|
-
for (const colName of
|
|
153
|
+
for (const colName of perOrgUniqueCols) {
|
|
142
154
|
const sn = toSnake(colName);
|
|
143
155
|
indexes[`${colName}Unique`] = uniqueIndex(`${name}_org_${sn}_unique`)
|
|
144
156
|
.on(t.organizationId, t[colName]);
|
|
145
157
|
}
|
|
146
158
|
}
|
|
159
|
+
for (const colName of globalUniqueCols) {
|
|
160
|
+
const sn = toSnake(colName);
|
|
161
|
+
indexes[`${colName}Unique`] = uniqueIndex(`${name}_${sn}_unique`).on(t[colName]);
|
|
162
|
+
}
|
|
147
163
|
if (extraConfig)
|
|
148
164
|
Object.assign(indexes, extraConfig(t));
|
|
149
165
|
return indexes;
|
package/dist/define-table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-table.js","sourceRoot":"","sources":["../src/define-table.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"define-table.js","sourceRoot":"","sources":["../src/define-table.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"drizzle-orm": "^0.45.1",
|
|
15
15
|
"postgres": "^3.4.7",
|
|
16
|
-
"@porulle/core": "0.
|
|
16
|
+
"@porulle/core": "0.10.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^24.5.2",
|
package/src/column.ts
CHANGED
|
@@ -7,15 +7,23 @@
|
|
|
7
7
|
* import { defineTable, column } from "@porulle/db";
|
|
8
8
|
*
|
|
9
9
|
* export const giftCards = defineTable("gift_cards", {
|
|
10
|
+
* // Per-org unique (default): UNIQUE (organization_id, code)
|
|
10
11
|
* code: column.text({ unique: true }),
|
|
12
|
+
* // Globally unique across all tenants: UNIQUE (public_code)
|
|
13
|
+
* publicCode: column.text({ unique: "global" }),
|
|
11
14
|
* balance: column.integer(),
|
|
12
15
|
* status: column.text({ enum: ["active", "disabled"], default: "active" }),
|
|
13
16
|
* });
|
|
17
|
+
*
|
|
18
|
+
* Child tables (FK to org-scoped parent) have no organizationId; unique: true
|
|
19
|
+
* creates UNIQUE (col) on the child column.
|
|
14
20
|
*/
|
|
15
21
|
|
|
22
|
+
export type ColumnUnique = boolean | "global";
|
|
23
|
+
|
|
16
24
|
export interface TextColumnDef {
|
|
17
25
|
readonly _type: "text";
|
|
18
|
-
unique?:
|
|
26
|
+
unique?: ColumnUnique;
|
|
19
27
|
optional?: boolean;
|
|
20
28
|
enum?: readonly string[];
|
|
21
29
|
default?: string;
|
|
@@ -23,7 +31,7 @@ export interface TextColumnDef {
|
|
|
23
31
|
|
|
24
32
|
export interface IntegerColumnDef {
|
|
25
33
|
readonly _type: "integer";
|
|
26
|
-
unique?:
|
|
34
|
+
unique?: ColumnUnique;
|
|
27
35
|
optional?: boolean;
|
|
28
36
|
default?: number;
|
|
29
37
|
}
|
package/src/define-table.test.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { getTableColumns } from "drizzle-orm";
|
|
3
|
+
import { getTableConfig } from "drizzle-orm/pg-core";
|
|
4
|
+
import type { PgTable } from "drizzle-orm/pg-core";
|
|
3
5
|
import { defineTable } from "./define-table.js";
|
|
4
6
|
import { column } from "./column.js";
|
|
5
7
|
|
|
8
|
+
function uniqueIndexColumnNames(table: PgTable) {
|
|
9
|
+
const { indexes } = getTableConfig(table);
|
|
10
|
+
return indexes
|
|
11
|
+
.filter((idx) => idx.config.unique)
|
|
12
|
+
.map((idx) => ({
|
|
13
|
+
name: idx.config.name,
|
|
14
|
+
columns: idx.config.columns.map((col) => {
|
|
15
|
+
if (col && typeof col === "object" && "name" in col && col.name) {
|
|
16
|
+
return col.name as string;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
|
|
6
23
|
describe("defineTable", () => {
|
|
7
24
|
it("auto-injects id, organizationId, createdAt, updatedAt on top-level tables", () => {
|
|
8
25
|
const table = defineTable("test_products", {
|
|
@@ -76,4 +93,40 @@ describe("defineTable", () => {
|
|
|
76
93
|
const cols = getTableColumns(table);
|
|
77
94
|
expect(cols).toHaveProperty("status");
|
|
78
95
|
});
|
|
96
|
+
|
|
97
|
+
it("SEC-03: honors unique on child tables via UNIQUE (col)", () => {
|
|
98
|
+
const parent = defineTable("sec03_parent", {
|
|
99
|
+
slug: column.text({ unique: true }),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const child = defineTable("sec03_child", {
|
|
103
|
+
parentId: column.uuid({ references: parent }),
|
|
104
|
+
code: column.text({ unique: true }),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const uniqueIndexes = uniqueIndexColumnNames(child);
|
|
108
|
+
expect(uniqueIndexes).toHaveLength(1);
|
|
109
|
+
expect(uniqueIndexes[0]?.columns).toEqual(["code"]);
|
|
110
|
+
expect(uniqueIndexes[0]?.name).toBe("sec03_child_code_unique");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("SEC-04: unique true is per-org composite; unique global is single-column", () => {
|
|
114
|
+
const perOrg = defineTable("sec04_per_org", {
|
|
115
|
+
sku: column.text({ unique: true }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const perOrgIndexes = uniqueIndexColumnNames(perOrg);
|
|
119
|
+
expect(perOrgIndexes).toHaveLength(1);
|
|
120
|
+
expect(perOrgIndexes[0]?.columns).toEqual(["organization_id", "sku"]);
|
|
121
|
+
expect(perOrgIndexes[0]?.name).toBe("sec04_per_org_org_sku_unique");
|
|
122
|
+
|
|
123
|
+
const global = defineTable("sec04_global", {
|
|
124
|
+
publicCode: column.text({ unique: "global" }),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const globalIndexes = uniqueIndexColumnNames(global);
|
|
128
|
+
expect(globalIndexes).toHaveLength(1);
|
|
129
|
+
expect(globalIndexes[0]?.columns).toEqual(["public_code"]);
|
|
130
|
+
expect(globalIndexes[0]?.name).toBe("sec04_global_public_code_unique");
|
|
131
|
+
});
|
|
79
132
|
});
|
package/src/define-table.ts
CHANGED
|
@@ -13,9 +13,15 @@
|
|
|
13
13
|
* import { defineTable, column } from "@porulle/db";
|
|
14
14
|
*
|
|
15
15
|
* export const giftCards = defineTable("gift_cards", {
|
|
16
|
+
* // Per-org unique (default): UNIQUE (organization_id, code)
|
|
16
17
|
* code: column.text({ unique: true }),
|
|
18
|
+
* // Globally unique: UNIQUE (public_code)
|
|
19
|
+
* publicCode: column.text({ unique: "global" }),
|
|
17
20
|
* balance: column.integer(),
|
|
18
21
|
* });
|
|
22
|
+
*
|
|
23
|
+
* Child tables (FK to org-scoped parent) have no organizationId; unique: true
|
|
24
|
+
* creates UNIQUE (col) on the child column.
|
|
19
25
|
*/
|
|
20
26
|
|
|
21
27
|
import {
|
|
@@ -115,12 +121,16 @@ export function defineTable(
|
|
|
115
121
|
// Map user columns to Drizzle builders
|
|
116
122
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
123
|
const cols: Record<string, any> = {};
|
|
118
|
-
const
|
|
124
|
+
const perOrgUniqueCols: string[] = [];
|
|
125
|
+
const globalUniqueCols: string[] = [];
|
|
119
126
|
|
|
120
127
|
for (const [colName, def] of Object.entries(columnDefs)) {
|
|
121
128
|
cols[colName] = mapColumn(colName, def);
|
|
122
|
-
if ("unique" in def
|
|
123
|
-
|
|
129
|
+
if (!("unique" in def) || !def.unique) continue;
|
|
130
|
+
if (def.unique === "global" || isChild) {
|
|
131
|
+
globalUniqueCols.push(colName);
|
|
132
|
+
} else {
|
|
133
|
+
perOrgUniqueCols.push(colName);
|
|
124
134
|
}
|
|
125
135
|
}
|
|
126
136
|
|
|
@@ -155,13 +165,18 @@ export function defineTable(
|
|
|
155
165
|
if (!isChild) {
|
|
156
166
|
indexes.orgIdx = index(`idx_${name}_org`).on(t.organizationId);
|
|
157
167
|
|
|
158
|
-
for (const colName of
|
|
168
|
+
for (const colName of perOrgUniqueCols) {
|
|
159
169
|
const sn = toSnake(colName);
|
|
160
170
|
indexes[`${colName}Unique`] = uniqueIndex(`${name}_org_${sn}_unique`)
|
|
161
171
|
.on(t.organizationId, t[colName]);
|
|
162
172
|
}
|
|
163
173
|
}
|
|
164
174
|
|
|
175
|
+
for (const colName of globalUniqueCols) {
|
|
176
|
+
const sn = toSnake(colName);
|
|
177
|
+
indexes[`${colName}Unique`] = uniqueIndex(`${name}_${sn}_unique`).on(t[colName]);
|
|
178
|
+
}
|
|
179
|
+
|
|
165
180
|
if (extraConfig) Object.assign(indexes, extraConfig(t));
|
|
166
181
|
return indexes;
|
|
167
182
|
});
|