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,328 @@
1
+ import { r as currentTimestampValue } from "./dialect-helpers-B9uSp2GJ.mjs";
2
+ import { n as decodeCursor, r as encodeCursor } from "./types-BawVha09.mjs";
3
+ import { sql } from "kysely";
4
+ import { ulid } from "ulidx";
5
+
6
+ //#region src/redirects/patterns.ts
7
+ /**
8
+ * URL pattern matching for redirects.
9
+ *
10
+ * Uses Astro's route syntax: [param] for named segments, [...rest] for catch-all.
11
+ * Compiles patterns to safe regexes -- no user-supplied regex, no ReDoS risk.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const compiled = compilePattern("/old-blog/[...path]");
16
+ * const match = matchPattern(compiled, "/old-blog/2024/01/post");
17
+ * // match = { path: "2024/01/post" }
18
+ *
19
+ * interpolateDestination("/blog/[...path]", match);
20
+ * // "/blog/2024/01/post"
21
+ * ```
22
+ */
23
+ /** Matches [paramName] placeholders */
24
+ const PARAM_PATTERN = /\[(\w+)\]/g;
25
+ /** Matches [...splatName] placeholders */
26
+ const SPLAT_PATTERN = /\[\.\.\.(\w+)\]/g;
27
+ /** Combined pattern for validation: matches both [param] and [...splat] */
28
+ const ANY_PLACEHOLDER = /\[(?:\.\.\.)?(\w+)\]/g;
29
+ /** Split on capture groups in compiled regex string */
30
+ const CAPTURE_GROUP_SPLIT = /(\([^)]+\))/;
31
+ /** Escape regex-special characters in literal parts */
32
+ const REGEX_SPECIAL_CHARS = /[.*+?^${}|\\]/g;
33
+ /**
34
+ * Returns true if a source string contains [param] or [...splat] placeholders.
35
+ */
36
+ function isPattern(source) {
37
+ return source.match(ANY_PLACEHOLDER) !== null;
38
+ }
39
+ /**
40
+ * Compile a URL pattern into a regex for matching.
41
+ *
42
+ * - `[param]` matches a single path segment (`[^/]+`)
43
+ * - `[...rest]` matches one or more remaining segments (`.+`)
44
+ */
45
+ function compilePattern(source) {
46
+ const paramNames = [];
47
+ let regexStr = source.replace(SPLAT_PATTERN, (_match, name) => {
48
+ paramNames.push(name);
49
+ return "(.+)";
50
+ });
51
+ regexStr = regexStr.replace(PARAM_PATTERN, (_match, name) => {
52
+ paramNames.push(name);
53
+ return "([^/]+)";
54
+ });
55
+ const escaped = regexStr.split(CAPTURE_GROUP_SPLIT).map((part, i) => {
56
+ if (i % 2 === 1) return part;
57
+ return part.replace(REGEX_SPECIAL_CHARS, "\\$&");
58
+ }).join("");
59
+ return {
60
+ regex: new RegExp(`^${escaped}$`),
61
+ paramNames,
62
+ source
63
+ };
64
+ }
65
+ /**
66
+ * Match a path against a compiled pattern.
67
+ * Returns captured params or null if no match.
68
+ */
69
+ function matchPattern(compiled, path) {
70
+ const match = path.match(compiled.regex);
71
+ if (!match) return null;
72
+ const params = {};
73
+ for (let i = 0; i < compiled.paramNames.length; i++) {
74
+ const value = match[i + 1];
75
+ if (value !== void 0) params[compiled.paramNames[i]] = value;
76
+ }
77
+ return params;
78
+ }
79
+ /**
80
+ * Interpolate captured params into a destination pattern.
81
+ *
82
+ * @example
83
+ * interpolateDestination("/blog/[...path]", { path: "2024/01/post" })
84
+ * // "/blog/2024/01/post"
85
+ */
86
+ function interpolateDestination(destination, params) {
87
+ let result = destination.replace(SPLAT_PATTERN, (_match, name) => {
88
+ return params[name] ?? "";
89
+ });
90
+ result = result.replace(PARAM_PATTERN, (_match, name) => {
91
+ return params[name] ?? "";
92
+ });
93
+ return result;
94
+ }
95
+
96
+ //#endregion
97
+ //#region src/database/repositories/redirect.ts
98
+ function rowToRedirect(row) {
99
+ return {
100
+ id: row.id,
101
+ source: row.source,
102
+ destination: row.destination,
103
+ type: row.type,
104
+ isPattern: row.is_pattern === 1,
105
+ enabled: row.enabled === 1,
106
+ hits: row.hits,
107
+ lastHitAt: row.last_hit_at,
108
+ groupName: row.group_name,
109
+ auto: row.auto === 1,
110
+ createdAt: row.created_at,
111
+ updatedAt: row.updated_at
112
+ };
113
+ }
114
+ var RedirectRepository = class {
115
+ constructor(db) {
116
+ this.db = db;
117
+ }
118
+ async findById(id) {
119
+ const row = await this.db.selectFrom("_dineway_redirects").selectAll().where("id", "=", id).executeTakeFirst();
120
+ return row ? rowToRedirect(row) : null;
121
+ }
122
+ async findBySource(source) {
123
+ const row = await this.db.selectFrom("_dineway_redirects").selectAll().where("source", "=", source).executeTakeFirst();
124
+ return row ? rowToRedirect(row) : null;
125
+ }
126
+ async findMany(opts) {
127
+ const limit = Math.min(Math.max(opts.limit ?? 50, 1), 100);
128
+ let query = this.db.selectFrom("_dineway_redirects").selectAll().orderBy("created_at", "desc").orderBy("id", "desc").limit(limit + 1);
129
+ if (opts.search) {
130
+ const term = `%${opts.search}%`;
131
+ query = query.where((eb) => eb.or([eb("source", "like", term), eb("destination", "like", term)]));
132
+ }
133
+ if (opts.group !== void 0) query = query.where("group_name", "=", opts.group);
134
+ if (opts.enabled !== void 0) query = query.where("enabled", "=", opts.enabled ? 1 : 0);
135
+ if (opts.auto !== void 0) query = query.where("auto", "=", opts.auto ? 1 : 0);
136
+ if (opts.cursor) {
137
+ const decoded = decodeCursor(opts.cursor);
138
+ if (decoded) query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
139
+ }
140
+ const rows = await query.execute();
141
+ const items = rows.slice(0, limit).map(rowToRedirect);
142
+ const result = { items };
143
+ if (rows.length > limit) {
144
+ const last = items.at(-1);
145
+ result.nextCursor = encodeCursor(last.createdAt, last.id);
146
+ }
147
+ return result;
148
+ }
149
+ async create(input) {
150
+ const id = ulid();
151
+ const now = (/* @__PURE__ */ new Date()).toISOString();
152
+ const patternFlag = input.isPattern ?? isPattern(input.source);
153
+ await this.db.insertInto("_dineway_redirects").values({
154
+ id,
155
+ source: input.source,
156
+ destination: input.destination,
157
+ type: input.type ?? 301,
158
+ is_pattern: patternFlag ? 1 : 0,
159
+ enabled: input.enabled !== false ? 1 : 0,
160
+ hits: 0,
161
+ last_hit_at: null,
162
+ group_name: input.groupName ?? null,
163
+ auto: input.auto ? 1 : 0,
164
+ created_at: now,
165
+ updated_at: now
166
+ }).execute();
167
+ return await this.findById(id);
168
+ }
169
+ async update(id, input) {
170
+ if (!await this.findById(id)) return null;
171
+ const values = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
172
+ if (input.source !== void 0) {
173
+ values.source = input.source;
174
+ values.is_pattern = input.isPattern !== void 0 ? input.isPattern ? 1 : 0 : isPattern(input.source) ? 1 : 0;
175
+ } else if (input.isPattern !== void 0) values.is_pattern = input.isPattern ? 1 : 0;
176
+ if (input.destination !== void 0) values.destination = input.destination;
177
+ if (input.type !== void 0) values.type = input.type;
178
+ if (input.enabled !== void 0) values.enabled = input.enabled ? 1 : 0;
179
+ if (input.groupName !== void 0) values.group_name = input.groupName;
180
+ await this.db.updateTable("_dineway_redirects").set(values).where("id", "=", id).execute();
181
+ return await this.findById(id);
182
+ }
183
+ async delete(id) {
184
+ const result = await this.db.deleteFrom("_dineway_redirects").where("id", "=", id).executeTakeFirst();
185
+ return BigInt(result.numDeletedRows) > 0n;
186
+ }
187
+ async findExactMatch(path) {
188
+ const row = await this.db.selectFrom("_dineway_redirects").selectAll().where("source", "=", path).where("enabled", "=", 1).where("is_pattern", "=", 0).executeTakeFirst();
189
+ return row ? rowToRedirect(row) : null;
190
+ }
191
+ async findEnabledPatternRules() {
192
+ return (await this.db.selectFrom("_dineway_redirects").selectAll().where("enabled", "=", 1).where("is_pattern", "=", 1).execute()).map(rowToRedirect);
193
+ }
194
+ /**
195
+ * Match a request path against all enabled redirect rules.
196
+ * Checks exact matches first (indexed), then pattern rules.
197
+ * Returns the matched redirect and the resolved destination URL.
198
+ */
199
+ async matchPath(path) {
200
+ const exact = await this.findExactMatch(path);
201
+ if (exact) return {
202
+ redirect: exact,
203
+ resolvedDestination: exact.destination
204
+ };
205
+ const patterns = await this.findEnabledPatternRules();
206
+ for (const redirect of patterns) {
207
+ const params = matchPattern(compilePattern(redirect.source), path);
208
+ if (params) return {
209
+ redirect,
210
+ resolvedDestination: interpolateDestination(redirect.destination, params)
211
+ };
212
+ }
213
+ return null;
214
+ }
215
+ async recordHit(id) {
216
+ await sql`
217
+ UPDATE _dineway_redirects
218
+ SET hits = hits + 1, last_hit_at = ${currentTimestampValue(this.db)}, updated_at = ${currentTimestampValue(this.db)}
219
+ WHERE id = ${id}
220
+ `.execute(this.db);
221
+ }
222
+ /**
223
+ * Create an auto-redirect when a content slug changes.
224
+ * Uses the collection's URL pattern to compute old/new URLs.
225
+ * Collapses existing redirect chains pointing to the old URL.
226
+ */
227
+ async createAutoRedirect(collection, oldSlug, newSlug, contentId, urlPattern) {
228
+ const oldUrl = urlPattern ? urlPattern.replace("{slug}", oldSlug).replace("{id}", contentId) : `/${collection}/${oldSlug}`;
229
+ const newUrl = urlPattern ? urlPattern.replace("{slug}", newSlug).replace("{id}", contentId) : `/${collection}/${newSlug}`;
230
+ await this.collapseChains(oldUrl, newUrl);
231
+ const existing = await this.findBySource(oldUrl);
232
+ if (existing) return await this.update(existing.id, { destination: newUrl });
233
+ return this.create({
234
+ source: oldUrl,
235
+ destination: newUrl,
236
+ type: 301,
237
+ isPattern: false,
238
+ auto: true,
239
+ groupName: "Auto: slug change"
240
+ });
241
+ }
242
+ /**
243
+ * Update all redirects whose destination matches oldDestination
244
+ * to point to newDestination instead. Prevents redirect chains.
245
+ * Returns the number of updated rows.
246
+ */
247
+ async collapseChains(oldDestination, newDestination) {
248
+ const result = await this.db.updateTable("_dineway_redirects").set({
249
+ destination: newDestination,
250
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
251
+ }).where("destination", "=", oldDestination).executeTakeFirst();
252
+ return Number(result.numUpdatedRows);
253
+ }
254
+ async log404(entry) {
255
+ await this.db.insertInto("_dineway_404_log").values({
256
+ id: ulid(),
257
+ path: entry.path,
258
+ referrer: entry.referrer ?? null,
259
+ user_agent: entry.userAgent ?? null,
260
+ ip: entry.ip ?? null,
261
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
262
+ }).execute();
263
+ }
264
+ async find404s(opts) {
265
+ const limit = Math.min(Math.max(opts.limit ?? 50, 1), 100);
266
+ let query = this.db.selectFrom("_dineway_404_log").selectAll().orderBy("created_at", "desc").orderBy("id", "desc").limit(limit + 1);
267
+ if (opts.search) query = query.where("path", "like", `%${opts.search}%`);
268
+ if (opts.cursor) {
269
+ const decoded = decodeCursor(opts.cursor);
270
+ if (decoded) query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
271
+ }
272
+ const rows = await query.execute();
273
+ const items = rows.slice(0, limit).map((row) => ({
274
+ id: row.id,
275
+ path: row.path,
276
+ referrer: row.referrer,
277
+ userAgent: row.user_agent,
278
+ ip: row.ip,
279
+ createdAt: row.created_at
280
+ }));
281
+ const result = { items };
282
+ if (rows.length > limit) {
283
+ const last = items.at(-1);
284
+ result.nextCursor = encodeCursor(last.createdAt, last.id);
285
+ }
286
+ return result;
287
+ }
288
+ async get404Summary(limit = 50) {
289
+ return (await sql`
290
+ SELECT
291
+ path,
292
+ COUNT(*) as count,
293
+ MAX(created_at) as last_seen,
294
+ (
295
+ SELECT referrer FROM _dineway_404_log AS inner_log
296
+ WHERE inner_log.path = _dineway_404_log.path
297
+ AND referrer IS NOT NULL AND referrer != ''
298
+ GROUP BY referrer
299
+ ORDER BY COUNT(*) DESC
300
+ LIMIT 1
301
+ ) as top_referrer
302
+ FROM _dineway_404_log
303
+ GROUP BY path
304
+ ORDER BY count DESC
305
+ LIMIT ${limit}
306
+ `.execute(this.db)).rows.map((row) => ({
307
+ path: row.path,
308
+ count: Number(row.count),
309
+ lastSeen: row.last_seen,
310
+ topReferrer: row.top_referrer
311
+ }));
312
+ }
313
+ async delete404(id) {
314
+ const result = await this.db.deleteFrom("_dineway_404_log").where("id", "=", id).executeTakeFirst();
315
+ return BigInt(result.numDeletedRows) > 0n;
316
+ }
317
+ async clear404s() {
318
+ const result = await this.db.deleteFrom("_dineway_404_log").executeTakeFirst();
319
+ return Number(result.numDeletedRows);
320
+ }
321
+ async prune404s(olderThan) {
322
+ const result = await this.db.deleteFrom("_dineway_404_log").where("created_at", "<", olderThan).executeTakeFirst();
323
+ return Number(result.numDeletedRows);
324
+ }
325
+ };
326
+
327
+ //#endregion
328
+ export { RedirectRepository as t };