@manablox/db 0.1.0 → 0.2.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 +21 -0
- package/drizzle.config.ts +1 -1
- package/migrations/0005_menus.sql +44 -0
- package/migrations/0006_roles.sql +13 -0
- package/migrations/0007_apikey-permissions.sql +4 -0
- package/migrations/0008_workflows.sql +49 -0
- package/migrations/meta/0005_snapshot.json +2504 -0
- package/migrations/meta/0006_snapshot.json +2605 -0
- package/migrations/meta/0007_snapshot.json +2605 -0
- package/migrations/meta/0008_snapshot.json +2986 -0
- package/migrations/meta/_journal.json +28 -0
- package/package.json +10 -5
- package/src/cli/create-db.ts +30 -0
- package/src/cli/migrate.ts +2 -9
- package/src/client.ts +8 -1
- package/src/errors.ts +50 -0
- package/src/index.ts +8 -2
- package/src/migrate.ts +21 -0
- package/src/pagination.ts +52 -0
- package/src/query.ts +1 -5
- package/src/repositories/asset-usage.ts +1 -1
- package/src/repositories/asset.ts +13 -21
- package/src/repositories/content-type.ts +1 -1
- package/src/repositories/content.ts +150 -97
- package/src/repositories/index.ts +12 -0
- package/src/repositories/menu.ts +235 -0
- package/src/repositories/role.ts +85 -0
- package/src/repositories/space.ts +7 -2
- package/src/repositories/user.ts +171 -25
- package/src/repositories/webhook.ts +46 -0
- package/src/repositories/workflow.ts +306 -0
- package/src/schema/assets.ts +108 -0
- package/src/schema/auth.ts +166 -0
- package/src/schema/content-types.ts +31 -0
- package/src/schema/content.ts +133 -0
- package/src/schema/index.ts +38 -0
- package/src/schema/menus.ts +61 -0
- package/src/schema/relations.ts +64 -0
- package/src/schema/spaces.ts +20 -0
- package/src/schema/webhooks.ts +46 -0
- package/src/schema/workflows.ts +92 -0
- package/{test/helpers.ts → src/testing-fixtures.ts} +21 -35
- package/src/testing.ts +105 -0
- package/test/asset-usage.test.ts +3 -3
- package/test/menu.test.ts +126 -0
- package/test/publish.test.ts +31 -3
- package/test/query.test.ts +33 -3
- package/test/role.test.ts +81 -0
- package/test/tree.test.ts +3 -3
- package/test/user.test.ts +126 -0
- package/test/webhook.test.ts +48 -0
- package/vitest.config.ts +0 -2
- package/src/schema.ts +0 -513
|
@@ -36,6 +36,34 @@
|
|
|
36
36
|
"when": 1788544039067,
|
|
37
37
|
"tag": "0004_apikey-spaces",
|
|
38
38
|
"breakpoints": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"idx": 5,
|
|
42
|
+
"version": "7",
|
|
43
|
+
"when": 1788598393872,
|
|
44
|
+
"tag": "0005_menus",
|
|
45
|
+
"breakpoints": true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"idx": 6,
|
|
49
|
+
"version": "7",
|
|
50
|
+
"when": 1788636583704,
|
|
51
|
+
"tag": "0006_roles",
|
|
52
|
+
"breakpoints": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"idx": 7,
|
|
56
|
+
"version": "7",
|
|
57
|
+
"when": 1788639562448,
|
|
58
|
+
"tag": "0007_apikey-permissions",
|
|
59
|
+
"breakpoints": true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"idx": 8,
|
|
63
|
+
"version": "7",
|
|
64
|
+
"when": 1788650538612,
|
|
65
|
+
"tag": "0008_workflows",
|
|
66
|
+
"breakpoints": true
|
|
39
67
|
}
|
|
40
68
|
]
|
|
41
69
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manablox/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -8,14 +8,18 @@
|
|
|
8
8
|
"default": "./src/index.ts"
|
|
9
9
|
},
|
|
10
10
|
"./schema": {
|
|
11
|
-
"types": "./src/schema.ts",
|
|
12
|
-
"default": "./src/schema.ts"
|
|
11
|
+
"types": "./src/schema/index.ts",
|
|
12
|
+
"default": "./src/schema/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"./testing": {
|
|
15
|
+
"types": "./src/testing.ts",
|
|
16
|
+
"default": "./src/testing.ts"
|
|
13
17
|
}
|
|
14
18
|
},
|
|
15
19
|
"main": "./src/index.ts",
|
|
16
20
|
"types": "./src/index.ts",
|
|
17
21
|
"dependencies": {
|
|
18
|
-
"@manablox/core": "0.
|
|
22
|
+
"@manablox/core": "0.2.0",
|
|
19
23
|
"drizzle-orm": "^0.45.2",
|
|
20
24
|
"postgres": "^3.4.9"
|
|
21
25
|
},
|
|
@@ -32,6 +36,7 @@
|
|
|
32
36
|
"generate": "drizzle-kit generate",
|
|
33
37
|
"migrate": "tsx src/cli/migrate.ts",
|
|
34
38
|
"typecheck": "tsc --noEmit",
|
|
35
|
-
"test": "vitest run"
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"create-db": "tsx src/cli/create-db.ts"
|
|
36
41
|
}
|
|
37
42
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import postgres from 'postgres';
|
|
2
|
+
import { withDatabase } from '../testing.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates the database `DATABASE_URL` names, on the server it names, if it does not
|
|
6
|
+
* exist — `--fresh` drops it first. For throwaway databases (the end-to-end run) where
|
|
7
|
+
* nothing else provisions them.
|
|
8
|
+
*/
|
|
9
|
+
const url = process.env.DATABASE_URL;
|
|
10
|
+
if (!url) {
|
|
11
|
+
console.error('DATABASE_URL is required');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const name = new URL(url).pathname.replace(/^\//, '');
|
|
16
|
+
const fresh = process.argv.includes('--fresh');
|
|
17
|
+
const admin = postgres(withDatabase(url, 'postgres'), { max: 1 });
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
if (fresh) await admin.unsafe(`drop database if exists "${name}" with (force)`);
|
|
21
|
+
const [row] = await admin.unsafe(`select 1 as found from pg_database where datname = '${name}'`);
|
|
22
|
+
if (!row) {
|
|
23
|
+
await admin.unsafe(`create database "${name}"`);
|
|
24
|
+
console.info(`created database ${name}`);
|
|
25
|
+
} else {
|
|
26
|
+
console.info(`database ${name} exists`);
|
|
27
|
+
}
|
|
28
|
+
} finally {
|
|
29
|
+
await admin.end();
|
|
30
|
+
}
|
package/src/cli/migrate.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { dirname, resolve } from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
|
4
|
-
import { applyBootstrapSql } from '../bootstrap.js';
|
|
5
1
|
import { createDatabase } from '../client.js';
|
|
6
|
-
|
|
7
|
-
const migrationsFolder = resolve(dirname(fileURLToPath(import.meta.url)), '../../migrations');
|
|
2
|
+
import { runMigrations } from '../migrate.js';
|
|
8
3
|
|
|
9
4
|
const url = process.env.DATABASE_URL;
|
|
10
5
|
if (!url) {
|
|
@@ -15,9 +10,7 @@ if (!url) {
|
|
|
15
10
|
const handle = createDatabase({ url, max: 1 });
|
|
16
11
|
|
|
17
12
|
try {
|
|
18
|
-
|
|
19
|
-
await applyBootstrapSql(handle.sql);
|
|
20
|
-
await migrate(handle.db, { migrationsFolder });
|
|
13
|
+
await runMigrations(handle);
|
|
21
14
|
console.info('migrations applied');
|
|
22
15
|
} finally {
|
|
23
16
|
await handle.close();
|
package/src/client.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { DatabaseConfig } from '@manablox/core';
|
|
2
2
|
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
3
3
|
import postgres from 'postgres';
|
|
4
|
-
import * as schema from './schema.js';
|
|
4
|
+
import * as schema from './schema/index.js';
|
|
5
5
|
|
|
6
6
|
export type Database = PostgresJsDatabase<typeof schema>;
|
|
7
|
+
/** What `db.transaction((tx) => …)` hands its callback. */
|
|
8
|
+
export type Transaction = Parameters<Parameters<Database['transaction']>[0]>[0];
|
|
9
|
+
/**
|
|
10
|
+
* Anything a query can run on. Repository internals take this so the same helper serves
|
|
11
|
+
* a call inside a transaction and one outside it, without casting `tx` to `Database`.
|
|
12
|
+
*/
|
|
13
|
+
export type Executor = Database | Transaction;
|
|
7
14
|
export type Sql = ReturnType<typeof postgres>;
|
|
8
15
|
|
|
9
16
|
export interface DatabaseHandle {
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ErrorDetail, type ErrorKey, ManabloxError } from '@manablox/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Postgres reports a unique-constraint violation as SQLSTATE `23505` with the constraint
|
|
5
|
+
* name in the message. Drizzle wraps the driver error, so the code and the message sit on
|
|
6
|
+
* `cause`, while the outer message is only ever "Failed query: insert into …".
|
|
7
|
+
*/
|
|
8
|
+
export function isUniqueViolation(error: unknown, constraint: string): boolean {
|
|
9
|
+
const cause = (error as { cause?: { code?: string; message?: string } })?.cause;
|
|
10
|
+
const code = (error as { code?: string })?.code ?? cause?.code;
|
|
11
|
+
const detail = `${cause?.message ?? ''} ${(error as { message?: string })?.message ?? ''}`;
|
|
12
|
+
return code === '23505' && detail.includes(constraint);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UniqueViolationMapping {
|
|
16
|
+
/** The constraint name, or a distinctive part of it. */
|
|
17
|
+
constraint: string;
|
|
18
|
+
/** The detail reported for the field, e.g. `content.slug.duplicate`. */
|
|
19
|
+
key: ErrorKey;
|
|
20
|
+
path?: (string | number)[];
|
|
21
|
+
params?: Record<string, unknown>;
|
|
22
|
+
/** The error's own key. */
|
|
23
|
+
errorKey: ErrorKey;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Turns a unique violation on one constraint into the validation error the client would
|
|
28
|
+
* have received had the check run before the write, and rethrows anything else untouched.
|
|
29
|
+
* Use as `.catch((error) => rethrowUniqueViolation(error, { … }))`.
|
|
30
|
+
*/
|
|
31
|
+
export function rethrowUniqueViolation(error: unknown, mapping: UniqueViolationMapping): never {
|
|
32
|
+
if (!isUniqueViolation(error, mapping.constraint)) throw error;
|
|
33
|
+
|
|
34
|
+
const detail: ErrorDetail = {
|
|
35
|
+
key: mapping.key,
|
|
36
|
+
...(mapping.path ? { path: mapping.path } : {}),
|
|
37
|
+
...(mapping.params ? { params: mapping.params } : {}),
|
|
38
|
+
};
|
|
39
|
+
throw ManabloxError.validation([detail], mapping.errorKey);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A preset for one constraint: `const taken = uniqueViolation({ … })` once beside the
|
|
44
|
+
* service, then `.catch(taken({ machineName }))` on every write that can trip it. The
|
|
45
|
+
* params are the only part that differs per call, so they are the only argument left.
|
|
46
|
+
*/
|
|
47
|
+
export function uniqueViolation(preset: Omit<UniqueViolationMapping, 'params'>) {
|
|
48
|
+
return (params: Record<string, unknown>) => (error: unknown) =>
|
|
49
|
+
rethrowUniqueViolation(error, { ...preset, params });
|
|
50
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
export * from './bootstrap.js';
|
|
2
2
|
export * from './client.js';
|
|
3
3
|
export * from './columns.js';
|
|
4
|
+
export * from './errors.js';
|
|
5
|
+
export * from './migrate.js';
|
|
6
|
+
export * from './pagination.js';
|
|
4
7
|
export * from './query.js';
|
|
5
8
|
export * from './repositories/asset.js';
|
|
6
9
|
export * from './repositories/asset-usage.js';
|
|
7
10
|
export * from './repositories/content.js';
|
|
8
11
|
export * from './repositories/content-type.js';
|
|
9
12
|
export * from './repositories/index.js';
|
|
13
|
+
export * from './repositories/menu.js';
|
|
10
14
|
export * from './repositories/space.js';
|
|
11
15
|
export * from './repositories/user.js';
|
|
12
|
-
export *
|
|
13
|
-
export * from './
|
|
16
|
+
export * from './repositories/webhook.js';
|
|
17
|
+
export * from './repositories/workflow.js';
|
|
18
|
+
export * as schema from './schema/index.js';
|
|
19
|
+
export * from './schema/index.js';
|
package/src/migrate.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
|
4
|
+
import { applyBootstrapSql } from './bootstrap.js';
|
|
5
|
+
import type { DatabaseHandle } from './client.js';
|
|
6
|
+
|
|
7
|
+
/** The SQL files shipped with this package, next to `src/`. */
|
|
8
|
+
export const MIGRATIONS_FOLDER = resolve(dirname(fileURLToPath(import.meta.url)), '../migrations');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Brings a database up to the schema this version of the package expects.
|
|
12
|
+
*
|
|
13
|
+
* The same call the compose stack's `migrate` service and the `manablox migrate` CLI
|
|
14
|
+
* make, so an instance run from a dependency and one run from the repository apply the
|
|
15
|
+
* same files in the same order. Idempotent: drizzle records what it has applied.
|
|
16
|
+
*/
|
|
17
|
+
export async function runMigrations(handle: DatabaseHandle): Promise<void> {
|
|
18
|
+
// Extensions must exist before the generated migrations reference ltree columns.
|
|
19
|
+
await applyBootstrapSql(handle.sql);
|
|
20
|
+
await migrate(handle.db, { migrationsFolder: MIGRATIONS_FOLDER });
|
|
21
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getTableColumns, type SQL, sql } from 'drizzle-orm';
|
|
2
|
+
import type { PgColumn, PgTable } from 'drizzle-orm/pg-core';
|
|
3
|
+
import type { Executor } from './client.js';
|
|
4
|
+
import type { Pagination } from './query.js';
|
|
5
|
+
|
|
6
|
+
export interface Paginated<T> {
|
|
7
|
+
items: T[];
|
|
8
|
+
total: number;
|
|
9
|
+
limit: number;
|
|
10
|
+
offset: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One page of a table plus the total, in one round trip: a window `count(*) over ()`
|
|
15
|
+
* rides along with the rows. A page past the end comes back empty and so carries no
|
|
16
|
+
* count; only then is the total asked for separately, so a caller paging by `total`
|
|
17
|
+
* still learns the true size.
|
|
18
|
+
*/
|
|
19
|
+
export async function paginate<TTable extends PgTable>(
|
|
20
|
+
db: Executor,
|
|
21
|
+
table: TTable,
|
|
22
|
+
options: {
|
|
23
|
+
where?: SQL | undefined;
|
|
24
|
+
orderBy: SQL | PgColumn | (SQL | PgColumn)[];
|
|
25
|
+
pagination: Pagination;
|
|
26
|
+
},
|
|
27
|
+
): Promise<Paginated<TTable['$inferSelect']>> {
|
|
28
|
+
const orderBy = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
|
|
29
|
+
const rows = (await db
|
|
30
|
+
.select({ ...getTableColumns(table), total: sql<number>`count(*) over ()::int` })
|
|
31
|
+
.from(table as PgTable)
|
|
32
|
+
.where(options.where)
|
|
33
|
+
.orderBy(...orderBy)
|
|
34
|
+
.limit(options.pagination.limit)
|
|
35
|
+
.offset(options.pagination.offset)) as Array<TTable['$inferSelect'] & { total: number }>;
|
|
36
|
+
|
|
37
|
+
let total = rows[0]?.total ?? 0;
|
|
38
|
+
if (rows.length === 0 && options.pagination.offset > 0) {
|
|
39
|
+
const counted = await db
|
|
40
|
+
.select({ count: sql<number>`count(*)::int` })
|
|
41
|
+
.from(table as PgTable)
|
|
42
|
+
.where(options.where);
|
|
43
|
+
total = counted[0]?.count ?? 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
items: rows.map(({ total: _total, ...row }) => row as TTable['$inferSelect']),
|
|
48
|
+
total,
|
|
49
|
+
limit: options.pagination.limit,
|
|
50
|
+
offset: options.pagination.offset,
|
|
51
|
+
};
|
|
52
|
+
}
|
package/src/query.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ContentTypeRegistry, type FilterOperator, ManabloxError } from '@manablox/core';
|
|
2
2
|
import { and, eq, inArray, isNull, type SQL, sql } from 'drizzle-orm';
|
|
3
3
|
import type { PgColumn } from 'drizzle-orm/pg-core';
|
|
4
|
-
import type { contents, publishedContents } from './schema.js';
|
|
4
|
+
import type { contents, publishedContents } from './schema/index.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Both content tables share a column set, so one filter builder serves the draft and
|
|
@@ -38,7 +38,6 @@ export interface ContentFilter {
|
|
|
38
38
|
slug?: string | undefined;
|
|
39
39
|
permalink?: string | undefined;
|
|
40
40
|
localizationId?: string | undefined;
|
|
41
|
-
visibleInMenu?: boolean | undefined;
|
|
42
41
|
/** Full-text query against `title` + field contributions. */
|
|
43
42
|
search?: string | undefined;
|
|
44
43
|
fields?: FieldFilter[] | undefined;
|
|
@@ -81,9 +80,6 @@ export function buildContentWhere(
|
|
|
81
80
|
if (filter.localizationId) conditions.push(eq(table.localizationId, filter.localizationId));
|
|
82
81
|
if (filter.slug) conditions.push(eq(table.slug, filter.slug));
|
|
83
82
|
if (filter.permalink !== undefined) conditions.push(eq(table.permalink, filter.permalink));
|
|
84
|
-
if (filter.visibleInMenu !== undefined) {
|
|
85
|
-
conditions.push(eq(table.visibleInMenu, filter.visibleInMenu));
|
|
86
|
-
}
|
|
87
83
|
if (filter.typeIds?.length) conditions.push(inArray(table.typeId, filter.typeIds));
|
|
88
84
|
if (filter.ids?.length) conditions.push(inArray(table.id, filter.ids));
|
|
89
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { and, eq, inArray, notInArray, sql } from 'drizzle-orm';
|
|
2
2
|
import type { Database } from '../client.js';
|
|
3
|
-
import { assetUsages, contents, publishedContents } from '../schema.js';
|
|
3
|
+
import { assetUsages, contents, publishedContents } from '../schema/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The asset → document reachability index.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type Loose, ManabloxError } from '@manablox/core';
|
|
2
2
|
import { and, desc, eq, inArray, sql } from 'drizzle-orm';
|
|
3
3
|
import type { Database } from '../client.js';
|
|
4
|
-
import { type
|
|
5
|
-
import type {
|
|
4
|
+
import { type Paginated, paginate } from '../pagination.js';
|
|
5
|
+
import type { Pagination } from '../query.js';
|
|
6
|
+
import { type AssetRow, type AssetVariantRow, assets, assetVariants } from '../schema/index.js';
|
|
6
7
|
|
|
7
8
|
export interface AssetWriteData {
|
|
8
9
|
id?: string | undefined;
|
|
@@ -69,25 +70,11 @@ export class AssetRepository {
|
|
|
69
70
|
);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.from(assets)
|
|
78
|
-
.where(where)
|
|
79
|
-
.orderBy(desc(assets.createdAt))
|
|
80
|
-
.limit(pagination.limit)
|
|
81
|
-
.offset(pagination.offset),
|
|
82
|
-
this.db.select({ count: sql<number>`count(*)::int` }).from(assets).where(where),
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
items,
|
|
87
|
-
total: counted[0]?.count ?? 0,
|
|
88
|
-
limit: pagination.limit,
|
|
89
|
-
offset: pagination.offset,
|
|
90
|
-
};
|
|
73
|
+
return paginate(this.db, assets, {
|
|
74
|
+
where: and(...conditions),
|
|
75
|
+
orderBy: desc(assets.createdAt),
|
|
76
|
+
pagination,
|
|
77
|
+
});
|
|
91
78
|
}
|
|
92
79
|
|
|
93
80
|
async create(data: AssetWriteData): Promise<AssetRow> {
|
|
@@ -158,6 +145,11 @@ export class AssetRepository {
|
|
|
158
145
|
return rows[0] ?? null;
|
|
159
146
|
}
|
|
160
147
|
|
|
148
|
+
/** Drops every variant row; the caller removes the files. */
|
|
149
|
+
async deleteVariants(assetId: string): Promise<void> {
|
|
150
|
+
await this.db.delete(assetVariants).where(eq(assetVariants.assetId, assetId));
|
|
151
|
+
}
|
|
152
|
+
|
|
161
153
|
async addVariant(data: {
|
|
162
154
|
assetId: string;
|
|
163
155
|
preset: string;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from '@manablox/core';
|
|
7
7
|
import { eq } from 'drizzle-orm';
|
|
8
8
|
import type { Database } from '../client.js';
|
|
9
|
-
import { type ContentTypeRow, contentTypes } from '../schema.js';
|
|
9
|
+
import { type ContentTypeRow, contentTypes } from '../schema/index.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Persistence for *runtime-defined* content types only. Code-defined types come from
|