@instructure/platform-modules 0.1.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.
Files changed (81) hide show
  1. package/README.md +75 -0
  2. package/dist/adapters/canvasGraphql/index.d.ts +54 -0
  3. package/dist/adapters/canvasGraphql/index.d.ts.map +1 -0
  4. package/dist/adapters/canvasGraphql/normalize.d.ts +345 -0
  5. package/dist/adapters/canvasGraphql/normalize.d.ts.map +1 -0
  6. package/dist/adapters/canvasGraphql/queries.d.ts +69 -0
  7. package/dist/adapters/canvasGraphql/queries.d.ts.map +1 -0
  8. package/dist/adapters/inMemory.d.ts +63 -0
  9. package/dist/adapters/inMemory.d.ts.map +1 -0
  10. package/dist/adapters/index.d.ts +3 -0
  11. package/dist/adapters/index.d.ts.map +1 -0
  12. package/dist/adapters/limits.d.ts +86 -0
  13. package/dist/adapters/limits.d.ts.map +1 -0
  14. package/dist/adapters/types.d.ts +87 -0
  15. package/dist/adapters/types.d.ts.map +1 -0
  16. package/dist/cache/index.d.ts +3 -0
  17. package/dist/cache/index.d.ts.map +1 -0
  18. package/dist/cache/invalidate.d.ts +52 -0
  19. package/dist/cache/invalidate.d.ts.map +1 -0
  20. package/dist/cache/keys.d.ts +86 -0
  21. package/dist/cache/keys.d.ts.map +1 -0
  22. package/dist/components/FilterChips.d.ts +19 -0
  23. package/dist/components/FilterChips.d.ts.map +1 -0
  24. package/dist/components/ModuleCard.d.ts +32 -0
  25. package/dist/components/ModuleCard.d.ts.map +1 -0
  26. package/dist/components/ModuleItemRow.d.ts +17 -0
  27. package/dist/components/ModuleItemRow.d.ts.map +1 -0
  28. package/dist/components/ModulesList.d.ts +69 -0
  29. package/dist/components/ModulesList.d.ts.map +1 -0
  30. package/dist/components/ModulesList.stories.d.ts +138 -0
  31. package/dist/components/ModulesList.stories.d.ts.map +1 -0
  32. package/dist/components/StatusPill.d.ts +18 -0
  33. package/dist/components/StatusPill.d.ts.map +1 -0
  34. package/dist/components/index.d.ts +7 -0
  35. package/dist/components/index.d.ts.map +1 -0
  36. package/dist/components/labels.d.ts +89 -0
  37. package/dist/components/labels.d.ts.map +1 -0
  38. package/dist/config/index.d.ts +3 -0
  39. package/dist/config/index.d.ts.map +1 -0
  40. package/dist/config/registry.d.ts +38 -0
  41. package/dist/config/registry.d.ts.map +1 -0
  42. package/dist/config/schemas.d.ts +16 -0
  43. package/dist/config/schemas.d.ts.map +1 -0
  44. package/dist/fixtures/generate.d.ts +48 -0
  45. package/dist/fixtures/generate.d.ts.map +1 -0
  46. package/dist/fixtures/index.d.ts +3 -0
  47. package/dist/fixtures/index.d.ts.map +1 -0
  48. package/dist/fixtures/scenarios.d.ts +42 -0
  49. package/dist/fixtures/scenarios.d.ts.map +1 -0
  50. package/dist/hooks/constants.d.ts +49 -0
  51. package/dist/hooks/constants.d.ts.map +1 -0
  52. package/dist/hooks/index.d.ts +7 -0
  53. package/dist/hooks/index.d.ts.map +1 -0
  54. package/dist/hooks/queryOptions.d.ts +58 -0
  55. package/dist/hooks/queryOptions.d.ts.map +1 -0
  56. package/dist/hooks/useItemsMode.d.ts +114 -0
  57. package/dist/hooks/useItemsMode.d.ts.map +1 -0
  58. package/dist/hooks/useModuleItemQueries.d.ts +56 -0
  59. package/dist/hooks/useModuleItemQueries.d.ts.map +1 -0
  60. package/dist/hooks/useModuleItems.d.ts +41 -0
  61. package/dist/hooks/useModuleItems.d.ts.map +1 -0
  62. package/dist/hooks/useModules.d.ts +50 -0
  63. package/dist/hooks/useModules.d.ts.map +1 -0
  64. package/dist/hooks/useModulesPageData.d.ts +78 -0
  65. package/dist/hooks/useModulesPageData.d.ts.map +1 -0
  66. package/dist/i18n.d.ts +92 -0
  67. package/dist/i18n.d.ts.map +1 -0
  68. package/dist/index.d.ts +19 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +1966 -0
  71. package/dist/types/domain.d.ts +147 -0
  72. package/dist/types/domain.d.ts.map +1 -0
  73. package/dist/types/enums.d.ts +93 -0
  74. package/dist/types/enums.d.ts.map +1 -0
  75. package/dist/types/extensions.d.ts +48 -0
  76. package/dist/types/extensions.d.ts.map +1 -0
  77. package/dist/types/index.d.ts +4 -0
  78. package/dist/types/index.d.ts.map +1 -0
  79. package/dist/types/schemas.d.ts +38 -0
  80. package/dist/types/schemas.d.ts.map +1 -0
  81. package/package.json +80 -0
@@ -0,0 +1,147 @@
1
+ import { z } from 'zod';
2
+ import { CompletionRequirementTypeSchema, ModuleItemStatusSchema, ModuleItemTypeSchema, ModuleStatusSchema } from './enums';
3
+ /**
4
+ * The normalised domain model shared by every host and every transport.
5
+ *
6
+ * This is deliberately smaller than what either Canvas LMS or Canvas Career
7
+ * fetches today: it is the *intersection* of what both need to render a
8
+ * modules page. Anything host-specific (blueprint locks, mastery paths,
9
+ * assignment overrides) rides in `extensions` rather than widening the model,
10
+ * so a host can never make the shared contract depend on its own UI.
11
+ *
12
+ * Three normalisation rules hold at every adapter boundary:
13
+ *
14
+ * 1. Ids are strings. GraphQL hands back `_id`/`id`, REST hands back numbers.
15
+ * 2. `published` is always populated. REST omits the field for students,
16
+ * which means "published"; the adapter resolves that, not the caller.
17
+ * 3. Item types are one closed enum. Canvas GraphQL distinguishes new quizzes
18
+ * with `isNewQuiz`, REST with `quiz_lti`; both collapse to `'quiz'` here.
19
+ * Every enum in this file is inferred from `./enums`, so the type and the
20
+ * runtime validator can never disagree.
21
+ */
22
+ /**
23
+ * Every closed enum below is inferred from its Zod schema in `./enums`, never
24
+ * written out twice. See that file for the members, where they come from, and
25
+ * the runtime crash the single source of truth prevents.
26
+ */
27
+ export type ModuleItemType = z.infer<typeof ModuleItemTypeSchema>;
28
+ /** Per-item state, from the designer's annotation. @see ./enums */
29
+ export type ModuleItemStatus = z.infer<typeof ModuleItemStatusSchema>;
30
+ /** Per-module rollup, from the designer's annotation. @see ./enums */
31
+ export type ModuleStatus = z.infer<typeof ModuleStatusSchema>;
32
+ /**
33
+ * A student's progress through a module.
34
+ *
35
+ * Optional throughout: teacher and anonymous views have no progression, and
36
+ * an adapter that doesn't fetch it must leave this undefined rather than
37
+ * fabricate zeroes — "0 of 4 requirements met" and "we didn't ask" are
38
+ * different things, and only one of them should render a progress bar.
39
+ */
40
+ export interface ModuleProgress {
41
+ status: ModuleStatus;
42
+ /** Requirements the student has satisfied. */
43
+ requirementsMet: number;
44
+ /** Requirements the module defines. Mirrors `Module.requirementCount`. */
45
+ requirementCount: number;
46
+ /** 0–100, precomputed so every host rounds it the same way. */
47
+ percentComplete: number;
48
+ /** Latest due date across the module's items; drives the header byline. */
49
+ latestDueAt?: string | null;
50
+ /** Items past due and unsubmitted. Drives the "Missing" filter chip. */
51
+ missingCount: number;
52
+ }
53
+ /** The six types Canvas validates, plus `unknown` for forward compat. @see ./enums */
54
+ export type CompletionRequirementType = z.infer<typeof CompletionRequirementTypeSchema>;
55
+ /** A completion requirement attached to a module item. */
56
+ export interface CompletionRequirement {
57
+ /**
58
+ * Id of the module **item** this requirement applies to — not an id of the
59
+ * requirement itself, which Canvas does not give one.
60
+ *
61
+ * Named `itemId` rather than `id` because that join is the non-obvious part:
62
+ * `ModuleCompletionRequirement.id` on the wire is the content tag id, which is
63
+ * what lets `completionRequirements` be matched against `ModuleItem.id` and
64
+ * against `ModuleProgression.requirementsMet`.
65
+ */
66
+ itemId: string;
67
+ type: CompletionRequirementType;
68
+ minScore?: number | null;
69
+ minPercentage?: number | null;
70
+ }
71
+ export interface ModuleItem {
72
+ id: string;
73
+ moduleId: string;
74
+ title: string;
75
+ type: ModuleItemType;
76
+ /** 1-based position within the module, as Canvas reports it. */
77
+ position: number;
78
+ /** Nesting depth used for visual indentation. */
79
+ indent: number;
80
+ published: boolean;
81
+ /** Canvas URL the item navigates to, when it has one. */
82
+ url?: string | null;
83
+ completionRequirement?: CompletionRequirement | null;
84
+ /** ISO 8601. Overrides are already applied by the adapter when applicable. */
85
+ dueAt?: string | null;
86
+ /**
87
+ * Student-view submission state. Undefined when not fetched (teacher view,
88
+ * anonymous, or an adapter that skips it) — distinct from `'not_started'`.
89
+ */
90
+ status?: ModuleItemStatus;
91
+ /** Points the item is worth. Null for types that cannot be graded. */
92
+ pointsPossible?: number | null;
93
+ /**
94
+ * Host-specific fields the shared contract does not model. Opaque to this
95
+ * package: passed through untouched, never merged, never read directly.
96
+ *
97
+ * Read it through the accessors in `types/extensions.ts` rather than by key —
98
+ * they own the `unknown` narrowing and document what is in here.
99
+ */
100
+ extensions?: Record<string, unknown>;
101
+ }
102
+ export interface Module {
103
+ id: string;
104
+ name: string;
105
+ position: number;
106
+ published: boolean;
107
+ /**
108
+ * Total items in the module, independent of how many have been fetched.
109
+ * Drives the items-mode policy and any "showing 10 of 40" affordance.
110
+ */
111
+ itemCount: number;
112
+ /**
113
+ * Present in `inline` mode, absent in `onDemand` mode. An empty array means
114
+ * "fetched, and the module is genuinely empty" — distinct from `undefined`,
115
+ * which means "not fetched yet".
116
+ */
117
+ items?: ModuleItem[];
118
+ unlockAt?: string | null;
119
+ prerequisiteModuleIds: string[];
120
+ requirementCount?: number | null;
121
+ requireSequentialProgress: boolean;
122
+ completionRequirements?: CompletionRequirement[];
123
+ /**
124
+ * The student's progress. Undefined in teacher/anonymous views.
125
+ *
126
+ * Module-level by design: the status pill, the progress bar, the header due
127
+ * date and every filter chip read from here, so the whole filter row works
128
+ * without any item having been fetched. That is what keeps `onDemand` mode
129
+ * viable on a 120-module course.
130
+ */
131
+ progress?: ModuleProgress;
132
+ extensions?: Record<string, unknown>;
133
+ }
134
+ /** Relay-style cursor pagination, normalised across GraphQL and REST. */
135
+ export interface PageInfo {
136
+ hasNextPage: boolean;
137
+ endCursor: string | null;
138
+ }
139
+ export interface ModulesPage {
140
+ modules: Module[];
141
+ pageInfo: PageInfo;
142
+ }
143
+ export interface ModuleItemsPage {
144
+ items: ModuleItem[];
145
+ pageInfo: PageInfo;
146
+ }
147
+ //# sourceMappingURL=domain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../src/types/domain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAC5B,OAAO,KAAK,EACV,+BAA+B,EAC/B,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,SAAS,CAAA;AAEhB;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAEjE,mEAAmE;AACnE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAErE,sEAAsE;AACtE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE7D;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAA;IACpB,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAA;IACvB,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,CAAA;IACxB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAA;IACvB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,sFAAsF;AACtF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAA;AAEvF,0DAA0D;AAC1D,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,yBAAyB,CAAA;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,cAAc,CAAA;IACpB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,OAAO,CAAA;IAClB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,qBAAqB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAA;IACpD,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAiBrB;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,yBAAyB,EAAE,OAAO,CAAA;IAClC,sBAAsB,CAAC,EAAE,qBAAqB,EAAE,CAAA;IAChD;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED,yEAAyE;AACzE,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,EAAE,QAAQ,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,QAAQ,EAAE,QAAQ,CAAA;CACnB"}
@@ -0,0 +1,93 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Every closed enum in the contract, defined **once**, as a Zod schema.
4
+ *
5
+ * The types in `domain.ts` are `z.infer`red from these rather than hand-written
6
+ * beside them. That inversion is the point, and it fixes a trap: when the union
7
+ * and the `z.enum` were two separate lists, the two ways of desynchronising them
8
+ * behaved differently.
9
+ *
10
+ * - Add a member to the `z.enum` but not the union → compile error. Loud.
11
+ * - Add a member to the union but not the `z.enum` → **compiles clean**. The UI
12
+ * can switch on it, an adapter can return it, tests pass. Then real data
13
+ * arrives, `ModuleItemSchema.parse()` throws "invalid enum value", and the
14
+ * modules page dies in the browser.
15
+ *
16
+ * Not hypothetical: `AiExperience` is a Canvas item type this adapter already
17
+ * drops, so "someone adds `'ai_experience'` to `ModuleItemType`" is plausibly the
18
+ * next change made to this file.
19
+ *
20
+ * They live in their own module, rather than in `schemas.ts`, so that `domain.ts`
21
+ * can derive from them without a cycle — `schemas.ts` imports the object types
22
+ * *from* `domain.ts` for its `z.ZodType<Module>` annotations.
23
+ *
24
+ * The explicit `z.ZodEnum<[...]>` annotations are required by the repo's
25
+ * `isolatedDeclarations`; they duplicate the literals, but a mismatch there is a
26
+ * compile error rather than a runtime one.
27
+ */
28
+ export declare const ModuleItemTypeSchema: z.ZodEnum<[
29
+ 'assignment',
30
+ 'quiz',
31
+ 'discussion',
32
+ 'page',
33
+ 'file',
34
+ 'external_url',
35
+ 'external_tool',
36
+ 'sub_header'
37
+ ]>;
38
+ /**
39
+ * Per-item state as the student-view design labels it.
40
+ *
41
+ * Taken from the designer's annotation in the Modules Foundation file rather than
42
+ * invented here: "Not started / In progress / Submitted / Graded / Missing /
43
+ * Locked". Derived by the adapter from submission state, not stored on the item
44
+ * in Canvas.
45
+ */
46
+ export declare const ModuleItemStatusSchema: z.ZodEnum<[
47
+ 'not_started',
48
+ 'in_progress',
49
+ 'submitted',
50
+ 'graded',
51
+ 'missing',
52
+ 'locked'
53
+ ]>;
54
+ /**
55
+ * Per-module rollup state.
56
+ *
57
+ * Also from the design annotation: "Not started / In progress / All submitted /
58
+ * All graded / Missing (1+ item past due) / Locked".
59
+ *
60
+ * NOTE: the mock renders a pill reading "All items complete" which is not in that
61
+ * list. Treated as a presentation label over `all_graded`/`all_submitted` until
62
+ * the designer says otherwise — flagged rather than guessed at.
63
+ */
64
+ export declare const ModuleStatusSchema: z.ZodEnum<[
65
+ 'not_started',
66
+ 'in_progress',
67
+ 'all_submitted',
68
+ 'all_graded',
69
+ 'missing',
70
+ 'locked'
71
+ ]>;
72
+ /**
73
+ * The kinds of completion requirement Canvas can attach to a module item.
74
+ *
75
+ * Canvas validates the same six exhaustively when a teacher saves a module
76
+ * (`app/models/context_module.rb:684,687`), so this is the whole set, not a
77
+ * sample of it.
78
+ *
79
+ * `unknown` is the forward-compatibility member: a seventh type shipping in
80
+ * Canvas must not make this package fail to parse a module. It lands here, the
81
+ * label humanises it, and nothing downstream has to handle a string it has never
82
+ * heard of.
83
+ */
84
+ export declare const CompletionRequirementTypeSchema: z.ZodEnum<[
85
+ 'must_view',
86
+ 'must_mark_done',
87
+ 'must_contribute',
88
+ 'must_submit',
89
+ 'min_score',
90
+ 'min_percentage',
91
+ 'unknown'
92
+ ]>;
93
+ //# sourceMappingURL=enums.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,eAAO,MAAM,oBAAoB,EAAE,CAAC,CAAC,OAAO,CAC1C;IACE,YAAY;IACZ,MAAM;IACN,YAAY;IACZ,MAAM;IACN,MAAM;IACN,cAAc;IACd,eAAe;IACf,YAAY;CACb,CAUD,CAAA;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,CAAC,CAAC,OAAO,CAC5C;IAAC,aAAa;IAAE,aAAa;IAAE,WAAW;IAAE,QAAQ;IAAE,SAAS;IAAE,QAAQ;CAAC,CACU,CAAA;AAEtF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,EAAE,CAAC,CAAC,OAAO,CACxC;IAAC,aAAa;IAAE,aAAa;IAAE,eAAe;IAAE,YAAY;IAAE,SAAS;IAAE,QAAQ;CAAC,CACU,CAAA;AAE9F;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,+BAA+B,EAAE,CAAC,CAAC,OAAO,CACrD;IACE,WAAW;IACX,gBAAgB;IAChB,iBAAiB;IACjB,aAAa;IACb,WAAW;IACX,gBAAgB;IAChB,SAAS;CACV,CASD,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { ModuleItem } from './domain';
2
+ /**
3
+ * The `extensions` escape hatch, and the only sanctioned way through it.
4
+ *
5
+ * `Module.extensions` and `ModuleItem.extensions` are `Record<string, unknown>`
6
+ * so a host can carry a field the shared contract does not model. The bag itself
7
+ * is opaque to this package — never read, never merged, passed through as it
8
+ * arrives — which is what stops one host's UI needs from widening the model
9
+ * everyone else consumes.
10
+ *
11
+ * Opaque does not mean untyped at the edges. Every field that rides in here gets
12
+ * a key constant and a reader in this file, so:
13
+ *
14
+ * - the key is spelled once rather than stringly-typed at each use site,
15
+ * - the reader owns the `unknown` → narrow conversion, and a host that writes
16
+ * the wrong shape gets `null` rather than a render crash,
17
+ * - `grep` over this file answers "what is actually in extensions today".
18
+ *
19
+ * Keys are namespaced by the host that owns them. Two hosts adding a field they
20
+ * both call `duration` must not collide, and an unprefixed key would make that
21
+ * collision silent.
22
+ */
23
+ /**
24
+ * Minutes Canvas Career estimates an item takes.
25
+ *
26
+ * Career-only: Canvas LMS has no such field, does not fetch it, and renders
27
+ * nothing for it. It lives here rather than on `ModuleItem` for exactly that
28
+ * reason — a field that is null for one of the two hosts is not part of the
29
+ * intersection the domain model is supposed to be.
30
+ *
31
+ * Canvas exposes it as an `ISO8601Duration` (`PT45M`); the adapter converts to
32
+ * whole minutes, because that is the only unit either UI renders.
33
+ */
34
+ export declare const EXT_CAREER_ESTIMATED_DURATION_MINUTES = "career.estimatedDurationMinutes";
35
+ /**
36
+ * @returns the estimate in whole minutes, or `null` when the host did not supply
37
+ * one — including when it supplied something that is not a positive number.
38
+ */
39
+ export declare function getEstimatedDurationMinutes(item: Pick<ModuleItem, 'extensions'>): number | null;
40
+ /**
41
+ * Merge an estimate into an extensions bag, or leave the bag absent.
42
+ *
43
+ * Returns `undefined` for a null estimate rather than an object holding a null,
44
+ * so an item with nothing host-specific about it carries no `extensions` key at
45
+ * all. That keeps the common case — every Canvas LMS item — free.
46
+ */
47
+ export declare function withEstimatedDurationMinutes(extensions: Record<string, unknown> | undefined, minutes: number | null | undefined): Record<string, unknown> | undefined;
48
+ //# sourceMappingURL=extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/types/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qCAAqC,oCAAoC,CAAA;AAEtF;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,MAAM,GAAG,IAAI,CAI/F;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC/C,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACjC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAGrC"}
@@ -0,0 +1,4 @@
1
+ export type { ModuleItemStatus, ModuleProgress, ModuleStatus, CompletionRequirement, CompletionRequirementType, Module, ModuleItem, ModuleItemsPage, ModuleItemType, ModulesPage, PageInfo, } from './domain';
2
+ export { CompletionRequirementSchema, CompletionRequirementTypeSchema, ModuleItemStatusSchema, ModuleProgressSchema, ModuleStatusSchema, ModuleItemSchema, ModuleItemsPageSchema, ModuleItemTypeSchema, ModuleSchema, ModulesPageSchema, PageInfoSchema, } from './schemas';
3
+ export { EXT_CAREER_ESTIMATED_DURATION_MINUTES, getEstimatedDurationMinutes, withEstimatedDurationMinutes, } from './extensions';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,qBAAqB,EACrB,yBAAyB,EACzB,MAAM,EACN,UAAU,EACV,eAAe,EACf,cAAc,EACd,WAAW,EACX,QAAQ,GACT,MAAM,UAAU,CAAA;AACjB,OAAO,EACL,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,iBAAiB,EACjB,cAAc,GACf,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,qCAAqC,EACrC,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,cAAc,CAAA"}
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { CompletionRequirement, Module, ModuleItem, ModuleItemsPage, ModuleProgress, ModulesPage, PageInfo } from './domain';
3
+ export { CompletionRequirementTypeSchema, ModuleItemStatusSchema, ModuleItemTypeSchema, ModuleStatusSchema, } from './enums';
4
+ /**
5
+ * Runtime schemas for the domain model.
6
+ *
7
+ * These validate at the *adapter boundary*, after normalisation — not at the
8
+ * wire. That is deliberate: the wire shapes differ per transport, but every
9
+ * adapter must produce something that satisfies these, which is what makes
10
+ * "one contract, two backends" enforceable rather than aspirational.
11
+ *
12
+ * Canvas LMS ships no response validation today and Canvas Career validates
13
+ * its own REST shapes; neither can catch the case this catches — one adapter
14
+ * quietly drifting from the other.
15
+ */
16
+ export declare const ModuleProgressSchema: z.ZodType<ModuleProgress>;
17
+ export declare const CompletionRequirementSchema: z.ZodType<CompletionRequirement>;
18
+ export declare const ModuleItemSchema: z.ZodType<ModuleItem>;
19
+ export declare const ModuleSchema: z.ZodType<Module>;
20
+ /**
21
+ * Relay page info, with the dead-cursor combination refused.
22
+ *
23
+ * `hasNextPage: true` with a null `endCursor` type-checks but cannot be acted
24
+ * on: the hooks report `hasNextPage` straight off the last page, while paging is
25
+ * driven by `endCursor`, where null means "done" to TanStack Query. That pair
26
+ * therefore renders a "Load more" control that can never load anything — the
27
+ * silent-truncation failure this package exists to prevent, wearing a button.
28
+ *
29
+ * Relay's own spec makes it unreachable for a GraphQL adapter, but a REST
30
+ * adapter parsing an RFC-5988 `Link` header is one plausible bug away from it:
31
+ * see a `rel="next"`, set `hasNextPage`, then fail to extract the cursor from
32
+ * the URL. The refine is what makes it unrepresentable rather than merely
33
+ * unlikely.
34
+ */
35
+ export declare const PageInfoSchema: z.ZodType<PageInfo>;
36
+ export declare const ModulesPageSchema: z.ZodType<ModulesPage>;
37
+ export declare const ModuleItemsPageSchema: z.ZodType<ModuleItemsPage>;
38
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/types/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EACV,qBAAqB,EACrB,MAAM,EACN,UAAU,EACV,eAAe,EACf,cAAc,EACd,WAAW,EACX,QAAQ,EACT,MAAM,UAAU,CAAA;AAWjB,OAAO,EACL,+BAA+B,EAC/B,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,SAAS,CAAA;AAEhB;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,oBAAoB,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAOzD,CAAA;AAEF,eAAO,MAAM,2BAA2B,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAKvE,CAAA;AAEF,eAAO,MAAM,gBAAgB,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAcjD,CAAA;AAEF,eAAO,MAAM,YAAY,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAczC,CAAA;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAU3C,CAAA;AAEJ,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAGnD,CAAA;AAEF,eAAO,MAAM,qBAAqB,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,CAG3D,CAAA"}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@instructure/platform-modules",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "vite build",
19
+ "dev": "vite build --watch",
20
+ "test": "vitest run",
21
+ "test:coverage": "vitest run --coverage",
22
+ "type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
23
+ "type-check:tests": "tsc -p tsconfig.test.json",
24
+ "test:watch": "vitest"
25
+ },
26
+ "peerDependencies": {
27
+ "@instructure/ui-alerts": "^11.0.0",
28
+ "@instructure/ui-buttons": "^11.0.0",
29
+ "@instructure/ui-flex": "^11.0.0",
30
+ "@instructure/ui-heading": "^11.0.0",
31
+ "@instructure/ui-icons": "^11.0.0",
32
+ "@instructure/ui-menu": "^11.0.0",
33
+ "@instructure/ui-pill": "^11.0.0",
34
+ "@instructure/ui-progress": "^11.0.0",
35
+ "@instructure/ui-spinner": "^11.0.0",
36
+ "@instructure/ui-text": "^11.0.0",
37
+ "@instructure/ui-text-input": "^11.0.0",
38
+ "@instructure/ui-view": "^11.0.0",
39
+ "@tanstack/react-query": "^5.0.0",
40
+ "graphql": "^16.0.0",
41
+ "react": "^18.0.0",
42
+ "react-dom": "^18.0.0",
43
+ "zod": "^3.23.8"
44
+ },
45
+ "dependencies": {
46
+ "@instructure/platform-instui-bindings": "workspace:*",
47
+ "graphql-tag": "^2.12.6",
48
+ "p-limit": "^6.1.0"
49
+ },
50
+ "devDependencies": {
51
+ "@instructure/platform-mock-canvas-api": "workspace:*",
52
+ "@instructure/ui-alerts": "11.6.0",
53
+ "@instructure/ui-buttons": "11.6.0",
54
+ "@instructure/ui-flex": "11.6.0",
55
+ "@instructure/ui-heading": "11.6.0",
56
+ "@instructure/ui-icons": "11.6.0",
57
+ "@instructure/ui-menu": "11.6.0",
58
+ "@instructure/ui-pill": "11.6.0",
59
+ "@instructure/ui-progress": "11.6.0",
60
+ "@instructure/ui-spinner": "11.6.0",
61
+ "@instructure/ui-text": "11.6.0",
62
+ "@instructure/ui-text-input": "11.6.0",
63
+ "@instructure/ui-view": "11.6.0",
64
+ "@storybook/react": "^10.0.8",
65
+ "@testing-library/jest-dom": "^6.9.1",
66
+ "@testing-library/react": "^16.0.0",
67
+ "@testing-library/user-event": "^14.0.0",
68
+ "@types/react": "^18.3.0",
69
+ "@types/react-dom": "^18.3.0",
70
+ "@vitejs/plugin-react": "^4.0.0",
71
+ "@vitest/coverage-v8": "^4.0.17",
72
+ "graphql": "^16.9.0",
73
+ "jsdom": "^25.0.0",
74
+ "typescript": "^5.3.0",
75
+ "vite": "^6.0.0",
76
+ "vite-plugin-dts": "^4.0.0",
77
+ "vitest": "^4.0.0",
78
+ "zod": "^3.23.8"
79
+ }
80
+ }