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,327 @@
1
+ import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
2
+ import { t as FIELD_TYPES } from "./types-DuNbGKjF.mjs";
3
+
4
+ //#region src/seed/validate.ts
5
+ /**
6
+ * Seed file validation
7
+ *
8
+ * Validates a seed file structure before applying it.
9
+ */
10
+ var validate_exports = /* @__PURE__ */ __exportAll({ validateSeed: () => validateSeed });
11
+ const COLLECTION_FIELD_SLUG_PATTERN = /^[a-z][a-z0-9_]*$/;
12
+ const SLUG_PATTERN = /^[a-z0-9-]+$/;
13
+ const REDIRECT_TYPES = new Set([
14
+ 301,
15
+ 302,
16
+ 307,
17
+ 308
18
+ ]);
19
+ const CRLF_PATTERN = /[\r\n]/;
20
+ /** Type guard for Record<string, unknown> */
21
+ function isRecord(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ function isValidRedirectPath(path) {
25
+ if (!path.startsWith("/") || path.startsWith("//") || CRLF_PATTERN.test(path)) return false;
26
+ try {
27
+ return !decodeURIComponent(path).split("/").includes("..");
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Validate a seed file
34
+ *
35
+ * @param data - Unknown data to validate as a seed file
36
+ * @returns Validation result with errors and warnings
37
+ */
38
+ function validateSeed(data) {
39
+ const errors = [];
40
+ const warnings = [];
41
+ if (!data || typeof data !== "object") return {
42
+ valid: false,
43
+ errors: ["Seed must be an object"],
44
+ warnings: []
45
+ };
46
+ const seed = data;
47
+ if (!seed.version) errors.push("Seed must have a version field");
48
+ else if (seed.version !== "1") errors.push(`Unsupported seed version: ${String(seed.version)}`);
49
+ if (seed.collections) if (!Array.isArray(seed.collections)) errors.push("collections must be an array");
50
+ else {
51
+ const collectionSlugs = /* @__PURE__ */ new Set();
52
+ for (let i = 0; i < seed.collections.length; i++) {
53
+ const collection = seed.collections[i];
54
+ const prefix = `collections[${i}]`;
55
+ if (!collection.slug) errors.push(`${prefix}: slug is required`);
56
+ else {
57
+ if (!COLLECTION_FIELD_SLUG_PATTERN.test(collection.slug)) errors.push(`${prefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`);
58
+ if (collectionSlugs.has(collection.slug)) errors.push(`${prefix}.slug: duplicate collection slug "${collection.slug}"`);
59
+ collectionSlugs.add(collection.slug);
60
+ }
61
+ if (!collection.label) errors.push(`${prefix}: label is required`);
62
+ if (!Array.isArray(collection.fields)) errors.push(`${prefix}.fields: must be an array`);
63
+ else {
64
+ const fieldSlugs = /* @__PURE__ */ new Set();
65
+ for (let j = 0; j < collection.fields.length; j++) {
66
+ const field = collection.fields[j];
67
+ const fieldPrefix = `${prefix}.fields[${j}]`;
68
+ if (!field.slug) errors.push(`${fieldPrefix}: slug is required`);
69
+ else {
70
+ if (!COLLECTION_FIELD_SLUG_PATTERN.test(field.slug)) errors.push(`${fieldPrefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`);
71
+ if (fieldSlugs.has(field.slug)) errors.push(`${fieldPrefix}.slug: duplicate field slug "${field.slug}" in collection "${collection.slug}"`);
72
+ fieldSlugs.add(field.slug);
73
+ }
74
+ if (!field.label) errors.push(`${fieldPrefix}: label is required`);
75
+ if (!field.type) errors.push(`${fieldPrefix}: type is required`);
76
+ else if (!FIELD_TYPES.includes(field.type)) errors.push(`${fieldPrefix}.type: unsupported field type "${field.type}"`);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ if (seed.taxonomies) if (!Array.isArray(seed.taxonomies)) errors.push("taxonomies must be an array");
82
+ else {
83
+ const taxonomyNames = /* @__PURE__ */ new Set();
84
+ for (let i = 0; i < seed.taxonomies.length; i++) {
85
+ const taxonomy = seed.taxonomies[i];
86
+ const prefix = `taxonomies[${i}]`;
87
+ if (!taxonomy.name) errors.push(`${prefix}: name is required`);
88
+ else {
89
+ if (taxonomyNames.has(taxonomy.name)) errors.push(`${prefix}.name: duplicate taxonomy name "${taxonomy.name}"`);
90
+ taxonomyNames.add(taxonomy.name);
91
+ }
92
+ if (!taxonomy.label) errors.push(`${prefix}: label is required`);
93
+ if (taxonomy.hierarchical === void 0) errors.push(`${prefix}: hierarchical is required`);
94
+ if (!Array.isArray(taxonomy.collections)) errors.push(`${prefix}.collections: must be an array`);
95
+ else if (taxonomy.collections.length === 0) warnings.push(`${prefix}.collections: taxonomy "${taxonomy.name}" is not assigned to any collections`);
96
+ if (taxonomy.terms) if (!Array.isArray(taxonomy.terms)) errors.push(`${prefix}.terms: must be an array`);
97
+ else {
98
+ const termSlugs = /* @__PURE__ */ new Set();
99
+ for (let j = 0; j < taxonomy.terms.length; j++) {
100
+ const term = taxonomy.terms[j];
101
+ const termPrefix = `${prefix}.terms[${j}]`;
102
+ if (!term.slug) errors.push(`${termPrefix}: slug is required`);
103
+ else {
104
+ if (termSlugs.has(term.slug)) errors.push(`${termPrefix}.slug: duplicate term slug "${term.slug}" in taxonomy "${taxonomy.name}"`);
105
+ termSlugs.add(term.slug);
106
+ }
107
+ if (!term.label) errors.push(`${termPrefix}: label is required`);
108
+ if (term.parent && taxonomy.hierarchical) {} else if (term.parent && !taxonomy.hierarchical) warnings.push(`${termPrefix}.parent: taxonomy "${taxonomy.name}" is not hierarchical, parent will be ignored`);
109
+ }
110
+ if (taxonomy.hierarchical && taxonomy.terms) for (let j = 0; j < taxonomy.terms.length; j++) {
111
+ const term = taxonomy.terms[j];
112
+ if (term.parent && !termSlugs.has(term.parent)) errors.push(`${prefix}.terms[${j}].parent: parent term "${term.parent}" not found in taxonomy`);
113
+ if (term.parent === term.slug) errors.push(`${prefix}.terms[${j}].parent: term cannot be its own parent`);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ if (seed.menus) if (!Array.isArray(seed.menus)) errors.push("menus must be an array");
119
+ else {
120
+ const menuNames = /* @__PURE__ */ new Set();
121
+ for (let i = 0; i < seed.menus.length; i++) {
122
+ const menu = seed.menus[i];
123
+ const prefix = `menus[${i}]`;
124
+ if (!menu.name) errors.push(`${prefix}: name is required`);
125
+ else {
126
+ if (menuNames.has(menu.name)) errors.push(`${prefix}.name: duplicate menu name "${menu.name}"`);
127
+ menuNames.add(menu.name);
128
+ }
129
+ if (!menu.label) errors.push(`${prefix}: label is required`);
130
+ if (!Array.isArray(menu.items)) errors.push(`${prefix}.items: must be an array`);
131
+ else validateMenuItems(menu.items, prefix, errors, warnings);
132
+ }
133
+ }
134
+ if (seed.redirects) if (!Array.isArray(seed.redirects)) errors.push("redirects must be an array");
135
+ else {
136
+ const redirectSources = /* @__PURE__ */ new Set();
137
+ for (let i = 0; i < seed.redirects.length; i++) {
138
+ const redirect = seed.redirects[i];
139
+ const prefix = `redirects[${i}]`;
140
+ if (!isRecord(redirect)) {
141
+ errors.push(`${prefix}: must be an object`);
142
+ continue;
143
+ }
144
+ const source = typeof redirect.source === "string" ? redirect.source : void 0;
145
+ const destination = typeof redirect.destination === "string" ? redirect.destination : void 0;
146
+ if (!source) errors.push(`${prefix}: source is required`);
147
+ else {
148
+ if (!isValidRedirectPath(source)) errors.push(`${prefix}.source: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`);
149
+ if (redirectSources.has(source)) errors.push(`${prefix}.source: duplicate redirect source "${source}"`);
150
+ redirectSources.add(source);
151
+ }
152
+ if (!destination) errors.push(`${prefix}: destination is required`);
153
+ else if (!isValidRedirectPath(destination)) errors.push(`${prefix}.destination: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`);
154
+ if (redirect.type !== void 0) {
155
+ if (typeof redirect.type !== "number" || !REDIRECT_TYPES.has(redirect.type)) errors.push(`${prefix}.type: must be 301, 302, 307, or 308`);
156
+ }
157
+ if (redirect.enabled !== void 0 && typeof redirect.enabled !== "boolean") errors.push(`${prefix}.enabled: must be a boolean`);
158
+ if (redirect.groupName !== void 0 && typeof redirect.groupName !== "string" && redirect.groupName !== null) errors.push(`${prefix}.groupName: must be a string or null`);
159
+ }
160
+ }
161
+ if (seed.widgetAreas) if (!Array.isArray(seed.widgetAreas)) errors.push("widgetAreas must be an array");
162
+ else {
163
+ const areaNames = /* @__PURE__ */ new Set();
164
+ for (let i = 0; i < seed.widgetAreas.length; i++) {
165
+ const area = seed.widgetAreas[i];
166
+ const prefix = `widgetAreas[${i}]`;
167
+ if (!area.name) errors.push(`${prefix}: name is required`);
168
+ else {
169
+ if (areaNames.has(area.name)) errors.push(`${prefix}.name: duplicate widget area name "${area.name}"`);
170
+ areaNames.add(area.name);
171
+ }
172
+ if (!area.label) errors.push(`${prefix}: label is required`);
173
+ if (!Array.isArray(area.widgets)) errors.push(`${prefix}.widgets: must be an array`);
174
+ else for (let j = 0; j < area.widgets.length; j++) {
175
+ const widget = area.widgets[j];
176
+ const widgetPrefix = `${prefix}.widgets[${j}]`;
177
+ if (!widget.type) errors.push(`${widgetPrefix}: type is required`);
178
+ else if (![
179
+ "content",
180
+ "menu",
181
+ "component"
182
+ ].includes(widget.type)) errors.push(`${widgetPrefix}.type: must be "content", "menu", or "component"`);
183
+ if (widget.type === "menu" && !widget.menuName) errors.push(`${widgetPrefix}: menuName is required for menu widgets`);
184
+ if (widget.type === "component" && !widget.componentId) errors.push(`${widgetPrefix}: componentId is required for component widgets`);
185
+ }
186
+ }
187
+ }
188
+ if (seed.sections) if (!Array.isArray(seed.sections)) errors.push("sections must be an array");
189
+ else {
190
+ const sectionSlugs = /* @__PURE__ */ new Set();
191
+ for (let i = 0; i < seed.sections.length; i++) {
192
+ const section = seed.sections[i];
193
+ const prefix = `sections[${i}]`;
194
+ if (!section.slug) errors.push(`${prefix}: slug is required`);
195
+ else {
196
+ if (!SLUG_PATTERN.test(section.slug)) errors.push(`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`);
197
+ if (sectionSlugs.has(section.slug)) errors.push(`${prefix}.slug: duplicate section slug "${section.slug}"`);
198
+ sectionSlugs.add(section.slug);
199
+ }
200
+ if (!section.title) errors.push(`${prefix}: title is required`);
201
+ if (!Array.isArray(section.content)) errors.push(`${prefix}.content: must be an array`);
202
+ if (section.source && !["theme", "import"].includes(section.source)) errors.push(`${prefix}.source: must be "theme" or "import"`);
203
+ }
204
+ }
205
+ if (seed.bylines) if (!Array.isArray(seed.bylines)) errors.push("bylines must be an array");
206
+ else {
207
+ const bylineIds = /* @__PURE__ */ new Set();
208
+ const bylineSlugs = /* @__PURE__ */ new Set();
209
+ for (let i = 0; i < seed.bylines.length; i++) {
210
+ const byline = seed.bylines[i];
211
+ const prefix = `bylines[${i}]`;
212
+ if (!byline.id) errors.push(`${prefix}: id is required`);
213
+ else {
214
+ if (bylineIds.has(byline.id)) errors.push(`${prefix}.id: duplicate byline id "${byline.id}"`);
215
+ bylineIds.add(byline.id);
216
+ }
217
+ if (!byline.slug) errors.push(`${prefix}: slug is required`);
218
+ else {
219
+ if (!SLUG_PATTERN.test(byline.slug)) errors.push(`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`);
220
+ if (bylineSlugs.has(byline.slug)) errors.push(`${prefix}.slug: duplicate byline slug "${byline.slug}"`);
221
+ bylineSlugs.add(byline.slug);
222
+ }
223
+ if (!byline.displayName) errors.push(`${prefix}: displayName is required`);
224
+ }
225
+ }
226
+ if (seed.content) if (typeof seed.content !== "object" || Array.isArray(seed.content)) errors.push("content must be an object (collection -> entries)");
227
+ else for (const [collectionSlug, entries] of Object.entries(seed.content)) {
228
+ if (!Array.isArray(entries)) {
229
+ errors.push(`content.${collectionSlug}: must be an array`);
230
+ continue;
231
+ }
232
+ const entryIds = /* @__PURE__ */ new Set();
233
+ for (let i = 0; i < entries.length; i++) {
234
+ const entry = entries[i];
235
+ const prefix = `content.${collectionSlug}[${i}]`;
236
+ if (!entry.id) errors.push(`${prefix}: id is required`);
237
+ else {
238
+ if (entryIds.has(entry.id)) errors.push(`${prefix}.id: duplicate entry id "${entry.id}" in collection "${collectionSlug}"`);
239
+ entryIds.add(entry.id);
240
+ }
241
+ if (!entry.slug) errors.push(`${prefix}: slug is required`);
242
+ if (!entry.data || typeof entry.data !== "object") errors.push(`${prefix}: data must be an object`);
243
+ if (entry.translationOf) {
244
+ if (!entry.locale) errors.push(`${prefix}: locale is required when translationOf is set`);
245
+ }
246
+ }
247
+ for (let i = 0; i < entries.length; i++) {
248
+ const entry = entries[i];
249
+ if (entry.translationOf && !entryIds.has(entry.translationOf)) errors.push(`content.${collectionSlug}[${i}].translationOf: references "${entry.translationOf}" which is not in this collection`);
250
+ }
251
+ }
252
+ if (seed.menus && seed.content) {
253
+ const allContentIds = /* @__PURE__ */ new Set();
254
+ for (const entries of Object.values(seed.content)) if (Array.isArray(entries)) {
255
+ for (const entry of entries) if (entry.id) allContentIds.add(entry.id);
256
+ }
257
+ for (const menu of seed.menus) if (Array.isArray(menu.items)) validateMenuItemRefs(menu.items, allContentIds, warnings);
258
+ }
259
+ if (seed.content) {
260
+ const seedBylineIds = new Set((seed.bylines ?? []).map((byline) => byline.id));
261
+ for (const [collectionSlug, entries] of Object.entries(seed.content)) {
262
+ if (!Array.isArray(entries)) continue;
263
+ for (let i = 0; i < entries.length; i++) {
264
+ const entry = entries[i];
265
+ if (!entry.bylines) continue;
266
+ if (!Array.isArray(entry.bylines)) {
267
+ errors.push(`content.${collectionSlug}[${i}].bylines: must be an array`);
268
+ continue;
269
+ }
270
+ for (let j = 0; j < entry.bylines.length; j++) {
271
+ const credit = entry.bylines[j];
272
+ const prefix = `content.${collectionSlug}[${i}].bylines[${j}]`;
273
+ if (!credit.byline) {
274
+ errors.push(`${prefix}.byline: is required`);
275
+ continue;
276
+ }
277
+ if (!seedBylineIds.has(credit.byline)) errors.push(`${prefix}.byline: references unknown byline "${credit.byline}"`);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ return {
283
+ valid: errors.length === 0,
284
+ errors,
285
+ warnings
286
+ };
287
+ }
288
+ /**
289
+ * Validate menu items recursively
290
+ */
291
+ function validateMenuItems(items, prefix, errors, warnings) {
292
+ for (let i = 0; i < items.length; i++) {
293
+ const raw = items[i];
294
+ const itemPrefix = `${prefix}.items[${i}]`;
295
+ if (!isRecord(raw)) {
296
+ errors.push(`${itemPrefix}: must be an object`);
297
+ continue;
298
+ }
299
+ const item = raw;
300
+ const itemType = typeof item.type === "string" ? item.type : void 0;
301
+ if (!itemType) errors.push(`${itemPrefix}: type is required`);
302
+ else if (![
303
+ "custom",
304
+ "page",
305
+ "post",
306
+ "taxonomy",
307
+ "collection"
308
+ ].includes(itemType)) errors.push(`${itemPrefix}.type: must be "custom", "page", "post", "taxonomy", or "collection"`);
309
+ if (itemType === "custom" && !item.url) errors.push(`${itemPrefix}: url is required for custom menu items`);
310
+ if ((itemType === "page" || itemType === "post") && !item.ref) errors.push(`${itemPrefix}: ref is required for page/post menu items`);
311
+ if (Array.isArray(item.children)) validateMenuItems(item.children, itemPrefix, errors, warnings);
312
+ }
313
+ }
314
+ /**
315
+ * Validate menu item references exist in content
316
+ */
317
+ function validateMenuItemRefs(items, contentIds, warnings) {
318
+ for (const item of items) {
319
+ if ((item.type === "page" || item.type === "post") && item.ref) {
320
+ if (!contentIds.has(item.ref)) warnings.push(`Menu item references content "${item.ref}" which is not in the seed file`);
321
+ }
322
+ if (item.children) validateMenuItemRefs(item.children, contentIds, warnings);
323
+ }
324
+ }
325
+
326
+ //#endregion
327
+ export { validate_exports as n, validateSeed as t };
@@ -0,0 +1,96 @@
1
+ //#region src/database/validate.ts
2
+ /**
3
+ * SQL Identifier Validation
4
+ *
5
+ * Validates identifiers (table names, column names, index names) before
6
+ * they are used in raw SQL expressions. This is the primary defense against
7
+ * SQL injection via dynamic identifier interpolation.
8
+ *
9
+ * @see AGENTS.md § Database: Never Interpolate Into SQL
10
+ */
11
+ /**
12
+ * Pattern for safe SQL identifiers.
13
+ * Must start with a lowercase letter, followed by lowercase letters, digits, or underscores.
14
+ */
15
+ const IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
16
+ /**
17
+ * Pattern for generic alphanumeric identifiers (case-insensitive).
18
+ * Must start with a letter, followed by letters, digits, or underscores.
19
+ */
20
+ const GENERIC_IDENTIFIER_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
21
+ /**
22
+ * Pattern for plugin identifiers.
23
+ * Must start with a lowercase letter, followed by lowercase letters, digits, underscores, or hyphens.
24
+ */
25
+ const PLUGIN_IDENTIFIER_PATTERN = /^[a-z][a-z0-9_-]*$/;
26
+ /**
27
+ * Maximum length for SQL identifiers.
28
+ * SQLite has no formal limit, but we cap at 128 for sanity.
29
+ */
30
+ const MAX_IDENTIFIER_LENGTH = 128;
31
+ /**
32
+ * Error thrown when an identifier fails validation.
33
+ */
34
+ var IdentifierError = class extends Error {
35
+ constructor(message, identifier) {
36
+ super(message);
37
+ this.identifier = identifier;
38
+ this.name = "IdentifierError";
39
+ }
40
+ };
41
+ /**
42
+ * Validate that a string is a safe SQL identifier.
43
+ *
44
+ * Safe identifiers match `/^[a-z][a-z0-9_]*$/` and are at most 128 characters.
45
+ * This prevents SQL injection when identifiers must be interpolated into raw SQL
46
+ * (e.g., dynamic table names, column names in json_extract paths).
47
+ *
48
+ * @param value - The string to validate
49
+ * @param label - Human-readable label for error messages (e.g., "field name", "table name")
50
+ * @throws {IdentifierError} If the value is not a valid identifier
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * validateIdentifier(fieldName, "field name");
55
+ * // safe to use in: json_extract(data, '$.${fieldName}')
56
+ * ```
57
+ */
58
+ function validateIdentifier(value, label = "identifier") {
59
+ if (!value || typeof value !== "string") throw new IdentifierError(`${label} must be a non-empty string`, String(value));
60
+ if (value.length > MAX_IDENTIFIER_LENGTH) throw new IdentifierError(`${label} must be ${MAX_IDENTIFIER_LENGTH} characters or less, got ${value.length}`, value);
61
+ if (!IDENTIFIER_PATTERN.test(value)) throw new IdentifierError(`${label} must match /^[a-z][a-z0-9_]*$/ (got "${value}")`, value);
62
+ }
63
+ /**
64
+ * Validate that a string is a safe SQL identifier, allowing hyphens.
65
+ *
66
+ * Like `validateIdentifier` but also permits hyphens, which appear in
67
+ * plugin IDs (e.g., "my-plugin"). Matches `/^[a-z][a-z0-9_-]*$/`.
68
+ *
69
+ * @param value - The string to validate
70
+ * @param label - Human-readable label for error messages
71
+ * @throws {IdentifierError} If the value is not valid
72
+ */
73
+ /**
74
+ * Validate that a string is a safe JSON field name for use in json_extract paths.
75
+ *
76
+ * More permissive than `validateIdentifier` — allows camelCase (mixed case)
77
+ * since JSON keys in plugin storage data blobs commonly use camelCase.
78
+ * Matches `/^[a-zA-Z][a-zA-Z0-9_]*$/`.
79
+ *
80
+ * @param value - The string to validate
81
+ * @param label - Human-readable label for error messages
82
+ * @throws {IdentifierError} If the value is not valid
83
+ */
84
+ function validateJsonFieldName(value, label = "JSON field name") {
85
+ if (!value || typeof value !== "string") throw new IdentifierError(`${label} must be a non-empty string`, String(value));
86
+ if (value.length > MAX_IDENTIFIER_LENGTH) throw new IdentifierError(`${label} must be ${MAX_IDENTIFIER_LENGTH} characters or less, got ${value.length}`, value);
87
+ if (!GENERIC_IDENTIFIER_PATTERN.test(value)) throw new IdentifierError(`${label} must match /^[a-zA-Z][a-zA-Z0-9_]*$/ (got "${value}")`, value);
88
+ }
89
+ function validatePluginIdentifier(value, label = "plugin identifier") {
90
+ if (!value || typeof value !== "string") throw new IdentifierError(`${label} must be a non-empty string`, String(value));
91
+ if (value.length > MAX_IDENTIFIER_LENGTH) throw new IdentifierError(`${label} must be ${MAX_IDENTIFIER_LENGTH} characters or less, got ${value.length}`, value);
92
+ if (!PLUGIN_IDENTIFIER_PATTERN.test(value)) throw new IdentifierError(`${label} must match /^[a-z][a-z0-9_-]*$/ (got "${value}")`, value);
93
+ }
94
+
95
+ //#endregion
96
+ export { validateJsonFieldName as n, validatePluginIdentifier as r, validateIdentifier as t };