@adminiumjs/manifest 0.3.39 → 0.3.41
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/forms.d.ts +120 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +244 -0
- package/dist/forms.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/mail-class.d.ts +11 -3
- package/dist/mail-class.d.ts.map +1 -1
- package/dist/mail-class.js +13 -1
- package/dist/mail-class.js.map +1 -1
- package/dist/mail-classes.d.ts +7 -0
- package/dist/mail-classes.d.ts.map +1 -1
- package/dist/mail-classes.js +6 -0
- package/dist/mail-classes.js.map +1 -1
- package/dist/outbox.d.ts +7 -0
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +66 -1
- package/dist/outbox.js.map +1 -1
- package/dist/public-access.d.ts +19 -0
- package/dist/public-access.d.ts.map +1 -1
- package/dist/public-access.js +93 -3
- package/dist/public-access.js.map +1 -1
- package/dist/schema.d.ts +165 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +81 -3
- package/dist/schema.js.map +1 -1
- package/dist/steps.d.ts +3 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +44 -1
- package/dist/steps.js.map +1 -1
- package/dist/words.d.ts +1 -1
- package/dist/words.d.ts.map +1 -1
- package/dist/words.js +71 -0
- package/dist/words.js.map +1 -1
- package/package.json +2 -2
package/dist/public-access.js
CHANGED
|
@@ -31,12 +31,29 @@ import { publicConsentSchema } from './people.js';
|
|
|
31
31
|
import { kindOf, rulesOf } from './capacity.js';
|
|
32
32
|
import { formulaColumns } from './formula.js';
|
|
33
33
|
import { refSchema, scalarSchema, settingRefSchema, valueFits, } from './refs.js';
|
|
34
|
+
import { FORMS_BECAME_TABLE_MAX } from './forms.js';
|
|
34
35
|
import { momentIssues, momentOffsetSchema, plainMomentSchema, wallTimeSchema } from './refs.js';
|
|
35
36
|
import { conditionIssues, linkedConditionSchema, reachedOnlyByUndo, stateConditionSchema } from './states.js';
|
|
36
37
|
/** The fewest characters (its prefix apart) of a code that opens its own row (`unlockBy.self`). */
|
|
37
38
|
export const SELF_UNLOCK_MIN_LENGTH = 10;
|
|
38
39
|
/** The key every entry uses unless it names another. */
|
|
39
40
|
export const CUSTOMER_KEY = 'customer';
|
|
41
|
+
/**
|
|
42
|
+
* The reserved key name of the entries that open a table to FORMS: an answer
|
|
43
|
+
* to a form may become a new row of the table, through this entry's
|
|
44
|
+
* `writable` columns, `defaults` and `anonymous` limits. No browser key of
|
|
45
|
+
* this name is ever made, and the public API never serves such an entry: it
|
|
46
|
+
* is read by the forms door alone, so opening a table to forms does not open
|
|
47
|
+
* it to the public API.
|
|
48
|
+
*/
|
|
49
|
+
export const FORMS_KEY = 'forms';
|
|
50
|
+
/**
|
|
51
|
+
* What a forms entry may say. A form only ever creates a row (it never reads
|
|
52
|
+
* one back, changes one or signs anyone in), so every word about reading,
|
|
53
|
+
* claims, sessions, children, files and the entry's own confirmation mail is
|
|
54
|
+
* left out: forms send their own.
|
|
55
|
+
*/
|
|
56
|
+
export const FORMS_ENTRY_WORDS = ['table', 'methods', 'key', 'writable', 'writableValues', 'defaults', 'requires', 'agrees', 'anonymous', 'requireSetting', 'module', 'source'];
|
|
40
57
|
/** The longest a plain-text value may be unless its column says longer: a name. */
|
|
41
58
|
export const PLAIN_TEXT_MAX = 80;
|
|
42
59
|
/** The most digits a plain-text column may take in all: "table 12", "2 without onions", never a phone number. */
|
|
@@ -592,6 +609,13 @@ export const publicAccessSchema = z
|
|
|
592
609
|
.optional(),
|
|
593
610
|
/** Which browser key serves the entry: `customer`, or one of `publicKeys`. */
|
|
594
611
|
key: keyNameSchema.optional(),
|
|
612
|
+
/**
|
|
613
|
+
* A forms entry only: the one `writable` text column a form's fixed
|
|
614
|
+
* "Source" value goes into (an enquiry's "From form": the form's name, or
|
|
615
|
+
* what the owner words it). The builder shows that row on this column;
|
|
616
|
+
* the answer door writes it as the form's settings say, like any other.
|
|
617
|
+
*/
|
|
618
|
+
source: refSchema.optional(),
|
|
595
619
|
/** The part of the app the entry belongs to: closed while it is off (see `modules.ts`). */
|
|
596
620
|
module: z.string().regex(/^[a-z][a-z0-9-]{0,39}$/, 'a module id is kebab-case').optional(),
|
|
597
621
|
/**
|
|
@@ -699,6 +723,9 @@ export const publicKeysSchema = z
|
|
|
699
723
|
.record(keyNameSchema, publicKeySchema)
|
|
700
724
|
.refine((keys) => !(CUSTOMER_KEY in keys) || Object.keys(keys[CUSTOMER_KEY] ?? {}).every((field) => field === 'peak'), {
|
|
701
725
|
message: '"customer" is the app\'s own key: only its peak is said here',
|
|
726
|
+
})
|
|
727
|
+
.refine((keys) => !(FORMS_KEY in keys), {
|
|
728
|
+
message: '"forms" is the name of the entries forms write through, and no browser key: name the key otherwise',
|
|
702
729
|
});
|
|
703
730
|
/**
|
|
704
731
|
* The own links of the rows a person of `identityTable` holds: each table a
|
|
@@ -765,6 +792,65 @@ export function resendAddressOf(outbox, kind, table, columnsOf) {
|
|
|
765
792
|
return null;
|
|
766
793
|
return { columns, ...(person === undefined ? {} : { person }) };
|
|
767
794
|
}
|
|
795
|
+
/** Whether anyone at all may read rows through an entry: no claim, no session, no person, no parent row it is read with. */
|
|
796
|
+
function readByAnyone(entry) {
|
|
797
|
+
return (entry.methods.includes('GET') &&
|
|
798
|
+
entry.kind !== 'availability' &&
|
|
799
|
+
entry.claim === undefined &&
|
|
800
|
+
entry.claimedBy === undefined &&
|
|
801
|
+
entry.identity === undefined &&
|
|
802
|
+
entry.visibleWith === undefined &&
|
|
803
|
+
entry.level === undefined);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* A forms entry's own rules: it creates (POST) and nothing else, under the
|
|
807
|
+
* anonymous limits every stranger's create has, with the columns a form may
|
|
808
|
+
* fill named; one per table; and no entry on any key lets anyone read the
|
|
809
|
+
* rows strangers add through it.
|
|
810
|
+
*/
|
|
811
|
+
function formsEntryIssues(entry, i, entries, appKey, index) {
|
|
812
|
+
const out = [];
|
|
813
|
+
const at = (...rest) => ['publicAccess', i, ...rest];
|
|
814
|
+
// Each answer keeps the row it became by its table's stored name: it must fit an answers table, whole.
|
|
815
|
+
const stored = appKey === undefined ? 0 : appKey.length + 1 + entry.table.length;
|
|
816
|
+
if (stored > FORMS_BECAME_TABLE_MAX) {
|
|
817
|
+
out.push({ path: at('table'), message: `a form's answer names the row it became by "<app>:<table>", at most ${String(FORMS_BECAME_TABLE_MAX)} characters: "${appKey ?? ''}:${entry.table}" is ${String(stored)}` });
|
|
818
|
+
}
|
|
819
|
+
const allowed = FORMS_ENTRY_WORDS;
|
|
820
|
+
for (const word of Object.keys(entry)) {
|
|
821
|
+
if (!allowed.includes(word))
|
|
822
|
+
out.push({ path: at(word), message: `a forms entry only lets a form add a row: "${word}" is not said on it` });
|
|
823
|
+
}
|
|
824
|
+
if (entry.methods.length !== 1 || entry.methods[0] !== 'POST')
|
|
825
|
+
out.push({ path: at('methods'), message: 'a forms entry adds a row and does nothing else: its methods are ["POST"]' });
|
|
826
|
+
if (entry.anonymous === undefined)
|
|
827
|
+
out.push({ path: at('anonymous'), message: 'a form is sent by strangers: a forms entry says its anonymous limits' });
|
|
828
|
+
if ((entry.writable ?? []).length === 0)
|
|
829
|
+
out.push({ path: at('writable'), message: 'a forms entry names the columns a form may fill (writable)' });
|
|
830
|
+
if (entry.source !== undefined) {
|
|
831
|
+
const named = index.column(entry.table, entry.source);
|
|
832
|
+
if (!(entry.writable ?? []).includes(entry.source)) {
|
|
833
|
+
out.push({ path: at('source'), message: `a form's source goes into one of the columns a form may fill: put "${entry.source}" in writable` });
|
|
834
|
+
}
|
|
835
|
+
else if (named !== undefined && (named.type === 'fk' || named.references !== undefined)) {
|
|
836
|
+
out.push({ path: at('source'), message: `"${entry.source}" links to another row: a form's source is words, so it goes into a text column` });
|
|
837
|
+
}
|
|
838
|
+
else if (named !== undefined && named.type !== 'text') {
|
|
839
|
+
out.push({ path: at('source'), message: `"${entry.source}" holds ${named.type}: a form's source is words, so it goes into a text column` });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (entries.findIndex((other) => other.key === FORMS_KEY && other.table === entry.table) !== i) {
|
|
843
|
+
out.push({ path: at('table'), message: `"${entry.table}" is opened to forms by another entry already: one forms entry per table` });
|
|
844
|
+
}
|
|
845
|
+
const reader = entries.findIndex((other) => other !== entry && other.table === entry.table && readByAnyone(other));
|
|
846
|
+
if (reader !== -1) {
|
|
847
|
+
out.push({
|
|
848
|
+
path: at('table'),
|
|
849
|
+
message: `strangers add rows to "${entry.table}" through forms, and entry ${String(reader)} lets anyone read its rows: everyone could read what people typed into a form`,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
return out;
|
|
853
|
+
}
|
|
768
854
|
/** Everything in `publicAccess` and `publicKeys` that names something undeclared, or breaks a rule. */
|
|
769
855
|
export function publicAccessIssues(entries, ctx) {
|
|
770
856
|
const out = [];
|
|
@@ -894,9 +980,13 @@ export function publicAccessIssues(entries, ctx) {
|
|
|
894
980
|
}
|
|
895
981
|
const column = (ref) => index.column(entry.table, ref);
|
|
896
982
|
const key = entry.key ?? CUSTOMER_KEY;
|
|
897
|
-
if (key !== CUSTOMER_KEY && ctx.publicKeys?.[key] === undefined) {
|
|
983
|
+
if (key !== CUSTOMER_KEY && key !== FORMS_KEY && ctx.publicKeys?.[key] === undefined) {
|
|
898
984
|
out.push({ path: at('key'), message: `"${key}" is not one of the app's publicKeys` });
|
|
899
985
|
}
|
|
986
|
+
if (key === FORMS_KEY)
|
|
987
|
+
out.push(...formsEntryIssues(entry, i, entries, ctx.appKey, index));
|
|
988
|
+
else if (entry.source !== undefined)
|
|
989
|
+
out.push({ path: at('source'), message: 'a source is said only on a forms entry (key "forms"): the column a form\'s fixed "Source" value goes into' });
|
|
900
990
|
for (const list of ['select', 'writable']) {
|
|
901
991
|
for (const ref of entry[list] ?? []) {
|
|
902
992
|
if (!has(entry.table, ref))
|
|
@@ -906,8 +996,8 @@ export function publicAccessIssues(entries, ctx) {
|
|
|
906
996
|
// An entry anyone may call (no claim, no parent, no session asked) shows no personal data: the install refuses it too.
|
|
907
997
|
const sessionOnly = entry.level !== undefined && entry.claim === undefined && entry.claimedBy === undefined && entry.visibleWith === undefined;
|
|
908
998
|
const anyone = entry.claim === undefined && entry.visibleWith === undefined && (entry.claimedBy === undefined || entry.claimedBy.optional === true) && !sessionOnly;
|
|
909
|
-
// An availability entry shows no row, whatever it names.
|
|
910
|
-
if (anyone && entry.kind !== 'availability') {
|
|
999
|
+
// An availability entry shows no row, whatever it names; nor does a forms entry, which answers nobody with the row.
|
|
1000
|
+
if (anyone && entry.kind !== 'availability' && key !== FORMS_KEY) {
|
|
911
1001
|
// No select shows every column but a code Adminium makes and a secret, as the install reads it.
|
|
912
1002
|
const shown = entry.select ??
|
|
913
1003
|
table.columns.filter((c) => c.rules?.['code'] === undefined && c.rules?.['secret'] !== true).map((c) => c.ref);
|