opacacms 0.1.20 → 0.2.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 (139) hide show
  1. package/README.md +638 -732
  2. package/dist/admin/auth-client.d.ts +39 -39
  3. package/dist/admin/index.d.ts +1 -0
  4. package/dist/admin/index.js +2397 -1405
  5. package/dist/admin/react.d.ts +1 -1
  6. package/dist/admin/react.js +8 -0
  7. package/dist/admin/router.d.ts +1 -0
  8. package/dist/admin/stores/ui.d.ts +10 -0
  9. package/dist/admin/ui/admin-layout.d.ts +4 -4
  10. package/dist/admin/ui/components/DataDetailView.d.ts +1 -1
  11. package/dist/admin/ui/components/DetailSheet.d.ts +19 -0
  12. package/dist/admin/ui/components/PluginSettingsForm.d.ts +11 -0
  13. package/dist/admin/ui/components/fields/BooleanField.d.ts +2 -1
  14. package/dist/admin/ui/components/fields/DateField.d.ts +1 -1
  15. package/dist/admin/ui/components/fields/FieldLabel.d.ts +11 -0
  16. package/dist/admin/ui/components/fields/FileField.d.ts +1 -1
  17. package/dist/admin/ui/components/fields/NumberField.d.ts +1 -1
  18. package/dist/admin/ui/components/fields/RadioField.d.ts +1 -1
  19. package/dist/admin/ui/components/fields/RelationshipField.d.ts +3 -1
  20. package/dist/admin/ui/components/fields/SelectField.d.ts +1 -1
  21. package/dist/admin/ui/components/fields/TextAreaField.d.ts +1 -1
  22. package/dist/admin/ui/components/fields/TextField.d.ts +1 -1
  23. package/dist/admin/ui/components/fields/VirtualField.d.ts +1 -0
  24. package/dist/admin/ui/components/fields/index.d.ts +16 -16
  25. package/dist/admin/ui/components/fields/richtext-editor/index.d.ts +1 -1
  26. package/dist/admin/ui/components/media/AssetManagerModal.d.ts +1 -1
  27. package/dist/admin/ui/components/toast.d.ts +1 -1
  28. package/dist/admin/ui/components/ui/accordion.d.ts +1 -1
  29. package/dist/admin/ui/components/ui/button.d.ts +1 -1
  30. package/dist/admin/ui/components/ui/collapsible.d.ts +1 -1
  31. package/dist/admin/ui/components/ui/dialog.d.ts +1 -1
  32. package/dist/admin/ui/components/ui/group.d.ts +1 -1
  33. package/dist/admin/ui/components/ui/index.d.ts +17 -17
  34. package/dist/admin/ui/components/ui/input.d.ts +1 -1
  35. package/dist/admin/ui/components/ui/label.d.ts +1 -1
  36. package/dist/admin/ui/components/ui/radio-group.d.ts +1 -1
  37. package/dist/admin/ui/components/ui/relationship.d.ts +4 -4
  38. package/dist/admin/ui/components/ui/select.d.ts +1 -1
  39. package/dist/admin/ui/components/ui/sheet.d.ts +1 -1
  40. package/dist/admin/ui/components/ui/tabs.d.ts +1 -1
  41. package/dist/admin/ui/components/versions-sheet.d.ts +11 -0
  42. package/dist/admin/ui/views/media-registry-view.d.ts +1 -1
  43. package/dist/admin/ui/views/settings-view.d.ts +2 -2
  44. package/dist/admin/vue.d.ts +17 -0
  45. package/dist/admin/vue.js +8 -0
  46. package/dist/admin/webcomponent.js +2 -2
  47. package/dist/admin.css +1 -1
  48. package/dist/auth/index.d.ts +101 -41
  49. package/dist/{chunk-qkn1ykrj.js → chunk-0bq155dy.js} +94 -31
  50. package/dist/{chunk-2z8wxx9g.js → chunk-0gtxnxmd.js} +98 -25
  51. package/dist/{chunk-v521d72w.js → chunk-3rdhbedb.js} +1 -1
  52. package/dist/chunk-51z3x7kq.js +20 -0
  53. package/dist/{chunk-7fyepksb.js → chunk-526a3gqx.js} +1 -1
  54. package/dist/{chunk-wmvjvn7b.js → chunk-6qq3ne6b.js} +39 -1
  55. package/dist/{chunk-0am1m47g.js → chunk-6v1fw7q7.js} +5 -5
  56. package/dist/{chunk-zvwb67nd.js → chunk-7y1nbmw6.js} +36 -5
  57. package/dist/chunk-8scgdznr.js +44 -0
  58. package/dist/{chunk-erh6x75p.js → chunk-b3kr8w41.js} +58 -7
  59. package/dist/chunk-bexcv7xe.js +36 -0
  60. package/dist/{chunk-16vgcf3k.js → chunk-byq8g0rd.js} +1 -1
  61. package/dist/{chunk-wq314kkx.js → chunk-d1asgtke.js} +94 -31
  62. package/dist/{chunk-xtwc125q.js → chunk-dykn5hr6.js} +8 -8
  63. package/dist/{chunk-pxh5encs.js → chunk-esrg9qj0.js} +102 -44
  64. package/dist/chunk-fj19qccp.js +78 -0
  65. package/dist/{chunk-x2ejaftz.js → chunk-gmee4mdc.js} +98 -27
  66. package/dist/{chunk-xa7rjsn2.js → chunk-j53pz21t.js} +2 -2
  67. package/dist/{chunk-9y3m1xkx.js → chunk-kc4jfnv7.js} +480 -85
  68. package/dist/chunk-mkn49zmy.js +102 -0
  69. package/dist/{chunk-n1xraw7j.js → chunk-qb6ztvw9.js} +1 -1
  70. package/dist/{chunk-2kyhqvhc.js → chunk-qxt9vge8.js} +1 -1
  71. package/dist/chunk-r39em4yj.js +29 -0
  72. package/dist/chunk-rqyjjqgy.js +91 -0
  73. package/dist/chunk-rsf0tpy1.js +8 -0
  74. package/dist/chunk-swtcpvhf.js +2442 -0
  75. package/dist/chunk-t0zg026p.js +71 -0
  76. package/dist/chunk-twpvxfce.js +64 -0
  77. package/dist/{chunk-ybbbqj63.js → chunk-v9z61v3g.js} +15 -0
  78. package/dist/{chunk-jwjk85ze.js → chunk-ywm4t2gm.js} +6 -2
  79. package/dist/cli/commands/plugin-build.d.ts +1 -0
  80. package/dist/cli/commands/plugin-init.d.ts +1 -0
  81. package/dist/cli/commands/plugin-sync.d.ts +1 -0
  82. package/dist/cli/index.js +24 -6
  83. package/dist/config-utils.d.ts +1 -1
  84. package/dist/config.d.ts +21 -4
  85. package/dist/db/better-sqlite.d.ts +1 -1
  86. package/dist/db/better-sqlite.js +5 -5
  87. package/dist/db/bun-sqlite.d.ts +1 -1
  88. package/dist/db/bun-sqlite.js +5 -5
  89. package/dist/db/d1.d.ts +1 -1
  90. package/dist/db/d1.js +5 -5
  91. package/dist/db/index.js +9 -9
  92. package/dist/db/postgres.d.ts +1 -1
  93. package/dist/db/postgres.js +5 -5
  94. package/dist/db/sqlite.d.ts +1 -1
  95. package/dist/db/sqlite.js +5 -5
  96. package/dist/index.js +4 -3
  97. package/dist/plugins/index.d.ts +1 -0
  98. package/dist/plugins/ui-bridge.d.ts +12 -0
  99. package/dist/plugins/utils.d.ts +5 -0
  100. package/dist/runtimes/bun.js +13 -7
  101. package/dist/runtimes/cloudflare-workers.js +5 -5
  102. package/dist/runtimes/next.js +5 -5
  103. package/dist/runtimes/node.js +13 -7
  104. package/dist/schema/collection.d.ts +9 -30
  105. package/dist/schema/fields/base.d.ts +3 -2
  106. package/dist/schema/fields/index.d.ts +12 -0
  107. package/dist/schema/fields/validation.test.d.ts +1 -0
  108. package/dist/schema/global.d.ts +10 -15
  109. package/dist/schema/index.js +22 -14
  110. package/dist/server/admin-router.d.ts +2 -2
  111. package/dist/server/admin.d.ts +2 -1
  112. package/dist/server/collection-router.d.ts +1 -1
  113. package/dist/server/handlers.d.ts +10 -0
  114. package/dist/server/middlewares/admin.d.ts +2 -2
  115. package/dist/server/middlewares/auth.d.ts +1 -1
  116. package/dist/server/middlewares/context.d.ts +2 -0
  117. package/dist/server/middlewares/rate-limit.d.ts +1 -1
  118. package/dist/server/openapi.d.ts +2 -0
  119. package/dist/server/plugins-loader.d.ts +6 -0
  120. package/dist/server/router.d.ts +3 -3
  121. package/dist/server/routers/admin.d.ts +2 -2
  122. package/dist/server/routers/auth.d.ts +1 -1
  123. package/dist/server/routers/collections.d.ts +1 -1
  124. package/dist/server/routers/plugins.d.ts +18 -0
  125. package/dist/server/setup-middlewares.d.ts +2 -2
  126. package/dist/server/system-router.d.ts +1 -1
  127. package/dist/server.js +11 -7
  128. package/dist/storage/adapters/local.d.ts +1 -1
  129. package/dist/storage/adapters/s3.d.ts +1 -1
  130. package/dist/types.d.ts +227 -12
  131. package/dist/utils/logger.d.ts +13 -35
  132. package/dist/validation.d.ts +60 -8
  133. package/dist/validator.d.ts +1 -1
  134. package/package.json +21 -7
  135. package/dist/admin/ui/components/DataDetailSheet.d.ts +0 -13
  136. package/dist/admin/ui/components/ui/relationship-detail-sheet.d.ts +0 -9
  137. package/dist/chunk-62ev8gnc.js +0 -41
  138. package/dist/chunk-j4d50hrx.js +0 -20
  139. package/dist/chunk-nb7ctdg8.js +0 -311
@@ -3,18 +3,18 @@ import {
3
3
  flattenPayload,
4
4
  pushSchema,
5
5
  unflattenRow
6
- } from "./chunk-xtwc125q.js";
6
+ } from "./chunk-dykn5hr6.js";
7
7
  import {
8
8
  BaseDatabaseAdapter
9
9
  } from "./chunk-s8mqwnm1.js";
10
- import {
11
- logger
12
- } from "./chunk-62ev8gnc.js";
13
10
  import {
14
11
  flattenFields,
15
12
  getRelationalFields,
16
13
  toSnakeCase
17
- } from "./chunk-2kyhqvhc.js";
14
+ } from "./chunk-qxt9vge8.js";
15
+ import {
16
+ logger
17
+ } from "./chunk-t0zg026p.js";
18
18
 
19
19
  // src/db/d1.ts
20
20
  import fs from "node:fs/promises";
@@ -127,19 +127,11 @@ class D1Adapter extends BaseDatabaseAdapter {
127
127
  const dbFields = flattenFields(colDef?.fields || []);
128
128
  const validCols = new Set(dbFields.map((f) => toSnakeCase(f.name)));
129
129
  validCols.add("id");
130
- const ts = colDef?.timestamps !== false;
131
- if (ts) {
132
- const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
133
- const createdAtField = toSnakeCase(config.createdAt || "createdAt");
134
- const updatedAtField = toSnakeCase(config.updatedAt || "updatedAt");
135
- const now = new Date().toISOString();
136
- if (!flatData[createdAtField])
137
- flatData[createdAtField] = now;
138
- if (!flatData[updatedAtField])
139
- flatData[updatedAtField] = now;
140
- validCols.add(createdAtField);
141
- validCols.add(updatedAtField);
142
- }
130
+ validCols.add(toSnakeCase("createdAt"));
131
+ validCols.add(toSnakeCase("updatedAt"));
132
+ const now = new Date().toISOString();
133
+ flatData[toSnakeCase("createdAt")] = now;
134
+ flatData[toSnakeCase("updatedAt")] = now;
143
135
  const filteredData = {};
144
136
  for (const col of Object.keys(flatData)) {
145
137
  if (validCols.has(col)) {
@@ -272,7 +264,87 @@ class D1Adapter extends BaseDatabaseAdapter {
272
264
  qb = qb.orderBy("created_at", "desc");
273
265
  }
274
266
  const rows = await qb.execute();
275
- const docs = await Promise.all(rows.map((row) => this.findOne(collection, { id: row.id })));
267
+ if (rows.length === 0) {
268
+ const totalPages2 = Math.ceil(total / limit);
269
+ return {
270
+ docs: [],
271
+ totalDocs: total,
272
+ limit,
273
+ totalPages: totalPages2,
274
+ page,
275
+ pagingCounter: offset + 1,
276
+ hasNextPage: page * limit < total,
277
+ hasPrevPage: page > 1,
278
+ prevPage: page > 1 ? page - 1 : null,
279
+ nextPage: page < totalPages2 ? page + 1 : null
280
+ };
281
+ }
282
+ const rowIds = rows.map((r) => r.id);
283
+ const docs = rows.map((r) => unflattenRow(r));
284
+ const colDef = this._collections.find((c) => c.slug === collection);
285
+ if (colDef) {
286
+ const relationalFields = getRelationalFields(colDef.fields);
287
+ for (const field of relationalFields) {
288
+ if (!field.name)
289
+ continue;
290
+ const snakeName = toSnakeCase(field.name);
291
+ if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
292
+ const joinTableName = `${toSnakeCase(collection)}_${snakeName}_relations`.toLowerCase();
293
+ try {
294
+ const allRelations = await this._db.selectFrom(joinTableName).selectAll().where("source_id", "in", rowIds).orderBy("order", "asc").execute();
295
+ const relationsBySource = allRelations.reduce((acc, r) => {
296
+ if (!acc[r.source_id])
297
+ acc[r.source_id] = [];
298
+ acc[r.source_id].push(r.target_id);
299
+ return acc;
300
+ }, {});
301
+ const parts = field.name.split("__");
302
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
303
+ const rowId = rowIds[docIdx];
304
+ let current = docs[docIdx];
305
+ for (let i = 0;i < parts.length - 1; i++) {
306
+ if (!current[parts[i]])
307
+ current[parts[i]] = {};
308
+ current = current[parts[i]];
309
+ }
310
+ current[parts[parts.length - 1]] = relationsBySource[rowId] || [];
311
+ }
312
+ } catch (e) {}
313
+ } else if (field.type === "blocks" && field.blocks) {
314
+ const blocksBySource = {};
315
+ for (const b of field.blocks) {
316
+ const blockTableName = `${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
317
+ try {
318
+ const allBlocks = await this._db.selectFrom(blockTableName).selectAll().where("_parent_id", "in", rowIds).execute();
319
+ for (const blk of allBlocks) {
320
+ const uf = unflattenRow(blk);
321
+ uf.blockType = blk.block_type;
322
+ if (!blocksBySource[blk._parent_id])
323
+ blocksBySource[blk._parent_id] = [];
324
+ blocksBySource[blk._parent_id].push(uf);
325
+ }
326
+ } catch (e) {}
327
+ }
328
+ const parts = field.name.split("__");
329
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
330
+ const rowId = rowIds[docIdx];
331
+ const blockData = blocksBySource[rowId] || [];
332
+ blockData.sort((a, b) => a._order - b._order);
333
+ blockData.forEach((b) => {
334
+ delete b._order;
335
+ delete b._parentId;
336
+ });
337
+ let current = docs[docIdx];
338
+ for (let i = 0;i < parts.length - 1; i++) {
339
+ if (!current[parts[i]])
340
+ current[parts[i]] = {};
341
+ current = current[parts[i]];
342
+ }
343
+ current[parts[parts.length - 1]] = blockData;
344
+ }
345
+ }
346
+ }
347
+ }
276
348
  const totalPages = Math.ceil(total / limit);
277
349
  return {
278
350
  docs: docs.filter(Boolean),
@@ -515,21 +587,12 @@ class D1Adapter extends BaseDatabaseAdapter {
515
587
  const existing = await this.findGlobal(slug);
516
588
  const flatData = flattenPayload(data);
517
589
  const globalDef = this._globals.find((g) => g.slug === slug);
518
- const ts = globalDef?.timestamps !== false;
519
- if (ts) {
520
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
521
- const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
522
- flatData[updatedField] = new Date().toISOString();
523
- }
590
+ const now = new Date().toISOString();
591
+ flatData[toSnakeCase("updatedAt")] = now;
524
592
  if (!existing) {
525
593
  if (!flatData.id)
526
594
  flatData.id = "global";
527
- if (ts) {
528
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
529
- const createdField = toSnakeCase(config.createdAt || "createdAt");
530
- if (!flatData[createdField])
531
- flatData[createdField] = flatData[toSnakeCase(config.updatedAt || "updatedAt")];
532
- }
595
+ flatData[toSnakeCase("createdAt")] = now;
533
596
  await this._db.insertInto(tableName).values(flatData).execute();
534
597
  } else {
535
598
  await this._db.updateTable(tableName).set(flatData).where("id", "=", existing.id).execute();
@@ -1,23 +1,23 @@
1
- import {
2
- logger
3
- } from "./chunk-62ev8gnc.js";
4
1
  import {
5
2
  flattenFields,
6
3
  getRelationalFields,
7
4
  mapFieldToPostgresType,
8
5
  mapFieldToSQLiteType,
9
6
  toSnakeCase
10
- } from "./chunk-2kyhqvhc.js";
7
+ } from "./chunk-qxt9vge8.js";
11
8
  import {
12
9
  getSystemCollections,
13
10
  init_system_schema
14
- } from "./chunk-ybbbqj63.js";
11
+ } from "./chunk-v9z61v3g.js";
12
+ import {
13
+ logger
14
+ } from "./chunk-t0zg026p.js";
15
15
 
16
16
  // src/db/kysely/data-mapper.ts
17
17
  function toCamelCase(str) {
18
18
  if (str.startsWith("_"))
19
19
  return str;
20
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
20
+ return str.replace(/_+([a-z])/g, (_, letter) => letter.toUpperCase());
21
21
  }
22
22
  function flattenPayload(payload, prefix = "", excludeKeys = []) {
23
23
  if (typeof payload !== "object" || payload === null || Array.isArray(payload))
@@ -39,7 +39,7 @@ function flattenPayload(payload, prefix = "", excludeKeys = []) {
39
39
  if (value === undefined)
40
40
  continue;
41
41
  const colName = `${prefix}${toSnakeCase(key)}`;
42
- if (typeof value === "object" && value !== null) {
42
+ if (typeof value === "object" && value !== null && !(value instanceof Date)) {
43
43
  result[colName] = JSON.stringify(value);
44
44
  } else {
45
45
  result[colName] = value;
@@ -240,7 +240,7 @@ async function pushSchema(db, dialect, collections, globals = [], options = {})
240
240
  assertSafeIdentifier(tableName);
241
241
  expectedTableNames.add(tableName);
242
242
  const flattenedFields = flattenFields(collection.fields);
243
- const hasTimestamps = collection.timestamps !== false;
243
+ const hasTimestamps = true;
244
244
  const versions = collection.versions;
245
245
  if (!existingTableSet.has(tableName)) {
246
246
  logger.info(` -> Creating table: ${logger.format("green", `"${tableName}"`)}`);
@@ -3,16 +3,17 @@ import {
3
3
  flattenPayload,
4
4
  pushSchema,
5
5
  unflattenRow
6
- } from "./chunk-xtwc125q.js";
6
+ } from "./chunk-dykn5hr6.js";
7
7
  import {
8
8
  BaseDatabaseAdapter
9
9
  } from "./chunk-s8mqwnm1.js";
10
10
  import {
11
- logger
12
- } from "./chunk-62ev8gnc.js";
13
- import {
11
+ getRelationalFields,
14
12
  toSnakeCase
15
- } from "./chunk-2kyhqvhc.js";
13
+ } from "./chunk-qxt9vge8.js";
14
+ import {
15
+ logger
16
+ } from "./chunk-t0zg026p.js";
16
17
  import {
17
18
  __require
18
19
  } from "./chunk-8sqjbsgt.js";
@@ -131,16 +132,13 @@ class PostgresAdapter extends BaseDatabaseAdapter {
131
132
  }
132
133
  if (!flatData.id)
133
134
  flatData.id = crypto.randomUUID();
134
- const ts = colDef?.timestamps !== false;
135
- if (ts) {
136
- const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
137
- const createdField = toSnakeCase(config.createdAt || "createdAt");
138
- const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
139
- if (!flatData[createdField])
140
- flatData[createdField] = new Date().toISOString();
141
- if (!flatData[updatedField])
142
- flatData[updatedField] = new Date().toISOString();
143
- }
135
+ const now = new Date().toISOString();
136
+ const createdKey = toSnakeCase("createdAt");
137
+ const updatedKey = toSnakeCase("updatedAt");
138
+ if (!flatData[createdKey])
139
+ flatData[createdKey] = now;
140
+ if (!flatData[updatedKey])
141
+ flatData[updatedKey] = now;
144
142
  await tx.insertInto(tableName).values(flatData).execute();
145
143
  for (const [key, values] of Object.entries(hasManyData)) {
146
144
  const joinTableName = `${tableName}_${toSnakeCase(key)}_relations`.toLowerCase();
@@ -187,8 +185,8 @@ class PostgresAdapter extends BaseDatabaseAdapter {
187
185
  const unflattened = unflattenRow(row);
188
186
  const colDef = this._collections.find((c) => c.slug === collection);
189
187
  if (colDef) {
190
- const { getRelationalFields, toSnakeCase: toSnakeCase2 } = await import("./chunk-7fyepksb.js");
191
- const relationalFields = getRelationalFields(colDef.fields);
188
+ const { getRelationalFields: getRelationalFields2, toSnakeCase: toSnakeCase2 } = await import("./chunk-526a3gqx.js");
189
+ const relationalFields = getRelationalFields2(colDef.fields);
192
190
  for (const field of relationalFields) {
193
191
  if (!field.name)
194
192
  continue;
@@ -258,7 +256,87 @@ class PostgresAdapter extends BaseDatabaseAdapter {
258
256
  qb = qb.orderBy("created_at", "desc");
259
257
  }
260
258
  const rows = await qb.execute();
261
- const docs = await Promise.all(rows.map((row) => this.findOne(collection, { id: row.id })));
259
+ if (rows.length === 0) {
260
+ const totalPages2 = Math.ceil(total / limit);
261
+ return {
262
+ docs: [],
263
+ totalDocs: total,
264
+ limit,
265
+ totalPages: totalPages2,
266
+ page,
267
+ pagingCounter: offset + 1,
268
+ hasNextPage: page * limit < total,
269
+ hasPrevPage: page > 1,
270
+ prevPage: page > 1 ? page - 1 : null,
271
+ nextPage: page < totalPages2 ? page + 1 : null
272
+ };
273
+ }
274
+ const rowIds = rows.map((r) => r.id);
275
+ const docs = rows.map((r) => unflattenRow(r));
276
+ const colDef = this._collections.find((c) => c.slug === collection);
277
+ if (colDef) {
278
+ const relationalFields = getRelationalFields(colDef.fields);
279
+ for (const field of relationalFields) {
280
+ if (!field.name)
281
+ continue;
282
+ const snakeName = toSnakeCase(field.name);
283
+ if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
284
+ const joinTableName = `${toSnakeCase(collection)}_${snakeName}_relations`.toLowerCase();
285
+ try {
286
+ const allRelations = await this._db.selectFrom(joinTableName).selectAll().where("source_id", "in", rowIds).orderBy("order", "asc").execute();
287
+ const relationsBySource = allRelations.reduce((acc, r) => {
288
+ if (!acc[r.source_id])
289
+ acc[r.source_id] = [];
290
+ acc[r.source_id].push(r.target_id);
291
+ return acc;
292
+ }, {});
293
+ const parts = field.name.split("__");
294
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
295
+ const rowId = rowIds[docIdx];
296
+ let current = docs[docIdx];
297
+ for (let i = 0;i < parts.length - 1; i++) {
298
+ if (!current[parts[i]])
299
+ current[parts[i]] = {};
300
+ current = current[parts[i]];
301
+ }
302
+ current[parts[parts.length - 1]] = relationsBySource[rowId] || [];
303
+ }
304
+ } catch (e) {}
305
+ } else if (field.type === "blocks" && field.blocks) {
306
+ const blocksBySource = {};
307
+ for (const b of field.blocks) {
308
+ const blockTableName = `${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
309
+ try {
310
+ const allBlocks = await this._db.selectFrom(blockTableName).selectAll().where("_parent_id", "in", rowIds).execute();
311
+ for (const blk of allBlocks) {
312
+ const uf = unflattenRow(blk);
313
+ uf.blockType = blk.block_type;
314
+ if (!blocksBySource[blk._parent_id])
315
+ blocksBySource[blk._parent_id] = [];
316
+ blocksBySource[blk._parent_id].push(uf);
317
+ }
318
+ } catch (e) {}
319
+ }
320
+ const parts = field.name.split("__");
321
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
322
+ const rowId = rowIds[docIdx];
323
+ const blockData = blocksBySource[rowId] || [];
324
+ blockData.sort((a, b) => a._order - b._order);
325
+ blockData.forEach((b) => {
326
+ delete b._order;
327
+ delete b._parentId;
328
+ });
329
+ let current = docs[docIdx];
330
+ for (let i = 0;i < parts.length - 1; i++) {
331
+ if (!current[parts[i]])
332
+ current[parts[i]] = {};
333
+ current = current[parts[i]];
334
+ }
335
+ current[parts[parts.length - 1]] = blockData;
336
+ }
337
+ }
338
+ }
339
+ }
262
340
  const totalPages = Math.ceil(total / limit);
263
341
  return {
264
342
  docs: docs.filter(Boolean),
@@ -300,12 +378,7 @@ class PostgresAdapter extends BaseDatabaseAdapter {
300
378
  delete flatData[key];
301
379
  }
302
380
  }
303
- const ts = colDef?.timestamps !== false;
304
- if (ts) {
305
- const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
306
- const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
307
- flatData[updatedField] = new Date().toISOString();
308
- }
381
+ flatData.updatedAt = new Date().toISOString();
309
382
  const tableName = toSnakeCase(collection);
310
383
  if (Object.keys(flatData).length > 0) {
311
384
  await tx.updateTable(tableName).set(flatData).where("id", "=", current.id).execute();
@@ -365,13 +438,7 @@ class PostgresAdapter extends BaseDatabaseAdapter {
365
438
  delete flatData[key];
366
439
  }
367
440
  }
368
- const colDef = this._collections.find((c) => c.slug === collection);
369
- const ts = colDef?.timestamps !== false;
370
- if (ts) {
371
- const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
372
- const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
373
- flatData[updatedField] = new Date().toISOString();
374
- }
441
+ flatData.updatedAt = new Date().toISOString();
375
442
  if (Object.keys(flatData).length > 0) {
376
443
  const result = await qb.set(flatData).executeTakeFirst();
377
444
  return Number(result.numUpdatedRows || 0);
@@ -465,21 +532,12 @@ class PostgresAdapter extends BaseDatabaseAdapter {
465
532
  const globalDef = this._globals.find((g) => g.slug === slug);
466
533
  const jsonFields = globalDef?.fields.filter((f) => f.name && (["richtext", "json", "file"].includes(f.type) || f.localized)).map((f) => f.name) || [];
467
534
  const flatData = flattenPayload(data, "", jsonFields);
468
- const ts = globalDef?.timestamps !== false;
469
- if (ts) {
470
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
471
- const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
472
- flatData[updatedField] = new Date().toISOString();
473
- }
535
+ const now = new Date().toISOString();
536
+ flatData[toSnakeCase("updatedAt")] = now;
474
537
  if (!existing) {
475
538
  if (!flatData.id)
476
539
  flatData.id = "global";
477
- if (ts) {
478
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
479
- const createdField = toSnakeCase(config.createdAt || "createdAt");
480
- if (!flatData[createdField])
481
- flatData[createdField] = new Date().toISOString();
482
- }
540
+ flatData[toSnakeCase("createdAt")] = now;
483
541
  await this._db.insertInto(tableName).values(flatData).execute();
484
542
  } else {
485
543
  await this._db.updateTable(tableName).set(flatData).where("id", "=", existing.id).execute();
@@ -510,7 +568,7 @@ class PostgresAdapter extends BaseDatabaseAdapter {
510
568
  }
511
569
  }
512
570
  async migrate(collections, globals = []) {
513
- const { getSystemCollections } = await import("./chunk-v521d72w.js");
571
+ const { getSystemCollections } = await import("./chunk-3rdhbedb.js");
514
572
  this._collections = [...getSystemCollections(), ...collections];
515
573
  this._globals = globals;
516
574
  if (this.push && this._db) {
@@ -0,0 +1,78 @@
1
+ import {
2
+ pluginSyncCommand
3
+ } from "./chunk-twpvxfce.js";
4
+ import {
5
+ logger
6
+ } from "./chunk-t0zg026p.js";
7
+ import {
8
+ __require
9
+ } from "./chunk-8sqjbsgt.js";
10
+
11
+ // src/cli/commands/plugin-build.ts
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ async function pluginBuildCommand(projectRoot) {
15
+ const srcDir = path.resolve(projectRoot, "src");
16
+ const distDir = path.resolve(projectRoot, "dist");
17
+ if (!fs.existsSync(srcDir)) {
18
+ logger.error(`No 'src' directory found in ${projectRoot}.`);
19
+ process.exit(1);
20
+ }
21
+ logger.info("Syncing plugin UI...");
22
+ await pluginSyncCommand(projectRoot, false);
23
+ const entryFiles = ["index.tsx", "index.ts", "plugin.tsx", "plugin.ts"];
24
+ let mainEntry = "";
25
+ for (const file of entryFiles) {
26
+ const fullPath = path.join(srcDir, file);
27
+ if (fs.existsSync(fullPath)) {
28
+ mainEntry = fullPath;
29
+ break;
30
+ }
31
+ }
32
+ if (!mainEntry) {
33
+ logger.error(`Could not find a main entry file in ${srcDir}`);
34
+ process.exit(1);
35
+ }
36
+ logger.info(`Building plugin entry: ${path.basename(mainEntry)}...`);
37
+ if (!fs.existsSync(distDir)) {
38
+ fs.mkdirSync(distDir, { recursive: true });
39
+ }
40
+ const buildResult = await Bun.build({
41
+ entrypoints: [mainEntry],
42
+ outdir: distDir,
43
+ target: "node",
44
+ format: "esm",
45
+ external: [
46
+ "opacacms",
47
+ "opacacms/types",
48
+ "opacacms/plugins",
49
+ "opacacms/schema",
50
+ "hono",
51
+ "zod",
52
+ "react",
53
+ "react-dom"
54
+ ]
55
+ });
56
+ if (!buildResult.success) {
57
+ logger.error("Plugin build failed.");
58
+ for (const msg of buildResult.logs) {
59
+ console.error(msg);
60
+ }
61
+ process.exit(1);
62
+ }
63
+ try {
64
+ logger.info("Generating type declarations...");
65
+ const { execSync } = await import("node:child_process");
66
+ execSync("bunx tsc --emitDeclarationOnly --outDir dist", {
67
+ cwd: projectRoot,
68
+ stdio: "ignore"
69
+ });
70
+ logger.success("Types generated.");
71
+ } catch (err) {
72
+ logger.warn("Failed to generate types. Make sure typescript is installed.");
73
+ }
74
+ logger.success(`Plugin built successfully in ${distDir}!`);
75
+ }
76
+ export {
77
+ pluginBuildCommand
78
+ };
@@ -3,17 +3,18 @@ import {
3
3
  flattenPayload,
4
4
  pushSchema,
5
5
  unflattenRow
6
- } from "./chunk-xtwc125q.js";
6
+ } from "./chunk-dykn5hr6.js";
7
7
  import {
8
8
  BaseDatabaseAdapter
9
9
  } from "./chunk-s8mqwnm1.js";
10
- import {
11
- logger
12
- } from "./chunk-62ev8gnc.js";
13
10
  import {
14
11
  flattenFields,
12
+ getRelationalFields,
15
13
  toSnakeCase
16
- } from "./chunk-2kyhqvhc.js";
14
+ } from "./chunk-qxt9vge8.js";
15
+ import {
16
+ logger
17
+ } from "./chunk-t0zg026p.js";
17
18
  import {
18
19
  __require
19
20
  } from "./chunk-8sqjbsgt.js";
@@ -67,7 +68,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
67
68
  if (!colDef)
68
69
  return data;
69
70
  const result = { ...data };
70
- const { flattenFields: flattenFields2 } = await import("./chunk-7fyepksb.js");
71
+ const { flattenFields: flattenFields2 } = await import("./chunk-526a3gqx.js");
71
72
  const allFields = flattenFields2(colDef.fields);
72
73
  for (const field of allFields) {
73
74
  const colName = toSnakeCase(field.name);
@@ -139,12 +140,11 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
139
140
  const dbFields = flattenFields(colDef?.fields || []);
140
141
  const validCols = new Set(dbFields.map((f) => toSnakeCase(f.name)));
141
142
  validCols.add("id");
142
- const ts = colDef?.timestamps !== false;
143
- if (ts) {
144
- const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
145
- validCols.add(toSnakeCase(config.createdAt || "createdAt"));
146
- validCols.add(toSnakeCase(config.updatedAt || "updatedAt"));
147
- }
143
+ validCols.add(toSnakeCase("createdAt"));
144
+ validCols.add(toSnakeCase("updatedAt"));
145
+ const now = new Date().toISOString();
146
+ flatData[toSnakeCase("createdAt")] = now;
147
+ flatData[toSnakeCase("updatedAt")] = now;
148
148
  const filteredData = {};
149
149
  for (const col of Object.keys(flatData)) {
150
150
  if (validCols.has(col)) {
@@ -205,8 +205,8 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
205
205
  const unflattened = unflattenRow(row);
206
206
  const colDef = this._collections.find((c) => c.slug === collection);
207
207
  if (colDef) {
208
- const { getRelationalFields, toSnakeCase: toSnakeCase2 } = await import("./chunk-7fyepksb.js");
209
- const relationalFields = getRelationalFields(colDef.fields);
208
+ const { getRelationalFields: getRelationalFields2, toSnakeCase: toSnakeCase2 } = await import("./chunk-526a3gqx.js");
209
+ const relationalFields = getRelationalFields2(colDef.fields);
210
210
  for (const field of relationalFields) {
211
211
  if (!field.name)
212
212
  continue;
@@ -259,7 +259,87 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
259
259
  qb = qb.orderBy("created_at", "desc");
260
260
  }
261
261
  const rows = await qb.execute();
262
- const docs = await Promise.all(rows.map((row) => this.findOne(collection, { id: row.id })));
262
+ if (rows.length === 0) {
263
+ const totalPages2 = Math.ceil(total / limit);
264
+ return {
265
+ docs: [],
266
+ totalDocs: total,
267
+ limit,
268
+ totalPages: totalPages2,
269
+ page,
270
+ pagingCounter: offset + 1,
271
+ hasNextPage: page * limit < total,
272
+ hasPrevPage: page > 1,
273
+ prevPage: page > 1 ? page - 1 : null,
274
+ nextPage: page < totalPages2 ? page + 1 : null
275
+ };
276
+ }
277
+ const rowIds = rows.map((r) => r.id);
278
+ const docs = rows.map((r) => unflattenRow(r));
279
+ const colDef = this._collections.find((c) => c.slug === collection);
280
+ if (colDef) {
281
+ const relationalFields = getRelationalFields(colDef.fields);
282
+ for (const field of relationalFields) {
283
+ if (!field.name)
284
+ continue;
285
+ const snakeName = toSnakeCase(field.name);
286
+ if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
287
+ const joinTableName = `${toSnakeCase(collection)}_${snakeName}_relations`.toLowerCase();
288
+ try {
289
+ const allRelations = await this._db.selectFrom(joinTableName).selectAll().where("source_id", "in", rowIds).orderBy("order", "asc").execute();
290
+ const relationsBySource = allRelations.reduce((acc, r) => {
291
+ if (!acc[r.source_id])
292
+ acc[r.source_id] = [];
293
+ acc[r.source_id].push(r.target_id);
294
+ return acc;
295
+ }, {});
296
+ const parts = field.name.split("__");
297
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
298
+ const rowId = rowIds[docIdx];
299
+ let current = docs[docIdx];
300
+ for (let i = 0;i < parts.length - 1; i++) {
301
+ if (!current[parts[i]])
302
+ current[parts[i]] = {};
303
+ current = current[parts[i]];
304
+ }
305
+ current[parts[parts.length - 1]] = relationsBySource[rowId] || [];
306
+ }
307
+ } catch (e) {}
308
+ } else if (field.type === "blocks" && field.blocks) {
309
+ const blocksBySource = {};
310
+ for (const b of field.blocks) {
311
+ const blockTableName = `${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
312
+ try {
313
+ const allBlocks = await this._db.selectFrom(blockTableName).selectAll().where("_parent_id", "in", rowIds).execute();
314
+ for (const blk of allBlocks) {
315
+ const uf = unflattenRow(blk);
316
+ uf.blockType = blk.block_type;
317
+ if (!blocksBySource[blk._parent_id])
318
+ blocksBySource[blk._parent_id] = [];
319
+ blocksBySource[blk._parent_id].push(uf);
320
+ }
321
+ } catch (e) {}
322
+ }
323
+ const parts = field.name.split("__");
324
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
325
+ const rowId = rowIds[docIdx];
326
+ const blockData = blocksBySource[rowId] || [];
327
+ blockData.sort((a, b) => a._order - b._order);
328
+ blockData.forEach((b) => {
329
+ delete b._order;
330
+ delete b._parentId;
331
+ });
332
+ let current = docs[docIdx];
333
+ for (let i = 0;i < parts.length - 1; i++) {
334
+ if (!current[parts[i]])
335
+ current[parts[i]] = {};
336
+ current = current[parts[i]];
337
+ }
338
+ current[parts[parts.length - 1]] = blockData;
339
+ }
340
+ }
341
+ }
342
+ }
263
343
  const totalPages = Math.ceil(total / limit);
264
344
  return {
265
345
  docs: docs.filter(Boolean),
@@ -464,21 +544,12 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
464
544
  const existing = await this.findGlobal(slug);
465
545
  const flatData = flattenPayload(data);
466
546
  const globalDef = this._globals.find((g) => g.slug === slug);
467
- const ts = globalDef?.timestamps !== false;
468
- if (ts) {
469
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
470
- const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
471
- flatData[updatedField] = new Date().toISOString();
472
- }
547
+ const now = new Date().toISOString();
548
+ flatData[toSnakeCase("updatedAt")] = now;
473
549
  if (!existing) {
474
550
  if (!flatData.id)
475
551
  flatData.id = "global";
476
- if (ts) {
477
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
478
- const createdField = toSnakeCase(config.createdAt || "createdAt");
479
- if (!flatData[createdField])
480
- flatData[createdField] = flatData[toSnakeCase(config.updatedAt || "updatedAt")];
481
- }
552
+ flatData[toSnakeCase("createdAt")] = now;
482
553
  await this._db.insertInto(tableName).values(flatData).execute();
483
554
  } else {
484
555
  await this._db.updateTable(tableName).set(flatData).where("id", "=", existing.id).execute();