@flaredev/core 0.1.2 → 0.3.0
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/chunk-434JXSDV.js +410 -0
- package/dist/index.d.ts +109 -35
- package/dist/index.js +352 -80
- package/dist/{define-D5CtCkOm.d.ts → policy-BqRg9RVM.d.ts} +80 -4
- package/dist/server.d.ts +29 -2
- package/dist/server.js +273 -7
- package/package.json +2 -1
- package/dist/chunk-DARDHZPX.js +0 -192
package/dist/index.js
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_FILE_MAX_BYTES,
|
|
3
|
+
FILE_CATEGORIES,
|
|
4
|
+
PolicyError,
|
|
2
5
|
RESERVED_FIELD_NAMES,
|
|
3
6
|
ResourceDefinitionError,
|
|
7
|
+
STRING_FORMATS,
|
|
8
|
+
allowedActions,
|
|
4
9
|
camelCase,
|
|
10
|
+
can,
|
|
5
11
|
columnName,
|
|
12
|
+
countries,
|
|
13
|
+
countryName,
|
|
6
14
|
createValidators,
|
|
7
15
|
defaultFieldLabel,
|
|
16
|
+
definePolicy,
|
|
8
17
|
defineResource,
|
|
18
|
+
field,
|
|
9
19
|
fieldSchema,
|
|
10
20
|
fileKeyPrefix,
|
|
21
|
+
fileMaxBytes,
|
|
22
|
+
flagEmoji,
|
|
23
|
+
formatPhone,
|
|
11
24
|
humanize,
|
|
25
|
+
isColor,
|
|
26
|
+
isCountryCode,
|
|
27
|
+
isDomain,
|
|
28
|
+
isPhoneNumber,
|
|
29
|
+
isSlug,
|
|
12
30
|
kebabCase,
|
|
31
|
+
mimeTypesFor,
|
|
32
|
+
normalizeDomain,
|
|
13
33
|
pascalCase,
|
|
14
34
|
pluralize,
|
|
35
|
+
slugify,
|
|
15
36
|
snakeCase,
|
|
37
|
+
splitPhone,
|
|
16
38
|
storedFields,
|
|
39
|
+
toE164,
|
|
17
40
|
words
|
|
18
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-434JXSDV.js";
|
|
19
42
|
|
|
20
43
|
// src/password.ts
|
|
21
44
|
var ALGORITHM = "pbkdf2-sha256";
|
|
@@ -466,48 +489,6 @@ function randomLetters(length = 8) {
|
|
|
466
489
|
return Array.from(bytes, (byte) => String.fromCharCode(97 + byte % 26)).join("");
|
|
467
490
|
}
|
|
468
491
|
|
|
469
|
-
// src/resource/fields.ts
|
|
470
|
-
var FILE_CATEGORIES = {
|
|
471
|
-
image: ["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"],
|
|
472
|
-
pdf: ["application/pdf"],
|
|
473
|
-
video: ["video/*"],
|
|
474
|
-
audio: ["audio/*"],
|
|
475
|
-
text: ["text/plain"],
|
|
476
|
-
csv: ["text/csv"],
|
|
477
|
-
document: [
|
|
478
|
-
"application/msword",
|
|
479
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
480
|
-
"application/vnd.oasis.opendocument.text"
|
|
481
|
-
],
|
|
482
|
-
spreadsheet: [
|
|
483
|
-
"application/vnd.ms-excel",
|
|
484
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
485
|
-
"application/vnd.oasis.opendocument.spreadsheet"
|
|
486
|
-
],
|
|
487
|
-
archive: ["application/zip"]
|
|
488
|
-
};
|
|
489
|
-
function mimeTypesFor(accept) {
|
|
490
|
-
return [...new Set(accept.flatMap((category) => FILE_CATEGORIES[category]))];
|
|
491
|
-
}
|
|
492
|
-
var DEFAULT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
493
|
-
var fileMaxBytes = (field2) => field2.maxBytes ?? DEFAULT_FILE_MAX_BYTES;
|
|
494
|
-
function build(kind, options, extra = {}) {
|
|
495
|
-
return { kind, ...extra, ...options, required: options?.required !== false };
|
|
496
|
-
}
|
|
497
|
-
var field = {
|
|
498
|
-
string: (options) => build("string", options),
|
|
499
|
-
text: (options) => build("text", options, { list: false, searchable: false }),
|
|
500
|
-
int: (options) => build("int", options),
|
|
501
|
-
float: (options) => build("float", options),
|
|
502
|
-
boolean: (options) => build("boolean", options),
|
|
503
|
-
date: (options) => build("date", options),
|
|
504
|
-
datetime: (options) => build("datetime", options),
|
|
505
|
-
enum: (values, options) => build("enum", options, { options: values }),
|
|
506
|
-
file: (accept, options) => build("file", options, { accept, list: false, sortable: false }),
|
|
507
|
-
belongsTo: (target, options) => build("belongsTo", options, { target }),
|
|
508
|
-
hasMany: (target, options) => ({ kind: "hasMany", target, ...options, required: false })
|
|
509
|
-
};
|
|
510
|
-
|
|
511
492
|
// src/resource/relations.ts
|
|
512
493
|
var RelationError = class extends Error {
|
|
513
494
|
constructor(message) {
|
|
@@ -585,7 +566,7 @@ function statusTone(value) {
|
|
|
585
566
|
return TONES.find(([, pattern]) => pattern.test(normalized))?.[0] ?? "neutral";
|
|
586
567
|
}
|
|
587
568
|
function optionLabel(def, value) {
|
|
588
|
-
if (def.kind === "enum" && def.optionLabels?.[value]) return def.optionLabels[value];
|
|
569
|
+
if ((def.kind === "enum" || def.kind === "multiselect") && def.optionLabels?.[value]) return def.optionLabels[value];
|
|
589
570
|
const text2 = value.replace(/[_-]+/g, " ").trim();
|
|
590
571
|
return text2 ? text2[0].toUpperCase() + text2.slice(1) : value;
|
|
591
572
|
}
|
|
@@ -610,6 +591,12 @@ function formatValue(def, value, options = {}) {
|
|
|
610
591
|
}
|
|
611
592
|
case "enum":
|
|
612
593
|
return optionLabel(def, String(value));
|
|
594
|
+
case "multiselect":
|
|
595
|
+
return Array.isArray(value) && value.length ? value.map((item) => optionLabel(def, String(item))).join(", ") : "\u2014";
|
|
596
|
+
case "string":
|
|
597
|
+
if (def.format === "tel") return formatPhone(String(value));
|
|
598
|
+
if (def.format === "country") return `${flagEmoji(String(value))} ${countryName(String(value), locale.split("-")[0])}`.trim();
|
|
599
|
+
return String(value);
|
|
613
600
|
case "file":
|
|
614
601
|
return String(value).split("/").pop().replace(/^[0-9a-f-]{36}-/, "");
|
|
615
602
|
default:
|
|
@@ -623,6 +610,7 @@ function initialFormValues(resource, record) {
|
|
|
623
610
|
for (const [key, def] of storedFields(resource)) {
|
|
624
611
|
const source = record ? record[key] : "default" in def ? def.default : void 0;
|
|
625
612
|
if (def.kind === "boolean") values[key] = source === true || source === 1;
|
|
613
|
+
else if (def.kind === "multiselect") values[key] = JSON.stringify(Array.isArray(source) ? source : []);
|
|
626
614
|
else if (source === null || source === void 0) values[key] = "";
|
|
627
615
|
else if (source instanceof Date) values[key] = source.toISOString();
|
|
628
616
|
else values[key] = String(source);
|
|
@@ -637,6 +625,12 @@ function formValuesToInput(resource, values, mode) {
|
|
|
637
625
|
input[key] = raw === true;
|
|
638
626
|
continue;
|
|
639
627
|
}
|
|
628
|
+
if (def.kind === "multiselect") {
|
|
629
|
+
const picked = parseMultiValue(raw);
|
|
630
|
+
if (picked.length === 0 && mode === "create" && "default" in def && def.default !== void 0) continue;
|
|
631
|
+
input[key] = picked.length === 0 && !def.required ? null : picked;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
640
634
|
const text2 = typeof raw === "string" ? raw : "";
|
|
641
635
|
const empty = text2.trim() === "";
|
|
642
636
|
if (empty) {
|
|
@@ -655,6 +649,15 @@ function formValuesToInput(resource, values, mode) {
|
|
|
655
649
|
}
|
|
656
650
|
return input;
|
|
657
651
|
}
|
|
652
|
+
function parseMultiValue(raw) {
|
|
653
|
+
if (typeof raw !== "string" || raw === "") return [];
|
|
654
|
+
try {
|
|
655
|
+
const parsed = JSON.parse(raw);
|
|
656
|
+
return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string") : [];
|
|
657
|
+
} catch {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
}
|
|
658
661
|
function issuesByField(issues) {
|
|
659
662
|
const errors = {};
|
|
660
663
|
for (const issue of issues) {
|
|
@@ -665,49 +668,301 @@ function issuesByField(issues) {
|
|
|
665
668
|
return errors;
|
|
666
669
|
}
|
|
667
670
|
|
|
668
|
-
// src/
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
constructor(message) {
|
|
672
|
-
super(message);
|
|
673
|
-
this.name = "PolicyError";
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
function definePolicy(config) {
|
|
677
|
-
if (!/^[A-Z][A-Za-z0-9]*$/.test(config.resource)) {
|
|
678
|
-
throw new PolicyError(`Policy resource "${config.resource}" must be a PascalCase resource name.`);
|
|
679
|
-
}
|
|
680
|
-
const clean = (action) => {
|
|
681
|
-
const roles = config[action] ?? [];
|
|
682
|
-
for (const role of roles) {
|
|
683
|
-
if (!ROLE.test(role)) throw new PolicyError(`Policy ${config.resource}.${action}: invalid role "${role}" (lowercase letters, digits, _ or -, or "*").`);
|
|
684
|
-
}
|
|
685
|
-
return [...new Set(roles)];
|
|
686
|
-
};
|
|
687
|
-
return { resource: config.resource, read: clean("read"), create: clean("create"), update: clean("update"), delete: clean("delete") };
|
|
671
|
+
// src/seed.ts
|
|
672
|
+
function defineSeed(seed) {
|
|
673
|
+
return seed;
|
|
688
674
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
675
|
+
|
|
676
|
+
// src/fake.ts
|
|
677
|
+
var FIRST_NAMES = [
|
|
678
|
+
"Amina",
|
|
679
|
+
"Daniel",
|
|
680
|
+
"Grace",
|
|
681
|
+
"Wei",
|
|
682
|
+
"Sofia",
|
|
683
|
+
"Tendai",
|
|
684
|
+
"Noah",
|
|
685
|
+
"Priya",
|
|
686
|
+
"Lucas",
|
|
687
|
+
"Fatima",
|
|
688
|
+
"Ibrahim",
|
|
689
|
+
"Elena",
|
|
690
|
+
"Kwame",
|
|
691
|
+
"Maya",
|
|
692
|
+
"Hiroshi",
|
|
693
|
+
"Leila",
|
|
694
|
+
"Marcus",
|
|
695
|
+
"Chiara",
|
|
696
|
+
"Omar",
|
|
697
|
+
"Ana",
|
|
698
|
+
"Joseph",
|
|
699
|
+
"Nadia",
|
|
700
|
+
"Thabo",
|
|
701
|
+
"Yuki",
|
|
702
|
+
"Diego",
|
|
703
|
+
"Ruth",
|
|
704
|
+
"Samuel",
|
|
705
|
+
"Zara",
|
|
706
|
+
"Peter",
|
|
707
|
+
"Isabel",
|
|
708
|
+
"Kofi",
|
|
709
|
+
"Mei",
|
|
710
|
+
"Andr\xE9s",
|
|
711
|
+
"Halima",
|
|
712
|
+
"Jonas",
|
|
713
|
+
"Rania",
|
|
714
|
+
"Esther",
|
|
715
|
+
"Viktor",
|
|
716
|
+
"Aisha",
|
|
717
|
+
"Tom\xE1s"
|
|
718
|
+
];
|
|
719
|
+
var LAST_NAMES = [
|
|
720
|
+
"Okello",
|
|
721
|
+
"Silva",
|
|
722
|
+
"Nakamura",
|
|
723
|
+
"Mensah",
|
|
724
|
+
"Okafor",
|
|
725
|
+
"Rossi",
|
|
726
|
+
"Chen",
|
|
727
|
+
"Hassan",
|
|
728
|
+
"M\xFCller",
|
|
729
|
+
"Dubois",
|
|
730
|
+
"Kimani",
|
|
731
|
+
"Patel",
|
|
732
|
+
"Novak",
|
|
733
|
+
"Garcia",
|
|
734
|
+
"Haddad",
|
|
735
|
+
"Andersen",
|
|
736
|
+
"Osei",
|
|
737
|
+
"Reyes",
|
|
738
|
+
"Kowalski",
|
|
739
|
+
"Tadesse",
|
|
740
|
+
"Nguyen",
|
|
741
|
+
"Baptiste",
|
|
742
|
+
"Mwangi",
|
|
743
|
+
"Fernandes",
|
|
744
|
+
"Ivanov",
|
|
745
|
+
"Khan",
|
|
746
|
+
"Lindqvist",
|
|
747
|
+
"Santos",
|
|
748
|
+
"Abebe",
|
|
749
|
+
"Yusuf"
|
|
750
|
+
];
|
|
751
|
+
var COMPANY_HEADS = [
|
|
752
|
+
"North",
|
|
753
|
+
"Blue",
|
|
754
|
+
"Bright",
|
|
755
|
+
"Iron",
|
|
756
|
+
"River",
|
|
757
|
+
"Summit",
|
|
758
|
+
"Atlas",
|
|
759
|
+
"Harbour",
|
|
760
|
+
"Cedar",
|
|
761
|
+
"Copper",
|
|
762
|
+
"Lumen",
|
|
763
|
+
"Orbit",
|
|
764
|
+
"Stone",
|
|
765
|
+
"Vertex",
|
|
766
|
+
"Willow",
|
|
767
|
+
"Zenith",
|
|
768
|
+
"Kestrel",
|
|
769
|
+
"Meridian",
|
|
770
|
+
"Sable",
|
|
771
|
+
"Terra"
|
|
772
|
+
];
|
|
773
|
+
var COMPANY_TAILS = ["Labs", "Works", "Group", "Studio", "Systems", "Partners", "Supply", "Analytics", "Foods", "Logistics"];
|
|
774
|
+
var CITIES = [
|
|
775
|
+
"Kampala",
|
|
776
|
+
"Nairobi",
|
|
777
|
+
"Lagos",
|
|
778
|
+
"Accra",
|
|
779
|
+
"Cairo",
|
|
780
|
+
"Cape Town",
|
|
781
|
+
"Dar es Salaam",
|
|
782
|
+
"Casablanca",
|
|
783
|
+
"London",
|
|
784
|
+
"Berlin",
|
|
785
|
+
"Madrid",
|
|
786
|
+
"Lisbon",
|
|
787
|
+
"Warsaw",
|
|
788
|
+
"Stockholm",
|
|
789
|
+
"Amsterdam",
|
|
790
|
+
"Dublin",
|
|
791
|
+
"New York",
|
|
792
|
+
"Toronto",
|
|
793
|
+
"Chicago",
|
|
794
|
+
"Austin",
|
|
795
|
+
"S\xE3o Paulo",
|
|
796
|
+
"Bogot\xE1",
|
|
797
|
+
"Mexico City",
|
|
798
|
+
"Lima",
|
|
799
|
+
"Mumbai",
|
|
800
|
+
"Singapore",
|
|
801
|
+
"Tokyo",
|
|
802
|
+
"Seoul",
|
|
803
|
+
"Jakarta",
|
|
804
|
+
"Manila",
|
|
805
|
+
"Sydney",
|
|
806
|
+
"Auckland"
|
|
807
|
+
];
|
|
808
|
+
var COUNTRIES = [
|
|
809
|
+
["UG", "256"],
|
|
810
|
+
["KE", "254"],
|
|
811
|
+
["NG", "234"],
|
|
812
|
+
["GH", "233"],
|
|
813
|
+
["ZA", "27"],
|
|
814
|
+
["EG", "20"],
|
|
815
|
+
["US", "1"],
|
|
816
|
+
["CA", "1"],
|
|
817
|
+
["GB", "44"],
|
|
818
|
+
["IE", "353"],
|
|
819
|
+
["DE", "49"],
|
|
820
|
+
["FR", "33"],
|
|
821
|
+
["ES", "34"],
|
|
822
|
+
["PT", "351"],
|
|
823
|
+
["NL", "31"],
|
|
824
|
+
["SE", "46"],
|
|
825
|
+
["PL", "48"],
|
|
826
|
+
["BR", "55"],
|
|
827
|
+
["MX", "52"],
|
|
828
|
+
["IN", "91"],
|
|
829
|
+
["SG", "65"],
|
|
830
|
+
["JP", "81"],
|
|
831
|
+
["AU", "61"],
|
|
832
|
+
["AE", "971"]
|
|
833
|
+
];
|
|
834
|
+
var WORDS = [
|
|
835
|
+
"account",
|
|
836
|
+
"balance",
|
|
837
|
+
"shipment",
|
|
838
|
+
"invoice",
|
|
839
|
+
"review",
|
|
840
|
+
"signal",
|
|
841
|
+
"request",
|
|
842
|
+
"channel",
|
|
843
|
+
"vendor",
|
|
844
|
+
"budget",
|
|
845
|
+
"report",
|
|
846
|
+
"delivery",
|
|
847
|
+
"contract",
|
|
848
|
+
"renewal",
|
|
849
|
+
"forecast",
|
|
850
|
+
"meeting",
|
|
851
|
+
"proposal",
|
|
852
|
+
"discount",
|
|
853
|
+
"refund",
|
|
854
|
+
"ticket",
|
|
855
|
+
"quarter",
|
|
856
|
+
"pipeline",
|
|
857
|
+
"customer",
|
|
858
|
+
"supplier",
|
|
859
|
+
"warehouse",
|
|
860
|
+
"campaign",
|
|
861
|
+
"segment",
|
|
862
|
+
"retention",
|
|
863
|
+
"onboarding",
|
|
864
|
+
"escalation",
|
|
865
|
+
"follow-up",
|
|
866
|
+
"handover",
|
|
867
|
+
"kickoff",
|
|
868
|
+
"milestone",
|
|
869
|
+
"rollout",
|
|
870
|
+
"shortfall",
|
|
871
|
+
"surplus",
|
|
872
|
+
"threshold",
|
|
873
|
+
"variance",
|
|
874
|
+
"workflow"
|
|
875
|
+
];
|
|
876
|
+
var DOMAINS = ["example.com", "example.org", "example.net", "mail.example.com"];
|
|
877
|
+
var TLDS = ["com", "io", "co", "org", "net"];
|
|
878
|
+
var COLORS = ["#f2541d", "#2563eb", "#16a34a", "#d97706", "#7c3aed", "#0891b2", "#db2777", "#4b5563"];
|
|
879
|
+
var DAY_MS = 864e5;
|
|
880
|
+
var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
881
|
+
function encodeCrockford(value, length) {
|
|
882
|
+
let out = "";
|
|
883
|
+
let rest = value;
|
|
884
|
+
for (let i = 0; i < length; i++) {
|
|
885
|
+
out = CROCKFORD[Number(rest % 32n)] + out;
|
|
886
|
+
rest /= 32n;
|
|
887
|
+
}
|
|
888
|
+
return out;
|
|
694
889
|
}
|
|
695
|
-
function
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
890
|
+
function random(seed) {
|
|
891
|
+
let state = seed >>> 0;
|
|
892
|
+
return () => {
|
|
893
|
+
state = state + 1831565813 >>> 0;
|
|
894
|
+
let t = Math.imul(state ^ state >>> 15, 1 | state);
|
|
895
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
896
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
701
897
|
};
|
|
702
898
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
899
|
+
function createFake(seed = 20260101) {
|
|
900
|
+
const next = random(seed);
|
|
901
|
+
let count = 0;
|
|
902
|
+
const step = () => ++count;
|
|
903
|
+
const tail = BigInt(Math.floor(next() * 2 ** 40)) * 2n ** 40n;
|
|
904
|
+
let counter = 0n;
|
|
905
|
+
const int = (min, max) => min + Math.floor(next() * (max - min + 1));
|
|
906
|
+
const pick = (items) => items[Math.floor(next() * items.length)];
|
|
907
|
+
const slugify2 = (text2) => text2.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
908
|
+
const fake2 = {
|
|
909
|
+
int,
|
|
910
|
+
float: (min, max, decimals = 2) => Number((min + next() * (max - min)).toFixed(decimals)),
|
|
911
|
+
bool: (chance = 0.5) => next() < chance,
|
|
912
|
+
pick,
|
|
913
|
+
some(items, min = 1, max = items.length) {
|
|
914
|
+
const wanted = int(Math.min(min, items.length), Math.min(max, items.length));
|
|
915
|
+
const chosen = /* @__PURE__ */ new Set();
|
|
916
|
+
while (chosen.size < wanted) chosen.add(Math.floor(next() * items.length));
|
|
917
|
+
return [...chosen].sort((a, b) => a - b).map((index) => items[index]);
|
|
918
|
+
},
|
|
919
|
+
firstName: () => pick(FIRST_NAMES),
|
|
920
|
+
lastName: () => pick(LAST_NAMES),
|
|
921
|
+
fullName: () => `${pick(FIRST_NAMES)} ${pick(LAST_NAMES)}`,
|
|
922
|
+
email(name) {
|
|
923
|
+
const local = name ? slugify2(name).replace(/-/g, ".") : `${pick(FIRST_NAMES)}.${pick(LAST_NAMES)}`.toLowerCase();
|
|
924
|
+
return `${slugify2(local).replace(/-/g, ".")}${step()}@${pick(DOMAINS)}`;
|
|
925
|
+
},
|
|
926
|
+
company: () => `${pick(COMPANY_HEADS)} ${pick(COMPANY_TAILS)}`,
|
|
927
|
+
jobTitle: () => `${pick(["Head of", "Senior", "Lead", "Regional", "Junior"])} ${pick(["Operations", "Sales", "Support", "Engineering", "Finance", "Logistics"])}`,
|
|
928
|
+
city: () => pick(CITIES),
|
|
929
|
+
country: () => pick(COUNTRIES)[0],
|
|
930
|
+
phone: () => `+${pick(COUNTRIES)[1]}${String(int(7e8, 799999999))}`,
|
|
931
|
+
domain: () => `${slugify2(`${pick(COMPANY_HEADS)}${pick(COMPANY_TAILS)}`)}${step()}.${pick(TLDS)}`,
|
|
932
|
+
url: () => `https://${slugify2(pick(COMPANY_HEADS))}.${pick(TLDS)}/${slugify2(pick(WORDS))}`,
|
|
933
|
+
slug: (text2) => `${slugify2(text2 ?? `${pick(WORDS)}-${pick(WORDS)}`)}-${step()}`,
|
|
934
|
+
color: () => pick(COLORS),
|
|
935
|
+
words: (howMany = 3) => Array.from({ length: howMany }, () => pick(WORDS)).join(" "),
|
|
936
|
+
sentence() {
|
|
937
|
+
const words2 = Array.from({ length: int(5, 12) }, () => pick(WORDS)).join(" ");
|
|
938
|
+
return `${words2[0].toUpperCase()}${words2.slice(1)}.`;
|
|
939
|
+
},
|
|
940
|
+
paragraph(sentences = 3) {
|
|
941
|
+
return Array.from({ length: sentences }, () => fake2.sentence()).join(" ");
|
|
942
|
+
},
|
|
943
|
+
timestamp: (daysBack = 365) => Date.now() - int(0, daysBack) * DAY_MS - int(0, DAY_MS - 1),
|
|
944
|
+
date: (daysBack = 365) => new Date(fake2.timestamp(daysBack)).toISOString().slice(0, 10),
|
|
945
|
+
datetime: (daysBack = 365) => new Date(fake2.timestamp(daysBack)).toISOString(),
|
|
946
|
+
uuid() {
|
|
947
|
+
const hex = "0123456789abcdef";
|
|
948
|
+
let out = "";
|
|
949
|
+
for (let i = 0; i < 32; i++) out += hex[Math.floor(next() * 16)];
|
|
950
|
+
return `${out.slice(0, 8)}-${out.slice(8, 12)}-4${out.slice(13, 16)}-a${out.slice(17, 20)}-${out.slice(20, 32)}`;
|
|
951
|
+
},
|
|
952
|
+
id() {
|
|
953
|
+
counter += 1n;
|
|
954
|
+
return encodeCrockford(BigInt(Date.now()), 10) + encodeCrockford((tail + counter) % 2n ** 80n, 16);
|
|
955
|
+
},
|
|
956
|
+
get count() {
|
|
957
|
+
return count;
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
return fake2;
|
|
707
961
|
}
|
|
962
|
+
var fake = createFake();
|
|
708
963
|
|
|
709
964
|
// src/index.ts
|
|
710
|
-
var FLARE_VERSION = "0.
|
|
965
|
+
var FLARE_VERSION = "0.3.0";
|
|
711
966
|
export {
|
|
712
967
|
DEFAULT_FILE_MAX_BYTES,
|
|
713
968
|
FILE_CATEGORIES,
|
|
@@ -718,12 +973,16 @@ export {
|
|
|
718
973
|
RelationError,
|
|
719
974
|
ResourceDefinitionError,
|
|
720
975
|
SNIFF_BYTES,
|
|
976
|
+
STRING_FORMATS,
|
|
721
977
|
SUBSCRIPTION_STATUSES,
|
|
722
978
|
allowedActions,
|
|
723
979
|
camelCase,
|
|
724
980
|
can,
|
|
725
981
|
checkoutPaid,
|
|
726
982
|
columnName,
|
|
983
|
+
countries,
|
|
984
|
+
countryName,
|
|
985
|
+
createFake,
|
|
727
986
|
createMailer,
|
|
728
987
|
createObjectKey,
|
|
729
988
|
createStorage,
|
|
@@ -733,23 +992,33 @@ export {
|
|
|
733
992
|
definePolicy,
|
|
734
993
|
defineResource,
|
|
735
994
|
defineSeed,
|
|
995
|
+
fake,
|
|
736
996
|
field,
|
|
737
997
|
fieldSchema,
|
|
738
998
|
fileKeyPrefix,
|
|
739
999
|
fileMaxBytes,
|
|
1000
|
+
flagEmoji,
|
|
740
1001
|
formValuesToInput,
|
|
1002
|
+
formatPhone,
|
|
741
1003
|
formatValue,
|
|
742
1004
|
grantsAccess,
|
|
743
1005
|
hashPassword,
|
|
744
1006
|
humanize,
|
|
745
1007
|
importSigningKey,
|
|
746
1008
|
initialFormValues,
|
|
1009
|
+
isColor,
|
|
1010
|
+
isCountryCode,
|
|
1011
|
+
isDomain,
|
|
747
1012
|
isLiveSubscription,
|
|
1013
|
+
isPhoneNumber,
|
|
1014
|
+
isSlug,
|
|
748
1015
|
issuesByField,
|
|
749
1016
|
kebabCase,
|
|
750
1017
|
matchesContentType,
|
|
751
1018
|
mimeTypesFor,
|
|
1019
|
+
normalizeDomain,
|
|
752
1020
|
optionLabel,
|
|
1021
|
+
parseMultiValue,
|
|
753
1022
|
pascalCase,
|
|
754
1023
|
pluralize,
|
|
755
1024
|
randomLetters,
|
|
@@ -757,12 +1026,15 @@ export {
|
|
|
757
1026
|
renderTransactionalEmail,
|
|
758
1027
|
shouldApplySubscription,
|
|
759
1028
|
signToken,
|
|
1029
|
+
slugify,
|
|
760
1030
|
snakeCase,
|
|
761
1031
|
sniffMatches,
|
|
1032
|
+
splitPhone,
|
|
762
1033
|
statusTone,
|
|
763
1034
|
storedFields,
|
|
764
1035
|
subscriptionPriceId,
|
|
765
1036
|
subscriptionState,
|
|
1037
|
+
toE164,
|
|
766
1038
|
toSubscriptionStatus,
|
|
767
1039
|
verifyPassword,
|
|
768
1040
|
verifyToken,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* rule in a descriptor takes effect without re-running the generator. Only
|
|
7
7
|
* storage-level changes (kind, nullability, uniqueness, relations) need a migration.
|
|
8
8
|
*/
|
|
9
|
-
type FieldKind = "string" | "text" | "int" | "float" | "boolean" | "date" | "datetime" | "enum" | "file" | "belongsTo" | "hasMany";
|
|
9
|
+
type FieldKind = "string" | "text" | "int" | "float" | "boolean" | "date" | "datetime" | "enum" | "multiselect" | "file" | "belongsTo" | "hasMany";
|
|
10
10
|
/** Options shared by every stored (non-hasMany) field. */
|
|
11
11
|
interface CommonOptions<T> {
|
|
12
12
|
label?: string;
|
|
@@ -24,10 +24,23 @@ interface CommonOptions<T> {
|
|
|
24
24
|
/** Included in free-text search. Default true for string fields. */
|
|
25
25
|
searchable?: boolean;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* What a string holds. Each format is still a text column; the format decides
|
|
29
|
+
* validation, the admin input and how the value is shown.
|
|
30
|
+
*
|
|
31
|
+
* - email, url: validated addresses (url: http or https)
|
|
32
|
+
* - tel: an international number, stored E.164 (`+256772123456`), entered with a country picker
|
|
33
|
+
* - domain: a hostname such as `example.com`, stored lowercase
|
|
34
|
+
* - country: an ISO 3166-1 alpha-2 code (`UG`), chosen from a searchable list
|
|
35
|
+
* - color: a hex colour (`#f2541d`)
|
|
36
|
+
* - slug: lowercase words joined by hyphens (`my-first-post`)
|
|
37
|
+
*/
|
|
38
|
+
type StringFormat = "email" | "url" | "tel" | "domain" | "country" | "color" | "slug";
|
|
39
|
+
declare const STRING_FORMATS: readonly StringFormat[];
|
|
27
40
|
interface StringOptions extends CommonOptions<string> {
|
|
28
41
|
maxLength?: number;
|
|
29
42
|
minLength?: number;
|
|
30
|
-
format?:
|
|
43
|
+
format?: StringFormat;
|
|
31
44
|
pattern?: string;
|
|
32
45
|
}
|
|
33
46
|
interface TextOptions extends CommonOptions<string> {
|
|
@@ -43,6 +56,14 @@ type DateOptions = CommonOptions<string>;
|
|
|
43
56
|
interface EnumOptions<O extends string> extends CommonOptions<O> {
|
|
44
57
|
/** Display labels per option value. */
|
|
45
58
|
optionLabels?: Partial<Record<O, string>>;
|
|
59
|
+
/** Admin input: a dropdown (default) or radio buttons (best for a few options). */
|
|
60
|
+
widget?: "select" | "radio";
|
|
61
|
+
}
|
|
62
|
+
interface MultiSelectOptions<O extends string> extends Omit<CommonOptions<O[]>, "unique"> {
|
|
63
|
+
optionLabels?: Partial<Record<O, string>>;
|
|
64
|
+
/** Fewest / most options that can be picked. A required field needs at least one. */
|
|
65
|
+
minItems?: number;
|
|
66
|
+
maxItems?: number;
|
|
46
67
|
}
|
|
47
68
|
interface FileOptions extends CommonOptions<string> {
|
|
48
69
|
/** Maximum upload size in bytes. Default 10 MiB. */
|
|
@@ -75,6 +96,9 @@ type DateTimeField<R extends boolean = boolean> = Base<"datetime", R> & DateOpti
|
|
|
75
96
|
type EnumField<O extends string = string, R extends boolean = boolean> = Base<"enum", R> & EnumOptions<O> & {
|
|
76
97
|
options: readonly O[];
|
|
77
98
|
};
|
|
99
|
+
type MultiSelectField<O extends string = string, R extends boolean = boolean> = Base<"multiselect", R> & MultiSelectOptions<O> & {
|
|
100
|
+
options: readonly O[];
|
|
101
|
+
};
|
|
78
102
|
type FileField<R extends boolean = boolean> = Base<"file", R> & FileOptions & {
|
|
79
103
|
/** Accepted categories from the `file:[image,pdf]` grammar. */
|
|
80
104
|
accept: readonly FileCategory[];
|
|
@@ -88,7 +112,7 @@ type HasManyField = {
|
|
|
88
112
|
required: false;
|
|
89
113
|
target: string;
|
|
90
114
|
} & HasManyOptions;
|
|
91
|
-
type Field = StringField | TextField | IntField | FloatField | BooleanField | DateField | DateTimeField | EnumField | FileField | BelongsToField | HasManyField;
|
|
115
|
+
type Field = StringField | TextField | IntField | FloatField | BooleanField | DateField | DateTimeField | EnumField | MultiSelectField | FileField | BelongsToField | HasManyField;
|
|
92
116
|
type StoredField = Exclude<Field, HasManyField>;
|
|
93
117
|
/** File categories usable in `file:[...]` and the MIME types each allows. */
|
|
94
118
|
declare const FILE_CATEGORIES: {
|
|
@@ -119,6 +143,19 @@ declare const field: {
|
|
|
119
143
|
date: <const O extends DateOptions>(options?: O) => DateField<Req<O>> & O;
|
|
120
144
|
datetime: <const O extends DateOptions>(options?: O) => DateTimeField<Req<O>> & O;
|
|
121
145
|
enum: <const V extends readonly [string, ...string[]], const O extends EnumOptions<V[number]>>(values: V, options?: O) => EnumField<V[number], Req<O>> & O;
|
|
146
|
+
/** One of a list, picked from a dropdown. The same as `enum`. */
|
|
147
|
+
select: <const V extends readonly [string, ...string[]], const O extends EnumOptions<V[number]>>(values: V, options?: O) => EnumField<V[number], Req<O>> & O;
|
|
148
|
+
/** One of a list, picked with radio buttons. Stored like `enum`. */
|
|
149
|
+
radio: <const V extends readonly [string, ...string[]], const O extends EnumOptions<V[number]>>(values: V, options?: O) => EnumField<V[number], Req<O>> & O;
|
|
150
|
+
/** Any number of a list, picked with checkboxes. Stored as a JSON array. */
|
|
151
|
+
multiselect: <const V extends readonly [string, ...string[]], const O extends MultiSelectOptions<V[number]>>(values: V, options?: O) => MultiSelectField<V[number], Req<O>> & O;
|
|
152
|
+
email: <const O extends Omit<StringOptions, "format">>(options?: O) => StringField<Req<O>> & O;
|
|
153
|
+
url: <const O extends Omit<StringOptions, "format">>(options?: O) => StringField<Req<O>> & O;
|
|
154
|
+
tel: <const O extends Omit<StringOptions, "format">>(options?: O) => StringField<Req<O>> & O;
|
|
155
|
+
domain: <const O extends Omit<StringOptions, "format">>(options?: O) => StringField<Req<O>> & O;
|
|
156
|
+
country: <const O extends Omit<StringOptions, "format">>(options?: O) => StringField<Req<O>> & O;
|
|
157
|
+
color: <const O extends Omit<StringOptions, "format">>(options?: O) => StringField<Req<O>> & O;
|
|
158
|
+
slug: <const O extends Omit<StringOptions, "format">>(options?: O) => StringField<Req<O>> & O;
|
|
122
159
|
file: <const O extends FileOptions>(accept: readonly FileCategory[], options?: O) => FileField<Req<O>> & O;
|
|
123
160
|
belongsTo: <const O extends BelongsToOptions>(target: string, options?: O) => BelongsToField<Req<O>> & O;
|
|
124
161
|
hasMany: <const O extends HasManyOptions>(target: string, options?: O) => HasManyField & O;
|
|
@@ -131,6 +168,9 @@ type FieldValue<F> = F extends {
|
|
|
131
168
|
kind: "enum";
|
|
132
169
|
options: readonly (infer O)[];
|
|
133
170
|
} ? O : F extends {
|
|
171
|
+
kind: "multiselect";
|
|
172
|
+
options: readonly (infer M)[];
|
|
173
|
+
} ? M[] : F extends {
|
|
134
174
|
kind: "hasMany";
|
|
135
175
|
} ? never : string;
|
|
136
176
|
type StoredKeys<Fields> = {
|
|
@@ -229,4 +269,40 @@ declare function storedFields(resource: Resource): [string, StoredField & {
|
|
|
229
269
|
/** Database column name for a field key ("companyId" → "company_id"). */
|
|
230
270
|
declare const columnName: (key: string) => string;
|
|
231
271
|
|
|
232
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Resource-level policies: which roles may read, create, update, or delete a resource.
|
|
274
|
+
*
|
|
275
|
+
* Policies are plain data, like descriptors, so the API layer and the admin UI read the
|
|
276
|
+
* same rules at runtime. Field-level rules and per-record ownership are deliberately out
|
|
277
|
+
* of scope for v1.
|
|
278
|
+
*/
|
|
279
|
+
type PolicyAction = "list" | "read" | "create" | "update" | "delete";
|
|
280
|
+
/** Roles allowed per action; `"*"` means any signed-in user. */
|
|
281
|
+
interface Policy {
|
|
282
|
+
resource: string;
|
|
283
|
+
read: string[];
|
|
284
|
+
create: string[];
|
|
285
|
+
update: string[];
|
|
286
|
+
delete: string[];
|
|
287
|
+
}
|
|
288
|
+
interface PolicyConfig {
|
|
289
|
+
resource: string;
|
|
290
|
+
read?: string[];
|
|
291
|
+
create?: string[];
|
|
292
|
+
update?: string[];
|
|
293
|
+
delete?: string[];
|
|
294
|
+
}
|
|
295
|
+
declare class PolicyError extends Error {
|
|
296
|
+
constructor(message: string);
|
|
297
|
+
}
|
|
298
|
+
declare function definePolicy(config: PolicyConfig): Policy;
|
|
299
|
+
/**
|
|
300
|
+
* Whether `role` may perform `action`. Without a policy any signed-in user is allowed
|
|
301
|
+
* (callers must already have required a session); generate one with
|
|
302
|
+
* `flare gen policy <Resource> --roles …` to restrict a resource.
|
|
303
|
+
*/
|
|
304
|
+
declare function can(policy: Policy | undefined | null, role: string | null | undefined, action: PolicyAction): boolean;
|
|
305
|
+
/** Actions `role` may perform, for hiding UI it can't use. */
|
|
306
|
+
declare function allowedActions(policy: Policy | undefined | null, role: string | null | undefined): Record<Exclude<PolicyAction, "list">, boolean>;
|
|
307
|
+
|
|
308
|
+
export { fileMaxBytes as A, type BelongsToField as B, type CreateInput as C, DEFAULT_FILE_MAX_BYTES as D, type EnumField as E, type Field as F, mimeTypesFor as G, type HasManyField as H, type IntField as I, storedFields as J, type MultiSelectField as M, type Policy as P, type Resource as R, type StoredField as S, type TextField as T, type UpdateInput as U, type BooleanField as a, type DateField as b, type DateTimeField as c, FILE_CATEGORIES as d, type FieldKind as e, type FieldValue as f, type FileCategory as g, type FileField as h, type FloatField as i, type PolicyAction as j, type PolicyConfig as k, PolicyError as l, RESERVED_FIELD_NAMES as m, type RecordOf as n, type ResourceConfig as o, ResourceDefinitionError as p, STRING_FORMATS as q, type StringField as r, type StringFormat as s, allowedActions as t, can as u, columnName as v, defaultFieldLabel as w, definePolicy as x, defineResource as y, field as z };
|