dineway 0.1.3

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.
Files changed (96) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +89 -0
  3. package/dist/adapters-BlzWJG82.d.mts +106 -0
  4. package/dist/apply-CAPvMfoU.mjs +1339 -0
  5. package/dist/astro/index.d.mts +50 -0
  6. package/dist/astro/index.mjs +1326 -0
  7. package/dist/astro/middleware/auth.d.mts +30 -0
  8. package/dist/astro/middleware/auth.mjs +708 -0
  9. package/dist/astro/middleware/redirect.d.mts +21 -0
  10. package/dist/astro/middleware/redirect.mjs +62 -0
  11. package/dist/astro/middleware/request-context.d.mts +17 -0
  12. package/dist/astro/middleware/request-context.mjs +1371 -0
  13. package/dist/astro/middleware/setup.d.mts +19 -0
  14. package/dist/astro/middleware/setup.mjs +46 -0
  15. package/dist/astro/middleware.d.mts +12 -0
  16. package/dist/astro/middleware.mjs +1716 -0
  17. package/dist/astro/types.d.mts +269 -0
  18. package/dist/astro/types.mjs +1 -0
  19. package/dist/base64-F8-DUraK.mjs +58 -0
  20. package/dist/byline-DeWCMU_i.mjs +234 -0
  21. package/dist/bylines-DyqBV9EQ.mjs +137 -0
  22. package/dist/chunk-ClPoSABd.mjs +21 -0
  23. package/dist/cli/index.d.mts +1 -0
  24. package/dist/cli/index.mjs +3987 -0
  25. package/dist/client/external-auth-headers.d.mts +38 -0
  26. package/dist/client/external-auth-headers.mjs +101 -0
  27. package/dist/client/index.d.mts +397 -0
  28. package/dist/client/index.mjs +345 -0
  29. package/dist/config-Cq8H0SfX.mjs +46 -0
  30. package/dist/connection-C9pxzuag.mjs +52 -0
  31. package/dist/content-zSgdNmnt.mjs +836 -0
  32. package/dist/db/index.d.mts +4 -0
  33. package/dist/db/index.mjs +62 -0
  34. package/dist/db/libsql.d.mts +10 -0
  35. package/dist/db/libsql.mjs +21 -0
  36. package/dist/db/postgres.d.mts +10 -0
  37. package/dist/db/postgres.mjs +29 -0
  38. package/dist/db/sqlite.d.mts +10 -0
  39. package/dist/db/sqlite.mjs +15 -0
  40. package/dist/default-WYlzADZL.mjs +80 -0
  41. package/dist/dialect-helpers-B9uSp2GJ.mjs +89 -0
  42. package/dist/error-DrxtnGPg.mjs +26 -0
  43. package/dist/index-C-jx21qs.d.mts +4771 -0
  44. package/dist/index.d.mts +16 -0
  45. package/dist/index.mjs +30 -0
  46. package/dist/load-C6FCD1FU.mjs +27 -0
  47. package/dist/loader-qKmo0wAY.mjs +446 -0
  48. package/dist/manifest-schema-CTSEyIJ3.mjs +186 -0
  49. package/dist/media/index.d.mts +25 -0
  50. package/dist/media/index.mjs +54 -0
  51. package/dist/media/local-runtime.d.mts +38 -0
  52. package/dist/media/local-runtime.mjs +132 -0
  53. package/dist/media-DMTr80Gv.mjs +199 -0
  54. package/dist/mode-BlyYtIFO.mjs +22 -0
  55. package/dist/page/index.d.mts +148 -0
  56. package/dist/page/index.mjs +419 -0
  57. package/dist/placeholder-B3knXwNc.mjs +267 -0
  58. package/dist/placeholder-bOx1xCTY.d.mts +283 -0
  59. package/dist/plugin-utils.d.mts +57 -0
  60. package/dist/plugin-utils.mjs +77 -0
  61. package/dist/plugins/adapt-sandbox-entry.d.mts +21 -0
  62. package/dist/plugins/adapt-sandbox-entry.mjs +112 -0
  63. package/dist/query-BiaPl_g2.mjs +459 -0
  64. package/dist/redirect-JPqLAbxa.mjs +328 -0
  65. package/dist/registry-DSd1GWB8.mjs +851 -0
  66. package/dist/request-context.d.mts +49 -0
  67. package/dist/request-context.mjs +42 -0
  68. package/dist/runner-B5l1JfOj.d.mts +26 -0
  69. package/dist/runner-BGUGywgG.mjs +1529 -0
  70. package/dist/runtime.d.mts +25 -0
  71. package/dist/runtime.mjs +41 -0
  72. package/dist/search-BNruJHDL.mjs +11054 -0
  73. package/dist/seed/index.d.mts +3 -0
  74. package/dist/seed/index.mjs +15 -0
  75. package/dist/seo/index.d.mts +69 -0
  76. package/dist/seo/index.mjs +69 -0
  77. package/dist/storage/local.d.mts +38 -0
  78. package/dist/storage/local.mjs +165 -0
  79. package/dist/storage/s3.d.mts +31 -0
  80. package/dist/storage/s3.mjs +174 -0
  81. package/dist/tokens-4vgYuXsZ.mjs +170 -0
  82. package/dist/transport-C5FYnid7.mjs +417 -0
  83. package/dist/transport-gIL-e43D.d.mts +41 -0
  84. package/dist/types-BawVha09.mjs +30 -0
  85. package/dist/types-BgQeVaPj.d.mts +192 -0
  86. package/dist/types-CLLdsG3g.d.mts +103 -0
  87. package/dist/types-D38djUXv.d.mts +1196 -0
  88. package/dist/types-DShnjzb6.mjs +15 -0
  89. package/dist/types-DkvMXalq.d.mts +425 -0
  90. package/dist/types-DuNbGKjF.mjs +74 -0
  91. package/dist/types-ju-_ORz7.d.mts +182 -0
  92. package/dist/validate-CXnRKfJK.mjs +327 -0
  93. package/dist/validate-CqRJb_xU.mjs +96 -0
  94. package/dist/validate-DVKJJ-M_.d.mts +377 -0
  95. package/locals.d.ts +47 -0
  96. package/package.json +313 -0
@@ -0,0 +1,269 @@
1
+ import { f as MediaProvider, p as MediaProviderCapabilities } from "../placeholder-bOx1xCTY.mjs";
2
+ import { t as Database } from "../types-DkvMXalq.mjs";
3
+ import { Cn as DinewayConfig, Xi as ContentResponse, Yi as ContentListResponse, _i as MediaResponse, _n as EmailPipeline, gi as MediaListResponse, la as MediaItem, ni as SandboxRunner, vn as HookPipeline } from "../index-C-jx21qs.mjs";
4
+ import "../runner-B5l1JfOj.mjs";
5
+ import { r as ContentItem } from "../types-CLLdsG3g.mjs";
6
+ import { G as PublicPageContext, J as ResolvedPlugin, O as PageMetadataContribution, T as PageFragmentContribution, at as Element } from "../types-D38djUXv.mjs";
7
+ import "../validate-DVKJJ-M_.mjs";
8
+ import { d as Storage } from "../types-ju-_ORz7.mjs";
9
+ import "../index.mjs";
10
+ import { Kysely } from "kysely";
11
+
12
+ //#region src/astro/types.d.ts
13
+ /**
14
+ * Manifest collection definition
15
+ */
16
+ interface ManifestCollection {
17
+ label: string;
18
+ labelSingular: string;
19
+ supports: string[];
20
+ hasSeo: boolean;
21
+ urlPattern?: string;
22
+ fields: Record<string, {
23
+ kind: string;
24
+ label?: string;
25
+ required?: boolean;
26
+ widget?: string;
27
+ /**
28
+ * Field options. Two shapes:
29
+ * - Legacy enum: `Array<{ value, label }>` for select / multiSelect widgets
30
+ * - Plugin widgets: `Record<string, unknown>` for arbitrary per-field config
31
+ * (e.g. a checkbox grid receiving its column definitions)
32
+ */
33
+ options?: Array<{
34
+ value: string;
35
+ label: string;
36
+ }> | Record<string, unknown>;
37
+ }>;
38
+ }
39
+ /**
40
+ * Plugin manifest entry in the admin manifest
41
+ */
42
+ interface ManifestPlugin {
43
+ version?: string;
44
+ /** Package name for dynamic import (e.g., "@dineway-ai/plugin-audit-log") */
45
+ package?: string;
46
+ /** Whether the plugin is currently enabled */
47
+ enabled?: boolean;
48
+ /**
49
+ * How this plugin renders its admin UI:
50
+ * - "react": Trusted plugin with React components (default for trusted plugins)
51
+ * - "blocks": Declarative Block Kit UI via admin route handler
52
+ * - "none": No admin UI
53
+ */
54
+ adminMode?: "react" | "blocks" | "none";
55
+ adminPages?: Array<{
56
+ path: string;
57
+ label?: string;
58
+ icon?: string;
59
+ }>;
60
+ dashboardWidgets?: Array<{
61
+ id: string;
62
+ title?: string;
63
+ size?: string;
64
+ }>;
65
+ fieldWidgets?: Array<{
66
+ name: string;
67
+ label: string;
68
+ fieldTypes: string[];
69
+ elements?: Element[];
70
+ }>;
71
+ /** Portable Text block types provided by this plugin */
72
+ portableTextBlocks?: Array<{
73
+ type: string;
74
+ label: string;
75
+ icon?: string;
76
+ description?: string;
77
+ placeholder?: string;
78
+ fields?: Element[];
79
+ }>;
80
+ }
81
+ /**
82
+ * Auth mode indicator for the admin UI
83
+ * - "passkey": Built-in passkey authentication (default)
84
+ * - string: External auth provider type (e.g., "header-auth")
85
+ */
86
+ type ManifestAuthMode = string;
87
+ /**
88
+ * The Dineway manifest provided to the admin UI
89
+ */
90
+ interface DinewayManifest {
91
+ version: string;
92
+ hash: string;
93
+ collections: Record<string, ManifestCollection>;
94
+ plugins: Record<string, ManifestPlugin>;
95
+ /**
96
+ * Auth mode for the admin UI. When "passkey", the security settings
97
+ * (passkey management, self-signup domains) are shown. Any other
98
+ * value is treated as external auth, so those settings stay hidden.
99
+ */
100
+ authMode: ManifestAuthMode;
101
+ /**
102
+ * Whether self-signup is enabled (at least one allowed domain is active).
103
+ * Used by the login page to conditionally show the "Sign up" link.
104
+ */
105
+ signupEnabled?: boolean;
106
+ /**
107
+ * i18n configuration from Astro config.
108
+ * Only present when i18n is enabled (multiple locales configured).
109
+ */
110
+ i18n?: {
111
+ defaultLocale: string;
112
+ locales: string[];
113
+ prefixDefaultLocale?: boolean;
114
+ };
115
+ /**
116
+ * Taxonomy definitions for the admin sidebar.
117
+ */
118
+ taxonomies: Array<{
119
+ name: string;
120
+ label: string;
121
+ labelSingular?: string;
122
+ hierarchical: boolean;
123
+ collections: string[];
124
+ }>;
125
+ /**
126
+ * Whether the plugin marketplace is configured.
127
+ * When true, the admin UI can show marketplace browse/install features.
128
+ */
129
+ marketplace?: boolean;
130
+ }
131
+ /**
132
+ * Standard handler response shape used by all DinewayHandlers methods.
133
+ *
134
+ * The error shape matches `ApiResult` from the core package — typing it
135
+ * here lets route files use `result.error?.code` without unsafe casts while
136
+ * keeping the data side loosely coupled (defaults to `unknown`).
137
+ */
138
+ interface HandlerResponse<T = unknown> {
139
+ success: boolean;
140
+ data?: T;
141
+ error?: {
142
+ code: string;
143
+ message: string;
144
+ details?: Record<string, unknown>;
145
+ };
146
+ }
147
+ /**
148
+ * The Dineway API handlers provided via Astro.locals
149
+ *
150
+ * Data types default to `unknown` to avoid tight coupling with the core
151
+ * package. Handlers whose data shape is accessed in route files (e.g.
152
+ * handleContentGet, handleRevisionGet) use narrower types.
153
+ */
154
+ interface DinewayHandlers {
155
+ handleContentList: (collection: string, params: {
156
+ cursor?: string;
157
+ limit?: number;
158
+ status?: string;
159
+ orderBy?: string;
160
+ order?: "asc" | "desc";
161
+ locale?: string;
162
+ }) => Promise<HandlerResponse>;
163
+ handleContentGet: (collection: string, id: string, locale?: string) => Promise<HandlerResponse<{
164
+ item: {
165
+ id: string;
166
+ authorId: string | null;
167
+ [key: string]: unknown;
168
+ };
169
+ _rev?: string;
170
+ }>>;
171
+ handleContentCreate: (collection: string, body: {
172
+ data: Record<string, unknown>;
173
+ slug?: string;
174
+ status?: string;
175
+ authorId?: string;
176
+ locale?: string;
177
+ translationOf?: string;
178
+ createdAt?: string | null;
179
+ publishedAt?: string | null;
180
+ }) => Promise<HandlerResponse>;
181
+ handleContentUpdate: (collection: string, id: string, body: {
182
+ data?: Record<string, unknown>;
183
+ slug?: string;
184
+ status?: string;
185
+ authorId?: string | null;
186
+ _rev?: string;
187
+ }) => Promise<HandlerResponse>;
188
+ handleContentDelete: (collection: string, id: string) => Promise<HandlerResponse>;
189
+ handleContentListTrashed: (collection: string, params?: {
190
+ cursor?: string;
191
+ limit?: number;
192
+ }) => Promise<HandlerResponse>;
193
+ handleContentRestore: (collection: string, id: string) => Promise<HandlerResponse>;
194
+ handleContentPermanentDelete: (collection: string, id: string) => Promise<HandlerResponse>;
195
+ handleContentCountTrashed: (collection: string) => Promise<HandlerResponse>;
196
+ handleContentGetIncludingTrashed: (collection: string, id: string) => Promise<HandlerResponse>;
197
+ handleContentDuplicate: (collection: string, id: string, authorId?: string) => Promise<HandlerResponse>;
198
+ handleContentPublish: (collection: string, id: string) => Promise<HandlerResponse>;
199
+ handleContentUnpublish: (collection: string, id: string) => Promise<HandlerResponse>;
200
+ handleContentSchedule: (collection: string, id: string, scheduledAt: string) => Promise<HandlerResponse>;
201
+ handleContentUnschedule: (collection: string, id: string) => Promise<HandlerResponse>;
202
+ handleContentCountScheduled: (collection: string) => Promise<HandlerResponse>;
203
+ handleContentDiscardDraft: (collection: string, id: string) => Promise<HandlerResponse>;
204
+ handleContentCompare: (collection: string, id: string) => Promise<HandlerResponse>;
205
+ handleContentTranslations: (collection: string, id: string) => Promise<HandlerResponse>;
206
+ handleMediaList: (params: {
207
+ cursor?: string;
208
+ limit?: number;
209
+ mimeType?: string;
210
+ }) => Promise<HandlerResponse>;
211
+ handleMediaGet: (id: string) => Promise<HandlerResponse>;
212
+ handleMediaCreate: (input: {
213
+ filename: string;
214
+ mimeType: string;
215
+ size?: number;
216
+ width?: number;
217
+ height?: number;
218
+ storageKey: string;
219
+ contentHash?: string;
220
+ blurhash?: string;
221
+ dominantColor?: string;
222
+ authorId?: string;
223
+ }) => Promise<HandlerResponse>;
224
+ handleMediaUpdate: (id: string, input: {
225
+ alt?: string;
226
+ caption?: string;
227
+ width?: number;
228
+ height?: number;
229
+ }) => Promise<HandlerResponse>;
230
+ handleMediaDelete: (id: string) => Promise<HandlerResponse>;
231
+ handleRevisionList: (collection: string, entryId: string, params?: {
232
+ limit?: number;
233
+ }) => Promise<HandlerResponse>;
234
+ handleRevisionGet: (revisionId: string) => Promise<HandlerResponse<{
235
+ item: {
236
+ id: string;
237
+ collection: string;
238
+ entryId: string;
239
+ authorId: string | null;
240
+ [key: string]: unknown;
241
+ };
242
+ }>>;
243
+ handleRevisionRestore: (revisionId: string, callerUserId: string) => Promise<HandlerResponse>;
244
+ handlePluginApiRoute: (pluginId: string, method: string, path: string, request: Request) => Promise<HandlerResponse>;
245
+ getPluginRouteMeta: (pluginId: string, path: string) => {
246
+ public: boolean;
247
+ } | null;
248
+ getMediaProvider: (providerId: string) => MediaProvider | undefined;
249
+ getMediaProviderList: () => Array<{
250
+ id: string;
251
+ name: string;
252
+ icon?: string;
253
+ capabilities: MediaProviderCapabilities;
254
+ }>;
255
+ storage: Storage | null;
256
+ db: Kysely<Database>;
257
+ hooks: HookPipeline;
258
+ email: EmailPipeline | null;
259
+ configuredPlugins: ResolvedPlugin[];
260
+ config: DinewayConfig;
261
+ invalidateManifest: () => void;
262
+ getSandboxRunner: () => SandboxRunner | null;
263
+ syncMarketplacePlugins: () => Promise<void>;
264
+ setPluginStatus: (pluginId: string, status: "active" | "inactive") => Promise<void>;
265
+ collectPageMetadata: (page: PublicPageContext) => Promise<PageMetadataContribution[]>;
266
+ collectPageFragments: (page: PublicPageContext) => Promise<PageFragmentContribution[]>;
267
+ }
268
+ //#endregion
269
+ export { type ContentItem, type ContentListResponse, type ContentResponse, type Database, DinewayHandlers, DinewayManifest, HandlerResponse, ManifestAuthMode, ManifestCollection, ManifestPlugin, type MediaItem, type MediaListResponse, type MediaResponse, type Storage };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,58 @@
1
+ //#region src/utils/base64.ts
2
+ /**
3
+ * Base64 encoding/decoding utilities.
4
+ *
5
+ * Uses native Uint8Array.prototype.toBase64 / Uint8Array.fromBase64 when
6
+ * available in newer runtimes, falls back to btoa/atob.
7
+ *
8
+ * All base64url encoding uses the { alphabet: "base64url" } option natively
9
+ * or manual character replacement as fallback.
10
+ *
11
+ * Delete the fallback paths when the minimum Node version supports these
12
+ * methods natively.
13
+ */
14
+ const hasNative = typeof Uint8Array.prototype.toBase64 === "function" && typeof Uint8Array.fromBase64 === "function";
15
+ const BASE64_PLUS_PATTERN = /\+/g;
16
+ const BASE64_SLASH_PATTERN = /\//g;
17
+ const BASE64_PADDING_PATTERN = /=+$/;
18
+ const BASE64URL_DASH_PATTERN = /-/g;
19
+ const BASE64URL_UNDERSCORE_PATTERN = /_/g;
20
+ /** Encode a UTF-8 string as standard base64. */
21
+ function encodeBase64(str) {
22
+ const bytes = new TextEncoder().encode(str);
23
+ if (hasNative) return bytes.toBase64();
24
+ let binary = "";
25
+ for (const b of bytes) binary += String.fromCharCode(b);
26
+ return btoa(binary);
27
+ }
28
+ /** Decode a standard base64 string to a UTF-8 string. */
29
+ function decodeBase64(base64) {
30
+ if (hasNative) return new TextDecoder().decode(Uint8Array.fromBase64(base64));
31
+ const binary = atob(base64);
32
+ const bytes = new Uint8Array(binary.length);
33
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
34
+ return new TextDecoder().decode(bytes);
35
+ }
36
+ /** Encode bytes as base64url without padding. */
37
+ function encodeBase64url(bytes) {
38
+ if (hasNative) return bytes.toBase64({
39
+ alphabet: "base64url",
40
+ omitPadding: true
41
+ });
42
+ let binary = "";
43
+ for (const b of bytes) binary += String.fromCharCode(b);
44
+ return btoa(binary).replace(BASE64_PLUS_PATTERN, "-").replace(BASE64_SLASH_PATTERN, "_").replace(BASE64_PADDING_PATTERN, "");
45
+ }
46
+ /** Decode a base64url string (with or without padding) to bytes. */
47
+ function decodeBase64url(encoded) {
48
+ if (hasNative) return Uint8Array.fromBase64(encoded, { alphabet: "base64url" });
49
+ const base64 = encoded.replace(BASE64URL_DASH_PATTERN, "+").replace(BASE64URL_UNDERSCORE_PATTERN, "/");
50
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
51
+ const binary = atob(padded);
52
+ const bytes = new Uint8Array(binary.length);
53
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
54
+ return bytes;
55
+ }
56
+
57
+ //#endregion
58
+ export { encodeBase64url as i, decodeBase64url as n, encodeBase64 as r, decodeBase64 as t };
@@ -0,0 +1,234 @@
1
+ import { s as listTablesLike } from "./dialect-helpers-B9uSp2GJ.mjs";
2
+ import { t as validateIdentifier } from "./validate-CqRJb_xU.mjs";
3
+ import { n as decodeCursor, r as encodeCursor } from "./types-BawVha09.mjs";
4
+ import { sql } from "kysely";
5
+ import { ulid } from "ulidx";
6
+
7
+ //#region src/utils/chunks.ts
8
+ /**
9
+ * Split an array into chunks of at most `size` elements.
10
+ *
11
+ * Used to keep SQL `IN (?, ?, …)` clauses within conservative
12
+ * SQLite/libSQL bound-parameter limits.
13
+ */
14
+ function chunks(arr, size) {
15
+ if (arr.length === 0) return [];
16
+ const result = [];
17
+ for (let i = 0; i < arr.length; i += size) result.push(arr.slice(i, i + size));
18
+ return result;
19
+ }
20
+ /** Conservative default chunk size for SQL IN clauses on SQLite-compatible adapters. */
21
+ const SQL_BATCH_SIZE = 50;
22
+
23
+ //#endregion
24
+ //#region src/database/repositories/byline.ts
25
+ function rowToByline(row) {
26
+ return {
27
+ id: row.id,
28
+ slug: row.slug,
29
+ displayName: row.display_name,
30
+ bio: row.bio,
31
+ avatarMediaId: row.avatar_media_id,
32
+ websiteUrl: row.website_url,
33
+ userId: row.user_id,
34
+ isGuest: row.is_guest === 1,
35
+ createdAt: row.created_at,
36
+ updatedAt: row.updated_at
37
+ };
38
+ }
39
+ var BylineRepository = class {
40
+ constructor(db) {
41
+ this.db = db;
42
+ }
43
+ async findById(id) {
44
+ const row = await this.db.selectFrom("_dineway_bylines").selectAll().where("id", "=", id).executeTakeFirst();
45
+ return row ? rowToByline(row) : null;
46
+ }
47
+ async findBySlug(slug) {
48
+ const row = await this.db.selectFrom("_dineway_bylines").selectAll().where("slug", "=", slug).executeTakeFirst();
49
+ return row ? rowToByline(row) : null;
50
+ }
51
+ async findByUserId(userId) {
52
+ const row = await this.db.selectFrom("_dineway_bylines").selectAll().where("user_id", "=", userId).executeTakeFirst();
53
+ return row ? rowToByline(row) : null;
54
+ }
55
+ async findMany(options) {
56
+ const limit = Math.min(Math.max(options?.limit ?? 50, 1), 100);
57
+ let query = this.db.selectFrom("_dineway_bylines").selectAll().orderBy("created_at", "desc").orderBy("id", "desc").limit(limit + 1);
58
+ if (options?.search) {
59
+ const term = `%${options.search.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
60
+ query = query.where((eb) => eb.or([eb("display_name", "like", term), eb("slug", "like", term)]));
61
+ }
62
+ if (options?.isGuest !== void 0) query = query.where("is_guest", "=", options.isGuest ? 1 : 0);
63
+ if (options?.userId !== void 0) query = query.where("user_id", "=", options.userId);
64
+ if (options?.cursor) {
65
+ const decoded = decodeCursor(options.cursor);
66
+ if (decoded) query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
67
+ }
68
+ const rows = await query.execute();
69
+ const items = rows.slice(0, limit).map(rowToByline);
70
+ const result = { items };
71
+ if (rows.length > limit) {
72
+ const last = items.at(-1);
73
+ if (last) result.nextCursor = encodeCursor(last.createdAt, last.id);
74
+ }
75
+ return result;
76
+ }
77
+ async create(input) {
78
+ const id = ulid();
79
+ const now = (/* @__PURE__ */ new Date()).toISOString();
80
+ await this.db.insertInto("_dineway_bylines").values({
81
+ id,
82
+ slug: input.slug,
83
+ display_name: input.displayName,
84
+ bio: input.bio ?? null,
85
+ avatar_media_id: input.avatarMediaId ?? null,
86
+ website_url: input.websiteUrl ?? null,
87
+ user_id: input.userId ?? null,
88
+ is_guest: input.isGuest ? 1 : 0,
89
+ created_at: now,
90
+ updated_at: now
91
+ }).execute();
92
+ const byline = await this.findById(id);
93
+ if (!byline) throw new Error("Failed to create byline");
94
+ return byline;
95
+ }
96
+ async update(id, input) {
97
+ if (!await this.findById(id)) return null;
98
+ const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
99
+ if (input.slug !== void 0) updates.slug = input.slug;
100
+ if (input.displayName !== void 0) updates.display_name = input.displayName;
101
+ if (input.bio !== void 0) updates.bio = input.bio;
102
+ if (input.avatarMediaId !== void 0) updates.avatar_media_id = input.avatarMediaId;
103
+ if (input.websiteUrl !== void 0) updates.website_url = input.websiteUrl;
104
+ if (input.userId !== void 0) updates.user_id = input.userId;
105
+ if (input.isGuest !== void 0) updates.is_guest = input.isGuest ? 1 : 0;
106
+ await this.db.updateTable("_dineway_bylines").set(updates).where("id", "=", id).execute();
107
+ return await this.findById(id);
108
+ }
109
+ async delete(id) {
110
+ if (!await this.findById(id)) return false;
111
+ await this.db.transaction().execute(async (trx) => {
112
+ await trx.deleteFrom("_dineway_content_bylines").where("byline_id", "=", id).execute();
113
+ await trx.deleteFrom("_dineway_bylines").where("id", "=", id).execute();
114
+ const tableNames = await listTablesLike(trx, "ec_%");
115
+ for (const tableName of tableNames) {
116
+ validateIdentifier(tableName, "content table");
117
+ await sql`
118
+ UPDATE ${sql.ref(tableName)}
119
+ SET primary_byline_id = NULL
120
+ WHERE primary_byline_id = ${id}
121
+ `.execute(trx);
122
+ }
123
+ });
124
+ return true;
125
+ }
126
+ async getContentBylines(collectionSlug, contentId) {
127
+ return (await this.db.selectFrom("_dineway_content_bylines as cb").innerJoin("_dineway_bylines as b", "b.id", "cb.byline_id").select([
128
+ "cb.sort_order as sort_order",
129
+ "cb.role_label as role_label",
130
+ "b.id as id",
131
+ "b.slug as slug",
132
+ "b.display_name as display_name",
133
+ "b.bio as bio",
134
+ "b.avatar_media_id as avatar_media_id",
135
+ "b.website_url as website_url",
136
+ "b.user_id as user_id",
137
+ "b.is_guest as is_guest",
138
+ "b.created_at as created_at",
139
+ "b.updated_at as updated_at"
140
+ ]).where("cb.collection_slug", "=", collectionSlug).where("cb.content_id", "=", contentId).orderBy("cb.sort_order", "asc").execute()).map((row) => ({
141
+ byline: rowToByline(row),
142
+ sortOrder: row.sort_order,
143
+ roleLabel: row.role_label
144
+ }));
145
+ }
146
+ /**
147
+ * Batch-fetch byline credits for multiple content items in a single query.
148
+ * Returns a Map keyed by contentId.
149
+ */
150
+ async getContentBylinesMany(collectionSlug, contentIds) {
151
+ const result = /* @__PURE__ */ new Map();
152
+ if (contentIds.length === 0) return result;
153
+ const uniqueContentIds = [...new Set(contentIds)];
154
+ for (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {
155
+ const rows = await this.db.selectFrom("_dineway_content_bylines as cb").innerJoin("_dineway_bylines as b", "b.id", "cb.byline_id").select([
156
+ "cb.content_id as content_id",
157
+ "cb.sort_order as sort_order",
158
+ "cb.role_label as role_label",
159
+ "b.id as id",
160
+ "b.slug as slug",
161
+ "b.display_name as display_name",
162
+ "b.bio as bio",
163
+ "b.avatar_media_id as avatar_media_id",
164
+ "b.website_url as website_url",
165
+ "b.user_id as user_id",
166
+ "b.is_guest as is_guest",
167
+ "b.created_at as created_at",
168
+ "b.updated_at as updated_at"
169
+ ]).where("cb.collection_slug", "=", collectionSlug).where("cb.content_id", "in", chunk).orderBy("cb.sort_order", "asc").execute();
170
+ for (const row of rows) {
171
+ const contentId = row.content_id;
172
+ const credit = {
173
+ byline: rowToByline(row),
174
+ sortOrder: row.sort_order,
175
+ roleLabel: row.role_label
176
+ };
177
+ const existing = result.get(contentId);
178
+ if (existing) existing.push(credit);
179
+ else result.set(contentId, [credit]);
180
+ }
181
+ }
182
+ return result;
183
+ }
184
+ /**
185
+ * Batch-fetch byline profiles linked to user IDs in a single query.
186
+ * Returns a Map keyed by userId.
187
+ */
188
+ async findByUserIds(userIds) {
189
+ const result = /* @__PURE__ */ new Map();
190
+ if (userIds.length === 0) return result;
191
+ for (const chunk of chunks(userIds, SQL_BATCH_SIZE)) {
192
+ const rows = await this.db.selectFrom("_dineway_bylines").selectAll().where("user_id", "in", chunk).execute();
193
+ for (const row of rows) if (row.user_id) result.set(row.user_id, rowToByline(row));
194
+ }
195
+ return result;
196
+ }
197
+ async setContentBylines(collectionSlug, contentId, inputBylines) {
198
+ validateIdentifier(collectionSlug, "collection slug");
199
+ const tableName = `ec_${collectionSlug}`;
200
+ validateIdentifier(tableName, "content table");
201
+ const seen = /* @__PURE__ */ new Set();
202
+ const bylines = inputBylines.filter((item) => {
203
+ if (seen.has(item.bylineId)) return false;
204
+ seen.add(item.bylineId);
205
+ return true;
206
+ });
207
+ if (bylines.length > 0) {
208
+ const ids = bylines.map((item) => item.bylineId);
209
+ if ((await this.db.selectFrom("_dineway_bylines").select("id").where("id", "in", ids).execute()).length !== ids.length) throw new Error("One or more byline IDs do not exist");
210
+ }
211
+ await this.db.deleteFrom("_dineway_content_bylines").where("collection_slug", "=", collectionSlug).where("content_id", "=", contentId).execute();
212
+ for (let i = 0; i < bylines.length; i++) {
213
+ const item = bylines[i];
214
+ await this.db.insertInto("_dineway_content_bylines").values({
215
+ id: ulid(),
216
+ collection_slug: collectionSlug,
217
+ content_id: contentId,
218
+ byline_id: item.bylineId,
219
+ sort_order: i,
220
+ role_label: item.roleLabel ?? null,
221
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
222
+ }).execute();
223
+ }
224
+ await sql`
225
+ UPDATE ${sql.ref(tableName)}
226
+ SET primary_byline_id = ${bylines[0]?.bylineId ?? null}
227
+ WHERE id = ${contentId}
228
+ `.execute(this.db);
229
+ return await this.getContentBylines(collectionSlug, contentId);
230
+ }
231
+ };
232
+
233
+ //#endregion
234
+ export { SQL_BATCH_SIZE as n, chunks as r, BylineRepository as t };