@adminiumjs/manifest 0.3.0 → 0.3.2

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 (51) 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/documents.d.ts +115 -0
  6. package/dist/documents.d.ts.map +1 -0
  7. package/dist/documents.js +171 -0
  8. package/dist/documents.js.map +1 -0
  9. package/dist/formula.d.ts +124 -0
  10. package/dist/formula.d.ts.map +1 -0
  11. package/dist/formula.js +428 -0
  12. package/dist/formula.js.map +1 -0
  13. package/dist/index.d.ts +10 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +10 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/outbox.d.ts +354 -9
  18. package/dist/outbox.d.ts.map +1 -1
  19. package/dist/outbox.js +223 -9
  20. package/dist/outbox.js.map +1 -1
  21. package/dist/plan-model.d.ts +1 -1
  22. package/dist/plan-model.d.ts.map +1 -1
  23. package/dist/public-access.d.ts +48 -7
  24. package/dist/public-access.d.ts.map +1 -1
  25. package/dist/public-access.js +235 -28
  26. package/dist/public-access.js.map +1 -1
  27. package/dist/refs.d.ts +17 -0
  28. package/dist/refs.d.ts.map +1 -1
  29. package/dist/refs.js +12 -0
  30. package/dist/refs.js.map +1 -1
  31. package/dist/sample.d.ts +49 -1
  32. package/dist/sample.d.ts.map +1 -1
  33. package/dist/sample.js +39 -2
  34. package/dist/sample.js.map +1 -1
  35. package/dist/schema.d.ts +2501 -158
  36. package/dist/schema.d.ts.map +1 -1
  37. package/dist/schema.js +518 -26
  38. package/dist/schema.js.map +1 -1
  39. package/dist/shapes.d.ts +97 -0
  40. package/dist/shapes.d.ts.map +1 -0
  41. package/dist/shapes.js +190 -0
  42. package/dist/shapes.js.map +1 -0
  43. package/dist/states.d.ts +134 -0
  44. package/dist/states.d.ts.map +1 -0
  45. package/dist/states.js +244 -0
  46. package/dist/states.js.map +1 -0
  47. package/dist/validate.d.ts +14 -0
  48. package/dist/validate.d.ts.map +1 -1
  49. package/dist/validate.js +29 -2
  50. package/dist/validate.js.map +1 -1
  51. package/package.json +2 -2
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `addOns` — the add-ons an app needs, and the ones it works better with.
3
+ *
4
+ * ```json
5
+ * "addOns": {
6
+ * "requires": [{ "key": "invoices", "range": ">=1.1.0",
7
+ * "reason": { "en-US": "Proposals, invoices and receipts are made by this add-on." } }],
8
+ * "suggests": [{ "key": "holiday-calendars", "range": ">=1.1.0", "checked": true,
9
+ * "reason": { "en-US": "Marks public holidays as days off." } }],
10
+ * "features": [{ "id": "capacity-holidays", "label": { "en-US": "Holidays in Capacity" },
11
+ * "requires": ["holiday-calendars"] }]
12
+ * }
13
+ * ```
14
+ *
15
+ * Installing the app installs (or connects) every add-on it `requires`,
16
+ * before its own tables; one that is missing and cannot be had refuses the
17
+ * install before anything is written, and an add-on an installed app
18
+ * requires cannot be removed. A `suggests` entry is offered with the app,
19
+ * ticked when `checked`. A `feature` names what stops working without its
20
+ * add-ons, so a page may say `"feature": "<id>"` and stay hidden until they
21
+ * are there.
22
+ *
23
+ * Every `range` is a semver range over the add-on's version (`>=1.1.0`,
24
+ * `^1.1.0`); the reasons and labels are in every language the app speaks,
25
+ * US English always among them.
26
+ */
27
+ import { z } from 'zod';
28
+ import { type ReferenceIssue } from './refs.js';
29
+ export declare const addOnsSchema: z.ZodObject<{
30
+ requires: z.ZodOptional<z.ZodArray<z.ZodObject<{
31
+ key: z.ZodString;
32
+ range: z.ZodString;
33
+ reason: z.ZodRecord<z.ZodString, z.ZodString>;
34
+ }, z.core.$strict>>>;
35
+ suggests: z.ZodOptional<z.ZodArray<z.ZodObject<{
36
+ key: z.ZodString;
37
+ range: z.ZodString;
38
+ reason: z.ZodRecord<z.ZodString, z.ZodString>;
39
+ checked: z.ZodOptional<z.ZodBoolean>;
40
+ }, z.core.$strict>>>;
41
+ features: z.ZodOptional<z.ZodArray<z.ZodObject<{
42
+ id: z.ZodString;
43
+ label: z.ZodRecord<z.ZodString, z.ZodString>;
44
+ requires: z.ZodArray<z.ZodString>;
45
+ }, z.core.$strict>>>;
46
+ }, z.core.$strict>;
47
+ export type AddOnNeeds = z.infer<typeof addOnsSchema>;
48
+ /** The add-ons an app names at all: required first, then suggested. */
49
+ export declare function namedAddOns(needs: AddOnNeeds | undefined): {
50
+ key: string;
51
+ need: 'requires' | 'suggests';
52
+ }[];
53
+ /** Whether the app requires this add-on (so it is always there when the app runs). */
54
+ export declare function requiresAddOn(needs: AddOnNeeds | undefined, key: string): boolean;
55
+ /** Everything wrong with an app's `addOns` and the pages that name a feature. */
56
+ export declare function addOnNeedsIssues(m: {
57
+ key: string;
58
+ addOns?: AddOnNeeds | undefined;
59
+ pages?: readonly {
60
+ feature?: string | undefined;
61
+ }[] | undefined;
62
+ }): ReferenceIssue[];
63
+ //# sourceMappingURL=add-ons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-ons.d.ts","sourceRoot":"","sources":["../src/add-ons.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAa9D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;kBAkBd,CAAC;AACZ,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAEtD,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAA;CAAE,EAAE,CAK3G;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEjF;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,SAAS;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,EAAE,GAAG,SAAS,CAAC;CACjE,GAAG,cAAc,EAAE,CA6BnB"}
@@ -0,0 +1,100 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ /**
3
+ * `addOns` — the add-ons an app needs, and the ones it works better with.
4
+ *
5
+ * ```json
6
+ * "addOns": {
7
+ * "requires": [{ "key": "invoices", "range": ">=1.1.0",
8
+ * "reason": { "en-US": "Proposals, invoices and receipts are made by this add-on." } }],
9
+ * "suggests": [{ "key": "holiday-calendars", "range": ">=1.1.0", "checked": true,
10
+ * "reason": { "en-US": "Marks public holidays as days off." } }],
11
+ * "features": [{ "id": "capacity-holidays", "label": { "en-US": "Holidays in Capacity" },
12
+ * "requires": ["holiday-calendars"] }]
13
+ * }
14
+ * ```
15
+ *
16
+ * Installing the app installs (or connects) every add-on it `requires`,
17
+ * before its own tables; one that is missing and cannot be had refuses the
18
+ * install before anything is written, and an add-on an installed app
19
+ * requires cannot be removed. A `suggests` entry is offered with the app,
20
+ * ticked when `checked`. A `feature` names what stops working without its
21
+ * add-ons, so a page may say `"feature": "<id>"` and stay hidden until they
22
+ * are there.
23
+ *
24
+ * Every `range` is a semver range over the add-on's version (`>=1.1.0`,
25
+ * `^1.1.0`); the reasons and labels are in every language the app speaks,
26
+ * US English always among them.
27
+ */
28
+ import { z } from 'zod';
29
+ import { labelsSchema } from './refs.js';
30
+ import { parseSemverRange } from './semver.js';
31
+ const addOnKeySchema = z.string().regex(/^[a-z][a-z0-9-]{1,79}$/, 'an add-on key');
32
+ const rangeSchema = z
33
+ .string()
34
+ .min(1)
35
+ .max(120)
36
+ .refine((range) => parseSemverRange(range) !== null, { message: 'a semver range such as ">=1.1.0" or "^1.1.0"' });
37
+ const needSchema = z.object({ key: addOnKeySchema, range: rangeSchema, reason: labelsSchema }).strict();
38
+ export const addOnsSchema = z
39
+ .object({
40
+ requires: z.array(needSchema).min(1).max(8).optional(),
41
+ suggests: z.array(needSchema.extend({ checked: z.boolean().optional() }).strict()).min(1).max(8).optional(),
42
+ features: z
43
+ .array(z
44
+ .object({
45
+ id: z.string().regex(/^[a-z][a-z0-9-]{0,39}$/, 'a feature id is kebab-case'),
46
+ label: labelsSchema,
47
+ requires: z.array(addOnKeySchema).min(1).max(4),
48
+ })
49
+ .strict())
50
+ .min(1)
51
+ .max(16)
52
+ .optional(),
53
+ })
54
+ .strict();
55
+ /** The add-ons an app names at all: required first, then suggested. */
56
+ export function namedAddOns(needs) {
57
+ return [
58
+ ...(needs?.requires ?? []).map((n) => ({ key: n.key, need: 'requires' })),
59
+ ...(needs?.suggests ?? []).map((n) => ({ key: n.key, need: 'suggests' })),
60
+ ];
61
+ }
62
+ /** Whether the app requires this add-on (so it is always there when the app runs). */
63
+ export function requiresAddOn(needs, key) {
64
+ return (needs?.requires ?? []).some((n) => n.key === key);
65
+ }
66
+ /** Everything wrong with an app's `addOns` and the pages that name a feature. */
67
+ export function addOnNeedsIssues(m) {
68
+ const out = [];
69
+ const needs = m.addOns;
70
+ const seen = new Map();
71
+ for (const list of ['requires', 'suggests']) {
72
+ (needs?.[list] ?? []).forEach((need, i) => {
73
+ const at = ['addOns', list, i, 'key'];
74
+ if (need.key === m.key)
75
+ out.push({ path: at, message: 'an app does not need itself' });
76
+ const earlier = seen.get(need.key);
77
+ if (earlier !== undefined)
78
+ out.push({ path: at, message: `"${need.key}" is already named in ${earlier}` });
79
+ seen.set(need.key, list);
80
+ });
81
+ }
82
+ const features = new Set();
83
+ (needs?.features ?? []).forEach((feature, f) => {
84
+ if (features.has(feature.id))
85
+ out.push({ path: ['addOns', 'features', f, 'id'], message: `"${feature.id}" is declared twice` });
86
+ features.add(feature.id);
87
+ feature.requires.forEach((key, k) => {
88
+ if (!seen.has(key)) {
89
+ out.push({ path: ['addOns', 'features', f, 'requires', k], message: `"${key}" is neither required nor suggested by the app` });
90
+ }
91
+ });
92
+ });
93
+ (m.pages ?? []).forEach((page, p) => {
94
+ if (page.feature !== undefined && !features.has(page.feature)) {
95
+ out.push({ path: ['pages', p, 'feature'], message: `"${page.feature}" is not one of the app's addOns.features` });
96
+ }
97
+ });
98
+ return out;
99
+ }
100
+ //# sourceMappingURL=add-ons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-ons.js","sourceRoot":"","sources":["../src/add-ons.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Document profiles an app ships: which of its columns make a printed
3
+ * document (an invoice, a receipt, a statement), drawn by an add-on that
4
+ * renders documents.
5
+ *
6
+ * ```json
7
+ * "documents": [{
8
+ * "kind": "receipt", "addOn": "invoices", "table": "sales",
9
+ * "name": { "en-US": "Till receipt" },
10
+ * "mapping": {
11
+ * "number": { "column": "number" },
12
+ * "customerName": { "via": "customer_id", "column": "name" },
13
+ * "lines": { "collection": { "table": "sale_lines", "via": "sale_id", "orderBy": "position",
14
+ * "columns": { "description": "name", "qty": "qty", "amount": "amount" } } }
15
+ * }
16
+ * }]
17
+ * ```
18
+ *
19
+ * A slot reads a column of the row, a column of a row a foreign key points at
20
+ * (`via`), or a list of child rows in order. A list may leave rows out by
21
+ * their own columns, the way a statement's sources do: `where` keeps only the
22
+ * rows whose column holds one of its values, and `unless` drops a row whose
23
+ * column is true or set (a voided line: `"unless": "voided"`). The slots
24
+ * themselves are the add-on's words; Adminium checks the columns here and the
25
+ * slots against the add-on when the profile is made at install. A profile is
26
+ * the app's: made with real table names when the app is installed, removed
27
+ * with it.
28
+ *
29
+ * A statement is a document over one row (a client) and a period: it lists
30
+ * the documents and payments that point at that row, so it names them
31
+ * instead of one child list.
32
+ */
33
+ import { z } from 'zod';
34
+ import { type ReferenceIssue, type TableIndex } from './refs.js';
35
+ export declare const slotMappingSchema: z.ZodUnion<readonly [z.ZodObject<{
36
+ column: z.ZodString;
37
+ }, z.core.$strict>, z.ZodObject<{
38
+ via: z.ZodString;
39
+ column: z.ZodString;
40
+ }, z.core.$strict>, z.ZodObject<{
41
+ collection: z.ZodObject<{
42
+ table: z.ZodString;
43
+ via: z.ZodString;
44
+ orderBy: z.ZodOptional<z.ZodString>;
45
+ columns: z.ZodRecord<z.ZodString, z.ZodString>;
46
+ where: z.ZodOptional<z.ZodObject<{
47
+ column: z.ZodString;
48
+ in: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
49
+ }, z.core.$strict>>;
50
+ unless: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$strict>;
52
+ }, z.core.$strict>]>;
53
+ export type SlotMapping = z.infer<typeof slotMappingSchema>;
54
+ export declare const appDocumentSchema: z.ZodObject<{
55
+ kind: z.ZodString;
56
+ addOn: z.ZodString;
57
+ table: z.ZodString;
58
+ name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
59
+ mapping: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
60
+ column: z.ZodString;
61
+ }, z.core.$strict>, z.ZodObject<{
62
+ via: z.ZodString;
63
+ column: z.ZodString;
64
+ }, z.core.$strict>, z.ZodObject<{
65
+ collection: z.ZodObject<{
66
+ table: z.ZodString;
67
+ via: z.ZodString;
68
+ orderBy: z.ZodOptional<z.ZodString>;
69
+ columns: z.ZodRecord<z.ZodString, z.ZodString>;
70
+ where: z.ZodOptional<z.ZodObject<{
71
+ column: z.ZodString;
72
+ in: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
73
+ }, z.core.$strict>>;
74
+ unless: z.ZodOptional<z.ZodString>;
75
+ }, z.core.$strict>;
76
+ }, z.core.$strict>]>>;
77
+ statement: z.ZodOptional<z.ZodObject<{
78
+ documents: z.ZodObject<{
79
+ table: z.ZodString;
80
+ via: z.ZodString;
81
+ date: z.ZodString;
82
+ amount: z.ZodString;
83
+ number: z.ZodOptional<z.ZodString>;
84
+ where: z.ZodOptional<z.ZodObject<{
85
+ column: z.ZodString;
86
+ in: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
87
+ }, z.core.$strict>>;
88
+ unless: z.ZodOptional<z.ZodString>;
89
+ }, z.core.$strict>;
90
+ payments: z.ZodObject<{
91
+ table: z.ZodString;
92
+ via: z.ZodString;
93
+ date: z.ZodString;
94
+ amount: z.ZodString;
95
+ number: z.ZodOptional<z.ZodString>;
96
+ where: z.ZodOptional<z.ZodObject<{
97
+ column: z.ZodString;
98
+ in: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
99
+ }, z.core.$strict>>;
100
+ unless: z.ZodOptional<z.ZodString>;
101
+ }, z.core.$strict>;
102
+ }, z.core.$strict>>;
103
+ feature: z.ZodOptional<z.ZodString>;
104
+ requestValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
105
+ }, z.core.$strict>;
106
+ export type AppDocument = z.infer<typeof appDocumentSchema>;
107
+ /** Every column a mapping names, against the manifest's tables. */
108
+ export declare function mappingIssues(table: string, mapping: Readonly<Record<string, SlotMapping>>, index: TableIndex, at: (...rest: (string | number)[]) => (string | number)[]): ReferenceIssue[];
109
+ /** Everything wrong with an app's `documents` against its tables and add-on needs. */
110
+ export declare function appDocumentIssues(documents: readonly AppDocument[], ctx: {
111
+ index: TableIndex;
112
+ addOns: ReadonlySet<string>;
113
+ features: ReadonlySet<string>;
114
+ }): ReferenceIssue[];
115
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAA2B,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAO1F,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;oBAmB5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAgB5D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkBnB,CAAC;AACZ,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,mEAAmE;AACnE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,EAC9C,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GACxD,cAAc,EAAE,CA2BlB;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,WAAW,EAAE,EACjC,GAAG,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,GACrF,cAAc,EAAE,CAqClB"}
@@ -0,0 +1,171 @@
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. A list may leave rows out by
22
+ * their own columns, the way a statement's sources do: `where` keeps only the
23
+ * rows whose column holds one of its values, and `unless` drops a row whose
24
+ * column is true or set (a voided line: `"unless": "voided"`). The slots
25
+ * themselves are the add-on's words; Adminium checks the columns here and the
26
+ * slots against the add-on when the profile is made at install. A profile is
27
+ * the app's: made with real table names when the app is installed, removed
28
+ * with it.
29
+ *
30
+ * A statement is a document over one row (a client) and a period: it lists
31
+ * the documents and payments that point at that row, so it names them
32
+ * instead of one child list.
33
+ */
34
+ import { z } from 'zod';
35
+ import { refSchema, textOrLabels } from './refs.js';
36
+ const slotId = z.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/, 'a slot id');
37
+ /** Only rows whose column holds one of the values (sent invoices, not drafts). */
38
+ const whereSchema = z.object({ column: refSchema, in: z.array(z.union([z.string(), z.number(), z.boolean()])).min(1).max(16) }).strict();
39
+ export const slotMappingSchema = z.union([
40
+ z.object({ column: refSchema }).strict(),
41
+ z.object({ via: refSchema, column: refSchema }).strict(),
42
+ z
43
+ .object({
44
+ collection: z
45
+ .object({
46
+ table: refSchema,
47
+ via: refSchema,
48
+ orderBy: refSchema.optional(),
49
+ columns: z.record(slotId, refSchema),
50
+ /** Only the child rows whose column holds one of the values. */
51
+ where: whereSchema.optional(),
52
+ /** A child row whose column is true (or set) is left out: a voided line. */
53
+ unless: refSchema.optional(),
54
+ })
55
+ .strict(),
56
+ })
57
+ .strict(),
58
+ ]);
59
+ /** The rows a statement lists for its one row, and the columns it reads of them. */
60
+ const statementSourceSchema = z
61
+ .object({
62
+ table: refSchema,
63
+ via: refSchema,
64
+ date: refSchema,
65
+ amount: refSchema,
66
+ number: refSchema.optional(),
67
+ where: whereSchema.optional(),
68
+ /** A row whose column is true (or set) is left out: a voided payment. */
69
+ unless: refSchema.optional(),
70
+ })
71
+ .strict();
72
+ export const appDocumentSchema = z
73
+ .object({
74
+ kind: z.string().regex(/^[a-z][a-z0-9-]*$/, 'a document kind'),
75
+ addOn: z.string().regex(/^[a-z][a-z0-9-]{1,79}$/, 'an add-on key'),
76
+ table: refSchema,
77
+ name: textOrLabels,
78
+ mapping: z.record(slotId, slotMappingSchema),
79
+ statement: z.object({ documents: statementSourceSchema, payments: statementSourceSchema }).strict().optional(),
80
+ /** The feature this document belongs to (see `addOns.features`). */
81
+ feature: z.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional(),
82
+ /**
83
+ * The slots the app's own screen may fill when it asks for the document
84
+ * (a label sheet's `count`), and no others: a slot nothing maps, with no
85
+ * default, holding a number or a text. Anything a request sends for a slot
86
+ * not listed here is refused.
87
+ */
88
+ requestValues: z.array(slotId).min(1).max(8).optional(),
89
+ })
90
+ .strict();
91
+ /** Every column a mapping names, against the manifest's tables. */
92
+ export function mappingIssues(table, mapping, index, at) {
93
+ const out = [];
94
+ for (const [slot, source] of Object.entries(mapping)) {
95
+ const here = (...rest) => at('mapping', slot, ...rest);
96
+ if ('collection' in source) {
97
+ const c = source.collection;
98
+ if (index.table(c.table) === undefined) {
99
+ out.push({ path: here('collection', 'table'), message: `"${c.table}" is not a table of this manifest` });
100
+ continue;
101
+ }
102
+ const via = index.column(c.table, c.via);
103
+ if (via?.type !== 'fk' || via.references !== table)
104
+ out.push({ path: here('collection', 'via'), message: `"${c.table}.${c.via}" does not point at "${table}"` });
105
+ for (const ref of [...Object.values(c.columns), ...[c.orderBy, c.where?.column, c.unless].filter((r) => r !== undefined)]) {
106
+ if (!index.has(c.table, ref))
107
+ out.push({ path: here('collection'), message: `"${c.table}" has no column "${ref}"` });
108
+ }
109
+ }
110
+ else if ('via' in source) {
111
+ const via = index.column(table, source.via);
112
+ if (via?.type !== 'fk' || via.references === undefined) {
113
+ out.push({ path: here('via'), message: `"${source.via}" is not a foreign key of "${table}"` });
114
+ }
115
+ else if (!index.has(via.references, source.column)) {
116
+ out.push({ path: here('column'), message: `"${via.references}" has no column "${source.column}"` });
117
+ }
118
+ }
119
+ else if (!index.has(table, source.column)) {
120
+ out.push({ path: here('column'), message: `"${table}" has no column "${source.column}"` });
121
+ }
122
+ }
123
+ return out;
124
+ }
125
+ /** Everything wrong with an app's `documents` against its tables and add-on needs. */
126
+ export function appDocumentIssues(documents, ctx) {
127
+ const out = [];
128
+ const kinds = new Set();
129
+ documents.forEach((doc, d) => {
130
+ const at = (...rest) => ['documents', d, ...rest];
131
+ const key = `${doc.table}|${doc.kind}`;
132
+ if (kinds.has(key))
133
+ out.push({ path: at('kind'), message: `"${doc.table}" already has a "${doc.kind}" document` });
134
+ kinds.add(key);
135
+ if (!ctx.addOns.has(doc.addOn))
136
+ out.push({ path: at('addOn'), message: `"${doc.addOn}" is neither required nor suggested by the app` });
137
+ if (doc.feature !== undefined && !ctx.features.has(doc.feature))
138
+ out.push({ path: at('feature'), message: `"${doc.feature}" is not one of the app's addOns.features` });
139
+ if (ctx.index.table(doc.table) === undefined) {
140
+ out.push({ path: at('table'), message: `"${doc.table}" is not a table of this app` });
141
+ return;
142
+ }
143
+ out.push(...mappingIssues(doc.table, doc.mapping, ctx.index, at));
144
+ (doc.requestValues ?? []).forEach((slot, n) => {
145
+ if (doc.requestValues.indexOf(slot) !== n)
146
+ out.push({ path: at('requestValues', n), message: `"${slot}" is listed twice` });
147
+ // A mapped slot prints the row; a value sent for it would print something the row does not say.
148
+ else if (doc.mapping[slot] !== undefined)
149
+ out.push({ path: at('requestValues', n), message: `"${slot}" is mapped, so a request may not fill it` });
150
+ });
151
+ if (doc.statement !== undefined) {
152
+ for (const part of ['documents', 'payments']) {
153
+ const s = doc.statement[part];
154
+ const here = (...rest) => at('statement', part, ...rest);
155
+ if (ctx.index.table(s.table) === undefined) {
156
+ out.push({ path: here('table'), message: `"${s.table}" is not a table of this app` });
157
+ continue;
158
+ }
159
+ const via = ctx.index.column(s.table, s.via);
160
+ if (via?.type !== 'fk' || via.references !== doc.table)
161
+ out.push({ path: here('via'), message: `"${s.table}.${s.via}" does not point at "${doc.table}"` });
162
+ for (const ref of [s.date, s.amount, s.number, s.unless, s.where?.column]) {
163
+ if (ref !== undefined && !ctx.index.has(s.table, ref))
164
+ out.push({ path: here(), message: `"${s.table}" has no column "${ref}"` });
165
+ }
166
+ }
167
+ }
168
+ });
169
+ return out;
170
+ }
171
+ //# 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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"}