@updraft-solutions/mcrit-sdk 0.3.1 → 0.5.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 (49) hide show
  1. package/dist/chunk-AKLFMP7G.cjs +2525 -0
  2. package/dist/{chunk-MF7G76QS.cjs → chunk-IVMOR5SW.cjs} +26 -1
  3. package/dist/{chunk-D5DUVW34.js → chunk-KIMCBEY7.js} +26 -1
  4. package/dist/chunk-YCIXOVEC.js +2525 -0
  5. package/dist/format/index.cjs +2 -2
  6. package/dist/format/index.d.cts +13 -4
  7. package/dist/format/index.d.ts +13 -4
  8. package/dist/format/index.js +1 -1
  9. package/dist/index.cjs +201 -5
  10. package/dist/index.d.cts +3 -3
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.js +208 -12
  13. package/dist/schemas/index.cjs +198 -2
  14. package/dist/schemas/index.d.cts +3157 -128
  15. package/dist/schemas/index.d.ts +3157 -128
  16. package/dist/schemas/index.js +201 -5
  17. package/dist/{models-BWmpcfR8.d.cts → training-Der1DPU-.d.cts} +23757 -6528
  18. package/dist/{models-BWmpcfR8.d.ts → training-Der1DPU-.d.ts} +23757 -6528
  19. package/dist/types/index.cjs +1 -1
  20. package/dist/types/index.d.cts +277 -6
  21. package/dist/types/index.d.ts +277 -6
  22. package/dist/types/index.js +1 -1
  23. package/package.json +1 -1
  24. package/src/format/index.ts +51 -5
  25. package/src/index.ts +39 -0
  26. package/src/schemas/common.ts +37 -0
  27. package/src/schemas/course-milestone.ts +78 -0
  28. package/src/schemas/equipment.ts +27 -0
  29. package/src/schemas/form-template.ts +413 -0
  30. package/src/schemas/index.ts +7 -1
  31. package/src/schemas/location-map.ts +175 -0
  32. package/src/schemas/models.ts +110 -28
  33. package/src/schemas/newsletter.ts +362 -0
  34. package/src/schemas/todo.ts +46 -0
  35. package/src/schemas/training.ts +500 -0
  36. package/src/types/ai.ts +85 -0
  37. package/src/types/aircraft-block.ts +23 -0
  38. package/src/types/api.ts +3 -1
  39. package/src/types/background-task.ts +28 -0
  40. package/src/types/compliance.ts +27 -3
  41. package/src/types/index.ts +5 -0
  42. package/src/types/landing-fee.ts +92 -0
  43. package/src/types/models.ts +44 -0
  44. package/src/types/roles.ts +26 -0
  45. package/dist/chunk-7QF3OJ45.cjs +0 -1325
  46. package/dist/chunk-FS5I4MBG.js +0 -1325
  47. package/src/schemas/fee.ts +0 -37
  48. /package/dist/{chunk-LXNCAKJZ.js → chunk-2WJHTRIE.js} +0 -0
  49. /package/dist/{chunk-MOOGM3DW.cjs → chunk-DCEOET63.cjs} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@updraft-solutions/mcrit-sdk",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Shared TypeScript types, Zod schemas, and pure helpers for the MCRIT aviation fleet management platform",
5
5
  "homepage": "https://github.com/updraft-solutions/mcrit-sdk#readme",
6
6
  "bugs": {
@@ -3,10 +3,11 @@ import moment from "moment-timezone";
3
3
  const DEFAULT_TZ = "UTC";
4
4
 
5
5
  /**
6
- * Note on German locale: the SDK does not eagerly load `moment/locale/de`.
7
- * Consumers that want German formatting should import it once at their
8
- * app entry: `import "moment/locale/de"`. Otherwise `timeUntil` and other
9
- * locale-dependent functions will fall back to English.
6
+ * Locale note: `timeUntil` uses `Intl.RelativeTimeFormat` natively
7
+ * localized, no moment locale files required. The moment-based formatters
8
+ * below are locale-INdependent except for the `MMM` token
9
+ * (`DATE_FORMATS.MONTH_SHORT`), which renders English month abbreviations
10
+ * unless a consumer registers moment locale data itself.
10
11
  */
11
12
 
12
13
  /** Predefined moment format strings, frozen so callers can reference them safely. */
@@ -195,6 +196,25 @@ export function timeString(
195
196
  return formatted;
196
197
  }
197
198
 
199
+ const RELATIVE_TIME_UNITS: Array<{
200
+ unit: Intl.RelativeTimeFormatUnit;
201
+ ms: number;
202
+ }> = [
203
+ { unit: "year", ms: 365 * 24 * 60 * 60 * 1000 },
204
+ { unit: "month", ms: 30 * 24 * 60 * 60 * 1000 },
205
+ { unit: "day", ms: 24 * 60 * 60 * 1000 },
206
+ { unit: "hour", ms: 60 * 60 * 1000 },
207
+ { unit: "minute", ms: 60 * 1000 },
208
+ ];
209
+
210
+ /**
211
+ * Relative time ("vor 3 Tagen" / "in 2 Stunden") via the browser-native
212
+ * `Intl.RelativeTimeFormat` — fully localized for any BCP 47 locale with
213
+ * zero locale files, and immune to the moment CJS/ESM dual-instance
214
+ * locale-registry pitfall that used to silently produce English output.
215
+ * `numeric: "auto"` yields natural wording for adjacent days
216
+ * ("gestern" / "morgen").
217
+ */
198
218
  export function timeUntil(
199
219
  dateTime: string | Date | null | undefined,
200
220
  locale: string = "de",
@@ -202,7 +222,33 @@ export function timeUntil(
202
222
  if (!dateTime) {
203
223
  return "";
204
224
  }
205
- return moment(dateTime).locale(locale).fromNow();
225
+ const target =
226
+ dateTime instanceof Date ? dateTime.getTime() : Date.parse(dateTime);
227
+ if (Number.isNaN(target)) {
228
+ return "";
229
+ }
230
+ const diff = target - Date.now();
231
+
232
+ // A structurally valid but unsupported tag doesn't throw — it silently
233
+ // uses the system default. Check actual support so unknown locales land
234
+ // on the SDK default (de) deterministically; malformed tags throw and
235
+ // take the same path.
236
+ let resolved = "de";
237
+ try {
238
+ if (Intl.RelativeTimeFormat.supportedLocalesOf(locale).length > 0) {
239
+ resolved = locale;
240
+ }
241
+ } catch {
242
+ // malformed tag — keep the default
243
+ }
244
+ const formatter = new Intl.RelativeTimeFormat(resolved, { numeric: "auto" });
245
+
246
+ for (const { unit, ms } of RELATIVE_TIME_UNITS) {
247
+ if (Math.abs(diff) >= ms) {
248
+ return formatter.format(Math.round(diff / ms), unit);
249
+ }
250
+ }
251
+ return formatter.format(Math.round(diff / 1000), "second");
206
252
  }
207
253
 
208
254
  export function minutesFormatted(minutes: number | null | undefined): string {
package/src/index.ts CHANGED
@@ -11,6 +11,9 @@ export {
11
11
  type AirplanePermission,
12
12
  type AirplaneShareInvite,
13
13
  type LandingFee,
14
+ type Fee,
15
+ type FeeCategoryOptions,
16
+ type Tag,
14
17
  type AircraftRating,
15
18
  type UserAircraftRating,
16
19
  type UserQualification,
@@ -48,6 +51,12 @@ export {
48
51
  type Event,
49
52
  type Ticket,
50
53
  type Post,
54
+ type CreatePostInput,
55
+ type UpdatePostInput,
56
+ type CreateAirplaneIssueInput,
57
+ type UpdateAirplaneIssueInput,
58
+ type ResolveAirplaneIssueInput,
59
+ type DeferAirplaneIssueInput,
51
60
  type Comment,
52
61
  type CreateCommentInput,
53
62
  type UpdateCommentInput,
@@ -57,6 +66,7 @@ export {
57
66
  type Setting,
58
67
  // types/api.ts
59
68
  type DataWrappedResponse,
69
+ type ApiResponse,
60
70
  type PaginatedResponse,
61
71
  type Resource,
62
72
  type ToggleFavoriteResponse,
@@ -64,9 +74,38 @@ export {
64
74
  type ComplianceRequirement,
65
75
  type ComplianceRequirementGroup,
66
76
  type ComplianceGroup,
77
+ type ComplianceGroupSource,
67
78
  type ComplianceSummary,
68
79
  type ComplianceData,
69
80
  type ComplianceResponse,
81
+ // types/aircraft-block.ts
82
+ type AircraftBlock,
83
+ type AircraftBlockReason,
84
+ // types/background-task.ts
85
+ type BackgroundTask,
86
+ type BackgroundTaskStatus,
87
+ // types/ai.ts
88
+ type AiSuggestion,
89
+ type AiSuggestionStatus,
90
+ type AiSuggestedMilestone,
91
+ type CourseDescriptionSuggestionPayload,
92
+ type LogbookFlightLogPayload,
93
+ type LogbookSuggestionMeta,
94
+ type LogbookSuggestion,
95
+ type DiscardedRow,
96
+ type LogbookStageResult,
97
+ // types/roles.ts
98
+ type RoleListItem,
99
+ type PermissionItem,
100
+ type RoleDetail,
101
+ type GroupedPermissions,
102
+ // types/landing-fee.ts
103
+ type LandingFeeTemplateParams,
104
+ type LandingFeeImportResult,
105
+ type LandingFeeInvoicePreviewRow,
106
+ type LandingFeeInvoicePreview,
107
+ type LandingFeeInvoiceApplyPayload,
108
+ type LandingFeeInvoiceApplyResult,
70
109
  } from "./types/index.js";
71
110
 
72
111
  export * from "./money/index.js";
@@ -57,12 +57,41 @@ export const mediaCollection = z
57
57
  .optional();
58
58
 
59
59
  // User & Company
60
+ /** Minimal participant shape used in the impersonation payload. */
61
+ export const impersonationParticipantSchema = z.object({
62
+ id: z.number(),
63
+ name: z.string(),
64
+ email: z.string(),
65
+ });
66
+ export type ImpersonationParticipant = z.infer<
67
+ typeof impersonationParticipantSchema
68
+ >;
69
+
70
+ /**
71
+ * Impersonation state. Rides along on the authenticated user payload
72
+ * (`/auth/user` → `user.impersonation`), so it is the single source of
73
+ * truth — no polling required. The backend's `HandleImpersonation`
74
+ * middleware also stamps an `X-Impersonating` header on every non-admin
75
+ * response, which clients use to detect a session that ended server-side.
76
+ */
77
+ export const impersonationStateSchema = z.object({
78
+ is_impersonating: z.boolean(),
79
+ impersonator: impersonationParticipantSchema.optional(),
80
+ impersonated: impersonationParticipantSchema.optional(),
81
+ started_at: z.string().optional(),
82
+ expires_at: z.string().optional(),
83
+ });
84
+ export type ImpersonationState = z.infer<typeof impersonationStateSchema>;
85
+
60
86
  export const userSchema = z.object({
61
87
  type: z.literal("user"),
62
88
  id: z.number(),
63
89
  name: z.string(),
64
90
  email: z.string().optional(),
65
91
  mobile: z.string().optional(),
92
+ // Present only on the authenticated-user payload while an admin
93
+ // impersonation session is active.
94
+ impersonation: impersonationStateSchema.optional(),
66
95
  });
67
96
 
68
97
  export const companySchema = z.object({
@@ -72,5 +101,13 @@ export const companySchema = z.object({
72
101
  media: mediaCollection,
73
102
  can: z.record(z.string(), z.boolean().nullable()).optional(),
74
103
  permissions: z.array(z.string()).optional(),
104
+ // Company display timezone (companies.timezone, default Europe/Berlin
105
+ // server-side); booking/event surfaces render in this zone. Optional:
106
+ // slim company payloads may omit it.
107
+ timezone: z.string().optional(),
75
108
  ai_landing_fee_invoices_enabled: z.boolean().optional(),
109
+ // Active AI-feature entitlement keys (per-company subscription gating,
110
+ // e.g. "ai.logbook_import"). Serialized by CompanyResource alongside
111
+ // `permissions[]`; slim payloads may omit it.
112
+ entitlements: z.array(z.string()).optional(),
76
113
  });
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+
3
+ export const CourseMilestoneKindEnum = z.enum([
4
+ "progress",
5
+ "solo",
6
+ "cross_country",
7
+ "theory",
8
+ "practical",
9
+ "prerequisite",
10
+ "custom",
11
+ ]);
12
+ export type CourseMilestoneKind = z.infer<typeof CourseMilestoneKindEnum>;
13
+
14
+ export const courseMilestoneSchema = z.object({
15
+ type: z.literal("courseMilestone").optional(),
16
+ id: z.number(),
17
+ course_id: z.number(),
18
+ name: z.string(),
19
+ description: z.string().nullable().optional(),
20
+ kind: z.string(),
21
+ is_prerequisite: z.boolean(),
22
+ requires_form_template_id: z.number().nullable().optional(),
23
+ required_form_template: z
24
+ .object({ id: z.number(), name: z.string().optional() })
25
+ .nullable()
26
+ .optional(),
27
+ completion_rule: z.record(z.string(), z.unknown()).nullable().optional(),
28
+ sort_order: z.number(),
29
+ created_at: z.string().optional(),
30
+ updated_at: z.string().optional(),
31
+ can: z
32
+ .object({
33
+ update: z.boolean().nullable().optional(),
34
+ delete: z.boolean().nullable().optional(),
35
+ })
36
+ .optional(),
37
+ });
38
+ export type CourseMilestone = z.infer<typeof courseMilestoneSchema>;
39
+
40
+ export const CourseMilestoneFormSchema = z.object({
41
+ name: z.string().min(2).max(191),
42
+ description: z.string().nullable().optional(),
43
+ kind: z.string().max(64).default("progress"),
44
+ is_prerequisite: z.boolean().default(false),
45
+ requires_form_template_id: z.number().nullable().optional(),
46
+ completion_rule: z.record(z.string(), z.unknown()).nullable().optional(),
47
+ sort_order: z.number().int().min(0).optional(),
48
+ });
49
+ export type CourseMilestoneForm = z.infer<typeof CourseMilestoneFormSchema>;
50
+
51
+ export const courseEnrollmentMilestoneSchema = z.object({
52
+ type: z.literal("courseEnrollmentMilestone").optional(),
53
+ id: z.number(),
54
+ course_enrollment_id: z.number(),
55
+ course_milestone_id: z.number(),
56
+ milestone: courseMilestoneSchema.optional(),
57
+ achieved_at: z.string().nullable().optional(),
58
+ achieved_by_membership_id: z.number().nullable().optional(),
59
+ achieved_by: z
60
+ .object({
61
+ id: z.number(),
62
+ user: z.object({ name: z.string().optional() }).optional(),
63
+ })
64
+ .nullable()
65
+ .optional(),
66
+ notes: z.string().nullable().optional(),
67
+ is_achieved: z.boolean(),
68
+ can_be_achieved: z.boolean(),
69
+ can: z
70
+ .object({
71
+ achieve: z.boolean().nullable().optional(),
72
+ revoke: z.boolean().nullable().optional(),
73
+ })
74
+ .optional(),
75
+ });
76
+ export type CourseEnrollmentMilestone = z.infer<
77
+ typeof courseEnrollmentMilestoneSchema
78
+ >;
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Company-scoped equipment catalog entry (m-crit-api
5
+ * docs/features/airplane-equipment.md). Assigned to airplanes via the
6
+ * airplane PATCH `equipment` array.
7
+ */
8
+ export const equipmentSchema = z.object({
9
+ type: z.literal("equipment").optional(),
10
+ id: z.number(),
11
+ name: z.string(),
12
+ description: z.string().nullable().optional(),
13
+ manual_url: z.string().nullable().optional(),
14
+ });
15
+ export type Equipment = z.infer<typeof equipmentSchema>;
16
+
17
+ /**
18
+ * Write shape for the airplane PATCH: existing entries carry their id
19
+ * (description/manual_url on an id entry UPDATE the catalog entry);
20
+ * id-less entries are created on the fly by name.
21
+ */
22
+ export interface EquipmentEntry {
23
+ id?: number;
24
+ name: string;
25
+ description?: string | null;
26
+ manual_url?: string | null;
27
+ }