@examplary/apps 0.0.1 → 1.0.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/README.md CHANGED
@@ -31,7 +31,7 @@ app.addCapability(exportCapability);
31
31
  export default app;
32
32
  ```
33
33
 
34
- Hooks can read secrets set with `exp app set-secret NAME value`, which are never sent to the
34
+ Hooks can read secrets set with `exp set-secret NAME value`, which are never sent to the
35
35
  dashboard. Read them inside a hook, not at the top of a module; a missing secret is
36
36
  `undefined`:
37
37
 
@@ -52,12 +52,13 @@ Capabilities add features to the platform, each with hooks of its own:
52
52
  - `ResultsDestination` — somewhere teachers publish grades to, like a gradebook.
53
53
  - `AssessmentPrerequisite` — something a student has to meet before starting a test, like a
54
54
  secure browser, with a settings component for the exam's settings and a player component.
55
+ - `QuestionType` — a question type, which workspaces that install the app can use in tests.
55
56
 
56
- `app.addQuestionType(Essay)` adds a question type, defined with `defineQuestionType()`. Its
57
+ A `new QuestionType({...})` takes the question type's definition. Its
57
58
  `components`, `icon`, `readme` and `previewImage` are paths relative to `app.ts`, e.g.
58
59
  `./essay/component-assessment.tsx`. `Essay.addValidator(({ question }) => ({ valid, error }))`
59
- checks questions of that type, e.g. generated ones, as a hook. Workspaces that install the app get
60
- all of its question types.
60
+ checks questions of that type, e.g. generated ones, as a hook. `Essay.addSetting({...})` adds a
61
+ setting after those in the definition's `settings`.
61
62
 
62
63
  `app.addSettingsPrepareStep()`, `app.addSettingsValidateStep()` and `app.addUninstallStep()`
63
64
  hook into saving the app's settings and removing it from a workspace.
@@ -70,6 +71,6 @@ every hook call loads.
70
71
  A `component` setting renders a React component (a `.tsx` file that default-exports a
71
72
  `FrontendSettingsFieldComponent` from `@examplary/ui`), which calls `setValue()` and `submit()`.
72
73
 
73
- `npx @examplary/cli app upload` evaluates `app.ts`, bundles every component for the browser
74
+ `npx @examplary/cli publish` evaluates `app.ts`, bundles every component for the browser
74
75
  and the app itself for the server, uploads the assets and publishes the result. See
75
76
  [docs/apps.md](../../docs/apps.md) for how the platform handles apps.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@examplary/apps",
3
3
  "packageManager": "yarn@4.8.1",
4
- "version": "0.0.1",
4
+ "version": "1.0.0",
5
5
  "description": "Define Examplary apps and question types in TypeScript.",
6
6
  "scripts": {
7
7
  "release": "semantic-release -e semantic-release-monorepo"
@@ -16,5 +16,10 @@
16
16
  "types": "./src/index.ts",
17
17
  "dependencies": {
18
18
  "@examplary/schemas": "*"
19
+ },
20
+ "homepage": "https://examplary.ai/developers",
21
+ "author": {
22
+ "name": "Examplary AI",
23
+ "email": "hi@examplary.ai"
19
24
  }
20
25
  }
package/src/app.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Capability } from "./capabilities/Capability";
2
2
  import { resolveComponent, serializeField } from "./fields";
3
- import { AppQuestionType } from "./question-type";
4
3
  import {
5
4
  AppDefinition,
6
5
  FullAppDefinition,
@@ -41,7 +40,11 @@ class App {
41
40
 
42
41
  addCapability(capability: Capability) {
43
42
  const def = capability.toDefinition();
44
- this.definition.capabilities.push(def.capability);
43
+ if ("questionType" in def) {
44
+ this.definition.questionTypes.push(def.questionType);
45
+ } else {
46
+ this.definition.capabilities.push(def.capability);
47
+ }
45
48
  this.definition.assets.push(...def.assets);
46
49
  this.definition.hooks.push(...def.hooks);
47
50
  this.definition.components.push(...def.components);
@@ -65,13 +68,6 @@ class App {
65
68
  this.definition.settings.push(serializeField(settingDef));
66
69
  }
67
70
 
68
- /** Adds a question type, see `defineQuestionType()`. */
69
- addQuestionType(questionType: AppQuestionType) {
70
- const { questionType: definition, hooks } = questionType.toDefinition();
71
- this.definition.questionTypes.push(definition);
72
- this.definition.hooks.push(...hooks);
73
- }
74
-
75
71
  addSettingsValidateStep(
76
72
  fn: (settings: Record<string, any>) => Promise<boolean> | boolean,
77
73
  ) {
@@ -3,10 +3,13 @@ import {
3
3
  HookDefinition,
4
4
  ComponentDefinition,
5
5
  AssetDefinition,
6
+ QuestionTypeDefinition,
6
7
  } from "../types";
7
8
 
8
- export type ToCapabilityDefinition = {
9
- capability: CapabilityDefinition;
9
+ export type ToCapabilityDefinition = (
10
+ | { capability: CapabilityDefinition }
11
+ | { questionType: QuestionTypeDefinition }
12
+ ) & {
10
13
  hooks: HookDefinition[];
11
14
  components: ComponentDefinition[];
12
15
  assets: AssetDefinition[];
@@ -1,14 +1,30 @@
1
- import type { Question } from "@examplary/schemas";
1
+ import type { Question, SettingType } from "@examplary/schemas";
2
2
 
3
- import { HookDefinition, QuestionTypeDefinition } from "./types";
3
+ import { Capability, ToCapabilityDefinition } from "./Capability";
4
+ import { QuestionTypeDefinition } from "../types";
4
5
 
5
6
  export type QuestionValidation = { valid: boolean; error?: string };
6
7
 
7
- /** A question type of an app, see `defineQuestionType()`. */
8
- export class AppQuestionType {
8
+ /**
9
+ * A question type the app adds to Examplary. Its file paths are relative to
10
+ * `app.ts`, e.g. `./essay/component-assessment.tsx`.
11
+ */
12
+ export class QuestionType extends Capability {
13
+ protected definition: QuestionTypeDefinition;
9
14
  private functions: Record<string, Function> = {};
10
15
 
11
- constructor(private definition: QuestionTypeDefinition) {}
16
+ constructor(definition: QuestionTypeDefinition) {
17
+ super();
18
+ this.definition = definition;
19
+ }
20
+
21
+ /** Adds a setting teachers fill in for a question of this type, after those in its definition. */
22
+ addSetting(setting: SettingType) {
23
+ this.definition = {
24
+ ...this.definition,
25
+ settings: [...(this.definition.settings ?? []), setting],
26
+ };
27
+ }
12
28
 
13
29
  /**
14
30
  * Checks a question of this type, e.g. one AI generated, which is tried
@@ -22,10 +38,7 @@ export class AppQuestionType {
22
38
  this.functions["validate-question"] = fn;
23
39
  }
24
40
 
25
- toDefinition(): {
26
- questionType: QuestionTypeDefinition & { hooks?: Record<string, string> };
27
- hooks: HookDefinition[];
28
- } {
41
+ toDefinition(): ToCapabilityDefinition {
29
42
  const hookId = (name: string) =>
30
43
  `question-types.${this.definition.id}.${name}`;
31
44
  const names = Object.keys(this.functions);
@@ -43,13 +56,8 @@ export class AppQuestionType {
43
56
  id: hookId(name),
44
57
  fn: this.functions[name],
45
58
  })),
59
+ components: [],
60
+ assets: [],
46
61
  };
47
62
  }
48
63
  }
49
-
50
- /**
51
- * A question type, to add with `app.addQuestionType()`. Its file paths are
52
- * relative to `app.ts`, e.g. `./essay/component-assessment.tsx`.
53
- */
54
- export const defineQuestionType = (questionType: QuestionTypeDefinition) =>
55
- new AppQuestionType(questionType);
@@ -1,5 +1,6 @@
1
1
  export * from "./AssessmentPrerequisite";
2
2
  export * from "./Capability";
3
3
  export * from "./ExportOption";
4
+ export * from "./QuestionType";
4
5
  export * from "./ResultsDestination";
5
6
  export * from "./SettingsButton";
package/src/env.ts CHANGED
@@ -4,7 +4,7 @@ declare global {
4
4
  }
5
5
 
6
6
  /**
7
- * Secrets set with `exp app set-secret`, available in the app's hooks. Values
7
+ * Secrets set with `exp set-secret`, available in the app's hooks. Values
8
8
  * are read when accessed, so read them inside a hook, not at the top of a module.
9
9
  */
10
10
  export const env: Record<string, string | undefined> = new Proxy(
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@ export * from "./types";
2
2
  export type * from "./types";
3
3
 
4
4
  export * from "./app";
5
- export * from "./question-type";
6
5
  export * from "./capabilities";
7
6
  export * from "./context";
8
7
  export * from "./env";
package/src/types.ts CHANGED
@@ -88,7 +88,7 @@ export type AssetDefinition = {
88
88
  type TranslatablePath = string | Record<string, string>;
89
89
 
90
90
  /**
91
- * A question type the app provides. Its `components`, `functions` and `icon`,
91
+ * A question type the app provides. Its `components` and `icon`,
92
92
  * and its `readme` and `previewImage`, are paths relative to `app.ts`.
93
93
  */
94
94
  export type QuestionTypeDefinition = Omit<