xpt-shared-types 1.0.4 → 1.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.
package/README.md CHANGED
@@ -1,38 +1,121 @@
1
- # xpt-shared-types
2
-
3
- Shared TypeScript types and data for XPT projects.
4
-
5
- ## Usage
6
-
7
- 1. Install in your project:
8
-
9
- ```sh
10
- yarn add xpt-shared-types
11
- # or
12
- npm install xpt-shared-types
13
- ```
14
-
15
- Or publish to npm/GitHub Packages and install from there.
16
-
17
- 2. Import types and data:
18
- ```ts
19
- import { Country, COUNTRIES_LIST } from "xpt-shared-types";
20
- ```
21
-
22
- ## Development
23
-
24
- - Edit types in `src/types/`
25
- - Edit shared data in `src/data/`
26
- - Run `yarn build` to compile TypeScript
27
-
28
- ## Publish
29
-
30
- 1. Run `npm login` to authenticate with npm (if not already logged in)
31
- 2. Run `yarn build` to compile TypeScript
32
- 3. Run `yarn version --patch` to bump the version (e.g. `1.0.4` → `1.0.5`)
33
- 4. Run `npm publish` to publish to npm
34
- 5. Run `git push` to push the version commit and tag to GitHub
35
-
36
- ## License
37
-
38
- MIT
1
+ # xpt-shared-types
2
+
3
+ The API contract shared by `xpt-strapi` and `xpt-client`.
4
+
5
+ Strapi's content-type schemas are the single source of truth. This package
6
+ turns them into plain TypeScript so both projects describe the same API, and a
7
+ schema change breaks compilation at every stale usage instead of drifting
8
+ silently.
9
+
10
+ ## Layout
11
+
12
+ ```
13
+ src/
14
+ ├── generated/ # DO NOT EDIT — produced by scripts/sync-from-strapi.js
15
+ ├── enums.ts # one union per `enumeration` attribute
16
+ │ ├── models.ts # one interface per content type (read shapes)
17
+ │ └── inputs.ts # one interface per content type (write shapes)
18
+ ├── contracts/ # hand-written primitives the generated code builds on
19
+ ├── manual/ # types with no Strapi content type behind them
20
+ └── data/ # shared runtime data (COUNTRIES_LIST)
21
+ ```
22
+
23
+ Everything is re-exported from the package root:
24
+
25
+ ```ts
26
+ import type { Tournament, TournamentStatus, Populated } from 'xpt-shared-types';
27
+ import { COUNTRIES_LIST } from 'xpt-shared-types';
28
+ ```
29
+
30
+ ## Regenerating after a schema change
31
+
32
+ ```sh
33
+ yarn sync # reads ../xpt-strapi/src/**/content-types/**/schema.json
34
+ yarn build # tsc only
35
+ ```
36
+
37
+ `sync` is deliberately **not** part of `build`, so publishing does not require a
38
+ sibling `xpt-strapi` checkout. The generated output is committed — treat
39
+ `yarn sync` as something you run and commit, like a lockfile.
40
+
41
+ Point it elsewhere with an argument or `XPT_STRAPI_PATH`:
42
+
43
+ ```sh
44
+ node scripts/sync-from-strapi.js ../some/other/xpt-strapi
45
+ ```
46
+
47
+ It reads `schema.json`, **not** `xpt-strapi/types/generated/contentTypes.d.ts`.
48
+ The latter is Strapi's own output for its server-side types; it is regenerated
49
+ by `strapi develop` and expressed in `Schema.Attribute.*` wrappers that the
50
+ frontend cannot consume.
51
+
52
+ ## The rules the generator follows
53
+
54
+ **Everything except `id` and `documentId` is optional.** Not just relations.
55
+ This codebase uses Strapi's `fields` selection pervasively, so even a
56
+ schema-`required` attribute like `user.email` is absent from most responses.
57
+ The base model is the *minimum guarantee*, not the full row.
58
+
59
+ **Single values are `?: T | null`.** Strapi sends `null` for an unset value
60
+ rather than omitting the key. Lists come back as `[]`, so they are not nullable.
61
+
62
+ **Relations and media are optional regardless of the schema**, because whether
63
+ they come back depends entirely on the query's `populate`.
64
+
65
+ **`private` attributes are omitted**, because the content API never returns
66
+ them (for example `user.password`).
67
+
68
+ **Read and write shapes differ**, so both are generated. On read a relation is
69
+ a nested object; on write it is a documentId or a `connect`/`set` form, and
70
+ media is a numeric file id. Use `models.ts` for responses and `inputs.ts` for
71
+ request payloads — typing a mutation with a read model is a common mistake that
72
+ produces confusing optionality errors.
73
+
74
+ **Enums are named `<Model><PascalField>`** — `tournament.currentStatus` becomes
75
+ `TournamentCurrentStatus`. `TournamentStatus` is exported as a readability
76
+ alias.
77
+
78
+ ## Narrowing with `Populated<T, K>`
79
+
80
+ Because relations are always optional, record what a specific query actually
81
+ asked for:
82
+
83
+ ```ts
84
+ const tournament = await getTournament(slug); // Populated<Tournament, 'game' | 'prizes'>
85
+
86
+ tournament.game.title; // ok — the annotation says it was populated
87
+ tournament.region?.name; // still optional — this query did not populate it
88
+ ```
89
+
90
+ If the annotation and the query disagree, that is a bug a reader can see. Do
91
+ not reach for a cast: a cast on a schema-backed field is always avoidable, and
92
+ silently disables exactly the checking this package exists to provide.
93
+
94
+ ## Local development
95
+
96
+ The two apps are on different Yarn majors, so they link differently:
97
+
98
+ | Repo | Yarn | Dependency |
99
+ | --- | --- | --- |
100
+ | `xpt-strapi` | 1.22.5 (classic) | `"xpt-shared-types": "link:../xpt-shared-types"` |
101
+ | `xpt-client` | 4.1.1 (Berry) | `"xpt-shared-types": "portal:../xpt-shared-types"` |
102
+
103
+ `portal:` is Berry-only and fails on Yarn 1. Both symlink to this directory, so
104
+ edits are picked up after `yarn build` with no publish step.
105
+
106
+ > **Before deploying, revert both to the published range (`^1.x`) and publish
107
+ > this package.** The link/portal wiring is for local development only.
108
+
109
+ ## Publishing
110
+
111
+ `prepublishOnly` runs `yarn build`, so `dist/` cannot go stale.
112
+
113
+ 1. `yarn sync` and commit, if schemas changed
114
+ 2. `npm login`
115
+ 3. `yarn version --patch`
116
+ 4. `npm publish`
117
+ 5. `git push --follow-tags`
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Hand-written contract primitives.
3
+ *
4
+ * Everything here is maintained by hand — the generated models in
5
+ * `src/generated` import from this module, so keep the names stable.
6
+ */
7
+ /** Strapi 5 returns flat documents: no `attributes` envelope. */
8
+ export interface StrapiDocument {
9
+ id: number;
10
+ documentId: string;
11
+ createdAt?: string | null;
12
+ updatedAt?: string | null;
13
+ publishedAt?: string | null;
14
+ locale?: string | null;
15
+ }
16
+ /** A single size variant produced by the upload plugin. */
17
+ export interface StrapiMediaFormat {
18
+ name: string;
19
+ hash: string;
20
+ ext: string;
21
+ mime: string;
22
+ width: number;
23
+ height: number;
24
+ size: number;
25
+ url: string;
26
+ path?: string | null;
27
+ }
28
+ /**
29
+ * Size variants the upload plugin generates. The four named breakpoints are
30
+ * Strapi's defaults; the index signature covers custom ones.
31
+ */
32
+ export interface StrapiMediaFormats {
33
+ thumbnail?: StrapiMediaFormat;
34
+ small?: StrapiMediaFormat;
35
+ medium?: StrapiMediaFormat;
36
+ large?: StrapiMediaFormat;
37
+ [breakpoint: string]: StrapiMediaFormat | undefined;
38
+ }
39
+ /** `plugin::upload.file` */
40
+ export interface StrapiMedia {
41
+ id: number;
42
+ documentId?: string;
43
+ name: string;
44
+ alternativeText?: string | null;
45
+ caption?: string | null;
46
+ width?: number | null;
47
+ height?: number | null;
48
+ formats?: StrapiMediaFormats | null;
49
+ hash: string;
50
+ ext?: string | null;
51
+ mime: string;
52
+ size: number;
53
+ url: string;
54
+ previewUrl?: string | null;
55
+ provider?: string;
56
+ provider_metadata?: unknown;
57
+ folderPath?: string;
58
+ createdAt?: string;
59
+ updatedAt?: string;
60
+ publishedAt?: string;
61
+ locale?: string | null;
62
+ }
63
+ /** Rich-text `blocks` payload; shape is defined by the blocks renderer. */
64
+ export type BlocksContent = unknown[];
65
+ /**
66
+ * How a relation is sent on create/update. Strapi accepts a bare documentId,
67
+ * a numeric id, or the connect/set/disconnect long form.
68
+ */
69
+ export type RelationInput = string | number | {
70
+ id: number;
71
+ } | {
72
+ documentId: string;
73
+ } | {
74
+ connect?: Array<string | number>;
75
+ disconnect?: Array<string | number>;
76
+ } | {
77
+ set: Array<string | number>;
78
+ };
79
+ /** Media is written as the uploaded file's numeric id. */
80
+ export type MediaInput = number | number[] | null;
81
+ export interface Pagination {
82
+ page: number;
83
+ pageSize: number;
84
+ pageCount: number;
85
+ total: number;
86
+ }
87
+ export interface Meta {
88
+ pagination?: Pagination;
89
+ [key: string]: unknown;
90
+ }
91
+ /** Single-entity response. Strapi still sends `meta`, usually `{}`. */
92
+ export interface StrapiResponse<T> {
93
+ data: T;
94
+ meta?: Meta;
95
+ }
96
+ /** Collection response. */
97
+ export interface StrapiCollectionResponse<T> {
98
+ data: T[];
99
+ meta?: Meta;
100
+ }
101
+ export interface StrapiError {
102
+ status: number;
103
+ name: string;
104
+ message: string;
105
+ details?: unknown;
106
+ }
107
+ export interface StrapiErrorResponse {
108
+ data: null;
109
+ error: StrapiError;
110
+ }
111
+ /**
112
+ * Marks relations as present.
113
+ *
114
+ * Generated models leave every relation optional, because whether one comes
115
+ * back depends entirely on the query's `populate`. Use this at a call site to
116
+ * record what that particular query actually asked for:
117
+ *
118
+ * ```ts
119
+ * type WithGame = Populated<Tournament, 'game' | 'prizes'>;
120
+ * ```
121
+ *
122
+ * This is the type-level counterpart to remembering to populate — if the query
123
+ * and the annotation disagree, that is a bug the reader can see.
124
+ */
125
+ export type Populated<T, K extends keyof T> = T & {
126
+ [P in K]-?: NonNullable<T[P]>;
127
+ };
128
+ /** Recursively marks every relation on `T` as optional (the default shape). */
129
+ export type Unpopulated<T> = {
130
+ [P in keyof T]?: T[P];
131
+ };
132
+ /** `plugin::users-permissions.role` */
133
+ export interface UserRole {
134
+ id: number;
135
+ documentId?: string;
136
+ name: string;
137
+ description?: string | null;
138
+ type?: string | null;
139
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Hand-written contract primitives.
4
+ *
5
+ * Everything here is maintained by hand — the generated models in
6
+ * `src/generated` import from this module, so keep the names stable.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,2 +1,2 @@
1
- import { Country } from "../types/country";
1
+ import { Country } from "../manual/country";
2
2
  export declare const COUNTRIES_LIST: Country[];
@@ -0,0 +1,48 @@
1
+ /** `Friendship.status` */
2
+ export type FriendshipStatus = "pending" | "accepted";
3
+ /** `GameRequest.customLobbies` */
4
+ export type GameRequestCustomLobbies = "Yes" | "No" | "Not sure";
5
+ /** `GameRequest.genre` */
6
+ export type GameRequestGenre = "FPS" | "MOBA" | "Battle Royale" | "Fighting (FGC)" | "Sports" | "Strategy/RTS" | "Sim Racing" | "Card Game" | "Other";
7
+ /** `GameRequest.teamPlay` */
8
+ export type GameRequestTeamPlay = "No (solo)" | "Yes, 2v2" | "Yes, 3v3" | "Yes, 4v4+" | "Don't know";
9
+ /** `Match.lobbyStatus` */
10
+ export type MatchLobbyStatus = "waiting" | "ready" | "disputed" | "completed";
11
+ /** `Match.round` */
12
+ export type MatchRound = "Round 32" | "Round 16" | "Quarter Final" | "Semi-Final" | "Third Round" | "Finals" | "League";
13
+ /** `Match.streamPlatform` */
14
+ export type MatchStreamPlatform = "twitch" | "youtube" | "kick" | "other";
15
+ /** `Match.winnerSlot` */
16
+ export type MatchWinnerSlot = "home" | "away";
17
+ /** `Prize.rank` */
18
+ export type PrizeRank = "one" | "two" | "three";
19
+ /** `Referral.status` */
20
+ export type ReferralStatus = "pending" | "completed";
21
+ /** `Team.current_status` */
22
+ export type TeamCurrentStatus = "active" | "disbanded";
23
+ /** `TeamInvite.status` */
24
+ export type TeamInviteStatus = "pending" | "accepted" | "declined";
25
+ /** `TeamInvite.team_role` */
26
+ export type TeamInviteTeamRole = "captain" | "co_captain" | "player" | "substitute" | "coach";
27
+ /** `TeamPlayer.team_role` */
28
+ export type TeamPlayerTeamRole = "captain" | "co_captain" | "player" | "substitute" | "coach";
29
+ /** `Tournament.currentStatus` */
30
+ export type TournamentCurrentStatus = "draft" | "open" | "checkIn" | "seeding" | "live" | "completed" | "cancelled";
31
+ /** `TournamentParticipant.entryType` */
32
+ export type TournamentParticipantEntryType = "solo" | "team";
33
+ /** `TournamentParticipant.status` */
34
+ export type TournamentParticipantStatus = "registered" | "active" | "eliminated" | "completed";
35
+ /** `TournamentRole.role` */
36
+ export type TournamentRoleRole = "moderator" | "admin";
37
+ /** `TournamentStage.stageName` */
38
+ export type TournamentStageStageName = "Round 32" | "Round 16" | "Quarter Final" | "Semi-Final" | "Third Round" | "Finals" | "League";
39
+ /** `Tournament.teamSize` */
40
+ export type TournamentTeamSize = "one" | "two" | "three" | "four" | "five";
41
+ /** `Tournament.type` */
42
+ export type TournamentType = "Single Elimination" | "League" | "Double Elimination";
43
+ /** `UserTransaction.stripeStatus` */
44
+ export type UserTransactionStripeStatus = "pending" | "completed" | "failed" | "refunded" | "internal";
45
+ /** `UserTransaction.type` */
46
+ export type UserTransactionType = "purchase" | "debit" | "credit" | "refund" | "test";
47
+ /** Readability alias for `tournament.currentStatus`. */
48
+ export type TournamentStatus = TournamentCurrentStatus;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ // AUTO-GENERATED by scripts/sync-from-strapi.js — DO NOT EDIT MANUALLY.
3
+ // Source: xpt-strapi/src/**/content-types/**/schema.json
4
+ // Regenerate with: yarn sync
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export * from './enums';
2
+ export * from './models';
3
+ export * from './inputs';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ // AUTO-GENERATED by scripts/sync-from-strapi.js — DO NOT EDIT MANUALLY.
3
+ // Source: xpt-strapi/src/**/content-types/**/schema.json
4
+ // Regenerate with: yarn sync
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ __exportStar(require("./enums"), exports);
21
+ __exportStar(require("./models"), exports);
22
+ __exportStar(require("./inputs"), exports);