@protobase/cli 0.9.0 → 0.11.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.
@@ -0,0 +1,48 @@
1
+ import { ts } from "ts-morph";
2
+ //#region src/build/component-names.ts
3
+ const layoutModule = "@protobase/layout";
4
+ const componentImports = (source) => {
5
+ const named = /* @__PURE__ */ new Set();
6
+ const namespaces = /* @__PURE__ */ new Set();
7
+ for (const statement of source.statements) {
8
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== layoutModule) continue;
9
+ const bindings = statement.importClause?.namedBindings;
10
+ if (bindings && ts.isNamespaceImport(bindings)) namespaces.add(bindings.name.text);
11
+ if (bindings && ts.isNamedImports(bindings)) {
12
+ for (const element of bindings.elements) if ((element.propertyName ?? element.name).text === "component") named.add(element.name.text);
13
+ }
14
+ }
15
+ return {
16
+ named,
17
+ namespaces
18
+ };
19
+ };
20
+ /**
21
+ * The names a module declares with `component('Name')` from @protobase/layout, read from its source: the config does
22
+ * not run at build time. A name that is not a string literal is not seen.
23
+ */
24
+ const declaredComponents = (code, file) => {
25
+ if (!code.includes(layoutModule)) return [];
26
+ const source = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true, /\.[jt]sx$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
27
+ const { named, namespaces } = componentImports(source);
28
+ if (named.size === 0 && namespaces.size === 0) return [];
29
+ const isComponent = (callee) => ts.isIdentifier(callee) && named.has(callee.text) || ts.isPropertyAccessExpression(callee) && callee.name.text === "component" && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text);
30
+ const names = [];
31
+ const visit = (node) => {
32
+ if (ts.isCallExpression(node) && isComponent(node.expression) && node.arguments[0] && ts.isStringLiteralLike(node.arguments[0])) names.push(node.arguments[0].text);
33
+ ts.forEachChild(node, visit);
34
+ };
35
+ visit(source);
36
+ return names;
37
+ };
38
+ /** Adds the components every bundled module declares to `into`, for the check that the project's UI exports each. */
39
+ const collectComponents = (into) => ({
40
+ name: "protobase-component-names",
41
+ enforce: "pre",
42
+ transform(code, id) {
43
+ if (!/\.[cm]?[jt]sx?$/.test(id.replace(/\?.*$/, ""))) return void 0;
44
+ for (const name of declaredComponents(code, id)) into.add(name);
45
+ }
46
+ });
47
+ //#endregion
48
+ export { collectComponents, declaredComponents };
@@ -1,5 +1,6 @@
1
1
  import { hostModuleIds } from "../serve/host-module-ids.js";
2
2
  import { extensionImports } from "../project/extensions.js";
3
+ import { collectComponents } from "./component-names.js";
3
4
  import { bundleEntryCode } from "./entry-code.js";
4
5
  import { layoutCheck } from "./layout-check.js";
5
6
  import { findPackageDir, packageName } from "./packages/package-dir.js";
@@ -52,11 +53,12 @@ const servedImports = (native) => {
52
53
  }
53
54
  };
54
55
  };
55
- const buildConfigBundle = async ({ projectDir, outFile, bun, extensions = [] }) => {
56
+ const buildConfigBundle = async ({ projectDir, outFile, bun, extensions = [], components = /* @__PURE__ */ new Set() }) => {
56
57
  const file = path.join(projectDir, "protobase-bundle-entry.js");
57
58
  const native = /* @__PURE__ */ new Map();
58
59
  const plugins = [
59
60
  layoutCheck(),
61
+ collectComponents(components),
60
62
  extensionImports(projectDir, extensions),
61
63
  servedImports(native),
62
64
  entry(file, bundleEntryCode(projectDir, extensions))
@@ -3,7 +3,7 @@ import { buildConfigBundle } from "./config-bundle.js";
3
3
  import { protobaseVersion } from "../version/version.js";
4
4
  import { copyPackages } from "./packages/copy-packages.js";
5
5
  import { buildUiBundle } from "./ui-bundle.js";
6
- import { checkUiMerge } from "./ui-check.js";
6
+ import { checkUi } from "./ui-check.js";
7
7
  import { existsSync } from "node:fs";
8
8
  import path from "node:path";
9
9
  import { readFile, rm, writeFile } from "node:fs/promises";
@@ -20,13 +20,15 @@ const removeOwnNodeModules = async (outDir) => {
20
20
  };
21
21
  const buildDeployBundle = async ({ projectDir, outDir, bun, extensions = [] }) => {
22
22
  await removeOwnNodeModules(outDir);
23
- if (extensions.some((extension) => extension.ui)) await checkUiMerge(projectDir, extensions);
23
+ const components = /* @__PURE__ */ new Set();
24
24
  const native = await buildConfigBundle({
25
25
  projectDir,
26
26
  outFile: path.join(outDir, bundleLayout.server),
27
27
  bun,
28
- extensions
28
+ extensions,
29
+ components
29
30
  });
31
+ await checkUi(projectDir, extensions, components);
30
32
  const packages = native.length > 0 ? await copyPackages(native, path.join(outDir, bundleNodeModules)) : [];
31
33
  await buildUiBundle({
32
34
  outDir: path.join(outDir, bundleLayout.public),
@@ -1,24 +1,42 @@
1
1
  import { extensionImports } from "../project/extensions.js";
2
2
  import { viteBundle } from "./vite-bundle.js";
3
- import { mergedUiFile, projectUiPlugin } from "../project/project-ui.js";
3
+ import { projectUiFile, projectUiId, projectUiPlugin } from "../project/project-ui.js";
4
+ import { existsSync } from "node:fs";
4
5
  import path from "node:path";
5
6
  import { mkdtemp, rm } from "node:fs/promises";
6
7
  import { pathToFileURL } from "node:url";
7
8
  import { tmpdir } from "node:os";
8
9
  //#region src/build/ui-check.ts
9
- const checkUiMerge = async (projectDir, extensions) => {
10
+ const checkFile = (projectDir) => path.join(projectDir, "protobase-ui-check.js");
11
+ const checkCode = [
12
+ `import * as ui from '${projectUiId}'`,
13
+ `import { mergeUi, uiFromModule } from '@protobase/ui'`,
14
+ "export default Object.keys(mergeUi(uiFromModule(ui)).components ?? {})",
15
+ ""
16
+ ].join("\n");
17
+ const checkEntry = (file) => ({
18
+ name: "protobase-ui-check",
19
+ resolveId: (id) => id === file ? `\0${file}` : void 0,
20
+ load: (id) => id === `\0${file}` ? checkCode : void 0
21
+ });
22
+ const registeredComponents = async (projectDir, extensions) => {
23
+ if (!existsSync(projectUiFile(projectDir)) && !extensions.some((extension) => extension.ui)) return [];
10
24
  const scratch = await mkdtemp(path.join(tmpdir(), "protobase-ui-check-"));
11
- const outFile = path.join(scratch, "ui-check.js");
25
+ const outFile = path.join(scratch, "ui-check.mjs");
12
26
  try {
13
- const plugins = [projectUiPlugin(projectDir, extensions), extensionImports(projectDir, extensions)];
27
+ const plugins = [
28
+ checkEntry(checkFile(projectDir)),
29
+ projectUiPlugin(projectDir, extensions),
30
+ extensionImports(projectDir, extensions)
31
+ ];
14
32
  await viteBundle({
15
33
  root: projectDir,
16
- entry: mergedUiFile(projectDir),
34
+ entry: checkFile(projectDir),
17
35
  outFile,
18
36
  plugins,
19
37
  logLevel: "error"
20
38
  });
21
- await import(pathToFileURL(outFile).href);
39
+ return (await import(pathToFileURL(outFile).href)).default;
22
40
  } finally {
23
41
  await rm(scratch, {
24
42
  recursive: true,
@@ -26,5 +44,17 @@ const checkUiMerge = async (projectDir, extensions) => {
26
44
  });
27
45
  }
28
46
  };
47
+ /**
48
+ * Fails the build, instead of the app in the browser, when two UI configs define the same component or action
49
+ * handler, or when the config declares a component with `component('Name')` that protobase.ui.tsx does not export.
50
+ * It loads only browser code, which needs no secrets; nothing is rendered.
51
+ */
52
+ const checkUi = async (projectDir, extensions, declared) => {
53
+ if (declared.size === 0 && !extensions.some((extension) => extension.ui)) return;
54
+ const registered = new Set(await registeredComponents(projectDir, extensions));
55
+ const missing = [...declared].filter((name) => !registered.has(name)).sort();
56
+ if (missing.length === 0) return;
57
+ throw new Error(`The config declares ${missing.map((name) => `component('${name}')`).join(", ")}, but protobase.ui.tsx exports no component by ${missing.length === 1 ? "that name" : "those names"}. Export each from its own file and re-export it, such as: export { ${missing[0]} } from './ui/${missing[0]}'`);
58
+ };
29
59
  //#endregion
30
- export { checkUiMerge };
60
+ export { checkUi };
package/dist/dev/run.js CHANGED
@@ -79,7 +79,7 @@ const runDev = async (options, out) => {
79
79
  tailwindcss(),
80
80
  devServer({
81
81
  entry,
82
- exclude: [/^(?!\/api(\/|\?|$)).*/],
82
+ exclude: [/^(?!\/api(\/|\?|$)|\/\.well-known\/webauthn(\?|$)).*/],
83
83
  injectClientScript: false
84
84
  })
85
85
  ]
@@ -3,6 +3,11 @@ const words = (name) => name.split(/[^A-Za-z0-9]+/).filter(Boolean);
3
3
  const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
4
4
  const lowerFirst = (word) => word.charAt(0).toLowerCase() + word.slice(1);
5
5
  const identifier = (name) => /^[0-9]/.test(name) ? `c${name}` : name;
6
+ const snakeName = (name) => {
7
+ const parts = words(name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2"));
8
+ if (parts.length === 0) throw new Error(`Cannot derive a field name from "${name}"`);
9
+ return identifier(parts.map((part) => part.toLowerCase()).join("_"));
10
+ };
6
11
  const camelCase = (name) => {
7
12
  const parts = words(name);
8
13
  if (parts.length === 0) throw new Error(`Cannot derive a field name from "${name}"`);
@@ -25,4 +30,4 @@ const humanizeSingular = (name) => {
25
30
  return [...parts.slice(0, -1), singular(parts.at(-1).toLowerCase())].join(" ").replace(/^./, (c) => c.toUpperCase());
26
31
  };
27
32
  //#endregion
28
- export { camelCase, humanize, humanizeSingular, singular };
33
+ export { camelCase, humanize, humanizeSingular, singular, snakeName };
@@ -3,7 +3,7 @@ import { checkEnumValues } from "./check-values.js";
3
3
  import { columnDefault } from "./defaults.js";
4
4
  import { inferKind } from "./type-map.js";
5
5
  import { defaultColumn } from "./default-column.js";
6
- import { camelCase } from "./names.js";
6
+ import { snakeName } from "./names.js";
7
7
  import { isTenantColumn } from "./tenant.js";
8
8
  //#region src/infer/proposal.ts
9
9
  const columnIndexing = (table, column) => {
@@ -41,7 +41,7 @@ const defaultsOf = (table, column, kind) => {
41
41
  const proposeField = (table, column, context) => {
42
42
  const kind = inferKind(column, checkEnumValues(table.checks, column.name));
43
43
  const relation = relationOf(table, column.name, context);
44
- const name = camelCase(column.name);
44
+ const name = snakeName(column.name);
45
45
  const defaults = defaultsOf(table, column, kind);
46
46
  return {
47
47
  name,
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.9.0";
2
+ var version = "0.11.0";
3
3
  //#endregion
4
4
  export { version };
@@ -5,13 +5,14 @@ import { configExports, createAdmin, jobDefinitions } from "@protobase/server";
5
5
  * pages and subscriptions, and the rest of its config. `jobs` are the project's own and the delivery of its events.
6
6
  */
7
7
  const projectAdmin = (project, config, db) => {
8
- const { resources, views, pages, userMenu, subscriptions } = configExports(config);
8
+ const { resources, views, pages, userMenu, bottomBars, subscriptions } = configExports(config);
9
9
  const own = jobDefinitions(project.jobs ?? {});
10
10
  const admin = createAdmin({
11
11
  resources,
12
12
  views,
13
13
  pages,
14
14
  ...userMenu && { userMenu },
15
+ bottomBars,
15
16
  db,
16
17
  authenticate: project.authenticate,
17
18
  ...project.auth && { auth: project.auth },
@@ -10,11 +10,11 @@ const mergedUiCode = (projectDir, extensions) => {
10
10
  const own = projectUiFile(projectDir);
11
11
  const hasOwn = existsSync(own);
12
12
  return [
13
- `import { mergeUi } from '@protobase/ui'`,
13
+ `import { mergeUi, uiFromModule } from '@protobase/ui'`,
14
14
  ...files.map((file, i) => `import * as extension${i} from ${JSON.stringify(file)}`),
15
15
  ...hasOwn ? [`import * as project from ${JSON.stringify(own)}`] : [],
16
16
  "const named = (ui, file) => ({ ...ui, name: ui.name ?? file })",
17
- `export default mergeUi(${[...files.map((file, i) => `named(extension${i}.default ?? {}, ${JSON.stringify(file)})`), ...hasOwn ? [`named(project.default ?? {}, "protobase.ui.tsx")`] : []].join(", ")})`,
17
+ `export default mergeUi(${[...files.map((file, i) => `named(uiFromModule(extension${i}), ${JSON.stringify(file)})`), ...hasOwn ? [`named(uiFromModule(project), "protobase.ui.tsx")`] : []].join(", ")})`,
18
18
  ""
19
19
  ].join("\n");
20
20
  };
@@ -1,4 +1,5 @@
1
1
  import { tableKey } from "../db/model.js";
2
+ import { camelCase } from "../infer/names.js";
2
3
  import { parseSource } from "./project.js";
3
4
  import { fieldsObject, resourceChain } from "./existing-config.js";
4
5
  import { renderField } from "../infer/render-field.js";
@@ -19,7 +20,7 @@ const renderDataFile = (plan) => {
19
20
  return [
20
21
  `import { f, resource } from '@protobase/schema'`,
21
22
  "",
22
- `${docComment(plan)}export const ${plan.name} = resource('${plan.name}')`,
23
+ `${docComment(plan)}export const ${camelCase(plan.name)} = resource('${plan.name}')`,
23
24
  ` .table('${tableKey(plan.table)}')`,
24
25
  ` .fields({`,
25
26
  properties,
@@ -1,5 +1,5 @@
1
1
  import { defaultColumn } from "../infer/default-column.js";
2
- import { camelCase } from "../infer/names.js";
2
+ import { camelCase, snakeName } from "../infer/names.js";
3
3
  import { parseSource } from "./project.js";
4
4
  import path from "node:path";
5
5
  import { readFile, readdir } from "node:fs/promises";
@@ -65,9 +65,11 @@ const parseResource = (text, dir) => {
65
65
  const tableCall = methodCalls(chainTop, "table")[0];
66
66
  const table = tableCall && stringArg(tableCall);
67
67
  const fields = fieldsObject(chainTop);
68
- if (!name || !table || !fields) return void 0;
68
+ const identifier = root.getFirstAncestorByKind(SyntaxKind.VariableDeclaration)?.getName();
69
+ if (!name || !table || !fields || !identifier) return void 0;
69
70
  return {
70
71
  name,
72
+ identifier,
71
73
  table,
72
74
  dir,
73
75
  text,
@@ -77,7 +79,7 @@ const parseResource = (text, dir) => {
77
79
  })
78
80
  };
79
81
  };
80
- const findField = (resource, column) => resource.fields.find((field) => field.ignored ? field.column.toLowerCase() === column.toLowerCase() || field.name === camelCase(column) : field.column === column);
82
+ const findField = (resource, column) => resource.fields.find((field) => field.ignored ? field.column.toLowerCase() === column.toLowerCase() || field.name === snakeName(column) || field.name === camelCase(column) : field.column === column);
81
83
  const readExistingResources = async (configDir) => {
82
84
  const entries = await readdir(configDir, { withFileTypes: true }).catch((error) => {
83
85
  if (error.code === "ENOENT") return [];
@@ -1,5 +1,6 @@
1
1
  import { tableKey } from "../db/model.js";
2
2
  import { keyColumns } from "../db/keys.js";
3
+ import { camelCase } from "../infer/names.js";
3
4
  import { findField } from "./existing-config.js";
4
5
  import { proposeField } from "../infer/proposal.js";
5
6
  import { addFieldsToDataFile, renderDataFile } from "./data-file.js";
@@ -49,19 +50,20 @@ const planScaffold = async (input) => {
49
50
  const drift = [];
50
51
  const warnings = [];
51
52
  const registrations = [];
52
- const writeUi = async (dir, resource, table, current, decisions) => {
53
+ const writeUi = async (dir, resource, identifier, table, current, decisions) => {
53
54
  if (ui.length === 0) return;
54
55
  const file = path.join(configDir, dir, "ui.ts");
55
56
  const before = await input.readText(file);
56
57
  const target = {
57
58
  resource,
59
+ identifier,
58
60
  tableName: table.name,
59
61
  fields: uiFields(table, current, decisions)
60
62
  };
61
63
  changes.push(...change(file, before, before === void 0 ? renderUiFile(target, ui) : addUiSections(before, target, ui)));
62
64
  if (before === void 0) registrations.push({
63
65
  from: `./${dir}/ui`,
64
- names: [viewName(resource)]
66
+ names: [viewName(identifier)]
65
67
  });
66
68
  };
67
69
  for (const table of targets) {
@@ -75,7 +77,7 @@ const planScaffold = async (input) => {
75
77
  const file = path.join(configDir, current.dir, "data.ts");
76
78
  changes.push(...change(file, current.text, addFieldsToDataFile(current.text, decisions)));
77
79
  drift.push(...detectDrift(current, table, context(table)));
78
- await writeUi(current.dir, current.name, table, current, decisions);
80
+ await writeUi(current.dir, current.name, current.identifier, table, current, decisions);
79
81
  continue;
80
82
  }
81
83
  const name = names.get(key);
@@ -93,9 +95,9 @@ const planScaffold = async (input) => {
93
95
  });
94
96
  registrations.push({
95
97
  from: `./${name}/data`,
96
- names: [name]
98
+ names: [camelCase(name)]
97
99
  });
98
- await writeUi(name, name, table, void 0, decisions);
100
+ await writeUi(name, name, camelCase(name), table, void 0, decisions);
99
101
  }
100
102
  const indexFile = path.join(configDir, "index.ts");
101
103
  const indexBefore = await input.readText(indexFile);
@@ -114,12 +116,12 @@ const registerExisting = async (input, planned) => {
114
116
  const data = `./${resource.dir}/data`;
115
117
  if (!known.has(data)) registrations.push({
116
118
  from: data,
117
- names: [resource.name]
119
+ names: [resource.identifier]
118
120
  });
119
121
  const ui = `./${resource.dir}/ui`;
120
122
  if (await input.readText(path.join(input.configDir, resource.dir, "ui.ts")) !== void 0 && !known.has(ui)) registrations.push({
121
123
  from: ui,
122
- names: [viewName(resource.name)]
124
+ names: [viewName(resource.identifier)]
123
125
  });
124
126
  }
125
127
  return registrations;
@@ -1,15 +1,15 @@
1
1
  import { tableKey } from "../db/model.js";
2
- import { camelCase } from "../infer/names.js";
2
+ import { snakeName } from "../infer/names.js";
3
3
  //#region src/scaffold/resource-names.ts
4
4
  const assignNames = (tables, existing) => {
5
5
  const names = new Map(existing.map((resource) => [resource.table, resource.name]));
6
6
  const taken = new Set(names.values());
7
7
  const fresh = tables.filter((table) => !names.has(tableKey(table)));
8
8
  const counts = /* @__PURE__ */ new Map();
9
- for (const table of tables) counts.set(camelCase(table.name), (counts.get(camelCase(table.name)) ?? 0) + 1);
9
+ for (const table of tables) counts.set(snakeName(table.name), (counts.get(snakeName(table.name)) ?? 0) + 1);
10
10
  for (const table of fresh) {
11
- const plain = camelCase(table.name);
12
- const name = counts.get(plain) === 1 && !taken.has(plain) ? plain : camelCase(`${table.schema}_${table.name}`);
11
+ const plain = snakeName(table.name);
12
+ const name = counts.get(plain) === 1 && !taken.has(plain) ? plain : snakeName(`${table.schema}_${table.name}`);
13
13
  names.set(tableKey(table), name);
14
14
  taken.add(name);
15
15
  }
@@ -3,7 +3,7 @@ import { parseSource } from "./project.js";
3
3
  import { methodCalls } from "./existing-config.js";
4
4
  import { Node, SyntaxKind } from "ts-morph";
5
5
  //#region src/scaffold/ui-file.ts
6
- const auditField = /^(created|updated)(At|On)$/;
6
+ const auditField = /^(created|updated)(At|On|_at|_on)$/;
7
7
  const longText = /^(notes?|description|body|comment|comments)$/;
8
8
  const listSize = 5;
9
9
  const listColumns = (fields) => {
@@ -13,7 +13,7 @@ const listColumns = (fields) => {
13
13
  const picked = [...scalars.slice(0, listSize), ...scalars.length < 3 ? relations : []].slice(0, listSize);
14
14
  return (picked.length > 0 ? picked : fields.filter((f) => f.role === "key")).map((f) => f.name);
15
15
  };
16
- const viewName = (resource) => `${resource}View`;
16
+ const viewName = (identifier) => `${identifier}View`;
17
17
  const sectionText = {
18
18
  names: (t) => `.names({ singular: '${humanizeSingular(t.tableName)}', plural: '${humanize(t.tableName)}' })`,
19
19
  list: (t) => `.list((r) => ({ columns: [${listColumns(t.fields).map((n) => `r.${n}`).join(", ")}] }))`
@@ -21,9 +21,9 @@ const sectionText = {
21
21
  const lines = (sections, target) => sections.map((s) => ` ${sectionText[s](target)}`);
22
22
  const renderUiFile = (target, sections) => [
23
23
  `import { view } from '@protobase/schema'`,
24
- `import type { ${target.resource} } from './data'`,
24
+ `import type { ${target.identifier} } from './data'`,
25
25
  "",
26
- `export const ${viewName(target.resource)} = view<typeof ${target.resource}>('${target.resource}')`,
26
+ `export const ${viewName(target.identifier)} = view<typeof ${target.identifier}>('${target.resource}')`,
27
27
  ...lines(sections, target),
28
28
  ""
29
29
  ].join("\n");
package/dist/serve/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isApiPath } from "../bundle/manifest.js";
2
2
  import { serveStaticSite } from "./static-site.js";
3
- import { jobsDrainApp } from "@protobase/server";
3
+ import { jobsDrainApp, webauthnPath } from "@protobase/server";
4
4
  import { Hono } from "hono";
5
5
  import { logger } from "hono/logger";
6
6
  //#region src/serve/app.ts
@@ -8,7 +8,7 @@ const createServeApp = ({ admin, requestLog, site, jobs, drainToken }) => {
8
8
  const app = new Hono();
9
9
  if (requestLog) app.use(logger());
10
10
  app.get("/health", (c) => c.json({ status: "ok" }));
11
- if (site) app.use("*", (c, next) => isApiPath(site.api, c.req.path) ? next() : serveStaticSite(site, c.req.raw));
11
+ if (site) app.use("*", (c, next) => isApiPath(site.api, c.req.path) || c.req.path === webauthnPath ? next() : serveStaticSite(site, c.req.raw));
12
12
  if (jobs && drainToken) app.route("/", jobsDrainApp({
13
13
  runtime: jobs,
14
14
  token: drainToken
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/cli",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "The protobase command: scaffold, doctor, dev, build and serve a Protobase project.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -34,11 +34,11 @@
34
34
  "dependencies": {
35
35
  "@hono/node-server": "^2.1.3",
36
36
  "@hono/vite-dev-server": "^0.26.1",
37
- "@protobase/layout": "0.9.0",
38
- "@protobase/query": "0.9.0",
39
- "@protobase/schema": "0.9.0",
40
- "@protobase/server": "0.9.0",
41
- "@protobase/ui": "0.9.0",
37
+ "@protobase/layout": "0.11.0",
38
+ "@protobase/query": "0.11.0",
39
+ "@protobase/schema": "0.11.0",
40
+ "@protobase/server": "0.11.0",
41
+ "@protobase/ui": "0.11.0",
42
42
  "@scalar/hono-api-reference": "^0.12.10",
43
43
  "@tailwindcss/vite": "^4.3.3",
44
44
  "@vitejs/plugin-react": "^6.1.2",