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/bun-sqlite.ts
20
20
  import { Database } from "bun:sqlite";
@@ -82,7 +82,10 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
82
82
  if (value instanceof Date) {
83
83
  result[colName] = value.toISOString();
84
84
  } else if (typeof value === "string") {
85
- result[colName] = new Date(value).toISOString();
85
+ const d = new Date(value);
86
+ if (!isNaN(d.getTime())) {
87
+ result[colName] = d.toISOString();
88
+ }
86
89
  }
87
90
  break;
88
91
  case "richtext":
@@ -134,12 +137,11 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
134
137
  const dbFields = flattenFields(colDef?.fields || []);
135
138
  const validCols = new Set(dbFields.map((f) => toSnakeCase(f.name)));
136
139
  validCols.add("id");
137
- const ts = colDef?.timestamps !== false;
138
- if (ts) {
139
- const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
140
- validCols.add(toSnakeCase(config.createdAt || "createdAt"));
141
- validCols.add(toSnakeCase(config.updatedAt || "updatedAt"));
142
- }
140
+ validCols.add(toSnakeCase("createdAt"));
141
+ validCols.add(toSnakeCase("updatedAt"));
142
+ const now = new Date().toISOString();
143
+ flatData[toSnakeCase("createdAt")] = now;
144
+ flatData[toSnakeCase("updatedAt")] = now;
143
145
  const filteredData = {};
144
146
  for (const col of Object.keys(flatData)) {
145
147
  if (validCols.has(col)) {
@@ -267,7 +269,87 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
267
269
  qb = qb.orderBy("created_at", "desc");
268
270
  }
269
271
  const rows = await qb.execute();
270
- const docs = await Promise.all(rows.map((row) => this.findOne(collection, { id: row.id })));
272
+ if (rows.length === 0) {
273
+ const totalPages2 = Math.ceil(total / limit);
274
+ return {
275
+ docs: [],
276
+ totalDocs: total,
277
+ limit,
278
+ totalPages: totalPages2,
279
+ page,
280
+ pagingCounter: offset + 1,
281
+ hasNextPage: page * limit < total,
282
+ hasPrevPage: page > 1,
283
+ prevPage: page > 1 ? page - 1 : null,
284
+ nextPage: page < totalPages2 ? page + 1 : null
285
+ };
286
+ }
287
+ const rowIds = rows.map((r) => r.id);
288
+ const docs = rows.map((r) => unflattenRow(r));
289
+ const colDef = this._collections.find((c) => c.slug === collection);
290
+ if (colDef) {
291
+ const relationalFields = getRelationalFields(colDef.fields);
292
+ for (const field of relationalFields) {
293
+ if (!field.name)
294
+ continue;
295
+ const snakeName = toSnakeCase(field.name);
296
+ if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
297
+ const joinTableName = `${toSnakeCase(collection)}_${snakeName}_relations`.toLowerCase();
298
+ try {
299
+ const allRelations = await this._db.selectFrom(joinTableName).selectAll().where("source_id", "in", rowIds).orderBy("order", "asc").execute();
300
+ const relationsBySource = allRelations.reduce((acc, r) => {
301
+ if (!acc[r.source_id])
302
+ acc[r.source_id] = [];
303
+ acc[r.source_id].push(r.target_id);
304
+ return acc;
305
+ }, {});
306
+ const parts = field.name.split("__");
307
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
308
+ const rowId = rowIds[docIdx];
309
+ let current = docs[docIdx];
310
+ for (let i = 0;i < parts.length - 1; i++) {
311
+ if (!current[parts[i]])
312
+ current[parts[i]] = {};
313
+ current = current[parts[i]];
314
+ }
315
+ current[parts[parts.length - 1]] = relationsBySource[rowId] || [];
316
+ }
317
+ } catch (e) {}
318
+ } else if (field.type === "blocks" && field.blocks) {
319
+ const blocksBySource = {};
320
+ for (const b of field.blocks) {
321
+ const blockTableName = `${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
322
+ try {
323
+ const allBlocks = await this._db.selectFrom(blockTableName).selectAll().where("_parent_id", "in", rowIds).execute();
324
+ for (const blk of allBlocks) {
325
+ const uf = unflattenRow(blk);
326
+ uf.blockType = blk.block_type;
327
+ if (!blocksBySource[blk._parent_id])
328
+ blocksBySource[blk._parent_id] = [];
329
+ blocksBySource[blk._parent_id].push(uf);
330
+ }
331
+ } catch (e) {}
332
+ }
333
+ const parts = field.name.split("__");
334
+ for (let docIdx = 0;docIdx < docs.length; docIdx++) {
335
+ const rowId = rowIds[docIdx];
336
+ const blockData = blocksBySource[rowId] || [];
337
+ blockData.sort((a, b) => a._order - b._order);
338
+ blockData.forEach((b) => {
339
+ delete b._order;
340
+ delete b._parentId;
341
+ });
342
+ let current = docs[docIdx];
343
+ for (let i = 0;i < parts.length - 1; i++) {
344
+ if (!current[parts[i]])
345
+ current[parts[i]] = {};
346
+ current = current[parts[i]];
347
+ }
348
+ current[parts[parts.length - 1]] = blockData;
349
+ }
350
+ }
351
+ }
352
+ }
271
353
  const totalPages = Math.ceil(total / limit);
272
354
  return {
273
355
  docs: docs.filter(Boolean),
@@ -475,21 +557,12 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
475
557
  const existing = await this.findGlobal(slug);
476
558
  const flatData = flattenPayload(data);
477
559
  const globalDef = this._globals.find((g) => g.slug === slug);
478
- const ts = globalDef?.timestamps !== false;
479
- if (ts) {
480
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
481
- const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
482
- flatData[updatedField] = new Date().toISOString();
483
- }
560
+ const now = new Date().toISOString();
561
+ flatData[toSnakeCase("updatedAt")] = now;
484
562
  if (!existing) {
485
563
  if (!flatData.id)
486
564
  flatData.id = "global";
487
- if (ts) {
488
- const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
489
- const createdField = toSnakeCase(config.createdAt || "createdAt");
490
- if (!flatData[createdField])
491
- flatData[createdField] = flatData[toSnakeCase(config.updatedAt || "updatedAt")];
492
- }
565
+ flatData[toSnakeCase("createdAt")] = now;
493
566
  await this._db.insertInto(tableName).values(flatData).execute();
494
567
  } else {
495
568
  await this._db.updateTable(tableName).set(flatData).where("id", "=", existing.id).execute();
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getSystemCollections,
3
3
  init_system_schema
4
- } from "./chunk-ybbbqj63.js";
4
+ } from "./chunk-v9z61v3g.js";
5
5
  import"./chunk-8sqjbsgt.js";
6
6
  init_system_schema();
7
7
 
@@ -0,0 +1,20 @@
1
+ // src/server/plugins-loader.ts
2
+ async function loadPluginSettings(config) {
3
+ if (!config.db)
4
+ return {};
5
+ try {
6
+ const result = await config.db.find("_opaca_plugin_settings");
7
+ if (!result || !result.docs)
8
+ return {};
9
+ return result.docs.reduce((acc, curr) => {
10
+ if (curr.pluginName && curr.config) {
11
+ acc[curr.pluginName] = curr.config;
12
+ }
13
+ return acc;
14
+ }, {});
15
+ } catch (e) {
16
+ return {};
17
+ }
18
+ }
19
+
20
+ export { loadPluginSettings };
@@ -4,7 +4,7 @@ import {
4
4
  mapFieldToPostgresType,
5
5
  mapFieldToSQLiteType,
6
6
  toSnakeCase
7
- } from "./chunk-2kyhqvhc.js";
7
+ } from "./chunk-qxt9vge8.js";
8
8
  import"./chunk-8sqjbsgt.js";
9
9
  export {
10
10
  toSnakeCase,
@@ -3,7 +3,7 @@ import {
3
3
  mapFieldToPostgresType,
4
4
  mapFieldToSQLiteType,
5
5
  toSnakeCase
6
- } from "./chunk-2kyhqvhc.js";
6
+ } from "./chunk-qxt9vge8.js";
7
7
  import"./chunk-8sqjbsgt.js";
8
8
 
9
9
  // src/db/kysely/migration-generator.ts
@@ -60,6 +60,26 @@ function generateMigrationCode(collections, globals = [], dialect) {
60
60
  `;
61
61
  downCode = ` await db.schema.dropTable('${slug}').execute();
62
62
  ` + downCode;
63
+ if ("versions" in collection && collection.versions) {
64
+ const versionsTable = `${toSnakeCase(slug)}_versions`;
65
+ upCode += ` await db.schema.createTable('${versionsTable}')
66
+ `;
67
+ upCode += ` .addColumn('id', 'text', (col) => col.primaryKey())
68
+ `;
69
+ upCode += ` .addColumn('_parent_id', 'text', (col) => col.notNull())
70
+ `;
71
+ upCode += ` .addColumn('_version_data', '${dialect === "postgres" ? "jsonb" : "text"}', (col) => col.notNull())
72
+ `;
73
+ upCode += ` .addColumn('_status', 'text')
74
+ `;
75
+ upCode += ` .addColumn('created_at', '${tsType}', (col) => col.defaultTo(sql\`CURRENT_TIMESTAMP\`))
76
+ `;
77
+ upCode += ` .execute();
78
+
79
+ `;
80
+ downCode = ` await db.schema.dropTable('${versionsTable}').execute();
81
+ ` + downCode;
82
+ }
63
83
  for (const field of flattenedFields) {
64
84
  if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
65
85
  const colName = toSnakeCase(field.name);
@@ -188,6 +208,24 @@ function generateSQLCode(collections, globals = []) {
188
208
  );
189
209
 
190
210
  `;
211
+ if ("versions" in collection && collection.versions) {
212
+ const versionsTable = `${toSnakeCase(slug)}_versions`;
213
+ sql += `CREATE TABLE IF NOT EXISTS "${versionsTable}" (
214
+ `;
215
+ sql += ` "id" TEXT PRIMARY KEY,
216
+ `;
217
+ sql += ` "_parent_id" TEXT NOT NULL,
218
+ `;
219
+ sql += ` "_version_data" TEXT NOT NULL,
220
+ `;
221
+ sql += ` "_status" TEXT,
222
+ `;
223
+ sql += ` "created_at" TEXT DEFAULT CURRENT_TIMESTAMP
224
+ `;
225
+ sql += `);
226
+
227
+ `;
228
+ }
191
229
  for (const field of flattenedFields) {
192
230
  if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
193
231
  const colName = toSnakeCase(field.name);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-62ev8gnc.js";
3
+ } from "./chunk-t0zg026p.js";
4
4
  import {
5
5
  __require
6
6
  } from "./chunk-8sqjbsgt.js";
@@ -17,8 +17,8 @@ async function migrateCreateCommand(opaca, migrationName = "migration", outDir)
17
17
  fs.mkdirSync(fullMigrationDir, { recursive: true });
18
18
  }
19
19
  const timestamp = new Date().toISOString().replace(/[-:T]/g, "").split(".")[0];
20
- const { generateMigrationCode, generateSQLCode } = await import("./chunk-wmvjvn7b.js");
21
- const { getSystemCollections } = await import("./chunk-v521d72w.js");
20
+ const { generateMigrationCode, generateSQLCode } = await import("./chunk-6qq3ne6b.js");
21
+ const { getSystemCollections } = await import("./chunk-3rdhbedb.js");
22
22
  let dialect = "sqlite";
23
23
  if (db.name === "postgres")
24
24
  dialect = "postgres";
@@ -104,9 +104,9 @@ async function migrateD1Command(migrationDir, isRemote, binding = "DB") {
104
104
  isRemote ? "--remote" : "--local",
105
105
  "--yes"
106
106
  ];
107
- const result = spawnSync("bunx", wranglerArgs, {
107
+ const result = spawnSync("bun", ["x", ...wranglerArgs], {
108
108
  stdio: "inherit",
109
- shell: true,
109
+ shell: false,
110
110
  cwd: process.cwd()
111
111
  });
112
112
  if (result.status !== 0) {
@@ -1,3 +1,7 @@
1
+ import {
2
+ logger
3
+ } from "./chunk-t0zg026p.js";
4
+
1
5
  // src/validation.ts
2
6
  import { z } from "zod";
3
7
  var FieldTypeSchema = z.enum([
@@ -20,7 +24,8 @@ var FieldTypeSchema = z.enum([
20
24
  "tabs",
21
25
  "join",
22
26
  "array",
23
- "virtual"
27
+ "virtual",
28
+ "ui"
24
29
  ]);
25
30
  var BaseFieldSchema = z.object({
26
31
  name: z.string(),
@@ -41,7 +46,8 @@ var BaseFieldSchema = z.object({
41
46
  components: z.object({
42
47
  Field: z.string().optional(),
43
48
  Cell: z.string().optional()
44
- }).optional()
49
+ }).optional(),
50
+ customProps: z.record(z.string(), z.any()).optional()
45
51
  }).optional()
46
52
  });
47
53
  var FieldSchema = z.lazy(() => z.discriminatedUnion("type", [
@@ -154,6 +160,9 @@ var FieldSchema = z.lazy(() => z.discriminatedUnion("type", [
154
160
  type: z.literal("virtual"),
155
161
  resolve: z.function(),
156
162
  returnType: z.string().optional()
163
+ }),
164
+ BaseFieldSchema.extend({
165
+ type: z.literal("ui")
157
166
  })
158
167
  ]));
159
168
  var AccessConfigSchema = z.object({
@@ -183,7 +192,7 @@ var CollectionSchema = z.object({
183
192
  createdAt: z.string().optional(),
184
193
  updatedAt: z.string().optional()
185
194
  })
186
- ]).optional(),
195
+ ]).optional().transform(() => true),
187
196
  auth: z.boolean().optional(),
188
197
  hooks: CollectionHooksSchema.optional(),
189
198
  access: AccessConfigSchema.optional(),
@@ -228,7 +237,7 @@ var GlobalSchema = z.object({
228
237
  createdAt: z.string().optional(),
229
238
  updatedAt: z.string().optional()
230
239
  })
231
- ]).optional(),
240
+ ]).optional().transform(() => true),
232
241
  access: AccessConfigSchema.optional()
233
242
  });
234
243
  var AdminConfigSchema = z.object({
@@ -266,6 +275,11 @@ var OpacaAuthConfigSchema = z.object({
266
275
  enabled: z.boolean(),
267
276
  issuer: z.string()
268
277
  }).optional()
278
+ }).optional(),
279
+ logger: z.object({
280
+ disabled: z.boolean().optional(),
281
+ disableColors: z.boolean().optional(),
282
+ level: z.enum(["debug", "info", "warn", "error"]).optional()
269
283
  }).optional()
270
284
  });
271
285
  var ApiConfigSchema = z.object({
@@ -277,9 +291,19 @@ var ApiConfigSchema = z.object({
277
291
  store: z.any().optional(),
278
292
  provider: z.function().optional(),
279
293
  keyGenerator: z.function().optional()
294
+ }).optional(),
295
+ openAPI: z.object({
296
+ enabled: z.boolean().optional().default(false),
297
+ path: z.string().optional().default("/reference"),
298
+ theme: z.string().optional(),
299
+ layout: z.string().optional(),
300
+ hideModels: z.boolean().optional(),
301
+ hideDownloadButton: z.boolean().optional(),
302
+ customCss: z.string().optional()
280
303
  }).optional()
281
304
  });
282
305
  var OpacaConfigSchema = z.object({
306
+ plugins: z.array(z.any()).optional(),
283
307
  collections: z.array(CollectionSchema),
284
308
  globals: z.array(GlobalSchema).optional(),
285
309
  db: z.any(),
@@ -321,7 +345,14 @@ function recursivelyBuild(obj) {
321
345
  return built;
322
346
  }
323
347
  function defineConfig(config) {
324
- const builtConfig = recursivelyBuild(config);
348
+ let builtConfig = recursivelyBuild(config);
349
+ if (builtConfig.plugins && Array.isArray(builtConfig.plugins)) {
350
+ for (const plugin of builtConfig.plugins) {
351
+ if (plugin.onInit) {
352
+ builtConfig = plugin.onInit({ config: builtConfig, logger }) || builtConfig;
353
+ }
354
+ }
355
+ }
325
356
  const result = OpacaConfigSchema.safeParse(builtConfig);
326
357
  if (!result.success) {
327
358
  throw new Error(`Invalid OpacaCMS Configuration: ${result.error.message}`);
@@ -0,0 +1,44 @@
1
+ // src/admin/stores/ui.ts
2
+ import { persistentAtom } from "@nanostores/persistent";
3
+ import { atom } from "nanostores";
4
+ var $toasts = atom([]);
5
+ var $isSidebarCollapsed = persistentAtom("opaca-sidebar-collapsed", false, {
6
+ encode: JSON.stringify,
7
+ decode: JSON.parse
8
+ });
9
+ function toggleSidebar() {
10
+ $isSidebarCollapsed.set(!$isSidebarCollapsed.get());
11
+ }
12
+ function notify(message, type = "success") {
13
+ const id = Math.random().toString(36).substring(2, 9);
14
+ $toasts.set([...$toasts.get(), { id, message, type }]);
15
+ }
16
+ function clearToast(id) {
17
+ $toasts.set($toasts.get().filter((t) => t.id !== id));
18
+ }
19
+ var $customSidebarItems = atom([]);
20
+ function registerSidebarItem(item) {
21
+ const current = $customSidebarItems.get();
22
+ if (!current.find((i) => i.path === item.path)) {
23
+ if (item.render && !item.component) {
24
+ item.component = `opaca-plugin-render-${Math.random().toString(36).substring(2, 9)}`;
25
+ if (typeof customElements !== "undefined" && !customElements.get(item.component)) {
26
+ const componentName = item.component;
27
+ const renderFn = item.render;
28
+
29
+ class OpacaGenericPlugin extends HTMLElement {
30
+ connectedCallback() {
31
+ const serverUrl = this.getAttribute("server-url") || "";
32
+ const config = JSON.parse(this.getAttribute("config") || "{}");
33
+ const user = JSON.parse(this.getAttribute("user") || "{}");
34
+ this.innerHTML = renderFn(serverUrl, config, user);
35
+ }
36
+ }
37
+ customElements.define(componentName, OpacaGenericPlugin);
38
+ }
39
+ }
40
+ $customSidebarItems.set([...current, item]);
41
+ }
42
+ }
43
+
44
+ export { $toasts, $isSidebarCollapsed, toggleSidebar, notify, clearToast, $customSidebarItems, registerSidebarItem };
@@ -1,12 +1,15 @@
1
1
  import {
2
2
  getSystemCollections,
3
3
  init_system_schema
4
- } from "./chunk-ybbbqj63.js";
4
+ } from "./chunk-v9z61v3g.js";
5
+ import {
6
+ OpacaLogger
7
+ } from "./chunk-t0zg026p.js";
5
8
 
6
9
  // src/auth/index.ts
7
10
  import { apiKey } from "@better-auth/api-key";
8
11
  import { betterAuth } from "better-auth";
9
- import { admin } from "better-auth/plugins";
12
+ import { admin, openAPI } from "better-auth/plugins";
10
13
  import { CamelCasePlugin } from "kysely";
11
14
 
12
15
  // src/auth/premissions.ts
@@ -81,6 +84,10 @@ async function createAuth(config) {
81
84
  ac,
82
85
  roles
83
86
  }),
87
+ openAPI({
88
+ disableDefaultReference: true,
89
+ path: "/open-api"
90
+ }),
84
91
  ...userAuth.features?.apiKeys?.enabled ? [
85
92
  apiKey({
86
93
  defaultPrefix: "opaca_",
@@ -158,19 +165,50 @@ async function createAuth(config) {
158
165
  }
159
166
  }
160
167
  },
161
- plugins
168
+ plugins,
169
+ logger: {
170
+ disabled: config.logger?.disabled,
171
+ disableColors: config.logger?.disableColors,
172
+ level: config.logger?.level,
173
+ log: (level, message, ...args) => {
174
+ const rebrand = (msg) => {
175
+ if (typeof msg !== "string")
176
+ return String(msg);
177
+ return msg.replace(/\[better-auth\]\s*/g, "").replace(/BETTER_AUTH_SECRET/g, "OPACA_SECRET").replace(/`npx auth secret`/g, "`openssl rand -base64 32`").replace(/npx auth secret/g, "openssl rand -base64 32").replace(/^Warning:\s*/i, "");
178
+ };
179
+ const branded = rebrand(message);
180
+ const brandedArgs = args.map((a) => typeof a === "string" ? rebrand(a) : a);
181
+ const authLogger = new OpacaLogger(config.logger);
182
+ switch (level) {
183
+ case "debug":
184
+ authLogger.debug(branded, ...brandedArgs);
185
+ break;
186
+ case "info":
187
+ authLogger.info(branded, ...brandedArgs);
188
+ break;
189
+ case "warn":
190
+ authLogger.warn(branded, ...brandedArgs);
191
+ break;
192
+ case "error":
193
+ authLogger.error(branded, ...brandedArgs);
194
+ break;
195
+ default:
196
+ authLogger.info(branded, ...brandedArgs);
197
+ }
198
+ }
199
+ }
162
200
  });
163
201
  }
164
202
 
165
203
  // src/config-utils.ts
166
204
  init_system_schema();
167
- function sanitizeConfig(config) {
205
+ function sanitizeConfig(config, settings = {}) {
168
206
  const collections = [...config.collections];
169
- const supportsAuth = config.db.name === "sqlite" || config.db.name === "postgres" || config.db.name === "d1";
207
+ const supportsAuth = ["sqlite", "postgres", "d1", "bun-sqlite", "better-sqlite3"].includes(config.db.name);
170
208
  const systemCollections = getSystemCollections();
171
209
  for (const systemCol of systemCollections) {
172
210
  const isAsset = systemCol.slug === "_opaca_assets";
173
- const isAuth = ["_users", "_sessions", "_accounts", "_verifications", "_api_keys"].includes(systemCol.slug);
211
+ const isAuth = ["_users", "_sessions", "_accounts", "_verifications", "_api_keys", "_opaca_plugin_settings"].includes(systemCol.slug);
174
212
  if (isAsset && config.storages || isAuth && supportsAuth) {
175
213
  if (!collections.find((col) => col.slug === systemCol.slug)) {
176
214
  collections.push({
@@ -235,7 +273,20 @@ function sanitizeConfig(config) {
235
273
  acc[key] = {};
236
274
  return acc;
237
275
  }, {}) : {},
238
- i18n: config.i18n
276
+ i18n: config.i18n,
277
+ plugins: config.plugins?.map((p) => ({
278
+ name: p.name,
279
+ label: p.label,
280
+ description: p.description,
281
+ version: p.version,
282
+ author: p.author,
283
+ homepage: p.homepage,
284
+ icon: p.icon,
285
+ adminAssets: p.adminAssets ? p.adminAssets() : undefined,
286
+ adminUI: p.adminUI,
287
+ settings: settings[p.name] || {},
288
+ configSchema: p.configSchema ? p.configSchema.map(sanitizeField) : undefined
289
+ }))
239
290
  };
240
291
  }
241
292
 
@@ -0,0 +1,36 @@
1
+ import {
2
+ defineCustomField
3
+ } from "./chunk-rqyjjqgy.js";
4
+
5
+ // src/admin/react.tsx
6
+ import React from "react";
7
+ import { createRoot } from "react-dom/client";
8
+ function defineReactField(tagName, Component) {
9
+ defineCustomField(tagName, {
10
+ mount: (container, props) => {
11
+ const el = container;
12
+ const root = createRoot(el);
13
+ el._opacaReactRoot = root;
14
+ root.render(React.createElement(Component, props));
15
+ },
16
+ update: (container, props) => {
17
+ const el = container;
18
+ const root = el._opacaReactRoot;
19
+ if (root) {
20
+ root.render(React.createElement(Component, props));
21
+ }
22
+ },
23
+ unmount: (container) => {
24
+ const el = container;
25
+ const root = el._opacaReactRoot;
26
+ if (root) {
27
+ setTimeout(() => {
28
+ root.unmount();
29
+ delete el._opacaReactRoot;
30
+ }, 0);
31
+ }
32
+ }
33
+ });
34
+ }
35
+
36
+ export { defineReactField };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  notify
3
- } from "./chunk-j4d50hrx.js";
3
+ } from "./chunk-8scgdznr.js";
4
4
 
5
5
  // src/admin/stores/auth.ts
6
6
  import { atom, computed } from "nanostores";