@nexpress/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/audit-54XLVCWD.js +14 -0
  4. package/dist/audit-54XLVCWD.js.map +1 -0
  5. package/dist/auth.d.ts +640 -0
  6. package/dist/auth.js +94 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/can-YLUHRJAB.js +19 -0
  9. package/dist/can-YLUHRJAB.js.map +1 -0
  10. package/dist/chunk-2G264RCD.js +68 -0
  11. package/dist/chunk-2G264RCD.js.map +1 -0
  12. package/dist/chunk-2YDGE7YX.js +92 -0
  13. package/dist/chunk-2YDGE7YX.js.map +1 -0
  14. package/dist/chunk-473S4TER.js +538 -0
  15. package/dist/chunk-473S4TER.js.map +1 -0
  16. package/dist/chunk-4ZLMEKFX.js +18 -0
  17. package/dist/chunk-4ZLMEKFX.js.map +1 -0
  18. package/dist/chunk-55FU6WED.js +179 -0
  19. package/dist/chunk-55FU6WED.js.map +1 -0
  20. package/dist/chunk-6YI5K2TI.js +1959 -0
  21. package/dist/chunk-6YI5K2TI.js.map +1 -0
  22. package/dist/chunk-BHK3AD3Q.js +41 -0
  23. package/dist/chunk-BHK3AD3Q.js.map +1 -0
  24. package/dist/chunk-CRUQBZUF.js +39 -0
  25. package/dist/chunk-CRUQBZUF.js.map +1 -0
  26. package/dist/chunk-CTSQ7BRI.js +175 -0
  27. package/dist/chunk-CTSQ7BRI.js.map +1 -0
  28. package/dist/chunk-DK2JBJH7.js +81 -0
  29. package/dist/chunk-DK2JBJH7.js.map +1 -0
  30. package/dist/chunk-DP2PREDU.js +597 -0
  31. package/dist/chunk-DP2PREDU.js.map +1 -0
  32. package/dist/chunk-EQ2Z3KMD.js +24 -0
  33. package/dist/chunk-EQ2Z3KMD.js.map +1 -0
  34. package/dist/chunk-FZ7O6DWI.js +305 -0
  35. package/dist/chunk-FZ7O6DWI.js.map +1 -0
  36. package/dist/chunk-ISLYFQWL.js +1270 -0
  37. package/dist/chunk-ISLYFQWL.js.map +1 -0
  38. package/dist/chunk-JJL74ZPK.js +68 -0
  39. package/dist/chunk-JJL74ZPK.js.map +1 -0
  40. package/dist/chunk-JKXAPSU4.js +24 -0
  41. package/dist/chunk-JKXAPSU4.js.map +1 -0
  42. package/dist/chunk-KU5M27ZC.js +24 -0
  43. package/dist/chunk-KU5M27ZC.js.map +1 -0
  44. package/dist/chunk-LSHHRDVR.js +34 -0
  45. package/dist/chunk-LSHHRDVR.js.map +1 -0
  46. package/dist/chunk-M43PGOQY.js +715 -0
  47. package/dist/chunk-M43PGOQY.js.map +1 -0
  48. package/dist/chunk-MEJAHXIO.js +150 -0
  49. package/dist/chunk-MEJAHXIO.js.map +1 -0
  50. package/dist/chunk-NUCGHWCF.js +101 -0
  51. package/dist/chunk-NUCGHWCF.js.map +1 -0
  52. package/dist/chunk-OK5HOCQI.js +845 -0
  53. package/dist/chunk-OK5HOCQI.js.map +1 -0
  54. package/dist/chunk-OROPGO65.js +13 -0
  55. package/dist/chunk-OROPGO65.js.map +1 -0
  56. package/dist/chunk-PPAS4SZR.js +176 -0
  57. package/dist/chunk-PPAS4SZR.js.map +1 -0
  58. package/dist/chunk-PPBWRKO2.js +171 -0
  59. package/dist/chunk-PPBWRKO2.js.map +1 -0
  60. package/dist/chunk-PZ5AY32C.js +10 -0
  61. package/dist/chunk-PZ5AY32C.js.map +1 -0
  62. package/dist/chunk-QO7LAQZH.js +321 -0
  63. package/dist/chunk-QO7LAQZH.js.map +1 -0
  64. package/dist/chunk-QVJ2HCAX.js +225 -0
  65. package/dist/chunk-QVJ2HCAX.js.map +1 -0
  66. package/dist/chunk-RIPHIRPP.js +68 -0
  67. package/dist/chunk-RIPHIRPP.js.map +1 -0
  68. package/dist/chunk-S27S42QY.js +134 -0
  69. package/dist/chunk-S27S42QY.js.map +1 -0
  70. package/dist/chunk-SBCVAC2Z.js +40 -0
  71. package/dist/chunk-SBCVAC2Z.js.map +1 -0
  72. package/dist/chunk-TFJ4MKPH.js +694 -0
  73. package/dist/chunk-TFJ4MKPH.js.map +1 -0
  74. package/dist/chunk-THX3SHYA.js +75 -0
  75. package/dist/chunk-THX3SHYA.js.map +1 -0
  76. package/dist/chunk-UGQSQO5B.js +222 -0
  77. package/dist/chunk-UGQSQO5B.js.map +1 -0
  78. package/dist/chunk-V2UNHGAP.js +26 -0
  79. package/dist/chunk-V2UNHGAP.js.map +1 -0
  80. package/dist/chunk-VGTPQXNQ.js +2790 -0
  81. package/dist/chunk-VGTPQXNQ.js.map +1 -0
  82. package/dist/chunk-VNIHXQ7W.js +194 -0
  83. package/dist/chunk-VNIHXQ7W.js.map +1 -0
  84. package/dist/chunk-WV272MPW.js +31 -0
  85. package/dist/chunk-WV272MPW.js.map +1 -0
  86. package/dist/chunk-X5KKBOUS.js +26 -0
  87. package/dist/chunk-X5KKBOUS.js.map +1 -0
  88. package/dist/chunk-XANPEOJC.js +17 -0
  89. package/dist/chunk-XANPEOJC.js.map +1 -0
  90. package/dist/chunk-XPVQIHAQ.js +83 -0
  91. package/dist/chunk-XPVQIHAQ.js.map +1 -0
  92. package/dist/chunk-ZCINJSS4.js +75 -0
  93. package/dist/chunk-ZCINJSS4.js.map +1 -0
  94. package/dist/community.d.ts +1425 -0
  95. package/dist/community.js +206 -0
  96. package/dist/community.js.map +1 -0
  97. package/dist/config-2GDU7PCK.js +32 -0
  98. package/dist/config-2GDU7PCK.js.map +1 -0
  99. package/dist/context-MNZ4QXPC.js +16 -0
  100. package/dist/context-MNZ4QXPC.js.map +1 -0
  101. package/dist/db-schema.d.ts +4 -0
  102. package/dist/db-schema.js +102 -0
  103. package/dist/db-schema.js.map +1 -0
  104. package/dist/db.d.ts +7 -0
  105. package/dist/db.js +117 -0
  106. package/dist/db.js.map +1 -0
  107. package/dist/digest-SY42GQSU.js +17 -0
  108. package/dist/digest-SY42GQSU.js.map +1 -0
  109. package/dist/errors-5OS3S2J3.js +22 -0
  110. package/dist/errors-5OS3S2J3.js.map +1 -0
  111. package/dist/host-OBOI4MJK.js +51 -0
  112. package/dist/host-OBOI4MJK.js.map +1 -0
  113. package/dist/i18n.d.ts +301 -0
  114. package/dist/i18n.js +68 -0
  115. package/dist/i18n.js.map +1 -0
  116. package/dist/index-B6-_vr_m.d.ts +590 -0
  117. package/dist/index-CY55LC0u.d.ts +4722 -0
  118. package/dist/index-CeiTvwbp.d.ts +168 -0
  119. package/dist/index-XwP1ET8b.d.ts +61 -0
  120. package/dist/index.d.ts +2037 -0
  121. package/dist/index.js +2205 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/job-log-VZXWQUDK.js +24 -0
  124. package/dist/job-log-VZXWQUDK.js.map +1 -0
  125. package/dist/jobs.d.ts +4 -0
  126. package/dist/jobs.js +76 -0
  127. package/dist/jobs.js.map +1 -0
  128. package/dist/logger-DqGaOU_j.d.ts +29 -0
  129. package/dist/logger-S7REWDNE.js +16 -0
  130. package/dist/logger-S7REWDNE.js.map +1 -0
  131. package/dist/media.d.ts +5 -0
  132. package/dist/media.js +41 -0
  133. package/dist/media.js.map +1 -0
  134. package/dist/mentions-2IHFVSHW.js +23 -0
  135. package/dist/mentions-2IHFVSHW.js.map +1 -0
  136. package/dist/mutes-EWAE5FZR.js +21 -0
  137. package/dist/mutes-EWAE5FZR.js.map +1 -0
  138. package/dist/notification-prefs-VPJDU7I6.js +21 -0
  139. package/dist/notification-prefs-VPJDU7I6.js.map +1 -0
  140. package/dist/observability.d.ts +156 -0
  141. package/dist/observability.js +32 -0
  142. package/dist/observability.js.map +1 -0
  143. package/dist/profanity-adapter-NU2JQSLX.js +12 -0
  144. package/dist/profanity-adapter-NU2JQSLX.js.map +1 -0
  145. package/dist/queue-XE5BC75T.js +14 -0
  146. package/dist/queue-XE5BC75T.js.map +1 -0
  147. package/dist/rate-limit.d.ts +99 -0
  148. package/dist/rate-limit.js +14 -0
  149. package/dist/rate-limit.js.map +1 -0
  150. package/dist/registry-XIXDEPVI.js +31 -0
  151. package/dist/registry-XIXDEPVI.js.map +1 -0
  152. package/dist/reputation-JRL2YQHM.js +11 -0
  153. package/dist/reputation-JRL2YQHM.js.map +1 -0
  154. package/dist/routes.d.ts +43 -0
  155. package/dist/routes.js +12 -0
  156. package/dist/routes.js.map +1 -0
  157. package/dist/scheduled-CIQM57HT.js +20 -0
  158. package/dist/scheduled-CIQM57HT.js.map +1 -0
  159. package/dist/seo.d.ts +410 -0
  160. package/dist/seo.js +44 -0
  161. package/dist/seo.js.map +1 -0
  162. package/dist/settings-FOBIESPB.js +17 -0
  163. package/dist/settings-FOBIESPB.js.map +1 -0
  164. package/dist/spam-adapter-XX3G737Z.js +12 -0
  165. package/dist/spam-adapter-XX3G737Z.js.map +1 -0
  166. package/dist/strings-VAE47B2C.js +29 -0
  167. package/dist/strings-VAE47B2C.js.map +1 -0
  168. package/dist/templates-IFVJMCJ6.js +12 -0
  169. package/dist/templates-IFVJMCJ6.js.map +1 -0
  170. package/dist/types-TlsbXS0T.d.ts +871 -0
  171. package/package.json +129 -0
@@ -0,0 +1,694 @@
1
+ import {
2
+ schema_exports
3
+ } from "./chunk-PPAS4SZR.js";
4
+ import {
5
+ readEnvPositiveInt
6
+ } from "./chunk-OROPGO65.js";
7
+
8
+ // src/db/connection.ts
9
+ import { drizzle } from "drizzle-orm/node-postgres";
10
+ import { Pool } from "pg";
11
+ var DEFAULT_CONNECTION_TIMEOUT_MS = readEnvPositiveInt("NP_DB_CONNECTION_TIMEOUT_MS", 5e3);
12
+ var DEFAULT_STATEMENT_TIMEOUT_MS = readEnvPositiveInt("NP_DB_STATEMENT_TIMEOUT_MS", 1e4);
13
+ function createDbConnection(config) {
14
+ const pool = config.pool ?? new Pool({
15
+ connectionString: config.connectionString,
16
+ connectionTimeoutMillis: DEFAULT_CONNECTION_TIMEOUT_MS,
17
+ statement_timeout: DEFAULT_STATEMENT_TIMEOUT_MS,
18
+ ...config.poolOptions
19
+ });
20
+ return drizzle(pool, { schema: schema_exports });
21
+ }
22
+
23
+ // src/db/generator.ts
24
+ function generateDrizzleSchema(collections, options) {
25
+ const schemaImport = options?.schemaImport ?? "@nexpress/core";
26
+ const collectionTables = /* @__PURE__ */ new Map();
27
+ for (const collection of collections) {
28
+ collectionTables.set(collection.slug, getCollectionTableIdentifier(collection.slug));
29
+ }
30
+ const tables = [];
31
+ for (const collection of collections) {
32
+ const tableIdentifier = getCollectionTableIdentifier(collection.slug);
33
+ const tableName = `np_c_${collection.slug}`;
34
+ const columns = getBaseColumns(collection);
35
+ const indexes = [`index("${tableName}_status_idx").on(table.status)`];
36
+ const relations = [
37
+ {
38
+ key: "createdByUser",
39
+ kind: "one",
40
+ targetIdentifier: "npUsers",
41
+ fields: ["createdBy"],
42
+ references: ["id"]
43
+ },
44
+ {
45
+ key: "updatedByUser",
46
+ kind: "one",
47
+ targetIdentifier: "npUsers",
48
+ fields: ["updatedBy"],
49
+ references: ["id"]
50
+ }
51
+ ];
52
+ const memberAuthored = Boolean(collection.community?.memberWrite?.create);
53
+ if (memberAuthored) {
54
+ columns.push(
55
+ 'memberAuthorId: uuid("member_author_id").references(() => npMembers.id, { onDelete: "set null" })'
56
+ );
57
+ indexes.push(
58
+ `index("${tableName}_member_author_idx").on(table.memberAuthorId)`
59
+ );
60
+ relations.push({
61
+ key: "memberAuthor",
62
+ kind: "one",
63
+ targetIdentifier: "npMembers",
64
+ fields: ["memberAuthorId"],
65
+ references: ["id"]
66
+ });
67
+ }
68
+ const scalarResult = collectScalarColumns(collection.fields, [], collectionTables);
69
+ columns.push(...scalarResult.columns);
70
+ relations.push(...scalarResult.relations);
71
+ if (hasSlugField(collection)) {
72
+ columns.push('slug: text("slug").notNull()');
73
+ if (collection.i18n) {
74
+ indexes.push(
75
+ `uniqueIndex("${tableName}_site_locale_slug_idx").on(table.siteId, table.locale, table.slug)`
76
+ );
77
+ } else {
78
+ indexes.push(
79
+ `uniqueIndex("${tableName}_site_slug_idx").on(table.siteId, table.slug)`
80
+ );
81
+ }
82
+ }
83
+ if (collection.i18n) {
84
+ columns.push('locale: text("locale").notNull()');
85
+ columns.push('translationGroupId: uuid("translation_group_id").notNull()');
86
+ indexes.push(
87
+ `index("${tableName}_translation_group_idx").on(table.translationGroupId)`
88
+ );
89
+ indexes.push(
90
+ `index("${tableName}_locale_idx").on(table.locale)`
91
+ );
92
+ }
93
+ columns.push(
94
+ 'siteId: text("site_id").default("default").notNull()'
95
+ );
96
+ indexes.push(`index("${tableName}_site_idx").on(table.siteId)`);
97
+ if (hasDraftVersions(collection)) {
98
+ columns.push(
99
+ '_status: text("_status", { enum: ["draft", "published"] }).default("draft").notNull()'
100
+ );
101
+ }
102
+ columns.push('searchVector: tsvector("search_vector")');
103
+ tables.push({
104
+ identifier: tableIdentifier,
105
+ tableName,
106
+ columns,
107
+ indexes,
108
+ relations
109
+ });
110
+ createNestedTables(
111
+ {
112
+ collectionSlug: collection.slug,
113
+ tableIdentifier,
114
+ tableName,
115
+ relationKeyPrefix: toCamelCase(collection.slug),
116
+ fieldPath: [],
117
+ parentReferenceName: `${toCamelCase(collection.slug)}Id`
118
+ },
119
+ collection.fields,
120
+ tables,
121
+ collectionTables
122
+ );
123
+ }
124
+ const body = tables.map(renderTable).join("\n\n");
125
+ const usesMembers = collections.some((c) => c.community?.memberWrite?.create);
126
+ const schemaImports = ["npMedia", "npUsers", ...usesMembers ? ["npMembers"] : []];
127
+ return [
128
+ 'import { relations } from "drizzle-orm";',
129
+ 'import { boolean, customType, doublePrecision, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";',
130
+ `import { ${schemaImports.join(", ")} } from "${schemaImport}";`,
131
+ "",
132
+ "const tsvector = customType<{ data: string }>({",
133
+ " dataType() {",
134
+ ' return "tsvector";',
135
+ " },",
136
+ "});",
137
+ "",
138
+ body
139
+ ].join("\n");
140
+ }
141
+ function createNestedTables(context, fields, tables, collectionTables) {
142
+ for (const field of fields) {
143
+ if (field.type === "group") {
144
+ createNestedTables(context, field.fields, tables, collectionTables);
145
+ continue;
146
+ }
147
+ if (field.type === "row" || field.type === "collapsible") {
148
+ createNestedTables(context, field.fields, tables, collectionTables);
149
+ continue;
150
+ }
151
+ if (field.type === "array") {
152
+ tables.push(createArrayTable(context, field, tables, collectionTables));
153
+ continue;
154
+ }
155
+ if (field.type === "relationship" && field.hasMany && typeof field.relationTo === "string") {
156
+ tables.push(
157
+ createHasManyJoinTable(context, { ...field, relationTo: field.relationTo, hasMany: true }, collectionTables)
158
+ );
159
+ }
160
+ }
161
+ }
162
+ function createArrayTable(context, field, tables, collectionTables) {
163
+ const path = [...context.fieldPath, field.name];
164
+ const tableName = `np_c_${context.collectionSlug}__${path.join("__")}`;
165
+ const identifier = getNestedTableIdentifier(context.collectionSlug, path);
166
+ const columns = [
167
+ 'id: uuid("id").defaultRandom().primaryKey()',
168
+ `parentId: uuid("parent_id").notNull().references(() => ${context.tableIdentifier}.id, { onDelete: "cascade" })`,
169
+ 'order: integer("order").default(0).notNull()'
170
+ ];
171
+ const relations = [
172
+ {
173
+ key: "parent",
174
+ kind: "one",
175
+ targetIdentifier: context.tableIdentifier,
176
+ fields: ["parentId"],
177
+ references: ["id"]
178
+ }
179
+ ];
180
+ const scalarResult = collectScalarColumns(field.fields, [], collectionTables);
181
+ columns.push(...scalarResult.columns);
182
+ relations.push(...scalarResult.relations);
183
+ const nestedContext = {
184
+ collectionSlug: context.collectionSlug,
185
+ tableIdentifier: identifier,
186
+ tableName,
187
+ relationKeyPrefix: `${context.relationKeyPrefix}${toPascalCase(field.name)}`,
188
+ fieldPath: path,
189
+ parentReferenceName: "parentId",
190
+ parentTargetIdentifier: context.tableIdentifier
191
+ };
192
+ createNestedTables(nestedContext, field.fields, tables, collectionTables);
193
+ return {
194
+ identifier,
195
+ tableName,
196
+ columns,
197
+ indexes: [`index("${tableName}_parent_idx").on(table.parentId)`],
198
+ relations
199
+ };
200
+ }
201
+ function createHasManyJoinTable(context, field, collectionTables) {
202
+ const path = [...context.fieldPath, field.name];
203
+ const tableName = `np_c_${context.collectionSlug}__${path.join("__")}`;
204
+ const identifier = getNestedTableIdentifier(context.collectionSlug, path);
205
+ const targetIdentifier = resolveRelationTarget(field.relationTo, collectionTables);
206
+ const parentReferenceName = context.fieldPath.length === 0 ? `${toCamelCase(context.collectionSlug)}Id` : "parentId";
207
+ return {
208
+ identifier,
209
+ tableName,
210
+ columns: [
211
+ 'id: uuid("id").defaultRandom().primaryKey()',
212
+ `${parentReferenceName}: uuid("${toSnakeCase(parentReferenceName)}").notNull().references(() => ${context.tableIdentifier}.id, { onDelete: "cascade" })`,
213
+ `targetId: uuid("target_id").notNull().references(() => ${targetIdentifier}.id, { onDelete: "cascade" })`,
214
+ 'order: integer("order").default(0).notNull()'
215
+ ],
216
+ indexes: [
217
+ `index("${tableName}_${toSnakeCase(parentReferenceName)}_idx").on(table.${parentReferenceName})`,
218
+ `uniqueIndex("${tableName}_parent_target_uidx").on(table.${parentReferenceName}, table.targetId)`
219
+ ],
220
+ relations: [
221
+ {
222
+ key: "parent",
223
+ kind: "one",
224
+ targetIdentifier: context.tableIdentifier,
225
+ fields: [parentReferenceName],
226
+ references: ["id"]
227
+ },
228
+ {
229
+ key: "target",
230
+ kind: "one",
231
+ targetIdentifier,
232
+ fields: ["targetId"],
233
+ references: ["id"]
234
+ }
235
+ ]
236
+ };
237
+ }
238
+ function collectScalarColumns(fields, prefix, collectionTables) {
239
+ const columns = [];
240
+ const relations = [];
241
+ for (const field of fields) {
242
+ if (field.type === "group") {
243
+ const nested = collectScalarColumns(field.fields, [...prefix, field.name], collectionTables);
244
+ columns.push(...nested.columns);
245
+ relations.push(...nested.relations);
246
+ continue;
247
+ }
248
+ if (field.type === "row" || field.type === "collapsible") {
249
+ const nested = collectScalarColumns(field.fields, prefix, collectionTables);
250
+ columns.push(...nested.columns);
251
+ relations.push(...nested.relations);
252
+ continue;
253
+ }
254
+ if (field.type === "array") {
255
+ continue;
256
+ }
257
+ if (field.type === "relationship" && field.hasMany) {
258
+ continue;
259
+ }
260
+ const propertyName = getFlattenedFieldName(prefix, field.name);
261
+ const columnName = toSnakeCase(propertyName);
262
+ const column = buildScalarColumn(field, propertyName, columnName, collectionTables);
263
+ if (!column) {
264
+ continue;
265
+ }
266
+ columns.push(...column.columns);
267
+ relations.push(...column.relations);
268
+ }
269
+ return { columns, relations };
270
+ }
271
+ function buildScalarColumn(field, propertyName, columnName, collectionTables) {
272
+ const notNull = field.required ? ".notNull()" : "";
273
+ switch (field.type) {
274
+ case "text":
275
+ case "textarea":
276
+ case "email":
277
+ case "select":
278
+ case "radio":
279
+ return { columns: [`${propertyName}: text("${columnName}")${notNull}`], relations: [] };
280
+ case "number": {
281
+ const builder = field.integerOnly ? "integer" : "doublePrecision";
282
+ return { columns: [`${propertyName}: ${builder}("${columnName}")${notNull}`], relations: [] };
283
+ }
284
+ case "richText":
285
+ case "blocks":
286
+ case "json":
287
+ return { columns: [`${propertyName}: jsonb("${columnName}")${notNull}`], relations: [] };
288
+ case "checkbox":
289
+ return { columns: [`${propertyName}: boolean("${columnName}")${notNull}`], relations: [] };
290
+ case "date":
291
+ return {
292
+ columns: [`${propertyName}: timestamp("${columnName}", { withTimezone: true })${notNull}`],
293
+ relations: []
294
+ };
295
+ case "upload": {
296
+ return {
297
+ columns: [`${propertyName}: uuid("${columnName}").references(() => npMedia.id)${notNull}`],
298
+ relations: [
299
+ {
300
+ key: propertyName,
301
+ kind: "one",
302
+ targetIdentifier: "npMedia",
303
+ fields: [propertyName],
304
+ references: ["id"]
305
+ }
306
+ ]
307
+ };
308
+ }
309
+ case "relationship": {
310
+ if (typeof field.relationTo !== "string") {
311
+ return null;
312
+ }
313
+ const targetIdentifier = resolveRelationTarget(field.relationTo, collectionTables);
314
+ return {
315
+ columns: [`${propertyName}: uuid("${columnName}").references(() => ${targetIdentifier}.id)${notNull}`],
316
+ relations: [
317
+ {
318
+ key: propertyName,
319
+ kind: "one",
320
+ targetIdentifier,
321
+ fields: [propertyName],
322
+ references: ["id"]
323
+ }
324
+ ]
325
+ };
326
+ }
327
+ default:
328
+ return null;
329
+ }
330
+ }
331
+ function getBaseColumns(collection) {
332
+ const columns = ['id: uuid("id").defaultRandom().primaryKey()'];
333
+ columns.push(
334
+ 'status: text("status", { enum: ["draft", "scheduled", "published", "archived", "pending"] }).default("draft").notNull()'
335
+ );
336
+ columns.push('createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()');
337
+ columns.push('updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()');
338
+ columns.push('createdBy: uuid("created_by").references(() => npUsers.id)');
339
+ columns.push('updatedBy: uuid("updated_by").references(() => npUsers.id)');
340
+ const visibility = 'visibility: text("visibility", { enum: ["public", "private"] }).default("public").notNull()';
341
+ if (collection.timestamps === false) {
342
+ return [columns[0], columns[1], columns[4], columns[5], visibility];
343
+ }
344
+ columns.push(visibility);
345
+ return columns;
346
+ }
347
+ function renderTable(table) {
348
+ const tableSource = [
349
+ `export const ${table.identifier} = pgTable(`,
350
+ ` "${table.tableName}",`,
351
+ " {",
352
+ ...table.columns.map((column) => ` ${column},`),
353
+ " },",
354
+ ` (table) => [${table.indexes.join(", ")}],`,
355
+ ");"
356
+ ].join("\n");
357
+ const relationsSource = [
358
+ `export const ${table.identifier}Relations = relations(${table.identifier}, ({ many, one }) => ({`,
359
+ ...table.relations.map((relation) => renderRelation(relation, table.identifier)),
360
+ "}));"
361
+ ].join("\n");
362
+ return `${tableSource}
363
+
364
+ ${relationsSource}`;
365
+ }
366
+ function renderRelation(relation, ownerIdentifier) {
367
+ if (relation.kind === "many") {
368
+ return ` ${relation.key}: many(${relation.targetIdentifier}),`;
369
+ }
370
+ const fields = (relation.fields ?? []).map((field) => `${ownerIdentifier}.${field}`).join(", ");
371
+ const references = (relation.references ?? []).map((reference) => `${relation.targetIdentifier}.${reference}`).join(", ");
372
+ return ` ${relation.key}: one(${relation.targetIdentifier}, { fields: [${fields}], references: [${references}] }),`;
373
+ }
374
+ function hasSlugField(collection) {
375
+ return collection.slugField !== void 0 && collection.slugField !== false;
376
+ }
377
+ function hasDraftVersions(collection) {
378
+ return Boolean(collection.versions?.drafts);
379
+ }
380
+ function resolveRelationTarget(relationTo, collectionTables) {
381
+ if (relationTo === "media") {
382
+ return "npMedia";
383
+ }
384
+ if (relationTo === "users") {
385
+ return "npUsers";
386
+ }
387
+ return collectionTables.get(relationTo) ?? getCollectionTableIdentifier(relationTo);
388
+ }
389
+ function getCollectionTableIdentifier(slug) {
390
+ return `${toCamelCase(slug)}Table`;
391
+ }
392
+ function getNestedTableIdentifier(collectionSlug, path) {
393
+ return `${toCamelCase(collectionSlug)}${path.map(toPascalCase).join("")}Table`;
394
+ }
395
+ function getFlattenedFieldName(prefix, name) {
396
+ if (prefix.length === 0) {
397
+ return toCamelCase(name);
398
+ }
399
+ return `${prefix.map(toPascalCase).join("")}${toPascalCase(name)}`.replace(/^./u, (char) => char.toLowerCase());
400
+ }
401
+ function toCamelCase(value) {
402
+ const parts = splitName(value);
403
+ const [first = "", ...rest] = parts;
404
+ return `${first}${rest.map(capitalize).join("")}`;
405
+ }
406
+ function toPascalCase(value) {
407
+ return splitName(value).map(capitalize).join("");
408
+ }
409
+ function toSnakeCase(value) {
410
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toLowerCase();
411
+ }
412
+ function splitName(value) {
413
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).map((part) => part.toLowerCase()).filter(Boolean);
414
+ }
415
+ function capitalize(value) {
416
+ return value.charAt(0).toUpperCase() + value.slice(1);
417
+ }
418
+
419
+ // src/db/type-generator.ts
420
+ function generateTypeScript(collections) {
421
+ const interfaces = collections.map((collection) => renderCollectionInterface(collection));
422
+ return interfaces.join("\n\n");
423
+ }
424
+ function collectHasManyFields(collection) {
425
+ const collCamel = toCamelCase2(collection.slug);
426
+ const parentColumn = `${collCamel}Id`;
427
+ const out = [];
428
+ for (const field of collection.fields) {
429
+ if (field.type === "relationship" && field.hasMany === true) {
430
+ const joinTable = `${collCamel}${toPascalCase2(field.name)}Table`;
431
+ out.push({ fieldName: field.name, joinTable, parentColumn });
432
+ }
433
+ }
434
+ return out;
435
+ }
436
+ function generateDocumentsModule(collections) {
437
+ const hasManyByCollection = new Map(
438
+ collections.map((c) => [c.slug, collectHasManyFields(c)])
439
+ );
440
+ const anyHasMany = Array.from(hasManyByCollection.values()).some(
441
+ (list) => list.length > 0
442
+ );
443
+ const coreImports = [
444
+ `import {`,
445
+ ` findDocuments,`,
446
+ ...anyHasMany ? [` getDb,`] : [],
447
+ ` getDocumentById,`,
448
+ ` type NpAuthUser,`,
449
+ ` type NpFindOptions,`,
450
+ ` type NpFindResult,`,
451
+ `} from "@nexpress/core";`
452
+ ].join("\n");
453
+ const drizzleImports = anyHasMany ? [
454
+ `import { inArray } from "drizzle-orm";`,
455
+ `import type { NodePgDatabase } from "drizzle-orm/node-postgres";`
456
+ ].join("\n") : "";
457
+ const joinTables = Array.from(
458
+ new Set(
459
+ Array.from(hasManyByCollection.values()).flat().map((d) => d.joinTable)
460
+ )
461
+ ).sort();
462
+ const joinTableImports = joinTables.length > 0 ? `import { ${joinTables.join(", ")} } from "./collections";` : "";
463
+ const imports = [coreImports, drizzleImports, joinTableImports].filter(Boolean).join("\n");
464
+ const interfaces = collections.map((c) => renderCollectionInterface(c)).join("\n\n");
465
+ const helpers = collections.map((c) => renderReadHelpers(c, hasManyByCollection.get(c.slug) ?? [])).join("\n\n");
466
+ return [imports, "", interfaces, "", helpers, ""].join("\n");
467
+ }
468
+ function renderReadHelpers(collection, hasMany) {
469
+ const docType = `${toPascalCase2(collection.slug)}Document`;
470
+ const findFnName = `find${toPascalCase2(collection.slug)}`;
471
+ const getFnName = `get${toPascalCase2(collection.slug)}Document`;
472
+ const slug = JSON.stringify(collection.slug);
473
+ const findFn = hasMany.length === 0 ? renderSimpleFindFn(findFnName, slug, docType, collection.slug) : renderHasManyFindFn(findFnName, slug, docType, collection.slug, hasMany);
474
+ return [
475
+ findFn,
476
+ "",
477
+ `/** Typed by-id fetch for the \`${collection.slug}\` collection. */`,
478
+ `export function ${getFnName}(`,
479
+ ` id: string,`,
480
+ ` user?: NpAuthUser,`,
481
+ `): Promise<${docType} | null> {`,
482
+ ` return getDocumentById<${docType}>(${slug}, id, user);`,
483
+ `}`
484
+ ].join("\n");
485
+ }
486
+ function renderSimpleFindFn(findFnName, slug, docType, slugLabel) {
487
+ return [
488
+ `/** Typed listing query for the \`${slugLabel}\` collection. */`,
489
+ `export function ${findFnName}(`,
490
+ ` options: NpFindOptions<${docType}> = {},`,
491
+ ` user?: NpAuthUser,`,
492
+ `): Promise<NpFindResult<${docType}>> {`,
493
+ ` return findDocuments<${docType}>(${slug}, options, user);`,
494
+ `}`
495
+ ].join("\n");
496
+ }
497
+ function renderHasManyFindFn(findFnName, slug, docType, slugLabel, hasMany) {
498
+ const descriptors = hasMany.map(
499
+ (d) => ` { field: ${JSON.stringify(d.fieldName)}, table: ${d.joinTable}, parent: ${d.joinTable}.${d.parentColumn} },`
500
+ ).join("\n");
501
+ return [
502
+ `/**`,
503
+ ` * Typed listing query for the \`${slugLabel}\` collection.`,
504
+ ` *`,
505
+ ` * Pre-resolves hasMany relationship filters in the where`,
506
+ ` * clause (${hasMany.map((d) => `\`${d.fieldName}\``).join(", ")}) by`,
507
+ ` * subquerying the join table for matching parent ids. Each`,
508
+ ` * field accepts a single target id (most common) or an array`,
509
+ ` * of target ids (OR semantics). Multiple hasMany filters`,
510
+ ` * intersect \u2014 \`where: { categories: catId, tags: tagId }\``,
511
+ ` * matches rows that have BOTH.`,
512
+ ` */`,
513
+ `export async function ${findFnName}(`,
514
+ ` options: NpFindOptions<${docType}> = {},`,
515
+ ` user?: NpAuthUser,`,
516
+ `): Promise<NpFindResult<${docType}>> {`,
517
+ ` const where = options.where ? { ...options.where } : {};`,
518
+ ` const hasManyDescriptors = [`,
519
+ descriptors,
520
+ ` ];`,
521
+ ``,
522
+ ` const matched: string[][] = [];`,
523
+ ` let touchedHasMany = false;`,
524
+ ` for (const { field, table, parent } of hasManyDescriptors) {`,
525
+ ` const value = (where as Record<string, unknown>)[field];`,
526
+ ` if (value === undefined) continue;`,
527
+ ` touchedHasMany = true;`,
528
+ ` delete (where as Record<string, unknown>)[field];`,
529
+ ` const targets = (Array.isArray(value) ? value : [value]).filter(`,
530
+ ` (v): v is string => typeof v === "string" && v.length > 0,`,
531
+ ` );`,
532
+ ` if (targets.length === 0) {`,
533
+ ` // Empty array short-circuits to no rows \u2014 match the`,
534
+ ` // pipeline's array-where semantics.`,
535
+ ` matched.push([]);`,
536
+ ` continue;`,
537
+ ` }`,
538
+ ` // Cast getDb() to NodePgDatabase so the drizzle builder`,
539
+ ` // chain (.select.from.where) carries proper return types.`,
540
+ ` // The empty-schema generic narrows the return shape away`,
541
+ ` // from any specific tables; the explicit \`{ id: string }[]\` `,
542
+ ` // cast at the end matches the projection.`,
543
+ ` const db = getDb() as unknown as NodePgDatabase<Record<string, never>>;`,
544
+ ` const rows = (await db`,
545
+ ` .select({ id: parent })`,
546
+ ` .from(table)`,
547
+ ` .where(inArray(table.targetId, targets))) as Array<{ id: string }>;`,
548
+ ` matched.push(rows.map((r) => r.id));`,
549
+ ` }`,
550
+ ``,
551
+ ` if (touchedHasMany) {`,
552
+ ` // Intersect across all hasMany filters. Empty intersection`,
553
+ ` // \u2192 return immediately; findDocuments would short-circuit`,
554
+ ` // on the empty-array where clause anyway, but the early`,
555
+ ` // exit saves a round-trip.`,
556
+ ` let ids = matched[0] ?? [];`,
557
+ ` for (let i = 1; i < matched.length; i++) {`,
558
+ ` const set = new Set(matched[i]);`,
559
+ ` ids = ids.filter((id) => set.has(id));`,
560
+ ` }`,
561
+ ``,
562
+ ` // Honor any pre-existing user id constraint. Without this,`,
563
+ ` // \`where: { id: someId, categories: catId }\` would silently`,
564
+ ` // drop the user's id filter and return every post in that`,
565
+ ` // category \u2014 a real foot-gun. Intersect instead.`,
566
+ ` const existingId = (where as Record<string, unknown>).id;`,
567
+ ` if (typeof existingId === "string") {`,
568
+ ` ids = ids.includes(existingId) ? [existingId] : [];`,
569
+ ` } else if (Array.isArray(existingId)) {`,
570
+ ` const allowed = new Set(`,
571
+ ` existingId.filter((v): v is string => typeof v === "string"),`,
572
+ ` );`,
573
+ ` ids = ids.filter((id) => allowed.has(id));`,
574
+ ` }`,
575
+ ``,
576
+ ` if (ids.length === 0) {`,
577
+ ` return {`,
578
+ ` docs: [],`,
579
+ ` totalDocs: 0,`,
580
+ ` totalPages: 0,`,
581
+ ` page: options.page ?? 1,`,
582
+ ` limit: options.limit ?? 20,`,
583
+ ` hasNextPage: false,`,
584
+ ` hasPrevPage: false,`,
585
+ ` };`,
586
+ ` }`,
587
+ ` (where as Record<string, unknown>).id = ids;`,
588
+ ` }`,
589
+ ``,
590
+ ` return findDocuments<${docType}>(${slug}, { ...options, where }, user);`,
591
+ `}`
592
+ ].join("\n");
593
+ }
594
+ function renderCollectionInterface(collection) {
595
+ const interfaceName = `${toPascalCase2(collection.slug)}Document`;
596
+ const fields = [
597
+ "id: string;",
598
+ 'status: "draft" | "published" | "archived" | "pending";',
599
+ "createdAt: Date;",
600
+ "updatedAt: Date;",
601
+ "createdBy: string | null;",
602
+ "updatedBy: string | null;"
603
+ ];
604
+ if (collection.community?.memberWrite?.create) {
605
+ fields.push("memberAuthorId: string | null;");
606
+ }
607
+ if (collection.slugField) {
608
+ fields.push("slug: string;");
609
+ }
610
+ if (collection.versions?.drafts) {
611
+ fields.push('_status: "draft" | "published";');
612
+ }
613
+ fields.push(...renderFields(collection.fields));
614
+ return [`export interface ${interfaceName} {`, ...fields.map((field) => ` ${field}`), "}"].join("\n");
615
+ }
616
+ function renderFields(fields, prefix = []) {
617
+ const lines = [];
618
+ for (const field of fields) {
619
+ if (field.type === "row" || field.type === "collapsible") {
620
+ lines.push(...renderFields(field.fields, prefix));
621
+ continue;
622
+ }
623
+ const fieldName = field.type === "group" ? getPropertyName(prefix, field.name) : "";
624
+ if (field.type === "group") {
625
+ const groupType = renderObjectType(field.fields);
626
+ lines.push(`${fieldName}: ${applyNullability(groupType, field.required)};`);
627
+ continue;
628
+ }
629
+ const propertyName = getPropertyName(prefix, field.name);
630
+ const typeSource = getTypeSource(field);
631
+ lines.push(`${propertyName}: ${applyNullability(typeSource, field.required)};`);
632
+ }
633
+ return lines;
634
+ }
635
+ function renderObjectType(fields) {
636
+ const members = renderFields(fields).map((field) => ` ${field}`);
637
+ return [`{`, ...members, `}`].join("\n");
638
+ }
639
+ function getTypeSource(field) {
640
+ switch (field.type) {
641
+ case "text":
642
+ case "textarea":
643
+ case "email":
644
+ case "select":
645
+ case "radio":
646
+ return "string";
647
+ case "number":
648
+ return "number";
649
+ case "checkbox":
650
+ return "boolean";
651
+ case "date":
652
+ return "Date";
653
+ case "upload":
654
+ return "string";
655
+ case "relationship":
656
+ return field.hasMany ? "string[]" : "string";
657
+ case "array":
658
+ return `Array<${renderObjectType(field.fields)}>`;
659
+ case "richText":
660
+ case "blocks":
661
+ case "json":
662
+ return "unknown";
663
+ default:
664
+ return "unknown";
665
+ }
666
+ }
667
+ function applyNullability(typeSource, required) {
668
+ return required ? typeSource : `${typeSource} | null`;
669
+ }
670
+ function getPropertyName(prefix, name) {
671
+ if (prefix.length === 0) {
672
+ return toCamelCase2(name);
673
+ }
674
+ return `${prefix[0]}${prefix.slice(1).map(toPascalCase2).join("")}${toPascalCase2(name)}`;
675
+ }
676
+ function toCamelCase2(value) {
677
+ const parts = splitName2(value);
678
+ const [first = "", ...rest] = parts;
679
+ return `${first}${rest.map(toPascalCase2).join("")}`;
680
+ }
681
+ function toPascalCase2(value) {
682
+ return splitName2(value).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
683
+ }
684
+ function splitName2(value) {
685
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).map((part) => part.toLowerCase()).filter(Boolean);
686
+ }
687
+
688
+ export {
689
+ createDbConnection,
690
+ generateDrizzleSchema,
691
+ generateTypeScript,
692
+ generateDocumentsModule
693
+ };
694
+ //# sourceMappingURL=chunk-TFJ4MKPH.js.map