opacacms 0.1.21 → 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 (137) hide show
  1. package/README.md +792 -50
  2. package/dist/admin/auth-client.d.ts +39 -39
  3. package/dist/admin/index.js +2360 -1392
  4. package/dist/admin/react.d.ts +1 -1
  5. package/dist/admin/react.js +8 -0
  6. package/dist/admin/router.d.ts +1 -0
  7. package/dist/admin/stores/ui.d.ts +10 -0
  8. package/dist/admin/ui/admin-layout.d.ts +4 -4
  9. package/dist/admin/ui/components/DataDetailView.d.ts +1 -1
  10. package/dist/admin/ui/components/DetailSheet.d.ts +19 -0
  11. package/dist/admin/ui/components/PluginSettingsForm.d.ts +11 -0
  12. package/dist/admin/ui/components/fields/BooleanField.d.ts +2 -1
  13. package/dist/admin/ui/components/fields/DateField.d.ts +1 -1
  14. package/dist/admin/ui/components/fields/FieldLabel.d.ts +11 -0
  15. package/dist/admin/ui/components/fields/FileField.d.ts +1 -1
  16. package/dist/admin/ui/components/fields/NumberField.d.ts +1 -1
  17. package/dist/admin/ui/components/fields/RadioField.d.ts +1 -1
  18. package/dist/admin/ui/components/fields/RelationshipField.d.ts +3 -1
  19. package/dist/admin/ui/components/fields/SelectField.d.ts +1 -1
  20. package/dist/admin/ui/components/fields/TextAreaField.d.ts +1 -1
  21. package/dist/admin/ui/components/fields/TextField.d.ts +1 -1
  22. package/dist/admin/ui/components/fields/VirtualField.d.ts +1 -0
  23. package/dist/admin/ui/components/fields/index.d.ts +16 -16
  24. package/dist/admin/ui/components/fields/richtext-editor/index.d.ts +1 -1
  25. package/dist/admin/ui/components/media/AssetManagerModal.d.ts +1 -1
  26. package/dist/admin/ui/components/toast.d.ts +1 -1
  27. package/dist/admin/ui/components/ui/accordion.d.ts +1 -1
  28. package/dist/admin/ui/components/ui/button.d.ts +1 -1
  29. package/dist/admin/ui/components/ui/collapsible.d.ts +1 -1
  30. package/dist/admin/ui/components/ui/dialog.d.ts +1 -1
  31. package/dist/admin/ui/components/ui/group.d.ts +1 -1
  32. package/dist/admin/ui/components/ui/index.d.ts +17 -17
  33. package/dist/admin/ui/components/ui/input.d.ts +1 -1
  34. package/dist/admin/ui/components/ui/label.d.ts +1 -1
  35. package/dist/admin/ui/components/ui/radio-group.d.ts +1 -1
  36. package/dist/admin/ui/components/ui/relationship.d.ts +4 -4
  37. package/dist/admin/ui/components/ui/select.d.ts +1 -1
  38. package/dist/admin/ui/components/ui/sheet.d.ts +1 -1
  39. package/dist/admin/ui/components/ui/tabs.d.ts +1 -1
  40. package/dist/admin/ui/components/versions-sheet.d.ts +11 -0
  41. package/dist/admin/ui/views/media-registry-view.d.ts +1 -1
  42. package/dist/admin/ui/views/settings-view.d.ts +2 -2
  43. package/dist/admin/vue.js +8 -0
  44. package/dist/admin/webcomponent.js +2 -2
  45. package/dist/admin.css +1 -1
  46. package/dist/auth/index.d.ts +101 -41
  47. package/dist/{chunk-0sdceeys.js → chunk-0bq155dy.js} +86 -6
  48. package/dist/{chunk-59sg3pw9.js → chunk-0gtxnxmd.js} +90 -7
  49. package/dist/{chunk-v521d72w.js → chunk-3rdhbedb.js} +1 -1
  50. package/dist/chunk-51z3x7kq.js +20 -0
  51. package/dist/{chunk-7fyepksb.js → chunk-526a3gqx.js} +1 -1
  52. package/dist/{chunk-wmvjvn7b.js → chunk-6qq3ne6b.js} +39 -1
  53. package/dist/{chunk-0am1m47g.js → chunk-6v1fw7q7.js} +5 -5
  54. package/dist/{chunk-t9v845m2.js → chunk-7y1nbmw6.js} +34 -3
  55. package/dist/chunk-8scgdznr.js +44 -0
  56. package/dist/{chunk-mycmsjd9.js → chunk-b3kr8w41.js} +57 -6
  57. package/dist/chunk-bexcv7xe.js +36 -0
  58. package/dist/{chunk-16vgcf3k.js → chunk-byq8g0rd.js} +1 -1
  59. package/dist/{chunk-fqastxq9.js → chunk-d1asgtke.js} +86 -6
  60. package/dist/{chunk-cpw2y3pn.js → chunk-dykn5hr6.js} +7 -7
  61. package/dist/{chunk-61kwqve4.js → chunk-esrg9qj0.js} +90 -9
  62. package/dist/chunk-fj19qccp.js +78 -0
  63. package/dist/{chunk-ekxkvqjm.js → chunk-gmee4mdc.js} +90 -9
  64. package/dist/{chunk-xa7rjsn2.js → chunk-j53pz21t.js} +2 -2
  65. package/dist/{chunk-xrfhhz85.js → chunk-kc4jfnv7.js} +480 -85
  66. package/dist/chunk-mkn49zmy.js +102 -0
  67. package/dist/{chunk-n1xraw7j.js → chunk-qb6ztvw9.js} +1 -1
  68. package/dist/{chunk-2kyhqvhc.js → chunk-qxt9vge8.js} +1 -1
  69. package/dist/chunk-r39em4yj.js +29 -0
  70. package/dist/chunk-rqyjjqgy.js +91 -0
  71. package/dist/chunk-rsf0tpy1.js +8 -0
  72. package/dist/chunk-swtcpvhf.js +2442 -0
  73. package/dist/chunk-t0zg026p.js +71 -0
  74. package/dist/chunk-twpvxfce.js +64 -0
  75. package/dist/{chunk-ybbbqj63.js → chunk-v9z61v3g.js} +15 -0
  76. package/dist/{chunk-jwjk85ze.js → chunk-ywm4t2gm.js} +6 -2
  77. package/dist/cli/commands/plugin-build.d.ts +1 -0
  78. package/dist/cli/commands/plugin-init.d.ts +1 -0
  79. package/dist/cli/commands/plugin-sync.d.ts +1 -0
  80. package/dist/cli/index.js +24 -6
  81. package/dist/config-utils.d.ts +1 -1
  82. package/dist/config.d.ts +21 -4
  83. package/dist/db/better-sqlite.d.ts +1 -1
  84. package/dist/db/better-sqlite.js +5 -5
  85. package/dist/db/bun-sqlite.d.ts +1 -1
  86. package/dist/db/bun-sqlite.js +5 -5
  87. package/dist/db/d1.d.ts +1 -1
  88. package/dist/db/d1.js +5 -5
  89. package/dist/db/index.js +9 -9
  90. package/dist/db/postgres.d.ts +1 -1
  91. package/dist/db/postgres.js +5 -5
  92. package/dist/db/sqlite.d.ts +1 -1
  93. package/dist/db/sqlite.js +5 -5
  94. package/dist/index.js +4 -3
  95. package/dist/plugins/index.d.ts +1 -0
  96. package/dist/plugins/ui-bridge.d.ts +12 -0
  97. package/dist/plugins/utils.d.ts +5 -0
  98. package/dist/runtimes/bun.js +13 -7
  99. package/dist/runtimes/cloudflare-workers.js +5 -5
  100. package/dist/runtimes/next.js +5 -5
  101. package/dist/runtimes/node.js +13 -7
  102. package/dist/schema/collection.d.ts +9 -26
  103. package/dist/schema/fields/base.d.ts +3 -2
  104. package/dist/schema/fields/index.d.ts +12 -0
  105. package/dist/schema/fields/validation.test.d.ts +1 -0
  106. package/dist/schema/global.d.ts +10 -7
  107. package/dist/schema/index.js +22 -6
  108. package/dist/server/admin-router.d.ts +2 -2
  109. package/dist/server/admin.d.ts +2 -1
  110. package/dist/server/collection-router.d.ts +1 -1
  111. package/dist/server/handlers.d.ts +10 -0
  112. package/dist/server/middlewares/admin.d.ts +2 -2
  113. package/dist/server/middlewares/auth.d.ts +1 -1
  114. package/dist/server/middlewares/context.d.ts +2 -0
  115. package/dist/server/middlewares/rate-limit.d.ts +1 -1
  116. package/dist/server/openapi.d.ts +2 -0
  117. package/dist/server/plugins-loader.d.ts +6 -0
  118. package/dist/server/router.d.ts +3 -3
  119. package/dist/server/routers/admin.d.ts +2 -2
  120. package/dist/server/routers/auth.d.ts +1 -1
  121. package/dist/server/routers/collections.d.ts +1 -1
  122. package/dist/server/routers/plugins.d.ts +18 -0
  123. package/dist/server/setup-middlewares.d.ts +2 -2
  124. package/dist/server/system-router.d.ts +1 -1
  125. package/dist/server.js +11 -7
  126. package/dist/storage/adapters/local.d.ts +1 -1
  127. package/dist/storage/adapters/s3.d.ts +1 -1
  128. package/dist/types.d.ts +222 -15
  129. package/dist/utils/logger.d.ts +13 -35
  130. package/dist/validation.d.ts +40 -0
  131. package/dist/validator.d.ts +1 -1
  132. package/package.json +21 -7
  133. package/dist/admin/ui/components/DataDetailSheet.d.ts +0 -13
  134. package/dist/admin/ui/components/ui/relationship-detail-sheet.d.ts +0 -9
  135. package/dist/chunk-62ev8gnc.js +0 -41
  136. package/dist/chunk-j4d50hrx.js +0 -20
  137. package/dist/chunk-nb7ctdg8.js +0 -311
@@ -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({
@@ -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
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";
@@ -3,18 +3,18 @@ import {
3
3
  flattenPayload,
4
4
  pushSchema,
5
5
  unflattenRow
6
- } from "./chunk-cpw2y3pn.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";
@@ -264,7 +264,87 @@ class D1Adapter extends BaseDatabaseAdapter {
264
264
  qb = qb.orderBy("created_at", "desc");
265
265
  }
266
266
  const rows = await qb.execute();
267
- 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
+ }
268
348
  const totalPages = Math.ceil(total / limit);
269
349
  return {
270
350
  docs: docs.filter(Boolean),
@@ -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;
@@ -3,16 +3,17 @@ import {
3
3
  flattenPayload,
4
4
  pushSchema,
5
5
  unflattenRow
6
- } from "./chunk-cpw2y3pn.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";
@@ -184,8 +185,8 @@ class PostgresAdapter extends BaseDatabaseAdapter {
184
185
  const unflattened = unflattenRow(row);
185
186
  const colDef = this._collections.find((c) => c.slug === collection);
186
187
  if (colDef) {
187
- const { getRelationalFields, toSnakeCase: toSnakeCase2 } = await import("./chunk-7fyepksb.js");
188
- const relationalFields = getRelationalFields(colDef.fields);
188
+ const { getRelationalFields: getRelationalFields2, toSnakeCase: toSnakeCase2 } = await import("./chunk-526a3gqx.js");
189
+ const relationalFields = getRelationalFields2(colDef.fields);
189
190
  for (const field of relationalFields) {
190
191
  if (!field.name)
191
192
  continue;
@@ -255,7 +256,87 @@ class PostgresAdapter extends BaseDatabaseAdapter {
255
256
  qb = qb.orderBy("created_at", "desc");
256
257
  }
257
258
  const rows = await qb.execute();
258
- 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
+ }
259
340
  const totalPages = Math.ceil(total / limit);
260
341
  return {
261
342
  docs: docs.filter(Boolean),
@@ -487,7 +568,7 @@ class PostgresAdapter extends BaseDatabaseAdapter {
487
568
  }
488
569
  }
489
570
  async migrate(collections, globals = []) {
490
- const { getSystemCollections } = await import("./chunk-v521d72w.js");
571
+ const { getSystemCollections } = await import("./chunk-3rdhbedb.js");
491
572
  this._collections = [...getSystemCollections(), ...collections];
492
573
  this._globals = globals;
493
574
  if (this.push && this._db) {