@media-quest/builder 0.0.24 → 0.0.26

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 (43) hide show
  1. package/package.json +15 -15
  2. package/src/Builder-option.ts +6 -8
  3. package/src/Builder-page.spec.ts +162 -320
  4. package/src/Builder-page.ts +182 -257
  5. package/src/Builder-question.spec.ts +57 -57
  6. package/src/Builder-question.ts +83 -82
  7. package/src/Builder-schema.spec.ts +96 -124
  8. package/src/Builder-schema.ts +25 -15
  9. package/src/Builder-text.spec.ts +11 -11
  10. package/src/Builder-text.ts +13 -13
  11. package/src/BuilderObject.ts +2 -33
  12. package/src/BuilderTag.ts +19 -20
  13. package/src/builder-compiler.ts +1 -1
  14. package/src/code-book/codebook-variable.ts +29 -0
  15. package/src/{codebook.ts → code-book/codebook.ts} +16 -16
  16. package/src/primitives/ID.spec.ts +39 -0
  17. package/src/primitives/ID.ts +138 -0
  18. package/src/primitives/page-prefix.ts +4 -3
  19. package/src/primitives/varID.ts +1 -0
  20. package/src/public-api.ts +28 -24
  21. package/src/rulebuilder/Builder-rule.spec.ts +3 -2
  22. package/src/rulebuilder/Builder-rule.ts +2 -1
  23. package/src/rulebuilder/RuleBuilder-test-utils.ts +12 -8
  24. package/src/rulebuilder/RuleInput.ts +30 -30
  25. package/src/rulebuilder/RuleVariable.ts +11 -26
  26. package/src/rulebuilder/SingleSelectItem.ts +9 -8
  27. package/src/rulebuilder/condition/Builder-condition-group.ts +14 -6
  28. package/src/rulebuilder/condition/Builder-condition.spec.ts +12 -12
  29. package/src/rulebuilder/condition/Builder-condition.ts +17 -13
  30. package/src/rulebuilder/index.ts +16 -3
  31. package/src/rulebuilder/page-action-manager.ts +4 -2
  32. package/src/rulebuilder/rule2/Rule2.ts +8 -8
  33. package/src/schema-config.ts +2 -2
  34. package/src/sum-score/sum-score-answer.ts +6 -0
  35. package/src/sum-score/sum-score-manager.spec.ts +189 -0
  36. package/src/sum-score/sum-score-manager.ts +154 -0
  37. package/src/sum-score/sum-score-membership.ts +45 -0
  38. package/src/sum-score/sum-score-variable.spec.ts +151 -0
  39. package/src/sum-score/sum-score-variable.ts +36 -0
  40. package/src/{variable → sum-score}/sum-score.ts +122 -138
  41. package/tsconfig.tsbuildinfo +1 -1
  42. package/src/variable/mq-variable.spec.ts +0 -146
  43. package/src/variable/mq-variable.ts +0 -68
@@ -2,25 +2,28 @@ import type { BuilderPageDto, BuilderPageType } from "./Builder-page";
2
2
  import { BuilderPage } from "./Builder-page";
3
3
  import type {
4
4
  BuilderRuleDto,
5
- CustomVariable,
6
5
  ExcludeByPageAction,
7
6
  ExcludeByTagAction,
8
7
  JumpToPageAction,
8
+ RuleCustomVariable,
9
9
  } from "./rulebuilder";
10
10
  import { BuilderRule, RuleInput } from "./rulebuilder";
11
- import type { QuestionVariable } from "./rulebuilder/RuleVariable";
11
+ import type { RuleQuestionVariable } from "./rulebuilder/RuleVariable";
12
12
  import type { BuilderTagDto } from "./BuilderTag";
13
13
  import { BuilderTag, TagCollection } from "./BuilderTag";
14
14
  import { DefaultThemeCompiler } from "./theme/default-theme-compiler";
15
15
  import { ImageFile } from "./media-files";
16
- import { DUtil, SchemaID } from "@media-quest/engine";
16
+ import { DUtil } from "@media-quest/engine";
17
17
  import { PagePrefix } from "./primitives/page-prefix";
18
18
  import { SchemaPrefix, SchemaPrefixValue } from "./primitives/schema-prefix";
19
- import { CodeBook } from "./codebook";
20
- import { PredefinedVariable } from "./variable/mq-variable";
19
+ import { CodeBook } from "./code-book/codebook";
20
+ import { CodebookPredefinedVariable } from "./code-book/codebook-variable";
21
21
  import { SchemaConfig } from "./schema-config";
22
22
  import { CompilerOption, CompilerOutput } from "./builder-compiler";
23
- import { SumScoreVariable } from "./variable/sum-score";
23
+
24
+ import { SumScoreVariableDto } from "./sum-score/sum-score-variable";
25
+ import { SchemaID } from "./primitives/ID";
26
+ import { SumScoreMembershipDto } from "./sum-score/sum-score-membership";
24
27
 
25
28
  const U = DUtil;
26
29
 
@@ -33,14 +36,15 @@ export interface BuilderSchemaDto {
33
36
  readonly pages: BuilderPageDto[];
34
37
  readonly baseHeight: number;
35
38
  readonly baseWidth: number;
36
- readonly predefinedVariables?: Array<PredefinedVariable>;
37
- readonly sumScoreVariables?: Array<SumScoreVariable>;
39
+ readonly predefinedVariables?: Array<CodebookPredefinedVariable>;
40
+ readonly sumScoreVariables?: ReadonlyArray<SumScoreVariableDto>;
41
+ readonly sumScoreMemberShips?: ReadonlyArray<SumScoreMembershipDto>;
38
42
  readonly rules: ReadonlyArray<BuilderRuleDto>;
39
43
  readonly tags: ReadonlyArray<BuilderTagDto>;
40
44
  }
41
45
 
42
46
  class SumScoreVariableCollection {
43
- private _all: Array<SumScoreVariable> = [];
47
+ private _all: Array<SumScoreVariableDto> = [];
44
48
  }
45
49
 
46
50
  export class BuilderSchema {
@@ -50,13 +54,13 @@ export class BuilderSchema {
50
54
  backgroundColor = "#000000";
51
55
  pages: BuilderPage[] = [];
52
56
  mainImage: ImageFile | false = false;
53
- predefinedVariables: PredefinedVariable[] = [];
57
+ predefinedVariables: CodebookPredefinedVariable[] = [];
54
58
  private _rules: BuilderRule[] = [];
55
- private _sumVariables: SumScoreVariable[] = [];
59
+ private _sumVariables: SumScoreVariableDto[] = [];
56
60
  get rules(): ReadonlyArray<BuilderRule> {
57
61
  return [...this._rules];
58
62
  }
59
- get sumScoreVariables(): ReadonlyArray<SumScoreVariable> {
63
+ get sumScoreVariables(): ReadonlyArray<SumScoreVariableDto> {
60
64
  return [...this._sumVariables];
61
65
  }
62
66
 
@@ -77,6 +81,7 @@ export class BuilderSchema {
77
81
  const schemaPrefix = SchemaPrefix.castOrCreateRandom(dto.prefix);
78
82
  const schema = new BuilderSchema(dto.id, dto.name, schemaPrefix);
79
83
  const pages = dto.pages.map(BuilderPage.fromJson);
84
+
80
85
  schema._tagCollection.init(dto.tags);
81
86
  schema.backgroundColor = dto.backgroundColor;
82
87
  schema.baseHeight = dto.baseHeight;
@@ -87,6 +92,8 @@ export class BuilderSchema {
87
92
  schema.mainImage = dto.mainImage ?? false;
88
93
  const rulesDto = dto.rules ?? [];
89
94
  const ruleInput = schema.getRuleInput();
95
+
96
+ schema._sumVariables = Array.isArray(dto.sumScoreVariables) ? [...dto.sumScoreVariables] : [];
90
97
  schema._rules = rulesDto.map((r) => BuilderRule.fromDto(r, ruleInput));
91
98
  return schema;
92
99
  }
@@ -106,6 +113,7 @@ export class BuilderSchema {
106
113
  rules,
107
114
  tags,
108
115
  predefinedVariables: this.predefinedVariables,
116
+ sumScoreVariables: [...this.sumScoreVariables],
109
117
  mainImage: this.mainImage,
110
118
  prefix: this.prefix.value,
111
119
  };
@@ -130,9 +138,11 @@ export class BuilderSchema {
130
138
  return newPage;
131
139
  }
132
140
 
133
- addSumScoreVariable(variable: SumScoreVariable) {
141
+ addSumScoreVariable(variable: SumScoreVariableDto) {
134
142
  // TODO VALIDATE.
135
143
  this._sumVariables.push({ ...variable });
144
+
145
+ return variable;
136
146
  }
137
147
 
138
148
  insertPage(page: BuilderPage, atIndex: number): boolean {
@@ -204,8 +214,8 @@ export class BuilderSchema {
204
214
  }
205
215
 
206
216
  getRuleInput(): RuleInput {
207
- const qVars: QuestionVariable[] = [];
208
- const cVars: CustomVariable[] = [];
217
+ const qVars: RuleQuestionVariable[] = [];
218
+ const cVars: RuleCustomVariable[] = [];
209
219
  const pageIdActions: ExcludeByPageAction[] = [];
210
220
  const tagActions: ExcludeByTagAction[] = this.tags.map((t) => {
211
221
  const tag = t.tagText;
@@ -1,24 +1,24 @@
1
1
  import type { BuilderTextDto } from "./Builder-text";
2
2
  import { BuilderText } from "./Builder-text";
3
- import type { BuilderObjectId } from "./BuilderObject";
3
+ import { TextID } from "./primitives/ID";
4
4
 
5
5
  const textDto: BuilderTextDto = {
6
- id: "dto1" as BuilderObjectId.TextID,
7
- name: "dto1-name",
8
- text: "text one",
9
- translationRequired: true,
6
+ id: "dto1" as TextID,
7
+ name: "dto1-name",
8
+ text: "text one",
9
+ translationRequired: true,
10
10
  };
11
11
 
12
12
  let text1 = BuilderText.create("text one created");
13
13
 
14
14
  beforeEach(() => {
15
- text1 = BuilderText.create("text one created");
15
+ text1 = BuilderText.create("text one created");
16
16
  });
17
17
 
18
18
  describe("Builder Text", () => {
19
- test("Can serialize", () => {
20
- const instance1 = BuilderText.fromJson(textDto);
21
- const json = instance1.toJson();
22
- expect(textDto).toStrictEqual(json);
23
- });
19
+ test("Can serialize", () => {
20
+ const instance1 = BuilderText.fromJson(textDto);
21
+ const json = instance1.toJson();
22
+ expect(textDto).toStrictEqual(json);
23
+ });
24
24
  });
@@ -1,17 +1,18 @@
1
- import { BuilderObjectId, BuilderObject } from './BuilderObject';
1
+ import { BuilderObject } from "./BuilderObject";
2
+ import { TextID } from "./primitives/ID";
2
3
 
3
4
  export interface BuilderTextDto {
4
- readonly id: BuilderObjectId.TextID;
5
+ readonly id: TextID;
5
6
  text: string;
6
7
  name: string;
7
8
  translationRequired: boolean;
8
9
  }
9
10
 
10
- export class BuilderText extends BuilderObject<'builder-text', BuilderTextDto> {
11
- readonly objectType = 'builder-text';
12
- id: BuilderObjectId.TextID;
13
- text = '';
14
- name = '';
11
+ export class BuilderText extends BuilderObject<"builder-text", BuilderTextDto> {
12
+ readonly objectType = "builder-text";
13
+ id: TextID;
14
+ text = "";
15
+ name = "";
15
16
  translateRequired = false;
16
17
  // audio: {id: B}
17
18
  private constructor(dto: BuilderTextDto) {
@@ -23,12 +24,12 @@ export class BuilderText extends BuilderObject<'builder-text', BuilderTextDto> {
23
24
  }
24
25
 
25
26
  public static create(name: string) {
26
- const id = BuilderObjectId.textId();
27
+ const id = TextID.create();
27
28
  const dto: BuilderTextDto = {
28
29
  id,
29
30
  name,
30
- text: '',
31
- translationRequired: false
31
+ text: "",
32
+ translationRequired: false,
32
33
  };
33
34
  const instance = new BuilderText(dto);
34
35
  return instance;
@@ -39,9 +40,8 @@ export class BuilderText extends BuilderObject<'builder-text', BuilderTextDto> {
39
40
  }
40
41
 
41
42
  clone(): BuilderTextDto {
42
- const newId = BuilderObjectId.textId();
43
43
  const dto = this.toJson();
44
- const withNewId: BuilderTextDto = { ...dto, id: newId };
44
+ const withNewId: BuilderTextDto = { ...dto, id: TextID.create() };
45
45
  return withNewId;
46
46
  }
47
47
 
@@ -50,7 +50,7 @@ export class BuilderText extends BuilderObject<'builder-text', BuilderTextDto> {
50
50
  id: this.id,
51
51
  name: this.name,
52
52
  text: this.text,
53
- translationRequired: this.translateRequired
53
+ translationRequired: this.translateRequired,
54
54
  };
55
55
  return dto;
56
56
  }
@@ -1,6 +1,3 @@
1
- import { DUtil } from "@media-quest/engine";
2
-
3
- const U = DUtil;
4
1
  /**
5
2
  * Builder objects are complex objects that are embedded inside
6
3
  * a Builder-schema, and needs to be serialized to json. Often these objects
@@ -14,40 +11,12 @@ type BuilderObjectType =
14
11
  | "builder-text"
15
12
  | "builder-rule"
16
13
  | "builder-tag"
14
+ | "builder-sum-score-variable"
15
+ | "builder-sum-score-membership"
17
16
  | "builder-condition"
18
17
  | "builder-variable"
19
18
  | "builder-condition-group";
20
19
 
21
- export namespace BuilderObjectId {
22
- export type QuestionOptionID = string & { __OPTION__ID__: true };
23
- export type QuestionID = string & { __QUESTION__ID__: true };
24
- export type TextID = string & { __TEXT__ID__: true };
25
- export type MainTextID = string & { __MAIN__TEXT__ID__: true };
26
- export type TagId = string & { __TAG__ID__: true };
27
- export const createTagId = (): TagId => {
28
- return createId("builder-tag") as TagId;
29
- };
30
-
31
- export const mainTextId = (): MainTextID => {
32
- return createId("builder-main-text") as MainTextID;
33
- };
34
- export const textId = (): TextID => {
35
- return createId("builder-text") as TextID;
36
- };
37
-
38
- export const questionOptionId = (): QuestionOptionID => {
39
- return createId("builder-question-option") as QuestionOptionID;
40
- };
41
- export const questionId = (): QuestionID => {
42
- return createId("builder-question") as QuestionID;
43
- };
44
-
45
- const createId = (type: BuilderObjectType): string => {
46
- const id = U.randomString(24);
47
- return type + "-" + id;
48
- };
49
- }
50
-
51
20
  export abstract class BuilderObject<T extends BuilderObjectType, Dto extends {}> {
52
21
  // abstract readonly id: string;
53
22
  abstract readonly objectType: T;
package/src/BuilderTag.ts CHANGED
@@ -1,25 +1,26 @@
1
- import { BuilderObjectId, BuilderObject } from './BuilderObject';
1
+ import { BuilderObject } from "./BuilderObject";
2
+ import { TagID } from "./primitives/ID";
2
3
 
3
4
  export interface BuilderTagDto {
4
- readonly id: BuilderObjectId.TagId;
5
+ readonly id: TagID;
5
6
  readonly tag: string;
6
7
  readonly description: string;
7
8
  }
8
9
 
9
- export class BuilderTag extends BuilderObject<'builder-tag', BuilderTagDto> {
10
- readonly objectType: 'builder-tag' = 'builder-tag';
11
- readonly id: BuilderObjectId.TagId;
12
- tagText = '';
13
- tagDescription = '';
10
+ export class BuilderTag extends BuilderObject<"builder-tag", BuilderTagDto> {
11
+ readonly objectType: "builder-tag" = "builder-tag";
12
+ readonly id: TagID;
13
+ tagText = "";
14
+ tagDescription = "";
14
15
  public static readonly MAX_LENGTH = 20;
15
16
  public static readonly MIN_LENGTH = 1;
16
17
 
17
- public static readonly create = (tag: string, description: string = '') => {
18
- const id = BuilderObjectId.createTagId();
18
+ public static readonly create = (tag: string, description: string = "") => {
19
+ const id = TagID.create();
19
20
  const dto: BuilderTagDto = {
20
21
  id,
21
22
  tag,
22
- description
23
+ description,
23
24
  };
24
25
  return new BuilderTag(dto);
25
26
  };
@@ -27,12 +28,12 @@ export class BuilderTag extends BuilderObject<'builder-tag', BuilderTagDto> {
27
28
  return new BuilderTag(dto);
28
29
  };
29
30
  protected constructor(dto: BuilderTagDto) {
30
- const id = dto.id ?? BuilderObjectId.createTagId();
31
+ const id = TagID.validateOrCreate(dto.id);
31
32
  const withId = { ...dto, id };
32
33
  super(withId);
33
34
  this.id = id;
34
- this.tagText = dto.tag ?? '';
35
- this.tagDescription = dto.description ?? '';
35
+ this.tagText = dto.tag ?? "";
36
+ this.tagDescription = dto.description ?? "";
36
37
  }
37
38
  clone(): BuilderTagDto {
38
39
  return this.toJson();
@@ -54,17 +55,15 @@ export class TagCollection implements Iterable<BuilderTag> {
54
55
  return list[Symbol.iterator]();
55
56
  }
56
57
  private constructor(initialTags: ReadonlyArray<BuilderTag>) {
57
- initialTags.forEach(tag => {
58
+ initialTags.forEach((tag) => {
58
59
  this._tags.add(tag);
59
60
  });
60
61
  }
61
62
 
62
63
  init(tags: ReadonlyArray<BuilderTagDto>) {
63
- const dtoList: ReadonlyArray<BuilderTagDto> = Array.isArray(tags)
64
- ? tags
65
- : [];
64
+ const dtoList: ReadonlyArray<BuilderTagDto> = Array.isArray(tags) ? tags : [];
66
65
  const all = dtoList.map(BuilderTag.fromDto);
67
- all.forEach(tag => {
66
+ all.forEach((tag) => {
68
67
  this._tags.add(tag);
69
68
  });
70
69
  }
@@ -83,14 +82,14 @@ export class TagCollection implements Iterable<BuilderTag> {
83
82
 
84
83
  toJson(): ReadonlyArray<BuilderTagDto> {
85
84
  const list = [...this._tags];
86
- const dtoList = list.map(t => t.toJson());
85
+ const dtoList = list.map((t) => t.toJson());
87
86
  return dtoList;
88
87
  }
89
88
 
90
89
  deleteAll(tags: Iterable<BuilderTag>) {
91
90
  const l = tags[Symbol.iterator]();
92
91
  const asList = [...tags];
93
- asList.forEach(t => {
92
+ asList.forEach((t) => {
94
93
  this.delete(t);
95
94
  });
96
95
  }
@@ -1,5 +1,5 @@
1
1
  import { SchemaDto } from "@media-quest/engine";
2
- import { Codebook } from "./codebook";
2
+ import { Codebook } from "./code-book/codebook";
3
3
  import { SchemaConfig } from "./schema-config";
4
4
 
5
5
  export interface CompilerOutput {
@@ -0,0 +1,29 @@
1
+ import { PageID } from "../primitives/ID";
2
+
3
+ /**
4
+ * Interface representing a code book question sum-score.
5
+ *
6
+ * @interface
7
+ */
8
+ export interface CodeBookQuestionVariable {
9
+ readonly kind: "codebook-question-variable";
10
+ readonly label: string;
11
+ readonly varId: string;
12
+ readonly pageId: PageID;
13
+ readonly pagePrefix: string;
14
+ readonly modulePrefix: string;
15
+ /**
16
+ * Show the position of the question on page (a, b, c, d, ...)
17
+ */
18
+ readonly questionPrefix: string;
19
+ readonly pagePosition: number;
20
+ readonly options: ReadonlyArray<{ label: string; value: number }>;
21
+ }
22
+
23
+ export interface CodebookPredefinedVariable {
24
+ readonly kind: "codebook-predefined-variable";
25
+ readonly modulePrefix: string;
26
+ readonly moduleID: string;
27
+ defaultValue: number;
28
+ options: Array<{ label: string; value: number }>;
29
+ }
@@ -1,37 +1,37 @@
1
- import { BuilderPageDto } from "./Builder-page";
2
- import { BuilderSchemaDto } from "./Builder-schema";
3
- import { PageVariable, PredefinedVariable } from "./variable/mq-variable";
1
+ import { BuilderPageDto } from "../Builder-page";
2
+ import { BuilderSchemaDto } from "../Builder-schema";
3
+ import { CodeBookQuestionVariable, CodebookPredefinedVariable } from "./codebook-variable";
4
4
 
5
5
  export interface Codebook {
6
- readonly predefinedVariables: ReadonlyArray<PredefinedVariable>;
7
- readonly pageVariables: ReadonlyArray<PageVariable>;
6
+ readonly predefinedVariables: ReadonlyArray<CodebookPredefinedVariable>;
7
+ readonly pageVariables: ReadonlyArray<CodeBookQuestionVariable>;
8
8
  }
9
9
 
10
10
  const fromPage = (
11
11
  page: BuilderPageDto,
12
12
  pagePosition: number,
13
13
  modulePrefix: string,
14
- ): PageVariable[] => {
15
- const variables: PageVariable[] = [];
14
+ ): CodeBookQuestionVariable[] => {
15
+ const variables: CodeBookQuestionVariable[] = [];
16
16
 
17
17
  if (page._type !== "question") {
18
18
  // TODO Implement form field variables
19
19
  return [];
20
20
  }
21
21
 
22
- const options: PageVariable["options"] = page.defaultQuestion.options.map((o) => {
22
+ const options: CodeBookQuestionVariable["options"] = page.defaultQuestion.options.map((o) => {
23
23
  return { value: o.value, label: o.label };
24
24
  });
25
25
 
26
- const variable: PageVariable = {
27
- kind: "numeric-variable",
26
+ const variable: CodeBookQuestionVariable = {
27
+ kind: "codebook-question-variable",
28
28
  label: page.mainText.text,
29
+ pageId: page.id,
30
+ pagePrefix: page.prefix,
29
31
  options,
30
32
  modulePrefix,
31
- origin: "question",
32
- pageId: page.id,
33
33
  pagePosition,
34
- pagePrefix: page.prefix,
34
+ questionPrefix: "",
35
35
  varId: modulePrefix + "_" + page.prefix,
36
36
  };
37
37
 
@@ -48,8 +48,8 @@ const fromPage = (
48
48
  const getPageVariablesFromPages = (
49
49
  pages: BuilderPageDto[],
50
50
  modulePrefix: string,
51
- ): PageVariable[] => {
52
- const variables: PageVariable[] = [];
51
+ ): CodeBookQuestionVariable[] => {
52
+ const variables: CodeBookQuestionVariable[] = [];
53
53
  pages.forEach((page, index) => {
54
54
  const pageVariables = fromPage(page, index, modulePrefix);
55
55
  variables.push(...pageVariables);
@@ -61,7 +61,7 @@ const fromSchema = (schema: BuilderSchemaDto): Codebook => {
61
61
  const modulePrefix = schema.prefix;
62
62
  const pageVariables = getPageVariablesFromPages(schema.pages, modulePrefix);
63
63
  const vs = schema.predefinedVariables;
64
- const predefinedVariables: PredefinedVariable[] = vs ? [...vs] : [];
64
+ const predefinedVariables: CodebookPredefinedVariable[] = vs ? [...vs] : [];
65
65
  return { pageVariables, predefinedVariables };
66
66
  };
67
67
 
@@ -0,0 +1,39 @@
1
+ import { PageID, SchemaID } from "./ID";
2
+
3
+ describe("ID Functions work", () => {
4
+ //Generate test for schema prefix
5
+ test("SCHEMA_ID isFunction works", () => {
6
+ const id = SchemaID.create();
7
+ expect(SchemaID.is(id)).toBe(true);
8
+ expect(SchemaID.is("")).toBe(false);
9
+ expect(SchemaID.is("a")).toBe(false);
10
+ expect(SchemaID.is("a".repeat(10))).toBe(true);
11
+ expect(SchemaID.is("a".repeat(9))).toBe(false);
12
+ });
13
+ test("SCHEMA_ID ensure works", () => {
14
+ const id = SchemaID.create();
15
+ expect(SchemaID.validateOrCreate(id)).toBe(id);
16
+ expect(SchemaID.validateOrCreate("")).not.toBe("");
17
+ expect(SchemaID.validateOrCreate("")).not.toBe("a");
18
+ expect(SchemaID.validateOrCreate("a".repeat(10))).toBe("a".repeat(10));
19
+ expect(SchemaID.validateOrCreate("a".repeat(9))).not.toBe("a".repeat(9));
20
+ expect(SchemaID.validateOrCreate("ABcdefghigKLML")).toBe("ABcdefghigKLML");
21
+ });
22
+ test("PageID isFunction works", () => {
23
+ const id = PageID.create();
24
+ expect(PageID.is(id)).toBe(true);
25
+ expect(PageID.is("")).toBe(false);
26
+ expect(PageID.is("a")).toBe(false);
27
+ expect(PageID.is("a".repeat(10))).toBe(true);
28
+ expect(PageID.is("a".repeat(9))).toBe(false);
29
+ });
30
+ test("PageID ensure works", () => {
31
+ const id = PageID.create();
32
+ expect(PageID.validateOrCreate(id)).toBe(id);
33
+ expect(PageID.validateOrCreate("")).not.toBe("");
34
+ expect(PageID.validateOrCreate("")).not.toBe("a");
35
+ expect(PageID.validateOrCreate("a".repeat(10))).toBe("a".repeat(10));
36
+ expect(PageID.validateOrCreate("a".repeat(9))).not.toBe("a".repeat(9));
37
+ expect(PageID.validateOrCreate("ABcdefghigKLML")).toBe("ABcdefghigKLML");
38
+ });
39
+ });
@@ -0,0 +1,138 @@
1
+ // The default length of an ID
2
+ const ID_LENGTH = 32;
3
+
4
+ // The minimum length of an ID
5
+ const MIN_LENGTH = 10;
6
+
7
+ export type ID<B extends string> = string & { __ID__: B };
8
+
9
+ const isID = <const B extends string>(idName: B, id?: string): id is ID<B> => {
10
+ if (typeof id !== "string") return false;
11
+ return id.length >= MIN_LENGTH;
12
+ };
13
+
14
+ const createIDByName = <const B extends string>(idName: B): ID<B> => {
15
+ const letters = "abcdefghijklmnopqrstuvyz";
16
+ const all = letters + letters.toUpperCase();
17
+ let result = "";
18
+
19
+ for (let i = 0; i < ID_LENGTH; i++) {
20
+ const char = all.charAt(Math.floor(Math.random() * all.length));
21
+ result += char;
22
+ }
23
+
24
+ return result as ID<B>;
25
+ };
26
+ const createDummyID = <const B extends string>(idName: B, letter: string): ID<B> => {
27
+ return letter.repeat(ID_LENGTH) as ID<B>;
28
+ };
29
+ interface Id<T extends string> {
30
+ name: T;
31
+ is: (id: string) => id is ID<T>;
32
+ validateOrCreate: (id: string) => ID<T>;
33
+ validateOrThrow: (id: string) => ID<T>;
34
+ create: () => ID<T>;
35
+ dummy: {
36
+ a: ID<T>;
37
+ b: ID<T>;
38
+ c: ID<T>;
39
+ d: ID<T>;
40
+ e: ID<T>;
41
+ f: ID<T>;
42
+ g: ID<T>;
43
+ h: ID<T>;
44
+ i: ID<T>;
45
+ j: ID<T>;
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Creates a object with helper functions for a specific ID type
51
+ * @param idName
52
+ */
53
+ export const createTypedIdSingleton = <const B extends string>(idName: B): Id<B> => {
54
+ /**
55
+ * Creates a new ID of the correct type
56
+ */
57
+ const create = (): ID<B> => createIDByName(idName);
58
+
59
+ /**
60
+ * Checks if the id is of the correct type
61
+ * @param id
62
+ */
63
+ const is = (id: string): id is ID<B> => isID(idName, id);
64
+
65
+ /**
66
+ * Checks if the id is of the correct type, if not it throws an error
67
+ * @param id
68
+ */
69
+ const validateOrThrow = (id: string): ID<B> => {
70
+ if (!is(id)) {
71
+ throw new Error(`Invalid id: ${id}`);
72
+ }
73
+ return id;
74
+ };
75
+
76
+ /**
77
+ * The lowercase name of the id (SCHEMA, PAGE, etc)
78
+ */
79
+ const name: B = idName;
80
+
81
+ /**
82
+ * Ensures that the id is of the correct type, if not it creates a new one
83
+ * @param id
84
+ */
85
+ const validateOrCreate = (id: string): ID<B> => {
86
+ return is(id) ? id : create();
87
+ };
88
+
89
+ const a = createDummyID(idName, "a");
90
+ const b = createDummyID(idName, "b");
91
+ const c = createDummyID(idName, "c");
92
+ const d = createDummyID(idName, "d");
93
+ const e = createDummyID(idName, "e");
94
+ const f = createDummyID(idName, "f");
95
+ const g = createDummyID(idName, "g");
96
+ const h = createDummyID(idName, "h");
97
+ const i = createDummyID(idName, "i");
98
+ const j = createDummyID(idName, "j");
99
+ const list = [a, b, c, d, e, f, g, h, i, j];
100
+
101
+ const dummy = {
102
+ a,
103
+ b,
104
+ c,
105
+ d,
106
+ e,
107
+ f,
108
+ g,
109
+ h,
110
+ i,
111
+ j,
112
+ list,
113
+ };
114
+
115
+ return Object.freeze({ create, is, validateOrCreate, validateOrThrow, name, dummy });
116
+ };
117
+
118
+ export type SchemaID = ID<"SCHEMA">;
119
+ export const SchemaID = createTypedIdSingleton("SCHEMA");
120
+
121
+ export type PageID = ID<"PAGE">;
122
+ export const PageID = createTypedIdSingleton("PAGE");
123
+
124
+ export type TagID = ID<"TAG">;
125
+ export const TagID = createTypedIdSingleton("TAG");
126
+ export type OptionID = ID<"OPTION">;
127
+ export const OptionID = createTypedIdSingleton("OPTION");
128
+
129
+ export type TextID = ID<"TEXT">;
130
+ export const TextID = createTypedIdSingleton("TEXT");
131
+ export type QuestionID = ID<"QUESTION">;
132
+ export const QuestionID = createTypedIdSingleton("QUESTION");
133
+
134
+ export type SumScoreVariableID = ID<"SUM_SCORE_VARIABLE">;
135
+ export const SumScoreVariableID = createTypedIdSingleton("SUM_SCORE_VARIABLE");
136
+
137
+ export type SumScoreMemberShipID = ID<"SUM_SCORE_MEMBERSHIP">;
138
+ export const SumScoreMemberShipID = createTypedIdSingleton("SUM_SCORE_MEMBERSHIP");
@@ -31,15 +31,16 @@ export class PagePrefix {
31
31
  return value;
32
32
  };
33
33
  public static castOrCreateRandom = (value: string): PagePrefix => {
34
- const v = PagePrefix.isValid(value) ? value : createRandomPrefix<PagePrefixValue>(PagePrefix.randomLen);
34
+ const v = PagePrefix.isValid(value)
35
+ ? value
36
+ : createRandomPrefix<PagePrefixValue>(PagePrefix.randomLen);
35
37
  return new PagePrefix(v);
36
38
  };
37
39
 
38
40
  public static isValid = (prefix: string | 999): prefix is PagePrefixValue => {
39
41
  if (typeof prefix !== "string") return false;
40
42
  if (prefix.length < SchemaPrefix.MIN_LENGTH) return false;
41
- if (prefix.length > SchemaPrefix.MAX_LENGTH) return false;
42
- return true;
43
+ return prefix.length <= SchemaPrefix.MAX_LENGTH;
43
44
  };
44
45
 
45
46
  get value(): PagePrefixValue {
@@ -3,6 +3,7 @@ import { PagePrefixValue } from "./page-prefix";
3
3
  import { SchemaPrefixValue } from "./schema-prefix";
4
4
 
5
5
  export type VarID = `${SchemaPrefixValue}_${PagePrefixValue}`;
6
+
6
7
  export const VarID = {
7
8
  create: (schemaPrefix: SchemaPrefixValue, pagePrefix: PagePrefixValue): VarID => {
8
9
  const varId = schemaPrefix + "_" + pagePrefix;