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,186 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/plugins/manifest-schema.ts
4
+ /**
5
+ * Zod schema for PluginManifest validation
6
+ *
7
+ * Used to validate manifest.json from plugin bundles at every parse site:
8
+ * - Client-side download (marketplace.ts extractBundle)
9
+ * - Bundle store load (plugins/bundle-store.ts read)
10
+ * - CLI publish preview (cli/commands/publish.ts readManifestFromTarball)
11
+ * - Marketplace ingest extends this with publishing-specific fields
12
+ */
13
+ const PLUGIN_CAPABILITIES = [
14
+ "network:fetch",
15
+ "network:fetch:any",
16
+ "read:content",
17
+ "write:content",
18
+ "read:media",
19
+ "write:media",
20
+ "read:users",
21
+ "email:send",
22
+ "email:provide",
23
+ "email:intercept",
24
+ "page:inject"
25
+ ];
26
+ /** Must stay in sync with FieldType in schema/types.ts */
27
+ const FIELD_TYPES = [
28
+ "string",
29
+ "text",
30
+ "number",
31
+ "integer",
32
+ "boolean",
33
+ "datetime",
34
+ "select",
35
+ "multiSelect",
36
+ "portableText",
37
+ "image",
38
+ "file",
39
+ "reference",
40
+ "json",
41
+ "slug",
42
+ "repeater"
43
+ ];
44
+ const HOOK_NAMES = [
45
+ "plugin:install",
46
+ "plugin:activate",
47
+ "plugin:deactivate",
48
+ "plugin:uninstall",
49
+ "content:beforeSave",
50
+ "content:afterSave",
51
+ "content:beforeDelete",
52
+ "content:afterDelete",
53
+ "content:afterPublish",
54
+ "content:afterUnpublish",
55
+ "media:beforeUpload",
56
+ "media:afterUpload",
57
+ "cron",
58
+ "email:beforeSend",
59
+ "email:deliver",
60
+ "email:afterSend",
61
+ "comment:beforeCreate",
62
+ "comment:moderate",
63
+ "comment:afterCreate",
64
+ "comment:afterModerate",
65
+ "page:metadata",
66
+ "page:fragments"
67
+ ];
68
+ /**
69
+ * Structured hook entry for manifest — name plus optional metadata.
70
+ * During a transition period, both plain strings and objects are accepted.
71
+ */
72
+ const manifestHookEntrySchema = z.object({
73
+ name: z.enum(HOOK_NAMES),
74
+ exclusive: z.boolean().optional(),
75
+ priority: z.number().int().optional(),
76
+ timeout: z.number().int().positive().optional()
77
+ });
78
+ /**
79
+ * Structured route entry for manifest — name plus optional metadata.
80
+ * Both plain strings and objects are accepted; strings are normalized
81
+ * to `{ name }` objects via `normalizeManifestRoute()`.
82
+ */
83
+ /** Route names must be safe path segments — alphanumeric, hyphens, underscores, forward slashes */
84
+ const routeNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9_\-/]*$/;
85
+ const manifestRouteEntrySchema = z.object({
86
+ name: z.string().min(1).regex(routeNamePattern, "Route name must be a safe path segment"),
87
+ public: z.boolean().optional()
88
+ });
89
+ /** Index field names must be valid identifiers to prevent SQL injection via JSON path expressions */
90
+ const indexFieldName = z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]*$/);
91
+ const storageCollectionSchema = z.object({
92
+ indexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])),
93
+ uniqueIndexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])).optional()
94
+ });
95
+ const baseSettingFields = {
96
+ label: z.string(),
97
+ description: z.string().optional()
98
+ };
99
+ const settingFieldSchema = z.discriminatedUnion("type", [
100
+ z.object({
101
+ ...baseSettingFields,
102
+ type: z.literal("string"),
103
+ default: z.string().optional(),
104
+ multiline: z.boolean().optional()
105
+ }),
106
+ z.object({
107
+ ...baseSettingFields,
108
+ type: z.literal("number"),
109
+ default: z.number().optional(),
110
+ min: z.number().optional(),
111
+ max: z.number().optional()
112
+ }),
113
+ z.object({
114
+ ...baseSettingFields,
115
+ type: z.literal("boolean"),
116
+ default: z.boolean().optional()
117
+ }),
118
+ z.object({
119
+ ...baseSettingFields,
120
+ type: z.literal("select"),
121
+ options: z.array(z.object({
122
+ value: z.string(),
123
+ label: z.string()
124
+ })),
125
+ default: z.string().optional()
126
+ }),
127
+ z.object({
128
+ ...baseSettingFields,
129
+ type: z.literal("secret")
130
+ })
131
+ ]);
132
+ const adminPageSchema = z.object({
133
+ path: z.string(),
134
+ label: z.string(),
135
+ icon: z.string().optional()
136
+ });
137
+ const dashboardWidgetSchema = z.object({
138
+ id: z.string(),
139
+ size: z.enum([
140
+ "full",
141
+ "half",
142
+ "third"
143
+ ]).optional(),
144
+ title: z.string().optional()
145
+ });
146
+ const pluginAdminConfigSchema = z.object({
147
+ entry: z.string().optional(),
148
+ settingsSchema: z.record(z.string(), settingFieldSchema).optional(),
149
+ pages: z.array(adminPageSchema).optional(),
150
+ widgets: z.array(dashboardWidgetSchema).optional(),
151
+ fieldWidgets: z.array(z.object({
152
+ name: z.string().min(1),
153
+ label: z.string().min(1),
154
+ fieldTypes: z.array(z.enum(FIELD_TYPES)),
155
+ elements: z.array(z.object({
156
+ type: z.string(),
157
+ action_id: z.string(),
158
+ label: z.string().optional()
159
+ }).passthrough()).optional()
160
+ })).optional()
161
+ });
162
+ /**
163
+ * Zod schema matching the PluginManifest interface from types.ts.
164
+ *
165
+ * Every JSON.parse of a manifest.json should validate through this.
166
+ */
167
+ const pluginManifestSchema = z.object({
168
+ id: z.string().min(1),
169
+ version: z.string().min(1),
170
+ capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),
171
+ allowedHosts: z.array(z.string()),
172
+ storage: z.record(z.string(), storageCollectionSchema),
173
+ hooks: z.array(z.union([z.enum(HOOK_NAMES), manifestHookEntrySchema])),
174
+ routes: z.array(z.union([z.string().min(1).regex(routeNamePattern, "Route name must be a safe path segment"), manifestRouteEntrySchema])),
175
+ admin: pluginAdminConfigSchema
176
+ });
177
+ /**
178
+ * Normalize a manifest route entry — plain strings become `{ name }` objects.
179
+ */
180
+ function normalizeManifestRoute(entry) {
181
+ if (typeof entry === "string") return { name: entry };
182
+ return entry;
183
+ }
184
+
185
+ //#endregion
186
+ export { pluginManifestSchema as i, PLUGIN_CAPABILITIES as n, normalizeManifestRoute as r, HOOK_NAMES as t };
@@ -0,0 +1,25 @@
1
+ import { _ as MediaValue, a as ComponentEmbed, b as mediaItemToValue, c as EmbedResult, d as MediaListResult, f as MediaProvider, g as MediaUploadInput, h as MediaProviderItem, i as AudioEmbed, l as ImageEmbed, m as MediaProviderDescriptor, n as generatePlaceholder, o as CreateMediaProviderFn, p as MediaProviderCapabilities, r as normalizeMediaValue, s as EmbedOptions, t as PlaceholderData, u as MediaListOptions, v as ThumbnailOptions, y as VideoEmbed } from "../placeholder-bOx1xCTY.mjs";
2
+
3
+ //#region src/media/local.d.ts
4
+ interface LocalMediaConfig {
5
+ /** Whether the local provider is enabled (default true) */
6
+ enabled?: boolean;
7
+ }
8
+ /**
9
+ * Local media provider configuration
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { localMedia } from "dineway/media";
14
+ *
15
+ * dineway({
16
+ * mediaProviders: [
17
+ * localMedia(), // Uses defaults
18
+ * // or: localMedia({ enabled: false }) to disable
19
+ * ],
20
+ * })
21
+ * ```
22
+ */
23
+ declare function localMedia(config?: LocalMediaConfig): MediaProviderDescriptor;
24
+ //#endregion
25
+ export { type AudioEmbed, type ComponentEmbed, type CreateMediaProviderFn, type EmbedOptions, type EmbedResult, type ImageEmbed, type LocalMediaConfig, type MediaListOptions, type MediaListResult, type MediaProvider, type MediaProviderCapabilities, type MediaProviderDescriptor, type MediaProviderItem, type MediaUploadInput, type MediaValue, type PlaceholderData, type ThumbnailOptions, type VideoEmbed, generatePlaceholder, localMedia, mediaItemToValue, normalizeMediaValue };
@@ -0,0 +1,54 @@
1
+ import { n as normalizeMediaValue, t as generatePlaceholder } from "../placeholder-B3knXwNc.mjs";
2
+
3
+ //#region src/media/types.ts
4
+ /**
5
+ * Convert a MediaProviderItem to a MediaValue for storage
6
+ */
7
+ function mediaItemToValue(providerId, item) {
8
+ return {
9
+ provider: providerId,
10
+ id: item.id,
11
+ filename: item.filename,
12
+ mimeType: item.mimeType,
13
+ width: item.width,
14
+ height: item.height,
15
+ alt: item.alt,
16
+ meta: item.meta
17
+ };
18
+ }
19
+
20
+ //#endregion
21
+ //#region src/media/local.ts
22
+ /**
23
+ * Local media provider configuration
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { localMedia } from "dineway/media";
28
+ *
29
+ * dineway({
30
+ * mediaProviders: [
31
+ * localMedia(), // Uses defaults
32
+ * // or: localMedia({ enabled: false }) to disable
33
+ * ],
34
+ * })
35
+ * ```
36
+ */
37
+ function localMedia(config = {}) {
38
+ return {
39
+ id: "local",
40
+ name: "Library",
41
+ icon: "📁",
42
+ entrypoint: "dineway/media/local-runtime",
43
+ capabilities: {
44
+ browse: true,
45
+ search: false,
46
+ upload: true,
47
+ delete: true
48
+ },
49
+ config: { enabled: config.enabled ?? true }
50
+ };
51
+ }
52
+
53
+ //#endregion
54
+ export { generatePlaceholder, localMedia, mediaItemToValue, normalizeMediaValue };
@@ -0,0 +1,38 @@
1
+ import { h as MediaProviderItem, o as CreateMediaProviderFn } from "../placeholder-bOx1xCTY.mjs";
2
+ import { t as Database } from "../types-DkvMXalq.mjs";
3
+ import "../index-C-jx21qs.mjs";
4
+ import "../runner-B5l1JfOj.mjs";
5
+ import "../types-D38djUXv.mjs";
6
+ import "../validate-DVKJJ-M_.mjs";
7
+ import { d as Storage } from "../types-ju-_ORz7.mjs";
8
+ import "../index.mjs";
9
+ import { Kysely } from "kysely";
10
+
11
+ //#region src/media/local-runtime.d.ts
12
+ interface LocalMediaRuntimeConfig {
13
+ enabled?: boolean;
14
+ db?: Kysely<Database>;
15
+ storage?: Storage;
16
+ }
17
+ /**
18
+ * Create the local media provider
19
+ */
20
+ declare const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>;
21
+ /**
22
+ * Helper to convert a MediaRepository item to MediaProviderItem
23
+ */
24
+ declare function repoItemToProviderItem(item: {
25
+ id: string;
26
+ filename: string;
27
+ mimeType: string;
28
+ size: number | null;
29
+ width: number | null;
30
+ height: number | null;
31
+ alt: string | null;
32
+ caption: string | null;
33
+ storageKey: string;
34
+ blurhash: string | null;
35
+ dominantColor: string | null;
36
+ }): MediaProviderItem;
37
+ //#endregion
38
+ export { LocalMediaRuntimeConfig, createMediaProvider, repoItemToProviderItem };
@@ -0,0 +1,132 @@
1
+ import "../base64-F8-DUraK.mjs";
2
+ import "../types-BawVha09.mjs";
3
+ import { t as MediaRepository } from "../media-DMTr80Gv.mjs";
4
+
5
+ //#region src/media/local-runtime.ts
6
+ /**
7
+ * Create the local media provider
8
+ */
9
+ const createMediaProvider = (config) => {
10
+ const { db, storage } = config;
11
+ if (!db) throw new Error("Local media provider requires database connection");
12
+ const repo = new MediaRepository(db);
13
+ return {
14
+ async list(options) {
15
+ const result = await repo.findMany({
16
+ cursor: options.cursor,
17
+ limit: options.limit,
18
+ mimeType: options.mimeType
19
+ });
20
+ return {
21
+ items: result.items.map((item) => ({
22
+ id: item.id,
23
+ filename: item.filename,
24
+ mimeType: item.mimeType,
25
+ size: item.size ?? void 0,
26
+ width: item.width ?? void 0,
27
+ height: item.height ?? void 0,
28
+ alt: item.alt ?? void 0,
29
+ previewUrl: `/_dineway/api/media/file/${item.storageKey}`,
30
+ meta: {
31
+ storageKey: item.storageKey,
32
+ caption: item.caption,
33
+ blurhash: item.blurhash,
34
+ dominantColor: item.dominantColor
35
+ }
36
+ })),
37
+ nextCursor: result.nextCursor
38
+ };
39
+ },
40
+ async get(id) {
41
+ const item = await repo.findById(id);
42
+ if (!item) return null;
43
+ return {
44
+ id: item.id,
45
+ filename: item.filename,
46
+ mimeType: item.mimeType,
47
+ size: item.size ?? void 0,
48
+ width: item.width ?? void 0,
49
+ height: item.height ?? void 0,
50
+ alt: item.alt ?? void 0,
51
+ previewUrl: `/_dineway/api/media/file/${item.storageKey}`,
52
+ meta: {
53
+ storageKey: item.storageKey,
54
+ caption: item.caption,
55
+ blurhash: item.blurhash,
56
+ dominantColor: item.dominantColor
57
+ }
58
+ };
59
+ },
60
+ async upload(_input) {
61
+ if (!storage) throw new Error("Storage not configured for local media provider");
62
+ throw new Error("Local upload should use /_dineway/api/media endpoint");
63
+ },
64
+ async delete(id) {
65
+ const item = await repo.findById(id);
66
+ if (!item) return;
67
+ if (storage) try {
68
+ await storage.delete(item.storageKey);
69
+ } catch {}
70
+ await repo.delete(id);
71
+ },
72
+ getEmbed(value, _options) {
73
+ const src = `/_dineway/api/media/file/${typeof value.meta?.storageKey === "string" ? value.meta.storageKey : value.id}`;
74
+ const mimeType = value.mimeType || "";
75
+ if (mimeType.startsWith("image/")) return {
76
+ type: "image",
77
+ src,
78
+ width: value.width,
79
+ height: value.height,
80
+ alt: value.alt
81
+ };
82
+ if (mimeType.startsWith("video/")) return {
83
+ type: "video",
84
+ src,
85
+ width: value.width,
86
+ height: value.height,
87
+ controls: true,
88
+ preload: "metadata"
89
+ };
90
+ if (mimeType.startsWith("audio/")) return {
91
+ type: "audio",
92
+ src,
93
+ controls: true,
94
+ preload: "metadata"
95
+ };
96
+ return {
97
+ type: "image",
98
+ src,
99
+ width: value.width,
100
+ height: value.height,
101
+ alt: value.alt
102
+ };
103
+ },
104
+ getThumbnailUrl(id, _mimeType) {
105
+ return `/_dineway/api/media/file/${id}`;
106
+ }
107
+ };
108
+ };
109
+ /**
110
+ * Helper to convert a MediaRepository item to MediaProviderItem
111
+ */
112
+ function repoItemToProviderItem(item) {
113
+ return {
114
+ id: item.id,
115
+ filename: item.filename,
116
+ mimeType: item.mimeType,
117
+ size: item.size ?? void 0,
118
+ width: item.width ?? void 0,
119
+ height: item.height ?? void 0,
120
+ alt: item.alt ?? void 0,
121
+ previewUrl: `/_dineway/api/media/file/${item.storageKey}`,
122
+ meta: {
123
+ storageKey: item.storageKey,
124
+ caption: item.caption,
125
+ blurhash: item.blurhash,
126
+ dominantColor: item.dominantColor
127
+ }
128
+ };
129
+ }
130
+
131
+ //#endregion
132
+ export { createMediaProvider, repoItemToProviderItem };
@@ -0,0 +1,199 @@
1
+ import { n as decodeCursor, r as encodeCursor } from "./types-BawVha09.mjs";
2
+ import { sql } from "kysely";
3
+ import { ulid } from "ulidx";
4
+
5
+ //#region src/database/repositories/media.ts
6
+ /** Escape LIKE wildcard characters and the escape char itself in user-supplied values */
7
+ function escapeLike(value) {
8
+ return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
9
+ }
10
+ /**
11
+ * Media repository for database operations
12
+ */
13
+ var MediaRepository = class {
14
+ constructor(db) {
15
+ this.db = db;
16
+ }
17
+ /**
18
+ * Create a new media item
19
+ */
20
+ async create(input) {
21
+ const id = ulid();
22
+ const now = (/* @__PURE__ */ new Date()).toISOString();
23
+ const row = {
24
+ id,
25
+ filename: input.filename,
26
+ mime_type: input.mimeType,
27
+ size: input.size ?? null,
28
+ width: input.width ?? null,
29
+ height: input.height ?? null,
30
+ alt: input.alt ?? null,
31
+ caption: input.caption ?? null,
32
+ storage_key: input.storageKey,
33
+ content_hash: input.contentHash ?? null,
34
+ blurhash: input.blurhash ?? null,
35
+ dominant_color: input.dominantColor ?? null,
36
+ status: input.status ?? "ready",
37
+ created_at: now,
38
+ author_id: input.authorId ?? null
39
+ };
40
+ await this.db.insertInto("media").values(row).execute();
41
+ return this.rowToItem(row);
42
+ }
43
+ /**
44
+ * Create a pending media item (for signed URL upload flow)
45
+ */
46
+ async createPending(input) {
47
+ return this.create({
48
+ ...input,
49
+ status: "pending"
50
+ });
51
+ }
52
+ /**
53
+ * Confirm upload (mark as ready)
54
+ */
55
+ async confirmUpload(id, metadata) {
56
+ if (!await this.findById(id)) return null;
57
+ const updates = { status: "ready" };
58
+ if (metadata?.width !== void 0) updates.width = metadata.width;
59
+ if (metadata?.height !== void 0) updates.height = metadata.height;
60
+ if (metadata?.size !== void 0) updates.size = metadata.size;
61
+ await this.db.updateTable("media").set(updates).where("id", "=", id).execute();
62
+ return this.findById(id);
63
+ }
64
+ /**
65
+ * Mark upload as failed
66
+ */
67
+ async markFailed(id) {
68
+ if (!await this.findById(id)) return null;
69
+ await this.db.updateTable("media").set({ status: "failed" }).where("id", "=", id).execute();
70
+ return this.findById(id);
71
+ }
72
+ /**
73
+ * Find media by ID
74
+ */
75
+ async findById(id) {
76
+ const row = await this.db.selectFrom("media").selectAll().where("id", "=", id).executeTakeFirst();
77
+ return row ? this.rowToItem(row) : null;
78
+ }
79
+ /**
80
+ * Find media by filename
81
+ * Useful for idempotent imports
82
+ */
83
+ async findByFilename(filename) {
84
+ const row = await this.db.selectFrom("media").selectAll().where("filename", "=", filename).executeTakeFirst();
85
+ return row ? this.rowToItem(row) : null;
86
+ }
87
+ /**
88
+ * Find media by content hash
89
+ * Used for deduplication - same content = same hash
90
+ */
91
+ async findByContentHash(contentHash) {
92
+ const row = await this.db.selectFrom("media").selectAll().where("content_hash", "=", contentHash).where("status", "=", "ready").executeTakeFirst();
93
+ return row ? this.rowToItem(row) : null;
94
+ }
95
+ /**
96
+ * Find many media items with cursor pagination
97
+ *
98
+ * Uses keyset pagination (cursor-based) for consistent results.
99
+ * The cursor encodes the created_at and id of the last item.
100
+ */
101
+ async findMany(options = {}) {
102
+ const limit = Math.min(options.limit || 50, 100);
103
+ let query = this.db.selectFrom("media").selectAll().orderBy("created_at", "desc").orderBy("id", "desc").limit(limit + 1);
104
+ if (options.cursor) {
105
+ const decoded = decodeCursor(options.cursor);
106
+ if (decoded) {
107
+ const { orderValue: createdAt, id: cursorId } = decoded;
108
+ query = query.where((eb) => eb.or([eb("created_at", "<", createdAt), eb.and([eb("created_at", "=", createdAt), eb("id", "<", cursorId)])]));
109
+ }
110
+ }
111
+ if (options.mimeType) {
112
+ const pattern = `${escapeLike(options.mimeType)}%`;
113
+ query = query.where(sql`mime_type LIKE ${pattern} ESCAPE '\\'`);
114
+ }
115
+ if (options.status !== "all") query = query.where("status", "=", options.status ?? "ready");
116
+ const rows = await query.execute();
117
+ const hasMore = rows.length > limit;
118
+ const items = rows.slice(0, limit).map((row) => this.rowToItem(row));
119
+ let nextCursor;
120
+ if (hasMore && items.length > 0) {
121
+ const lastItem = items.at(-1);
122
+ nextCursor = encodeCursor(lastItem.createdAt, lastItem.id);
123
+ }
124
+ return {
125
+ items,
126
+ nextCursor
127
+ };
128
+ }
129
+ /**
130
+ * Update media metadata
131
+ */
132
+ async update(id, input) {
133
+ if (!await this.findById(id)) return null;
134
+ const updates = {};
135
+ if (input.alt !== void 0) updates.alt = input.alt;
136
+ if (input.caption !== void 0) updates.caption = input.caption;
137
+ if (input.width !== void 0) updates.width = input.width;
138
+ if (input.height !== void 0) updates.height = input.height;
139
+ if (Object.keys(updates).length > 0) await this.db.updateTable("media").set(updates).where("id", "=", id).execute();
140
+ return this.findById(id);
141
+ }
142
+ /**
143
+ * Delete media item
144
+ */
145
+ async delete(id) {
146
+ return ((await this.db.deleteFrom("media").where("id", "=", id).executeTakeFirst()).numDeletedRows ?? 0) > 0;
147
+ }
148
+ /**
149
+ * Count media items
150
+ */
151
+ async count(mimeType) {
152
+ let query = this.db.selectFrom("media").select((eb) => eb.fn.count("id").as("count"));
153
+ if (mimeType) {
154
+ const pattern = `${escapeLike(mimeType)}%`;
155
+ query = query.where(sql`mime_type LIKE ${pattern} ESCAPE '\\'`);
156
+ }
157
+ const result = await query.executeTakeFirst();
158
+ return Number(result?.count || 0);
159
+ }
160
+ /**
161
+ * Delete pending uploads older than the given age.
162
+ * Pending uploads that were never confirmed indicate abandoned upload flows.
163
+ *
164
+ * Returns the storage keys of deleted rows so callers can remove the
165
+ * corresponding files from object storage.
166
+ */
167
+ async cleanupPendingUploads(maxAgeMs = 3600 * 1e3) {
168
+ const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
169
+ const rows = await this.db.selectFrom("media").select("storage_key").where("status", "=", "pending").where("created_at", "<", cutoff).execute();
170
+ if (rows.length === 0) return [];
171
+ await this.db.deleteFrom("media").where("status", "=", "pending").where("created_at", "<", cutoff).execute();
172
+ return rows.map((r) => r.storage_key);
173
+ }
174
+ /**
175
+ * Convert database row to MediaItem
176
+ */
177
+ rowToItem(row) {
178
+ return {
179
+ id: row.id,
180
+ filename: row.filename,
181
+ mimeType: row.mime_type,
182
+ size: row.size,
183
+ width: row.width,
184
+ height: row.height,
185
+ alt: row.alt,
186
+ caption: row.caption,
187
+ storageKey: row.storage_key,
188
+ contentHash: row.content_hash,
189
+ blurhash: row.blurhash,
190
+ dominantColor: row.dominant_color,
191
+ status: row.status,
192
+ createdAt: row.created_at,
193
+ authorId: row.author_id
194
+ };
195
+ }
196
+ };
197
+
198
+ //#endregion
199
+ export { MediaRepository as t };
@@ -0,0 +1,22 @@
1
+ //#region src/auth/mode.ts
2
+ /**
3
+ * Determine the active auth mode from config.
4
+ *
5
+ * Accepts `DinewayConfig` (or subtype) — checks for `auth` field via duck typing.
6
+ *
7
+ * @param config Dineway configuration
8
+ * @returns The active auth mode
9
+ */
10
+ function getAuthMode(config) {
11
+ const auth = config?.auth;
12
+ if (auth && "entrypoint" in auth && auth.entrypoint) return {
13
+ type: "external",
14
+ providerType: auth.type,
15
+ entrypoint: auth.entrypoint,
16
+ config: auth.config
17
+ };
18
+ return { type: "passkey" };
19
+ }
20
+
21
+ //#endregion
22
+ export { getAuthMode as t };