@adminiumjs/manifest 0.3.0-rc.4 → 0.3.1
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/add-ons.d.ts +63 -0
- package/dist/add-ons.d.ts.map +1 -0
- package/dist/add-ons.js +100 -0
- package/dist/add-ons.js.map +1 -0
- package/dist/booking.d.ts +100 -0
- package/dist/booking.d.ts.map +1 -0
- package/dist/booking.js +237 -0
- package/dist/booking.js.map +1 -0
- package/dist/documents.d.ts +100 -0
- package/dist/documents.d.ts.map +1 -0
- package/dist/documents.js +148 -0
- package/dist/documents.js.map +1 -0
- package/dist/formula.d.ts +124 -0
- package/dist/formula.d.ts.map +1 -0
- package/dist/formula.js +428 -0
- package/dist/formula.js.map +1 -0
- package/dist/index.d.ts +14 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/outbox.d.ts +543 -0
- package/dist/outbox.d.ts.map +1 -0
- package/dist/outbox.js +559 -0
- package/dist/outbox.js.map +1 -0
- package/dist/page-calendar.d.ts +42 -0
- package/dist/page-calendar.d.ts.map +1 -0
- package/dist/page-calendar.js +95 -0
- package/dist/page-calendar.js.map +1 -0
- package/dist/plan-model.d.ts +1 -1
- package/dist/plan-model.d.ts.map +1 -1
- package/dist/public-access.d.ts +208 -0
- package/dist/public-access.d.ts.map +1 -0
- package/dist/public-access.js +681 -0
- package/dist/public-access.js.map +1 -0
- package/dist/refs.d.ts +91 -0
- package/dist/refs.d.ts.map +1 -0
- package/dist/refs.js +82 -0
- package/dist/refs.js.map +1 -0
- package/dist/roles.d.ts +44 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/roles.js +75 -0
- package/dist/roles.js.map +1 -0
- package/dist/sample.d.ts +150 -2
- package/dist/sample.d.ts.map +1 -1
- package/dist/sample.js +101 -3
- package/dist/sample.js.map +1 -1
- package/dist/schema.d.ts +3464 -151
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +681 -143
- package/dist/schema.js.map +1 -1
- package/dist/semver.d.ts +28 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +85 -0
- package/dist/semver.js.map +1 -0
- package/dist/shapes.d.ts +97 -0
- package/dist/shapes.d.ts.map +1 -0
- package/dist/shapes.js +190 -0
- package/dist/shapes.js.map +1 -0
- package/dist/states.d.ts +134 -0
- package/dist/states.d.ts.map +1 -0
- package/dist/states.js +244 -0
- package/dist/states.js.map +1 -0
- package/dist/validate.d.ts +14 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +29 -2
- package/dist/validate.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
/**
|
|
3
|
+
* Document profiles an app ships: which of its columns make a printed
|
|
4
|
+
* document (an invoice, a receipt, a statement), drawn by an add-on that
|
|
5
|
+
* renders documents.
|
|
6
|
+
*
|
|
7
|
+
* ```json
|
|
8
|
+
* "documents": [{
|
|
9
|
+
* "kind": "receipt", "addOn": "invoices", "table": "sales",
|
|
10
|
+
* "name": { "en-US": "Till receipt" },
|
|
11
|
+
* "mapping": {
|
|
12
|
+
* "number": { "column": "number" },
|
|
13
|
+
* "customerName": { "via": "customer_id", "column": "name" },
|
|
14
|
+
* "lines": { "collection": { "table": "sale_lines", "via": "sale_id", "orderBy": "position",
|
|
15
|
+
* "columns": { "description": "name", "qty": "qty", "amount": "amount" } } }
|
|
16
|
+
* }
|
|
17
|
+
* }]
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* A slot reads a column of the row, a column of a row a foreign key points at
|
|
21
|
+
* (`via`), or a list of child rows in order. The slots themselves are the
|
|
22
|
+
* add-on's words; Adminium checks the columns here and the slots against the
|
|
23
|
+
* add-on when the profile is made at install. A profile is the app's: made
|
|
24
|
+
* with real table names when the app is installed, removed with it.
|
|
25
|
+
*
|
|
26
|
+
* A statement is a document over one row (a client) and a period: it lists
|
|
27
|
+
* the documents and payments that point at that row, so it names them
|
|
28
|
+
* instead of one child list.
|
|
29
|
+
*/
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
import { refSchema, textOrLabels } from './refs.js';
|
|
32
|
+
const slotId = z.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/, 'a slot id');
|
|
33
|
+
export const slotMappingSchema = z.union([
|
|
34
|
+
z.object({ column: refSchema }).strict(),
|
|
35
|
+
z.object({ via: refSchema, column: refSchema }).strict(),
|
|
36
|
+
z
|
|
37
|
+
.object({
|
|
38
|
+
collection: z
|
|
39
|
+
.object({
|
|
40
|
+
table: refSchema,
|
|
41
|
+
via: refSchema,
|
|
42
|
+
orderBy: refSchema.optional(),
|
|
43
|
+
columns: z.record(slotId, refSchema),
|
|
44
|
+
})
|
|
45
|
+
.strict(),
|
|
46
|
+
})
|
|
47
|
+
.strict(),
|
|
48
|
+
]);
|
|
49
|
+
/** The rows a statement lists for its one row, and the columns it reads of them. */
|
|
50
|
+
const statementSourceSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
table: refSchema,
|
|
53
|
+
via: refSchema,
|
|
54
|
+
date: refSchema,
|
|
55
|
+
amount: refSchema,
|
|
56
|
+
number: refSchema.optional(),
|
|
57
|
+
/** Only rows whose column holds one of the values (sent invoices, not drafts). */
|
|
58
|
+
where: z.object({ column: refSchema, in: z.array(z.union([z.string(), z.number(), z.boolean()])).min(1).max(16) }).strict().optional(),
|
|
59
|
+
/** A row whose column is true (or set) is left out: a voided payment. */
|
|
60
|
+
unless: refSchema.optional(),
|
|
61
|
+
})
|
|
62
|
+
.strict();
|
|
63
|
+
export const appDocumentSchema = z
|
|
64
|
+
.object({
|
|
65
|
+
kind: z.string().regex(/^[a-z][a-z0-9-]*$/, 'a document kind'),
|
|
66
|
+
addOn: z.string().regex(/^[a-z][a-z0-9-]{1,79}$/, 'an add-on key'),
|
|
67
|
+
table: refSchema,
|
|
68
|
+
name: textOrLabels,
|
|
69
|
+
mapping: z.record(slotId, slotMappingSchema),
|
|
70
|
+
statement: z.object({ documents: statementSourceSchema, payments: statementSourceSchema }).strict().optional(),
|
|
71
|
+
/** The feature this document belongs to (see `addOns.features`). */
|
|
72
|
+
feature: z.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional(),
|
|
73
|
+
})
|
|
74
|
+
.strict();
|
|
75
|
+
/** Every column a mapping names, against the manifest's tables. */
|
|
76
|
+
export function mappingIssues(table, mapping, index, at) {
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const [slot, source] of Object.entries(mapping)) {
|
|
79
|
+
const here = (...rest) => at('mapping', slot, ...rest);
|
|
80
|
+
if ('collection' in source) {
|
|
81
|
+
const c = source.collection;
|
|
82
|
+
if (index.table(c.table) === undefined) {
|
|
83
|
+
out.push({ path: here('collection', 'table'), message: `"${c.table}" is not a table of this manifest` });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const via = index.column(c.table, c.via);
|
|
87
|
+
if (via?.type !== 'fk' || via.references !== table)
|
|
88
|
+
out.push({ path: here('collection', 'via'), message: `"${c.table}.${c.via}" does not point at "${table}"` });
|
|
89
|
+
for (const ref of [...Object.values(c.columns), ...(c.orderBy === undefined ? [] : [c.orderBy])]) {
|
|
90
|
+
if (!index.has(c.table, ref))
|
|
91
|
+
out.push({ path: here('collection'), message: `"${c.table}" has no column "${ref}"` });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if ('via' in source) {
|
|
95
|
+
const via = index.column(table, source.via);
|
|
96
|
+
if (via?.type !== 'fk' || via.references === undefined) {
|
|
97
|
+
out.push({ path: here('via'), message: `"${source.via}" is not a foreign key of "${table}"` });
|
|
98
|
+
}
|
|
99
|
+
else if (!index.has(via.references, source.column)) {
|
|
100
|
+
out.push({ path: here('column'), message: `"${via.references}" has no column "${source.column}"` });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (!index.has(table, source.column)) {
|
|
104
|
+
out.push({ path: here('column'), message: `"${table}" has no column "${source.column}"` });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/** Everything wrong with an app's `documents` against its tables and add-on needs. */
|
|
110
|
+
export function appDocumentIssues(documents, ctx) {
|
|
111
|
+
const out = [];
|
|
112
|
+
const kinds = new Set();
|
|
113
|
+
documents.forEach((doc, d) => {
|
|
114
|
+
const at = (...rest) => ['documents', d, ...rest];
|
|
115
|
+
const key = `${doc.table}|${doc.kind}`;
|
|
116
|
+
if (kinds.has(key))
|
|
117
|
+
out.push({ path: at('kind'), message: `"${doc.table}" already has a "${doc.kind}" document` });
|
|
118
|
+
kinds.add(key);
|
|
119
|
+
if (!ctx.addOns.has(doc.addOn))
|
|
120
|
+
out.push({ path: at('addOn'), message: `"${doc.addOn}" is neither required nor suggested by the app` });
|
|
121
|
+
if (doc.feature !== undefined && !ctx.features.has(doc.feature))
|
|
122
|
+
out.push({ path: at('feature'), message: `"${doc.feature}" is not one of the app's addOns.features` });
|
|
123
|
+
if (ctx.index.table(doc.table) === undefined) {
|
|
124
|
+
out.push({ path: at('table'), message: `"${doc.table}" is not a table of this app` });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
out.push(...mappingIssues(doc.table, doc.mapping, ctx.index, at));
|
|
128
|
+
if (doc.statement !== undefined) {
|
|
129
|
+
for (const part of ['documents', 'payments']) {
|
|
130
|
+
const s = doc.statement[part];
|
|
131
|
+
const here = (...rest) => at('statement', part, ...rest);
|
|
132
|
+
if (ctx.index.table(s.table) === undefined) {
|
|
133
|
+
out.push({ path: here('table'), message: `"${s.table}" is not a table of this app` });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const via = ctx.index.column(s.table, s.via);
|
|
137
|
+
if (via?.type !== 'fk' || via.references !== doc.table)
|
|
138
|
+
out.push({ path: here('via'), message: `"${s.table}.${s.via}" does not point at "${doc.table}"` });
|
|
139
|
+
for (const ref of [s.date, s.amount, s.number, s.unless, s.where?.column]) {
|
|
140
|
+
if (ref !== undefined && !ctx.index.has(s.table, ref))
|
|
141
|
+
out.push({ path: here(), message: `"${s.table}" has no column "${ref}"` });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=documents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rules.formula` — a column Adminium works out from the other columns of the
|
|
3
|
+
* same row, on every write: a line's amount, a document's tax and total.
|
|
4
|
+
*
|
|
5
|
+
* ```json
|
|
6
|
+
* { "ref": "amount", "type": "decimal", "scale": "currency",
|
|
7
|
+
* "rules": { "formula": { "max": [0, { "sub": [{ "mul": ["qty", "rate"] }, { "coalesce": ["discount", 0] }] }] } } }
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* The grammar is small on purpose: numbers, the row's own columns, the four
|
|
11
|
+
* operations, `min`, `max`, `round`, `coalesce` and one `if`. Anything that
|
|
12
|
+
* reads another row is a `copy` or a `rollup`, which already know how to keep
|
|
13
|
+
* in step when that other row changes; a formula that could read across rows
|
|
14
|
+
* would need the same machinery again.
|
|
15
|
+
*
|
|
16
|
+
* THE ARITHMETIC IS EXACT. Every value becomes a fraction of two big
|
|
17
|
+
* integers, parsed from its decimal text (Postgres and MySQL hand a decimal
|
|
18
|
+
* back as `"12.5000"`, SQLite as the number `12.5`), and nothing goes through
|
|
19
|
+
* a float. Division stays exact; the result is rounded ONCE, half away from
|
|
20
|
+
* zero, to the column's scale. So `1 / 3 × 3` is exactly 1, and a total is the
|
|
21
|
+
* same on all three databases to the last minor unit.
|
|
22
|
+
*
|
|
23
|
+
* An empty column makes the result empty, unless `coalesce` says what to read
|
|
24
|
+
* instead: a draft line with no rate yet has no amount, not an amount of 0
|
|
25
|
+
* that looks like a price. Division by zero is empty too.
|
|
26
|
+
*
|
|
27
|
+
* Pure: a browser (an app's demo) evaluates the same formula the server does.
|
|
28
|
+
*/
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
import { type ColumnShape, type ReferenceIssue } from './refs.js';
|
|
31
|
+
export type FormulaExpr = number | string | {
|
|
32
|
+
add: FormulaExpr[];
|
|
33
|
+
} | {
|
|
34
|
+
sub: [FormulaExpr, FormulaExpr];
|
|
35
|
+
} | {
|
|
36
|
+
mul: FormulaExpr[];
|
|
37
|
+
} | {
|
|
38
|
+
div: [FormulaExpr, FormulaExpr];
|
|
39
|
+
} | {
|
|
40
|
+
min: FormulaExpr[];
|
|
41
|
+
} | {
|
|
42
|
+
max: FormulaExpr[];
|
|
43
|
+
} | {
|
|
44
|
+
round: FormulaExpr | [FormulaExpr, number];
|
|
45
|
+
} | {
|
|
46
|
+
coalesce: [FormulaExpr, FormulaExpr];
|
|
47
|
+
} | {
|
|
48
|
+
if: [FormulaCondition, FormulaExpr, FormulaExpr];
|
|
49
|
+
};
|
|
50
|
+
export type FormulaCondition = {
|
|
51
|
+
eq: [string, string | number | boolean];
|
|
52
|
+
} | {
|
|
53
|
+
neq: [string, string | number | boolean];
|
|
54
|
+
} | {
|
|
55
|
+
gt: [FormulaExpr, FormulaExpr];
|
|
56
|
+
} | {
|
|
57
|
+
gte: [FormulaExpr, FormulaExpr];
|
|
58
|
+
} | {
|
|
59
|
+
lt: [FormulaExpr, FormulaExpr];
|
|
60
|
+
} | {
|
|
61
|
+
lte: [FormulaExpr, FormulaExpr];
|
|
62
|
+
} | {
|
|
63
|
+
isNull: string;
|
|
64
|
+
} | {
|
|
65
|
+
and: FormulaCondition[];
|
|
66
|
+
} | {
|
|
67
|
+
or: FormulaCondition[];
|
|
68
|
+
};
|
|
69
|
+
export declare const formulaExprSchema: z.ZodType<FormulaExpr>;
|
|
70
|
+
export declare const formulaConditionSchema: z.ZodType<FormulaCondition>;
|
|
71
|
+
/** How deep a formula may nest: deeper is a formula nobody can read back. */
|
|
72
|
+
export declare const FORMULA_MAX_DEPTH = 8;
|
|
73
|
+
/** Every column a formula reads, in the order it first names them. */
|
|
74
|
+
export declare function formulaColumns(expr: FormulaExpr | FormulaCondition): string[];
|
|
75
|
+
/**
|
|
76
|
+
* What is wrong with one column's formula against its table: a column it
|
|
77
|
+
* names that the table lacks, a column used in arithmetic that holds no
|
|
78
|
+
* number, a formula deeper than {@link FORMULA_MAX_DEPTH}, one that reads
|
|
79
|
+
* itself. Cycles across a table's formulas are {@link formulaCycle}'s.
|
|
80
|
+
*/
|
|
81
|
+
export declare function formulaIssues(expr: FormulaExpr, own: string, column: (ref: string) => ColumnShape | undefined): string[];
|
|
82
|
+
/**
|
|
83
|
+
* The first cycle among a table's formula columns (`total` reads `tax`, which
|
|
84
|
+
* reads `total`), as the columns in order, or `null`. A cycle has no value to
|
|
85
|
+
* settle on, so it is refused before it is ever stored.
|
|
86
|
+
*/
|
|
87
|
+
export declare function formulaCycle(formulas: ReadonlyMap<string, FormulaExpr>): string[] | null;
|
|
88
|
+
/**
|
|
89
|
+
* A table's formula columns in the order they can be worked out: each after
|
|
90
|
+
* every formula column it reads. Assumes no cycle (checked at validation).
|
|
91
|
+
*/
|
|
92
|
+
export declare function formulaOrder(formulas: ReadonlyMap<string, FormulaExpr>): string[];
|
|
93
|
+
/** A fraction of two big integers; `d` is always positive. */
|
|
94
|
+
interface Ratio {
|
|
95
|
+
n: bigint;
|
|
96
|
+
d: bigint;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A stored value as an exact fraction: the decimal text of a number (never a
|
|
100
|
+
* float product), a decimal string as a driver returns it, a bigint, or a
|
|
101
|
+
* boolean (1 / 0, as MySQL and SQLite store one). `null` for an empty value or
|
|
102
|
+
* text that is not a number.
|
|
103
|
+
*/
|
|
104
|
+
export declare function toRatio(value: unknown): Ratio | null;
|
|
105
|
+
/** A fraction as decimal text with exactly `scale` places (`12.50`, `-3`, `0.125`). */
|
|
106
|
+
export declare function ratioText(value: Ratio, scale: number): string;
|
|
107
|
+
/**
|
|
108
|
+
* Work a formula out over a row, exactly: the result as decimal text at
|
|
109
|
+
* `scale` places, or `null` when an input it needs is empty or a divisor is
|
|
110
|
+
* zero. `row` holds the stored row with the new values over it.
|
|
111
|
+
*/
|
|
112
|
+
export declare function evaluateFormula(expr: FormulaExpr, row: Readonly<Record<string, unknown>>, scale: number): string | null;
|
|
113
|
+
/** Whether a condition holds for the row; a comparison with an empty side does not. */
|
|
114
|
+
export declare function holds(condition: FormulaCondition, row: Readonly<Record<string, unknown>>, scale: number): boolean;
|
|
115
|
+
/** The decimals a currency is written with (ISO 4217): JPY 0, most 2, KWD 3. Unknown → 2. */
|
|
116
|
+
export declare function currencyScale(code: string | null | undefined): number;
|
|
117
|
+
/** For the checks in `schema.ts`: every issue of a table's formulas, cycles included. */
|
|
118
|
+
export declare function tableFormulaIssues(columns: readonly (ColumnShape & {
|
|
119
|
+
rules?: {
|
|
120
|
+
formula?: FormulaExpr | undefined;
|
|
121
|
+
} | undefined;
|
|
122
|
+
})[], at: (column: number, ...rest: (string | number)[]) => (string | number)[]): ReferenceIssue[];
|
|
123
|
+
export {};
|
|
124
|
+
//# sourceMappingURL=formula.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formula.d.ts","sourceRoot":"","sources":["../src/formula.ts"],"names":[],"mappings":"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"}
|