@gigamusic/links 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/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # @gigamusic/links
2
2
 
3
- Linktree-style per-release link pages for the gigamusic platform. Ships a Drizzle schema (`linkPages`, `linkPageItems`), a typed queries factory, admin API handler factories, platform-detection helpers, and a small SVG `LinkPlatformIcon` component.
3
+ Linktree-style per-release link pages for the gigamusic platform. Ships admin API handler factories, platform-detection helpers, and a small SVG `LinkPlatformIcon` component.
4
+
5
+ The `link_pages` / `link_page_items` Drizzle schema **and** the link-page queries live in `@gigamusic/db` — they're folded into the single `createQueries(db)` so a consumer builds one query object for every table. This package re-exports the link-page row types for convenience.
4
6
 
5
7
  ```ts
6
8
  import {
7
- createLinkPageQueries,
8
9
  detectLinkPlatform,
9
10
  PLATFORM_LABELS,
10
11
  LinkPlatformIcon,
11
12
  type KnownLinkPlatform,
13
+ type LinkPageWithItems,
12
14
  } from "@gigamusic/links";
13
15
 
14
16
  // Admin handler factories live on the /server subpath
@@ -19,43 +21,32 @@ import {
19
21
  } from "@gigamusic/links/server";
20
22
  ```
21
23
 
22
- The public link-page UI is the consumer's responsibility — fetch with `createLinkPageQueries(db).getPublicLinkPageBySlug(slug)` and render whatever you want.
23
-
24
- ## Schema subpath
24
+ ## Reads and writes
25
25
 
26
- `@gigamusic/links/schema` exports the table objects and relations:
26
+ All link-page DB access comes from `@gigamusic/db`'s queries:
27
27
 
28
28
  ```ts
29
- import {
30
- linkPages,
31
- linkPageItems,
32
- linkPagesRelations,
33
- linkPageItemsRelations,
34
- linkPageColumns,
35
- linkPageItemColumns,
36
- } from "@gigamusic/links/schema";
29
+ import { createQueries } from "@gigamusic/db";
30
+
31
+ const queries = createQueries(db); // one object, every table
32
+
33
+ // Public link-page UI — fetch and render whatever you want:
34
+ const page = await queries.getPublicLinkPageBySlug(slug);
37
35
  ```
38
36
 
39
- Merge with `@gigamusic/db/schema` when constructing your Drizzle instance:
37
+ The admin handler factories from `@gigamusic/links/server` take that same `queries` object:
40
38
 
41
39
  ```ts
42
- import { drizzle } from "drizzle-orm/node-postgres";
43
- import { Pool } from "pg";
44
- import * as dbSchema from "@gigamusic/db/schema";
45
- import * as linksSchema from "@gigamusic/links/schema";
46
-
47
- const schema = { ...dbSchema, ...linksSchema };
48
- export const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }), { schema });
40
+ const handlers = createAdminLinkPagesHandlers({ queries });
49
41
  ```
50
42
 
51
43
  ## Release back-reference
52
44
 
53
- `linkPages.releaseId` is a nullable FK to `releases.id` from `@gigamusic/db`. Drizzle relations are declared independently on each side, so this package's `linkPagesRelations` already declares the `linkPage.release` direction. If you want a `release.linkPages` back-reference (for `db.query.releases.findFirst({ with: { linkPages: true } })`), add it in your own schema file by composing onto the base `releasesRelations`:
45
+ `linkPages.releaseId` is a nullable FK to `releases.id`. Both tables and their relations live in `@gigamusic/db/schema`; `linkPagesRelations` already declares the `linkPage.release` direction. If you want a `release.linkPages` back-reference (for `db.query.releases.findFirst({ with: { linkPages: true } })`), compose it onto the base `releasesRelations` in your own schema file:
54
46
 
55
47
  ```ts
56
48
  import { relations } from "drizzle-orm";
57
- import { releases } from "@gigamusic/db/schema";
58
- import { linkPages } from "@gigamusic/links/schema";
49
+ import { releases, linkPages } from "@gigamusic/db/schema";
59
50
 
60
51
  export const releasesRelations = relations(releases, ({ many }) => ({
61
52
  // ...keep the existing many() relations from @gigamusic/db/schema if you need them...
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gigamusic/links",
3
- "version": "3.0.0",
4
- "description": "Editable Linktree-style link pages for gigamusic artist sites. Ships a Drizzle schema, admin API handler factories, and platform-detection helpers.",
3
+ "version": "4.0.0",
4
+ "description": "Editable Linktree-style link pages for gigamusic artist sites. Ships admin API handler factories and platform-detection helpers (the Drizzle schema + queries live in @gigamusic/db).",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -17,10 +17,6 @@
17
17
  "./server": {
18
18
  "types": "./src/server.ts",
19
19
  "default": "./src/server.ts"
20
- },
21
- "./schema": {
22
- "types": "./src/schema/index.ts",
23
- "default": "./src/schema/index.ts"
24
20
  }
25
21
  },
26
22
  "files": [
@@ -32,23 +28,18 @@
32
28
  },
33
29
  "dependencies": {
34
30
  "zod": "^3.24.1",
35
- "@gigamusic/db": "3.0.0",
36
- "@gigamusic/core": "3.0.0"
31
+ "@gigamusic/core": "3.0.0",
32
+ "@gigamusic/db": "4.0.0"
37
33
  },
38
34
  "peerDependencies": {
39
- "drizzle-orm": ">=0.45",
40
35
  "next": ">=15",
41
36
  "react": ">=18 <20",
42
37
  "react-dom": ">=18 <20"
43
38
  },
44
39
  "devDependencies": {
45
40
  "@types/node": "^22.10.5",
46
- "@types/pg": "^8.11.10",
47
41
  "@types/react": "^19.0.0",
48
- "drizzle-kit": "^0.31.10",
49
- "drizzle-orm": "^0.45.2",
50
42
  "next": "^16.0.0",
51
- "pg": "^8.21.0",
52
43
  "react": "^19.0.0",
53
44
  "typescript": "^5.7.3",
54
45
  "vitest": "^2.1.8"
@@ -1,13 +1,8 @@
1
1
  import { revalidateTag } from "next/cache";
2
2
  import { z } from "zod";
3
3
  import { LinkPageInputSchema, LinkPageItemInputSchema } from "@gigamusic/core";
4
- import type {
5
- DefaultLinkPageTables,
6
- LinkPageInput,
7
- LinkPageItemInput,
8
- LinkPageQueries,
9
- LinkPageQueryTables,
10
- } from "../types";
4
+ import type { LinkPageInput, LinkPageItemInput } from "@gigamusic/core";
5
+ import type { DefaultTables, Queries, QueryTables } from "@gigamusic/db";
11
6
  import {
12
7
  INVALID_SLUG_MESSAGE,
13
8
  RESERVED_SLUGS,
@@ -32,7 +27,7 @@ export type RouteHandler<Params = Record<string, string>> = (
32
27
  /**
33
28
  * Deps for the admin-side link-page handler factories.
34
29
  *
35
- * Generic over the consumer's `LinkPageQueryTables` so the `Queries` and the
30
+ * Generic over the consumer's `QueryTables` so the `Queries` and the
36
31
  * `afterWrite` callbacks see consumer-extended `linkPages` / `linkPageItems`
37
32
  * row shapes when the consumer built tables via `buildLinkPages(...)` /
38
33
  * `buildLinkPageItems(...)`.
@@ -41,11 +36,11 @@ export type RouteHandler<Params = Record<string, string>> = (
41
36
  * `proxy.ts` / middleware. These handlers don't verify a session.
42
37
  */
43
38
  export interface LinkPagesAdminDeps<
44
- T extends LinkPageQueryTables = DefaultLinkPageTables,
39
+ T extends QueryTables = DefaultTables,
45
40
  TPageInput extends z.ZodObject<any> = typeof LinkPageInputSchema,
46
41
  TItemInput extends z.ZodObject<any> = typeof LinkPageItemInputSchema,
47
42
  > {
48
- queries: LinkPageQueries<T>;
43
+ queries: Queries<T>;
49
44
  /** Defaults to `LinkPageInputSchema`. Pass an `.extend()`ed schema for extras. */
50
45
  pageInputSchema?: TPageInput;
51
46
  /** Defaults to `LinkPageItemInputSchema`. Pass an `.extend()`ed schema for extras. */
@@ -80,7 +75,7 @@ function validateSlug(slug: string): { ok: true } | { ok: false; res: Response }
80
75
  * `app/api/admin/link-pages/route.ts` handlers — list + create.
81
76
  */
82
77
  export function createAdminLinkPagesHandlers<
83
- T extends LinkPageQueryTables = DefaultLinkPageTables,
78
+ T extends QueryTables = DefaultTables,
84
79
  TPageInput extends z.ZodObject<any> = typeof LinkPageInputSchema,
85
80
  TItemInput extends z.ZodObject<any> = typeof LinkPageItemInputSchema,
86
81
  >(deps: LinkPagesAdminDeps<T, TPageInput, TItemInput>): {
@@ -139,7 +134,7 @@ export function createAdminLinkPagesHandlers<
139
134
  * `app/api/admin/link-pages/[id]/route.ts` handlers — single-page CRUD.
140
135
  */
141
136
  export function createAdminLinkPageByIdHandlers<
142
- T extends LinkPageQueryTables = DefaultLinkPageTables,
137
+ T extends QueryTables = DefaultTables,
143
138
  TPageInput extends z.ZodObject<any> = typeof LinkPageInputSchema,
144
139
  TItemInput extends z.ZodObject<any> = typeof LinkPageItemInputSchema,
145
140
  >(deps: LinkPagesAdminDeps<T, TPageInput, TItemInput>): {
@@ -229,7 +224,7 @@ interface LinkPageItemBody {
229
224
  * DELETE removes an item by `itemId` in the query string.
230
225
  */
231
226
  export function createAdminLinkPageItemsHandlers<
232
- T extends LinkPageQueryTables = DefaultLinkPageTables,
227
+ T extends QueryTables = DefaultTables,
233
228
  TPageInput extends z.ZodObject<any> = typeof LinkPageInputSchema,
234
229
  TItemInput extends z.ZodObject<any> = typeof LinkPageItemInputSchema,
235
230
  >(deps: LinkPagesAdminDeps<T, TPageInput, TItemInput>): {
package/src/index.ts CHANGED
@@ -1,22 +1,16 @@
1
- // Public entry — server-safe types, queries, and platform helpers.
2
- // Admin handler factories live on the `./server` subpath.
1
+ // Public entry — platform helpers + UI, plus a convenience re-export of the
2
+ // link-page DB types. The queries themselves come from `@gigamusic/db`'s
3
+ // `createQueries(db, site)`; admin handler factories live on `./server`.
3
4
 
4
- // ── Types ────────────────────────────────────────────────────────────────
5
+ // ── Types (re-exported from @gigamusic/db, the single source for DB types) ─
5
6
  export type {
6
- DefaultLinkPageTables,
7
7
  LinkPage,
8
8
  LinkPageItem,
9
9
  LinkPageReleaseRef,
10
10
  LinkPageWithItems,
11
- LinkPageQueries,
12
- LinkPageQueryTables,
13
11
  LinkPageInput,
14
12
  LinkPageItemInput,
15
- } from "./types";
16
-
17
- // ── Queries factory ─────────────────────────────────────────────────────
18
- export { createLinkPageQueries } from "./queries/link-pages";
19
- export type { LinkPagesDb } from "./queries/link-pages";
13
+ } from "@gigamusic/db";
20
14
 
21
15
  // ── Platform detection ──────────────────────────────────────────────────
22
16
  export { detectLinkPlatform, PLATFORM_LABELS } from "./platforms/detect";
package/src/server.ts CHANGED
@@ -1,9 +1,7 @@
1
- // Server-only entry — admin API handler factories and the queries factory.
2
- // Importing from here in a `"use client"` file will cause a build error
3
- // because the handlers depend on `next/cache`.
1
+ // Server-only entry — admin API handler factories. Importing from here in a
2
+ // `"use client"` file will cause a build error because the handlers depend on
3
+ // `next/cache`. (The query factory lives in `@gigamusic/db`'s `createQueries`.)
4
4
 
5
- export { createLinkPageQueries } from "./queries/link-pages";
6
- export type { LinkPagesDb } from "./queries/link-pages";
7
5
  export {
8
6
  createAdminLinkPagesHandlers,
9
7
  createAdminLinkPageByIdHandlers,
@@ -1,211 +0,0 @@
1
- import { and, asc, desc, eq, max } from "drizzle-orm";
2
- import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
- import type * as gigamusicDbSchema from "@gigamusic/db/schema";
4
- import type * as linksSchema from "../schema/index";
5
- import {
6
- linkPageItems as baseLinkPageItems,
7
- linkPages as baseLinkPages,
8
- } from "../schema/index";
9
- import type {
10
- DefaultLinkPageTables,
11
- LinkPage,
12
- LinkPageInput,
13
- LinkPageItem,
14
- LinkPageItemInput,
15
- LinkPageQueries,
16
- LinkPageQueryTables,
17
- LinkPageWithItems,
18
- } from "../types";
19
-
20
- type RequiredSchema = typeof gigamusicDbSchema & typeof linksSchema;
21
-
22
- /** Drizzle DB type the link-pages factory needs — a superset that includes both link-pages tables and `releases`. */
23
- export type LinkPagesDb = NodePgDatabase<RequiredSchema>;
24
-
25
- /**
26
- * Factory that binds a Drizzle database into the `LinkPageQueries` contract.
27
- * Consumers typically merge the result with `@gigamusic/db`'s `createQueries`.
28
- *
29
- * Pass a `tables` arg to thread consumer-extended `linkPages` / `linkPageItems`
30
- * (built via the `buildLinkPages` / `buildLinkPageItems` factories) through
31
- * every query — the returned `LinkPageQueries<T>` rows carry the consumer's
32
- * extra columns. Single-arg callers fall back to the un-extended convenience
33
- * tables.
34
- *
35
- * The `db` instance must have been built with a schema that includes the
36
- * union of `@gigamusic/db/schema` and `@gigamusic/links/schema`; otherwise
37
- * the relational `with: { release: ... }` clause won't resolve at runtime.
38
- */
39
- export function createLinkPageQueries(db: LinkPagesDb): LinkPageQueries;
40
- export function createLinkPageQueries<T extends LinkPageQueryTables>(
41
- db: LinkPagesDb,
42
- tables: T,
43
- ): LinkPageQueries<T>;
44
- export function createLinkPageQueries<T extends LinkPageQueryTables>(
45
- db: LinkPagesDb,
46
- tables?: T,
47
- ): LinkPageQueries<T> {
48
- // Identity is preserved at runtime; the casts only widen for the type
49
- // checker so consumer-extended tables don't have to match the base shape
50
- // exactly.
51
- const linkPages = (tables?.linkPages ?? baseLinkPages) as unknown as typeof baseLinkPages;
52
- const linkPageItems = (tables?.linkPageItems ??
53
- baseLinkPageItems) as unknown as typeof baseLinkPageItems;
54
-
55
- return {
56
- listPublishedLinkPages: async (): Promise<LinkPage<T>[]> => {
57
- const rows = await db.query.linkPages.findMany({
58
- where: eq(linkPages.isPublished, true),
59
- orderBy: desc(linkPages.updatedAt),
60
- });
61
- return rows as unknown as LinkPage<T>[];
62
- },
63
-
64
- listAllLinkPages: async (): Promise<LinkPage<T>[]> => {
65
- const rows = await db.query.linkPages.findMany({
66
- orderBy: desc(linkPages.updatedAt),
67
- });
68
- return rows as unknown as LinkPage<T>[];
69
- },
70
-
71
- getPublicLinkPageBySlug: async (slug: string): Promise<LinkPageWithItems<T> | null> => {
72
- const row = await db.query.linkPages.findFirst({
73
- where: and(eq(linkPages.slug, slug), eq(linkPages.isPublished, true)),
74
- with: {
75
- release: {
76
- columns: {
77
- id: true,
78
- name: true,
79
- slug: true,
80
- coverImageUrl: true,
81
- isPublished: true,
82
- },
83
- },
84
- items: {
85
- where: eq(linkPageItems.isVisible, true),
86
- orderBy: asc(linkPageItems.position),
87
- },
88
- },
89
- });
90
- return (row ?? null) as unknown as LinkPageWithItems<T> | null;
91
- },
92
-
93
- getLinkPageById: async (id: number): Promise<LinkPageWithItems<T> | null> => {
94
- const row = await db.query.linkPages.findFirst({
95
- where: eq(linkPages.id, id),
96
- with: {
97
- release: {
98
- columns: {
99
- id: true,
100
- name: true,
101
- slug: true,
102
- coverImageUrl: true,
103
- isPublished: true,
104
- },
105
- },
106
- items: { orderBy: asc(linkPageItems.position) },
107
- },
108
- });
109
- return (row ?? null) as unknown as LinkPageWithItems<T> | null;
110
- },
111
-
112
- createLinkPage: async (input: LinkPageInput): Promise<LinkPage<T>> => {
113
- const [row] = await db
114
- .insert(linkPages)
115
- .values({
116
- title: input.title,
117
- slug: input.slug,
118
- description: input.description ?? null,
119
- releaseId: input.releaseId ?? null,
120
- coverImageUrl: input.coverImageUrl ?? null,
121
- isPublished: input.isPublished ?? true,
122
- })
123
- .returning();
124
- return row! as unknown as LinkPage<T>;
125
- },
126
-
127
- updateLinkPage: async (id: number, input: Partial<LinkPageInput>): Promise<LinkPage<T>> => {
128
- const data: Record<string, unknown> = {};
129
- if (input.title !== undefined) data.title = input.title;
130
- if (input.slug !== undefined) data.slug = input.slug;
131
- if (input.description !== undefined) data.description = input.description;
132
- if (input.releaseId !== undefined) data.releaseId = input.releaseId;
133
- if (input.coverImageUrl !== undefined) data.coverImageUrl = input.coverImageUrl;
134
- if (input.isPublished !== undefined) data.isPublished = input.isPublished;
135
- const [row] = await db.update(linkPages).set(data).where(eq(linkPages.id, id)).returning();
136
- return row! as unknown as LinkPage<T>;
137
- },
138
-
139
- deleteLinkPage: async (id: number): Promise<void> => {
140
- await db.delete(linkPages).where(eq(linkPages.id, id));
141
- },
142
-
143
- addLinkPageItem: async (
144
- pageId: number,
145
- input: LinkPageItemInput,
146
- ): Promise<LinkPageItem<T>> => {
147
- const [maxRow] = await db
148
- .select({ max: max(linkPageItems.position) })
149
- .from(linkPageItems)
150
- .where(eq(linkPageItems.pageId, pageId));
151
- const nextPosition = input.position ?? ((maxRow?.max ?? -1) + 1);
152
- const [row] = await db
153
- .insert(linkPageItems)
154
- .values({
155
- pageId,
156
- title: input.title,
157
- url: input.url,
158
- position: nextPosition,
159
- isVisible: input.isVisible ?? true,
160
- })
161
- .returning();
162
- return row! as unknown as LinkPageItem<T>;
163
- },
164
-
165
- updateLinkPageItem: async (
166
- id: number,
167
- input: Partial<LinkPageItemInput>,
168
- ): Promise<LinkPageItem<T>> => {
169
- const data: Record<string, unknown> = {};
170
- if (input.title !== undefined) data.title = input.title;
171
- if (input.url !== undefined) data.url = input.url;
172
- if (input.position !== undefined) data.position = input.position;
173
- if (input.isVisible !== undefined) data.isVisible = input.isVisible;
174
- const [row] = await db
175
- .update(linkPageItems)
176
- .set(data)
177
- .where(eq(linkPageItems.id, id))
178
- .returning();
179
- return row! as unknown as LinkPageItem<T>;
180
- },
181
-
182
- deleteLinkPageItem: async (id: number): Promise<void> => {
183
- await db.delete(linkPageItems).where(eq(linkPageItems.id, id));
184
- },
185
-
186
- /**
187
- * Rewrite every item's `position` to match the caller's preferred order
188
- * inside a single transaction. IDs not present in `orderedItemIds` keep
189
- * their current position — callers must include the full set to fully
190
- * resort.
191
- */
192
- reorderLinkPageItems: async (pageId: number, orderedItemIds: number[]): Promise<void> => {
193
- await db.transaction(async (tx) => {
194
- for (let position = 0; position < orderedItemIds.length; position++) {
195
- await tx
196
- .update(linkPageItems)
197
- .set({ position })
198
- .where(
199
- and(
200
- eq(linkPageItems.id, orderedItemIds[position]!),
201
- eq(linkPageItems.pageId, pageId),
202
- ),
203
- );
204
- }
205
- });
206
- },
207
- };
208
- }
209
-
210
- // Re-export DefaultLinkPageTables so the consumer can import it if they want.
211
- export type { DefaultLinkPageTables };
@@ -1,2 +0,0 @@
1
- export * from "./link-pages";
2
- export * from "./relations";
@@ -1,56 +0,0 @@
1
- import { boolean, index, integer, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
2
- import { releases } from "@gigamusic/db/schema";
3
- import type { ExtraColumns } from "@gigamusic/db/schema";
4
-
5
- // See @gigamusic/db/schema/releases.ts for the rationale on inlining
6
- // columns inside the factory (TTableName generic resolution).
7
- export function buildLinkPages<TExtras extends ExtraColumns = {}>(
8
- extras: TExtras = {} as TExtras,
9
- ) {
10
- return pgTable(
11
- "link_pages",
12
- {
13
- id: serial("id").primaryKey(),
14
- slug: text("slug").notNull().unique("link_pages_slug_key"),
15
- title: text("title").notNull(),
16
- description: text("description"),
17
- coverImageUrl: text("coverImageUrl"),
18
- releaseId: integer("releaseId").references(() => releases.id, { onDelete: "set null" }),
19
- isPublished: boolean("isPublished").notNull().default(true),
20
- createdAt: timestamp("createdAt", { mode: "date", precision: 3 }).notNull().defaultNow(),
21
- updatedAt: timestamp("updatedAt", { mode: "date", precision: 3 })
22
- .notNull()
23
- .$defaultFn(() => new Date())
24
- .$onUpdateFn(() => new Date()),
25
- ...extras,
26
- },
27
- (t) => [index("link_pages_releaseId_idx").on(t.releaseId)],
28
- );
29
- }
30
-
31
- export const linkPages = buildLinkPages();
32
-
33
- export function buildLinkPageItems<TExtras extends ExtraColumns = {}>(
34
- extras: TExtras = {} as TExtras,
35
- ) {
36
- return pgTable(
37
- "link_page_items",
38
- {
39
- id: serial("id").primaryKey(),
40
- pageId: integer("pageId").notNull().references(() => linkPages.id, { onDelete: "cascade" }),
41
- title: text("title").notNull(),
42
- url: text("url").notNull(),
43
- position: integer("position").notNull().default(0),
44
- isVisible: boolean("isVisible").notNull().default(true),
45
- createdAt: timestamp("createdAt", { mode: "date", precision: 3 }).notNull().defaultNow(),
46
- updatedAt: timestamp("updatedAt", { mode: "date", precision: 3 })
47
- .notNull()
48
- .$defaultFn(() => new Date())
49
- .$onUpdateFn(() => new Date()),
50
- ...extras,
51
- },
52
- (t) => [index("link_page_items_pageId_idx").on(t.pageId)],
53
- );
54
- }
55
-
56
- export const linkPageItems = buildLinkPageItems();
@@ -1,54 +0,0 @@
1
- import { relations } from "drizzle-orm";
2
- import type { PgTable } from "drizzle-orm/pg-core";
3
- import { releases as baseReleases } from "@gigamusic/db/schema";
4
- import {
5
- linkPages as baseLinkPages,
6
- linkPageItems as baseLinkPageItems,
7
- } from "./link-pages";
8
-
9
- // `PgTable<any>` so consumer concrete tables satisfy these factory params —
10
- // see @gigamusic/db's types.ts for the variance rationale.
11
- type AnyPgTableValue = PgTable<any>;
12
-
13
- export function createLinkPagesRelations(t: {
14
- linkPages: AnyPgTableValue;
15
- linkPageItems: AnyPgTableValue;
16
- releases: AnyPgTableValue;
17
- }) {
18
- const linkPages = t.linkPages as unknown as typeof baseLinkPages;
19
- const linkPageItems = t.linkPageItems as unknown as typeof baseLinkPageItems;
20
- const releases = t.releases as unknown as typeof baseReleases;
21
- return relations(linkPages, ({ one, many }) => ({
22
- release: one(releases, {
23
- fields: [linkPages.releaseId],
24
- references: [releases.id],
25
- }),
26
- items: many(linkPageItems),
27
- }));
28
- }
29
-
30
- export function createLinkPageItemsRelations(t: {
31
- linkPages: AnyPgTableValue;
32
- linkPageItems: AnyPgTableValue;
33
- }) {
34
- const linkPages = t.linkPages as unknown as typeof baseLinkPages;
35
- const linkPageItems = t.linkPageItems as unknown as typeof baseLinkPageItems;
36
- return relations(linkPageItems, ({ one }) => ({
37
- page: one(linkPages, {
38
- fields: [linkPageItems.pageId],
39
- references: [linkPages.id],
40
- }),
41
- }));
42
- }
43
-
44
- // Convenience top-level exports bound to the un-extended convenience tables.
45
- export const linkPagesRelations = createLinkPagesRelations({
46
- linkPages: baseLinkPages,
47
- linkPageItems: baseLinkPageItems,
48
- releases: baseReleases,
49
- });
50
-
51
- export const linkPageItemsRelations = createLinkPageItemsRelations({
52
- linkPages: baseLinkPages,
53
- linkPageItems: baseLinkPageItems,
54
- });
package/src/types.ts DELETED
@@ -1,76 +0,0 @@
1
- import type { PgTable } from "drizzle-orm/pg-core";
2
- import type { LinkPageInput, LinkPageItemInput } from "@gigamusic/core";
3
- import type * as linksSchema from "./schema/index";
4
-
5
- export type { LinkPageInput, LinkPageItemInput };
6
-
7
- // `PgTable<any>` (not `PgTable<TableConfig>`) so consumer concrete tables
8
- // satisfy this constraint — Drizzle's column generics are invariant on
9
- // column shape; see @gigamusic/db's types.ts for the full rationale.
10
- type AnyPgTableValue = PgTable<any>;
11
-
12
- /**
13
- * The bag of Drizzle table identities the link-page queries factory closes
14
- * over. Consumers can pass extended versions of these tables built via the
15
- * `buildLinkPages` / `buildLinkPageItems` factories; the `releases` reference
16
- * is the consumer's `releases` table (for the cover-image fallback join).
17
- */
18
- export interface LinkPageQueryTables {
19
- linkPages: AnyPgTableValue;
20
- linkPageItems: AnyPgTableValue;
21
- releases: AnyPgTableValue;
22
- }
23
-
24
- export interface DefaultLinkPageTables extends LinkPageQueryTables {
25
- linkPages: typeof linksSchema.linkPages;
26
- linkPageItems: typeof linksSchema.linkPageItems;
27
- // `releases` defaults to the gigamusic convenience table; consumers pass
28
- // their own extended releases when they have one.
29
- releases: AnyPgTableValue;
30
- }
31
-
32
- /** A single link button rendered inside a `LinkPage`. */
33
- export type LinkPageItem<T extends LinkPageQueryTables = DefaultLinkPageTables> =
34
- T["linkPageItems"]["$inferSelect"];
35
-
36
- /** Optional `Release` reference exposed to the public view for cover fallback. */
37
- export interface LinkPageReleaseRef {
38
- id: number;
39
- name: string;
40
- slug: string;
41
- coverImageUrl: string | null;
42
- isPublished: boolean;
43
- }
44
-
45
- /** Top-level "Linktree-style" page row. */
46
- export type LinkPage<T extends LinkPageQueryTables = DefaultLinkPageTables> =
47
- T["linkPages"]["$inferSelect"];
48
-
49
- /** A `LinkPage` joined with its visible (or all) items and an optional release ref. */
50
- export type LinkPageWithItems<T extends LinkPageQueryTables = DefaultLinkPageTables> =
51
- LinkPage<T> & {
52
- items: LinkPageItem<T>[];
53
- release?: LinkPageReleaseRef | null;
54
- };
55
-
56
- /**
57
- * Query helpers that close over a Drizzle DB. Consumers merge this with
58
- * `@gigamusic/db`'s `Queries` factory result (or pass it through wherever a
59
- * `LinkPageQueries` is needed).
60
- */
61
- export interface LinkPageQueries<T extends LinkPageQueryTables = DefaultLinkPageTables> {
62
- listPublishedLinkPages(): Promise<LinkPage<T>[]>;
63
- listAllLinkPages(): Promise<LinkPage<T>[]>;
64
- getPublicLinkPageBySlug(slug: string): Promise<LinkPageWithItems<T> | null>;
65
- getLinkPageById(id: number): Promise<LinkPageWithItems<T> | null>;
66
- createLinkPage(input: LinkPageInput): Promise<LinkPage<T>>;
67
- updateLinkPage(id: number, input: Partial<LinkPageInput>): Promise<LinkPage<T>>;
68
- deleteLinkPage(id: number): Promise<void>;
69
- addLinkPageItem(pageId: number, input: LinkPageItemInput): Promise<LinkPageItem<T>>;
70
- updateLinkPageItem(
71
- id: number,
72
- input: Partial<LinkPageItemInput>,
73
- ): Promise<LinkPageItem<T>>;
74
- deleteLinkPageItem(id: number): Promise<void>;
75
- reorderLinkPageItems(pageId: number, orderedItemIds: number[]): Promise<void>;
76
- }