@protobase/server 0.23.4 → 0.23.6
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.
- package/dist/admin-options.d.ts +6 -0
- package/dist/changes/capture-sql.js +2 -1
- package/dist/computed/column-sql.js +10 -0
- package/dist/computed/computed-plan.js +81 -0
- package/dist/computed/computed-schema.d.ts +15 -0
- package/dist/computed/computed-schema.js +54 -0
- package/dist/computed/formula-sql.js +22 -0
- package/dist/computed/rollup-sql.js +64 -0
- package/dist/computed/rollup-targets.js +43 -0
- package/dist/create-admin.js +3 -0
- package/dist/deps.d.ts +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/locale/locale-option.d.ts +5 -0
- package/dist/locale/locale-option.js +10 -0
- package/dist/meta.js +1 -0
- package/package.json +4 -4
package/dist/admin-options.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { PluginOption } from "./plugins/plugin-settings.js";
|
|
|
5
5
|
import { SourceOption } from "./sources/source-settings.js";
|
|
6
6
|
import { CorsOrigins } from "./api-cors.js";
|
|
7
7
|
import { AssistantOptions } from "./assistant/assistant-settings.js";
|
|
8
|
+
import { LocaleOption } from "./locale/locale-option.js";
|
|
8
9
|
import { RuntimeOptions } from "./runtime/runtime-settings.js";
|
|
9
10
|
import { ResolveOptions, RoleLimit } from "@protobase/schema";
|
|
10
11
|
//#region src/admin-options.d.ts
|
|
@@ -59,6 +60,11 @@ export type AdminOptions = {
|
|
|
59
60
|
* with `false`, the app draws no maps.
|
|
60
61
|
*/
|
|
61
62
|
maps?: MapOptions | false;
|
|
63
|
+
/**
|
|
64
|
+
* The language of the admin app's built-in screens and of its numbers and dates, for every user: `'en'` or `'nl'`.
|
|
65
|
+
* Unset, each user sees their browser's language when the screens come in it, and English otherwise.
|
|
66
|
+
*/
|
|
67
|
+
locale?: LocaleOption;
|
|
62
68
|
/**
|
|
63
69
|
* The plugins the admin app loads (see `PluginOption`), before the ones the platform lists in `PROTOBASE_PLUGINS`
|
|
64
70
|
* and, for those that load before sign-in, `PROTOBASE_PLUGINS_BEFORE_SIGN_IN`; `false` turns all of them off.
|
|
@@ -5,6 +5,7 @@ const quoteLiteral = (text) => `'${text.replaceAll("'", "''")}'`;
|
|
|
5
5
|
const qualified = ({ schema, name }) => schema ? `${quoteIdent(schema)}.${quoteIdent(name)}` : quoteIdent(name);
|
|
6
6
|
/** A Postgres array literal of text, every element quoted, so the same columns always give the same text. */
|
|
7
7
|
const textArray = (items) => `{${items.map((item) => `"${item.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`).join(",")}}`;
|
|
8
|
+
/** A name as Postgres keeps it: the whole characters within its first 63 bytes. */
|
|
8
9
|
const identifier = (name) => {
|
|
9
10
|
const encoder = new TextEncoder();
|
|
10
11
|
let kept = "";
|
|
@@ -123,4 +124,4 @@ const createTriggerSql = (schema, trigger, table) => {
|
|
|
123
124
|
return `create trigger ${quoteIdent(trigger.name)} ${timing} on ${table} for each row${when} execute function ${quoteIdent(schema)}."capture"(${args})`;
|
|
124
125
|
};
|
|
125
126
|
//#endregion
|
|
126
|
-
export { captureBody, captureFunctionSql, createTriggerSql, dispatchGroup, dispatchJobName, dropTriggerSql, isCaptured, outboxSql, quoteIdent, resourceTriggers, tableSql };
|
|
127
|
+
export { captureBody, captureFunctionSql, createTriggerSql, dispatchGroup, dispatchJobName, dropTriggerSql, identifier, isCaptured, outboxSql, quoteIdent, resourceTriggers, tableSql };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/computed/column-sql.ts
|
|
2
|
+
/** The column type of a rollup or formula field, as Postgres's `format_type` names it. */
|
|
3
|
+
const columnType = (field) => {
|
|
4
|
+
if (field.type === "integer") return "integer";
|
|
5
|
+
if (field.type === "bigint") return "bigint";
|
|
6
|
+
if (field.type === "decimal" && field.decimal) return `numeric(${field.decimal.precision},${field.decimal.scale})`;
|
|
7
|
+
throw new Error(`Not implemented: a rollup or formula column of type ${field.type}`);
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { columnType };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { quoteIdent } from "../changes/capture-sql.js";
|
|
2
|
+
import { columnType } from "./column-sql.js";
|
|
3
|
+
import { addFormulaSql, formulaComment, isFormulaComment } from "./formula-sql.js";
|
|
4
|
+
import { addRollupColumnSql, backfillSql, dropRollupFunctionSql, dropRollupTriggerSql, rollupBody, rollupFunctionName, rollupFunctionSql, rollupTriggerName, rollupTriggerSql, rollupsSchema } from "./rollup-sql.js";
|
|
5
|
+
import { rollupTargets } from "./rollup-targets.js";
|
|
6
|
+
//#region src/computed/computed-plan.ts
|
|
7
|
+
/** Whether a resource has rollup or formula fields. */
|
|
8
|
+
const hasComputed = (model) => Object.values(model.fields).some((field) => field.rollup || field.formula !== void 0);
|
|
9
|
+
const fieldName = (model, field) => `${model.name}.${field.name}`;
|
|
10
|
+
const formulaStatements = (model, field, table, found) => {
|
|
11
|
+
const name = `${table}.${quoteIdent(field.column)}`;
|
|
12
|
+
if (!found) return addFormulaSql(table, model, field);
|
|
13
|
+
if (!found.generated) throw new Error(`Column ${name} of formula field "${fieldName(model, field)}" is a plain column; drop or rename it in a migration, then write the formula's with \`protobase migration\``);
|
|
14
|
+
if (!isFormulaComment(found.comment)) throw new Error(`Column ${name} of formula field "${fieldName(model, field)}" is a generated column of the project's own; drop it in a migration, or remove .formula()`);
|
|
15
|
+
if (found.comment === formulaComment(model, field) && found.type === columnType(field)) return [];
|
|
16
|
+
return [`-- The formula of "${fieldName(model, field)}" changed: its column is generated again, and an index on it has to be created again\nalter table ${table} drop column ${quoteIdent(field.column)}`, ...addFormulaSql(table, model, field)];
|
|
17
|
+
};
|
|
18
|
+
const rollupColumnStatements = (target, found) => {
|
|
19
|
+
const name = `${target.table}.${quoteIdent(target.field.column)}`;
|
|
20
|
+
if (!found) return [addRollupColumnSql(target)];
|
|
21
|
+
if (found.generated) throw new Error(`Column ${name} of rollup field "${fieldName(target.model, target.field)}" is a generated column; drop it in a migration, then write the rollup's with \`protobase migration\``);
|
|
22
|
+
if (found.type !== columnType(target.field)) throw new Error(`Column ${name} of rollup field "${fieldName(target.model, target.field)}" is ${found.type}, not ${columnType(target.field)} as the field declares; change one of them`);
|
|
23
|
+
return [];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Compares the database with the resources' rollup and formula fields. Fails on what it cannot change safely: a
|
|
27
|
+
* missing table, a column of the project's own where one is declared, or a trigger of the same name that calls another
|
|
28
|
+
* function. A changed formula's column is dropped and added again; a rollup's function, trigger and values are written
|
|
29
|
+
* again when its column is new or its definition changed. Columns are never dropped otherwise.
|
|
30
|
+
*/
|
|
31
|
+
const computedPlan = (models, state) => {
|
|
32
|
+
const table = (model) => {
|
|
33
|
+
const found = state.tables[model.name];
|
|
34
|
+
if (!found) throw new Error(`The table of resource "${model.name}" does not exist`);
|
|
35
|
+
return found;
|
|
36
|
+
};
|
|
37
|
+
const column = (model, field) => state.columns.find((found) => found.table === table(model).oid && found.name === field.column);
|
|
38
|
+
const behind = [];
|
|
39
|
+
const formulas = [];
|
|
40
|
+
const regenerated = /* @__PURE__ */ new Set();
|
|
41
|
+
for (const model of models) for (const field of Object.values(model.fields).filter((candidate) => candidate.formula !== void 0)) {
|
|
42
|
+
const sql = formulaStatements(model, field, table(model).sql, column(model, field));
|
|
43
|
+
if (sql.length === 0) continue;
|
|
44
|
+
behind.push(fieldName(model, field));
|
|
45
|
+
if (column(model, field)) regenerated.add(fieldName(model, field));
|
|
46
|
+
formulas.push(...sql);
|
|
47
|
+
}
|
|
48
|
+
const readsRegenerated = ({ from, field }) => field.rollup.field !== void 0 && regenerated.has(`${from.name}.${field.rollup.field}`);
|
|
49
|
+
const targets = rollupTargets(models, (model) => table(model).sql);
|
|
50
|
+
const rollupColumns = [];
|
|
51
|
+
const droppedTriggers = [];
|
|
52
|
+
const rollups = [];
|
|
53
|
+
for (const target of targets) {
|
|
54
|
+
const columnSql = rollupColumnStatements(target, column(target.model, target.field));
|
|
55
|
+
const fn = state.functions.find((found) => found.name === rollupFunctionName(target));
|
|
56
|
+
const fromOid = table(target.from).oid;
|
|
57
|
+
const own = state.triggers.find((found) => found.table === fromOid && found.name === rollupTriggerName(target));
|
|
58
|
+
if (own && !state.functions.some((found) => found.oid === own.function)) throw new Error(`Table ${target.fromTable} has a trigger "${own.name}" of its own; rename it, since rollup field "${fieldName(target.model, target.field)}" needs that name`);
|
|
59
|
+
const stale = fn ? state.triggers.filter((found) => found.function === fn.oid && (found.table !== fromOid || found.name !== rollupTriggerName(target))) : [];
|
|
60
|
+
if (columnSql.length === 0 && fn !== void 0 && fn.definer && fn.source === rollupBody(target) && own?.function === fn.oid && stale.length === 0 && !readsRegenerated(target)) continue;
|
|
61
|
+
behind.push(fieldName(target.model, target.field));
|
|
62
|
+
rollupColumns.push(...columnSql);
|
|
63
|
+
droppedTriggers.push(...stale.map((found) => dropRollupTriggerSql(found.name, found.tableSql)), dropRollupTriggerSql(rollupTriggerName(target), target.fromTable));
|
|
64
|
+
rollups.push(rollupFunctionSql(target), rollupTriggerSql(target), backfillSql(target));
|
|
65
|
+
}
|
|
66
|
+
const wanted = targets.map(rollupFunctionName);
|
|
67
|
+
const dropped = state.functions.filter((found) => !wanted.includes(found.name)).map((found) => dropRollupFunctionSql(found.name));
|
|
68
|
+
return {
|
|
69
|
+
statements: [
|
|
70
|
+
...rollups.length > 0 ? [`create schema if not exists ${quoteIdent(rollupsSchema)}`] : [],
|
|
71
|
+
...dropped,
|
|
72
|
+
...droppedTriggers,
|
|
73
|
+
...rollupColumns,
|
|
74
|
+
...formulas,
|
|
75
|
+
...rollups
|
|
76
|
+
],
|
|
77
|
+
behind
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { computedPlan, hasComputed };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ResourceModel } from "@protobase/schema";
|
|
2
|
+
import { Db } from "@protobase/query";
|
|
3
|
+
//#region src/computed/computed-schema.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The SQL that brings the database to the resources' rollup and formula fields: their columns, and for each rollup
|
|
6
|
+
* the function and trigger that keep it, with its values set once; `undefined` when the database is current.
|
|
7
|
+
*/
|
|
8
|
+
export declare const computedMigration: (db: Db, models: readonly ResourceModel[]) => Promise<string | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Refuses to go on while a rollup or formula field's column, function or trigger is missing or out of date. The server
|
|
11
|
+
* never changes the schema itself: the project's migrations do, before the new version serves. Reads nothing without
|
|
12
|
+
* such fields.
|
|
13
|
+
*/
|
|
14
|
+
export declare const checkComputed: (db: Db, models: readonly ResourceModel[]) => Promise<void>;
|
|
15
|
+
//#endregion
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { tableSql } from "../changes/capture-sql.js";
|
|
2
|
+
import { migrationCommand } from "../changes/capture-schema.js";
|
|
3
|
+
import { rollupsSchema } from "./rollup-sql.js";
|
|
4
|
+
import { computedPlan, hasComputed } from "./computed-plan.js";
|
|
5
|
+
import { sql } from "kysely";
|
|
6
|
+
//#region src/computed/computed-schema.ts
|
|
7
|
+
const rowsOf = async (db, query) => (await query.execute(db)).rows;
|
|
8
|
+
const involved = (models) => {
|
|
9
|
+
const computed = models.filter(hasComputed);
|
|
10
|
+
const read = new Set(computed.flatMap((model) => Object.values(model.fields).flatMap((field) => field.rollup ? [field.rollup.from] : [])));
|
|
11
|
+
return models.filter((model) => hasComputed(model) || read.has(model.name));
|
|
12
|
+
};
|
|
13
|
+
const readComputedState = async (db, models) => {
|
|
14
|
+
const tables = {};
|
|
15
|
+
for (const model of involved(models)) {
|
|
16
|
+
const [found] = await rowsOf(db, sql`select c.oid::text as oid, format('%I.%I', n.nspname, c.relname) as sql from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.oid = to_regclass(${tableSql(model)})`);
|
|
17
|
+
tables[model.name] = found ?? null;
|
|
18
|
+
}
|
|
19
|
+
const wanted = models.filter(hasComputed).flatMap((model) => Object.values(model.fields).filter((field) => field.rollup || field.formula !== void 0).flatMap((field) => tables[model.name] ? [`${tables[model.name].oid}:${field.column}`] : []));
|
|
20
|
+
const columns = await rowsOf(db, sql`select a.attrelid::text as "table", a.attname as name, format_type(a.atttypid, a.atttypmod) as type, a.attgenerated <> '' as generated, col_description(a.attrelid, a.attnum) as comment
|
|
21
|
+
from pg_attribute a where a.attnum > 0 and not a.attisdropped and a.attrelid::text || ':' || a.attname = any(${wanted}::text[])`);
|
|
22
|
+
const functions = await rowsOf(db, sql`select p.oid::text as oid, p.proname as name, p.prosrc as source, p.prosecdef as definer from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = ${rollupsSchema}`);
|
|
23
|
+
const oids = Object.values(tables).flatMap((found) => found ? [found.oid] : []);
|
|
24
|
+
return {
|
|
25
|
+
tables,
|
|
26
|
+
columns,
|
|
27
|
+
functions,
|
|
28
|
+
triggers: await rowsOf(db, sql`select t.tgrelid::text as "table", format('%I.%I', ns.nspname, c.relname) as "tableSql", t.tgname as name, t.tgfoid::text as "function"
|
|
29
|
+
from pg_trigger t join pg_class c on c.oid = t.tgrelid join pg_namespace ns on ns.oid = c.relnamespace
|
|
30
|
+
where not t.tgisinternal and (t.tgfoid::text = any(${functions.map((found) => found.oid)}::text[]) or t.tgrelid::text = any(${oids}::text[]))`)
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const plan = async (db, models) => computedPlan(models, await readComputedState(db, models));
|
|
34
|
+
/**
|
|
35
|
+
* The SQL that brings the database to the resources' rollup and formula fields: their columns, and for each rollup
|
|
36
|
+
* the function and trigger that keep it, with its values set once; `undefined` when the database is current.
|
|
37
|
+
*/
|
|
38
|
+
const computedMigration = async (db, models) => {
|
|
39
|
+
const { statements } = await plan(db, models);
|
|
40
|
+
return statements.length === 0 ? void 0 : `${statements.join(";\n\n")};`;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Refuses to go on while a rollup or formula field's column, function or trigger is missing or out of date. The server
|
|
44
|
+
* never changes the schema itself: the project's migrations do, before the new version serves. Reads nothing without
|
|
45
|
+
* such fields.
|
|
46
|
+
*/
|
|
47
|
+
const checkComputed = async (db, models) => {
|
|
48
|
+
if (!models.some(hasComputed)) return;
|
|
49
|
+
const { behind } = await plan(db, models);
|
|
50
|
+
if (behind.length === 0) return;
|
|
51
|
+
throw new Error(`The rollup and formula fields ${behind.join(", ")} are not kept by the database as declared: their columns, functions or triggers are missing or out of date. Write the change as a migration with \`${migrationCommand}\`, apply it with the project's migrations, then start again.`);
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { checkComputed, computedMigration };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { quoteIdent } from "../changes/capture-sql.js";
|
|
2
|
+
import { columnType } from "./column-sql.js";
|
|
3
|
+
import { parseFormula } from "@protobase/schema/internal";
|
|
4
|
+
//#region src/computed/formula-sql.ts
|
|
5
|
+
const expressionSql = (node, model) => {
|
|
6
|
+
if (node.kind === "number") return `${node.value}::numeric`;
|
|
7
|
+
if (node.kind === "field") return `${quoteIdent(model.fields[node.name].column)}::numeric`;
|
|
8
|
+
if (node.kind === "negate") return `(- ${expressionSql(node.operand, model)})`;
|
|
9
|
+
if (node.kind === "binary") return `(${expressionSql(node.left, model)} ${node.op} ${expressionSql(node.right, model)})`;
|
|
10
|
+
if (node.kind === "coalesce") return `coalesce(${node.args.map((arg) => expressionSql(arg, model)).join(", ")})`;
|
|
11
|
+
return `round(${expressionSql(node.value, model)}${node.digits === void 0 ? "" : `, ${node.digits}`})`;
|
|
12
|
+
};
|
|
13
|
+
/** A formula field's generation expression, cast to its column's type. */
|
|
14
|
+
const formulaSql = (model, field) => `(${expressionSql(parseFormula(field.formula), model)})::${columnType(field)}`;
|
|
15
|
+
/** The comment that marks a formula's column as Protobase's, with the expression it was generated from. */
|
|
16
|
+
const formulaComment = (model, field) => `protobase formula: ${formulaSql(model, field)}`;
|
|
17
|
+
/** Whether a column's comment marks it as a formula's column. */
|
|
18
|
+
const isFormulaComment = (comment) => comment?.startsWith("protobase formula: ") ?? false;
|
|
19
|
+
/** The statements that add a formula's generated column to `table` and mark it. */
|
|
20
|
+
const addFormulaSql = (table, model, field) => [`alter table ${table} add column ${quoteIdent(field.column)} ${columnType(field)} generated always as (${formulaSql(model, field)}) stored`, `comment on column ${table}.${quoteIdent(field.column)} is '${formulaComment(model, field).replaceAll("'", "''")}'`];
|
|
21
|
+
//#endregion
|
|
22
|
+
export { addFormulaSql, formulaComment, formulaSql, isFormulaComment };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { identifier, quoteIdent } from "../changes/capture-sql.js";
|
|
2
|
+
import { columnType } from "./column-sql.js";
|
|
3
|
+
//#region src/computed/rollup-sql.ts
|
|
4
|
+
/** The schema of the functions that keep rollups. */
|
|
5
|
+
const rollupsSchema = "rollups";
|
|
6
|
+
/** The function that keeps one rollup, named for its resource's table and column. */
|
|
7
|
+
const rollupFunctionName = ({ model, field }) => identifier([
|
|
8
|
+
model.table.schema,
|
|
9
|
+
model.table.name,
|
|
10
|
+
field.column
|
|
11
|
+
].filter(Boolean).join("_"));
|
|
12
|
+
/** The trigger on `from`'s table that calls it. */
|
|
13
|
+
const rollupTriggerName = (target) => identifier(`${rollupFunctionName(target)}_rollup`);
|
|
14
|
+
const functionSql = (target) => `${quoteIdent(rollupsSchema)}.${quoteIdent(rollupFunctionName(target))}`;
|
|
15
|
+
const viaColumn = ({ from, field }) => quoteIdent(from.fields[field.rollup.via].column);
|
|
16
|
+
const keyColumn = ({ model }) => quoteIdent(model.fields[model.primaryKey[0]].column);
|
|
17
|
+
const aggregateSql = ({ from, field }) => {
|
|
18
|
+
const { fn, field: name } = field.rollup;
|
|
19
|
+
const value = name === void 0 ? "*" : `c.${quoteIdent(from.fields[name].column)}`;
|
|
20
|
+
if (fn === "count") return `count(${value})`;
|
|
21
|
+
if (fn === "sum") return `coalesce(sum(${value}), 0)`;
|
|
22
|
+
return `${fn}(${value})`;
|
|
23
|
+
};
|
|
24
|
+
const liveRecords = ({ from }) => from.softDelete ? ` and c.${quoteIdent(from.fields[from.softDelete].column)} is null` : "";
|
|
25
|
+
const refreshSql = (target, where) => `update ${target.table} as p set ${quoteIdent(target.field.column)} = (select ${aggregateSql(target)} from ${target.fromTable} as c where c.${viaColumn(target)} = p.${keyColumn(target)}${liveRecords(target)}) where ${where}`;
|
|
26
|
+
/**
|
|
27
|
+
* The body of a rollup's function: after a record of `from` is written, it sets the rollup of the record it points at,
|
|
28
|
+
* and of the one it pointed at before when the write moved it.
|
|
29
|
+
*/
|
|
30
|
+
const rollupBody = (target) => {
|
|
31
|
+
const via = viaColumn(target);
|
|
32
|
+
const key = keyColumn(target);
|
|
33
|
+
return `
|
|
34
|
+
begin
|
|
35
|
+
if TG_OP <> 'INSERT' then
|
|
36
|
+
${refreshSql(target, `p.${key} = OLD.${via}`)};
|
|
37
|
+
end if;
|
|
38
|
+
if TG_OP = 'INSERT' or (TG_OP = 'UPDATE' and NEW.${via} is distinct from OLD.${via}) then
|
|
39
|
+
${refreshSql(target, `p.${key} = NEW.${via}`)};
|
|
40
|
+
end if;
|
|
41
|
+
return null;
|
|
42
|
+
end
|
|
43
|
+
`;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The function runs as its owner, with a fixed search path and the tables qualified: a writer of `from` needs no rights
|
|
47
|
+
* on the rollup's table.
|
|
48
|
+
*/
|
|
49
|
+
const rollupFunctionSql = (target) => `create or replace function ${functionSql(target)}() returns trigger language plpgsql security definer set search_path = pg_catalog, pg_temp as $rollup$${rollupBody(target)}$rollup$`;
|
|
50
|
+
const readColumns = ({ from, field }) => [
|
|
51
|
+
field.rollup.via,
|
|
52
|
+
field.rollup.field,
|
|
53
|
+
from.softDelete
|
|
54
|
+
].filter((name) => name !== void 0).map((name) => quoteIdent(from.fields[name].column));
|
|
55
|
+
/** A rollup's trigger: after inserts, deletes and updates of the columns it reads, for each row. */
|
|
56
|
+
const rollupTriggerSql = (target) => `create trigger ${quoteIdent(rollupTriggerName(target))} after insert or delete or update of ${readColumns(target).join(", ")} on ${target.fromTable} for each row execute function ${functionSql(target)}()`;
|
|
57
|
+
const dropRollupTriggerSql = (name, table) => `drop trigger if exists ${quoteIdent(name)} on ${table}`;
|
|
58
|
+
const dropRollupFunctionSql = (name) => `drop function if exists ${quoteIdent(rollupsSchema)}.${quoteIdent(name)}() cascade`;
|
|
59
|
+
/** Sets every record's rollup from what `from` has now. */
|
|
60
|
+
const backfillSql = (target) => refreshSql(target, "true");
|
|
61
|
+
/** The statement that adds the rollup's column, empty until the backfill sets it. */
|
|
62
|
+
const addRollupColumnSql = ({ table, field }) => `alter table ${table} add column ${quoteIdent(field.column)} ${columnType(field)}`;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { addRollupColumnSql, backfillSql, dropRollupFunctionSql, dropRollupTriggerSql, rollupBody, rollupFunctionName, rollupFunctionSql, rollupTriggerName, rollupTriggerSql, rollupsSchema };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/computed/rollup-targets.ts
|
|
2
|
+
const numberTypes = [
|
|
3
|
+
"integer",
|
|
4
|
+
"bigint",
|
|
5
|
+
"decimal"
|
|
6
|
+
];
|
|
7
|
+
const targetProblems = (model, field, from) => {
|
|
8
|
+
const { from: name, via, field: read } = field.rollup;
|
|
9
|
+
if (!from) return [`rolls up "${name}", which is not a resource`];
|
|
10
|
+
if (from.table.name === model.table.name && from.table.schema === model.table.schema) return [`rolls up "${name}", whose table is its own (not implemented)`];
|
|
11
|
+
const relation = Object.hasOwn(from.fields, via) ? from.fields[via] : void 0;
|
|
12
|
+
const value = read !== void 0 && Object.hasOwn(from.fields, read) ? from.fields[read] : void 0;
|
|
13
|
+
return [
|
|
14
|
+
...model.primaryKey.length === 1 ? [] : ["is on a resource with a key of several columns (not implemented)"],
|
|
15
|
+
...!relation ? [`is via "${via}", which is not a field of "${name}"`] : [],
|
|
16
|
+
...relation && relation.relation?.resource !== model.name ? [`is via "${via}", which is no relation of "${name}" to "${model.name}"`] : [],
|
|
17
|
+
...relation?.relation && relation.relation.columns.length !== 1 ? [`is via "${via}", a relation of several columns (not implemented)`] : [],
|
|
18
|
+
...read !== void 0 && !value ? [`reads "${read}", which is not a field of "${name}"`] : [],
|
|
19
|
+
...value?.sensitive ? [`reads "${read}", which is sensitive`] : [],
|
|
20
|
+
...value && field.rollup.fn !== "count" && !numberTypes.includes(value.type) ? [`reads "${read}", which is ${value.type}, not a number`] : []
|
|
21
|
+
];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Every rollup of the resources with its tables, as `tableSql` names a resource's; fails on a rollup whose resource,
|
|
25
|
+
* relation or field is not there.
|
|
26
|
+
*/
|
|
27
|
+
const rollupTargets = (models, tableSql) => {
|
|
28
|
+
const byName = new Map(models.map((model) => [model.name, model]));
|
|
29
|
+
return models.flatMap((model) => Object.values(model.fields).filter((field) => field.rollup).map((field) => {
|
|
30
|
+
const from = byName.get(field.rollup.from);
|
|
31
|
+
const problems = targetProblems(model, field, from);
|
|
32
|
+
if (problems.length > 0) throw new Error(`Resource "${model.name}": ${problems.map((problem) => `rollup field "${field.name}" ${problem}`).join("; ")}`);
|
|
33
|
+
return {
|
|
34
|
+
model,
|
|
35
|
+
field,
|
|
36
|
+
from,
|
|
37
|
+
table: tableSql(model),
|
|
38
|
+
fromTable: tableSql(from)
|
|
39
|
+
};
|
|
40
|
+
}));
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { rollupTargets };
|
package/dist/create-admin.js
CHANGED
|
@@ -42,6 +42,7 @@ import { fileRoutes } from "./files/routes.js";
|
|
|
42
42
|
import { formRoutes } from "./forms/routes.js";
|
|
43
43
|
import { createFormsRuntime } from "./forms/runtime.js";
|
|
44
44
|
import { functionRoutes } from "./functions/routes.js";
|
|
45
|
+
import { localeSetting } from "./locale/locale-option.js";
|
|
45
46
|
import { mapSettings } from "./maps/map-settings.js";
|
|
46
47
|
import { pluginOptions, pluginsBeforeSignIn } from "./plugins/plugin-settings.js";
|
|
47
48
|
import { runtimeUrl } from "./runtime/runtime-settings.js";
|
|
@@ -114,6 +115,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
114
115
|
const runtime = runtimeUrl(options.runtime, globalThis.process?.env ?? {});
|
|
115
116
|
const errorReporting = errorReportingSettings(options.errorReporting, globalThis.process?.env ?? {});
|
|
116
117
|
const maps = mapSettings(options.maps, globalThis.process?.env ?? {});
|
|
118
|
+
const locale = localeSetting(options.locale);
|
|
117
119
|
const plugins = pluginOptions(options.plugins, globalThis.process?.env ?? {}, bundlePlugins);
|
|
118
120
|
const sources = sourceOptions(options.sources, globalThis.process?.env ?? {});
|
|
119
121
|
checkSourceNames(sources, {
|
|
@@ -156,6 +158,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
156
158
|
...runtime && { runtime },
|
|
157
159
|
...errorReporting && { errorReporting },
|
|
158
160
|
...maps && { maps },
|
|
161
|
+
...locale && { locale },
|
|
159
162
|
plugins,
|
|
160
163
|
sources,
|
|
161
164
|
...files && { files },
|
package/dist/deps.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ import "./plugins/plugin-settings.js";
|
|
|
6
6
|
import "./registry.js";
|
|
7
7
|
import "./scan-guard.js";
|
|
8
8
|
import { PageModel } from "@protobase/layout";
|
|
9
|
+
import { Locale } from "@protobase/schema/internal";
|
|
9
10
|
import { ResolveOptions, ViewModel } from "@protobase/schema";
|
|
10
11
|
import { Db } from "@protobase/query";
|
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ import { IssueTokenOptions, issueToken, maxTokenTtlSeconds } from "./better-auth
|
|
|
55
55
|
import { webauthnPath } from "./better-auth/origins.js";
|
|
56
56
|
import { authSchemaMigration, checkAuthSchema } from "./better-auth/auth-schema.js";
|
|
57
57
|
import { changesMigration, checkChanges, migrationCommand } from "./changes/capture-schema.js";
|
|
58
|
+
import { checkComputed, computedMigration } from "./computed/computed-schema.js";
|
|
58
59
|
import { isCaptured } from "./changes/capture-sql.js";
|
|
59
60
|
import { CapturedRow, CoalescedChange, coalesce } from "./changes/coalesce.js";
|
|
60
61
|
import { ResourceChange, recordOf, resourceChange } from "./changes/resource-change.js";
|
|
@@ -92,4 +93,4 @@ import { FatalJobError } from "./jobs/run-job.js";
|
|
|
92
93
|
import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/runtime.js";
|
|
93
94
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
94
95
|
import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
|
|
95
|
-
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, type HistoryOptions, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type MemberInput, MissingCredentials, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
|
96
|
+
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, type HistoryOptions, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type MemberInput, MissingCredentials, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,7 @@ import { withTrustedApps } from "./better-auth/app-tokens.js";
|
|
|
54
54
|
import { betterAuthAuthenticator } from "./better-auth/authenticator.js";
|
|
55
55
|
import { issueToken, maxTokenTtlSeconds } from "./better-auth/tokens.js";
|
|
56
56
|
import { authSchemaMigration, checkAuthSchema } from "./better-auth/auth-schema.js";
|
|
57
|
+
import { checkComputed, computedMigration } from "./computed/computed-schema.js";
|
|
57
58
|
import { webhooks } from "./webhooks/table/plugin.js";
|
|
58
59
|
import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
|
|
59
60
|
import { checkAi } from "./ai/check-ai.js";
|
|
@@ -69,4 +70,4 @@ import { aspectRatio, fileSize, fileType, imageSize, processor } from "./files/p
|
|
|
69
70
|
import { defineEmail } from "./mail/define-email.js";
|
|
70
71
|
import { startJobs } from "./jobs/runtime.js";
|
|
71
72
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
72
|
-
export { FatalJobError, HttpProblem, MissingCredentials, ModelError, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
|
73
|
+
export { FatalJobError, HttpProblem, MissingCredentials, ModelError, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, handleDroppedConnections, hasUsers, imageSize, isBackgroundWork, isCaptured, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isLocale, locales } from "@protobase/schema/internal";
|
|
2
|
+
//#region src/locale/locale-option.ts
|
|
3
|
+
/** The option, checked: a language the screens do not come in stops startup. */
|
|
4
|
+
const localeSetting = (option) => {
|
|
5
|
+
if (option === void 0) return void 0;
|
|
6
|
+
if (isLocale(option)) return option;
|
|
7
|
+
throw new Error(`options.locale is ${JSON.stringify(option)}: use one of ${locales.map((locale) => `'${locale}'`).join(", ")}`);
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { localeSetting };
|
package/dist/meta.js
CHANGED
|
@@ -86,6 +86,7 @@ const createMeta = (deps, appName) => {
|
|
|
86
86
|
...deps.runtime && seesRuntime(session.user.roles) && { runtime: { url: deps.runtime } },
|
|
87
87
|
...deps.errorReporting && { errorReporting: deps.errorReporting },
|
|
88
88
|
...deps.maps && { maps: deps.maps },
|
|
89
|
+
...deps.locale && { locale: deps.locale },
|
|
89
90
|
...deps.files && deps.files.trustedHosts.length > 0 && { files: { trustedHosts: deps.files.trustedHosts } },
|
|
90
91
|
...plugins.length > 0 && { plugins },
|
|
91
92
|
...sources.length > 0 && { sources }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.6",
|
|
4
4
|
"description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@better-auth/passkey": "^1.7.7",
|
|
33
33
|
"@better-auth/sso": "^1.7.7",
|
|
34
|
-
"@protobase/layout": "0.23.
|
|
35
|
-
"@protobase/query": "0.23.
|
|
36
|
-
"@protobase/schema": "0.23.
|
|
34
|
+
"@protobase/layout": "0.23.6",
|
|
35
|
+
"@protobase/query": "0.23.6",
|
|
36
|
+
"@protobase/schema": "0.23.6",
|
|
37
37
|
"@react-email/render": "^2.1.0",
|
|
38
38
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
39
39
|
"@sentry/core": "^11.7.0",
|