@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.
Files changed (67) hide show
  1. package/dist/add-ons.d.ts +63 -0
  2. package/dist/add-ons.d.ts.map +1 -0
  3. package/dist/add-ons.js +100 -0
  4. package/dist/add-ons.js.map +1 -0
  5. package/dist/booking.d.ts +100 -0
  6. package/dist/booking.d.ts.map +1 -0
  7. package/dist/booking.js +237 -0
  8. package/dist/booking.js.map +1 -0
  9. package/dist/documents.d.ts +100 -0
  10. package/dist/documents.d.ts.map +1 -0
  11. package/dist/documents.js +148 -0
  12. package/dist/documents.js.map +1 -0
  13. package/dist/formula.d.ts +124 -0
  14. package/dist/formula.d.ts.map +1 -0
  15. package/dist/formula.js +428 -0
  16. package/dist/formula.js.map +1 -0
  17. package/dist/index.d.ts +14 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +14 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/outbox.d.ts +543 -0
  22. package/dist/outbox.d.ts.map +1 -0
  23. package/dist/outbox.js +559 -0
  24. package/dist/outbox.js.map +1 -0
  25. package/dist/page-calendar.d.ts +42 -0
  26. package/dist/page-calendar.d.ts.map +1 -0
  27. package/dist/page-calendar.js +95 -0
  28. package/dist/page-calendar.js.map +1 -0
  29. package/dist/plan-model.d.ts +1 -1
  30. package/dist/plan-model.d.ts.map +1 -1
  31. package/dist/public-access.d.ts +208 -0
  32. package/dist/public-access.d.ts.map +1 -0
  33. package/dist/public-access.js +681 -0
  34. package/dist/public-access.js.map +1 -0
  35. package/dist/refs.d.ts +91 -0
  36. package/dist/refs.d.ts.map +1 -0
  37. package/dist/refs.js +82 -0
  38. package/dist/refs.js.map +1 -0
  39. package/dist/roles.d.ts +44 -0
  40. package/dist/roles.d.ts.map +1 -0
  41. package/dist/roles.js +75 -0
  42. package/dist/roles.js.map +1 -0
  43. package/dist/sample.d.ts +150 -2
  44. package/dist/sample.d.ts.map +1 -1
  45. package/dist/sample.js +101 -3
  46. package/dist/sample.js.map +1 -1
  47. package/dist/schema.d.ts +3464 -151
  48. package/dist/schema.d.ts.map +1 -1
  49. package/dist/schema.js +681 -143
  50. package/dist/schema.js.map +1 -1
  51. package/dist/semver.d.ts +28 -0
  52. package/dist/semver.d.ts.map +1 -0
  53. package/dist/semver.js +85 -0
  54. package/dist/semver.js.map +1 -0
  55. package/dist/shapes.d.ts +97 -0
  56. package/dist/shapes.d.ts.map +1 -0
  57. package/dist/shapes.js +190 -0
  58. package/dist/shapes.js.map +1 -0
  59. package/dist/states.d.ts +134 -0
  60. package/dist/states.d.ts.map +1 -0
  61. package/dist/states.js +244 -0
  62. package/dist/states.js.map +1 -0
  63. package/dist/validate.d.ts +14 -0
  64. package/dist/validate.d.ts.map +1 -1
  65. package/dist/validate.js +29 -2
  66. package/dist/validate.js.map +1 -1
  67. 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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"}