@ttt-productions/q-core 0.1.20 → 0.1.22
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/dist/constants/business.js +2 -2
- package/dist/constants/business.js.map +1 -1
- package/dist/constants/deployment.d.ts +2 -0
- package/dist/constants/deployment.d.ts.map +1 -0
- package/dist/constants/deployment.js +2 -0
- package/dist/constants/deployment.js.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +1 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/constants/media.js +1 -1
- package/dist/constants/media.js.map +1 -1
- package/dist/constants/sports.js +1 -1
- package/dist/constants/sports.js.map +1 -1
- package/dist/constants/telemetry.js +3 -3
- package/dist/constants/telemetry.js.map +1 -1
- package/dist/doc-schemas/admin-notifications.js +3 -3
- package/dist/doc-schemas/admin-notifications.js.map +1 -1
- package/dist/doc-schemas/aggregate-stats.js +3 -3
- package/dist/doc-schemas/aggregate-stats.js.map +1 -1
- package/dist/doc-schemas/atoms.d.ts +1 -1
- package/dist/doc-schemas/atoms.js +3 -3
- package/dist/doc-schemas/atoms.js.map +1 -1
- package/dist/doc-schemas/audit-event.js +2 -2
- package/dist/doc-schemas/audit-event.js.map +1 -1
- package/dist/doc-schemas/firestore-primitives.js +1 -1
- package/dist/doc-schemas/firestore-primitives.js.map +1 -1
- package/dist/doc-schemas/game-location.js +2 -2
- package/dist/doc-schemas/game-location.js.map +1 -1
- package/dist/doc-schemas/game-stat-history.js +1 -1
- package/dist/doc-schemas/game-stat-history.js.map +1 -1
- package/dist/doc-schemas/game-stats.js +6 -6
- package/dist/doc-schemas/game-stats.js.map +1 -1
- package/dist/doc-schemas/league-team.js +4 -4
- package/dist/doc-schemas/league-team.js.map +1 -1
- package/dist/doc-schemas/membership-roster-sync.js +2 -2
- package/dist/doc-schemas/membership-roster-sync.js.map +1 -1
- package/dist/doc-schemas/pending-media.d.ts +6 -6
- package/dist/doc-schemas/publicUser.js +1 -1
- package/dist/doc-schemas/publicUser.js.map +1 -1
- package/dist/doc-schemas/registration-start.js +1 -1
- package/dist/doc-schemas/registration-start.js.map +1 -1
- package/dist/doc-schemas/registry.d.ts +3 -3
- package/dist/doc-schemas/sport-game.js +4 -4
- package/dist/doc-schemas/sport-game.js.map +1 -1
- package/dist/doc-schemas/sport-league.js +1 -1
- package/dist/doc-schemas/sport-league.js.map +1 -1
- package/dist/doc-schemas/stats-operation.js +2 -2
- package/dist/doc-schemas/stats-operation.js.map +1 -1
- package/dist/doc-schemas/status-reconcile-queue.js +1 -1
- package/dist/doc-schemas/status-reconcile-queue.js.map +1 -1
- package/dist/doc-schemas/sweep-state.js +2 -2
- package/dist/doc-schemas/sweep-state.js.map +1 -1
- package/dist/doc-schemas/team-membership.js +1 -1
- package/dist/doc-schemas/team-membership.js.map +1 -1
- package/dist/doc-schemas/user-profile.js +1 -1
- package/dist/doc-schemas/user-profile.js.map +1 -1
- package/dist/doc-schemas/waiver-template.js +1 -1
- package/dist/doc-schemas/waiver-template.js.map +1 -1
- package/dist/doc-schemas/waivers-and-hardware.js +3 -3
- package/dist/doc-schemas/waivers-and-hardware.js.map +1 -1
- package/dist/domain-events/domain-events.js +2 -2
- package/dist/domain-events/domain-events.js.map +1 -1
- package/dist/ids/award-grant-ids.js +1 -1
- package/dist/ids/award-grant-ids.js.map +1 -1
- package/dist/ids/compound-ids.js +1 -1
- package/dist/ids/compound-ids.js.map +1 -1
- package/dist/ids/minted-ids.js +1 -1
- package/dist/ids/minted-ids.js.map +1 -1
- package/dist/ids/notification-ids.js +1 -1
- package/dist/ids/notification-ids.js.map +1 -1
- package/dist/ids/stat-event-ids.d.ts +1 -1
- package/dist/ids/stat-event-ids.js +2 -2
- package/dist/ids/stat-event-ids.js.map +1 -1
- package/dist/ids/stats-operation-ids.js +1 -1
- package/dist/ids/stats-operation-ids.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/input-formats/input-formats.js +1 -1
- package/dist/input-formats/input-formats.js.map +1 -1
- package/dist/media/file-origins.js +1 -1
- package/dist/media/file-origins.js.map +1 -1
- package/dist/media/picture-url.js +1 -1
- package/dist/media/picture-url.js.map +1 -1
- package/dist/migration-console/action-ids.js +1 -1
- package/dist/migration-console/action-ids.js.map +1 -1
- package/dist/migration-console/manifest.js +1 -1
- package/dist/migration-console/manifest.js.map +1 -1
- package/dist/notifications/admin-notification-config.js +1 -1
- package/dist/notifications/admin-notification-config.js.map +1 -1
- package/dist/notifications/occurrences.js +1 -1
- package/dist/notifications/occurrences.js.map +1 -1
- package/dist/paths/collections.js +3 -3
- package/dist/paths/collections.js.map +1 -1
- package/dist/paths/picture-owner-paths.js +1 -1
- package/dist/paths/picture-owner-paths.js.map +1 -1
- package/dist/paths/storage-paths.d.ts +1 -1
- package/dist/paths/storage-paths.js +1 -1
- package/dist/paths/storage-paths.js.map +1 -1
- package/dist/paths/utils.d.ts +1 -1
- package/dist/paths/utils.js +1 -1
- package/dist/paths/utils.js.map +1 -1
- package/dist/schemas/admin-notifications.js +1 -1
- package/dist/schemas/admin-notifications.js.map +1 -1
- package/dist/schemas/app-config.js +1 -1
- package/dist/schemas/app-config.js.map +1 -1
- package/dist/schemas/awards.js +1 -1
- package/dist/schemas/awards.js.map +1 -1
- package/dist/schemas/dead-letters.js +2 -2
- package/dist/schemas/dead-letters.js.map +1 -1
- package/dist/schemas/leagues.js +1 -1
- package/dist/schemas/leagues.js.map +1 -1
- package/dist/schemas/migration-console.js +1 -1
- package/dist/schemas/migration-console.js.map +1 -1
- package/dist/schemas/pictures.js +1 -1
- package/dist/schemas/pictures.js.map +1 -1
- package/dist/schemas/schedules.js +2 -2
- package/dist/schemas/schedules.js.map +1 -1
- package/dist/schemas/scoring.js +1 -1
- package/dist/schemas/scoring.js.map +1 -1
- package/dist/schemas/signed-waivers.js +1 -1
- package/dist/schemas/signed-waivers.js.map +1 -1
- package/dist/schemas/stats.js +6 -6
- package/dist/schemas/stats.js.map +1 -1
- package/dist/schemas/team-joining.js +3 -3
- package/dist/schemas/team-joining.js.map +1 -1
- package/dist/schemas/teams.js +7 -7
- package/dist/schemas/teams.js.map +1 -1
- package/dist/schemas/users.js +3 -3
- package/dist/schemas/users.js.map +1 -1
- package/dist/sports/basketball-scoring.js +4 -4
- package/dist/sports/basketball-scoring.js.map +1 -1
- package/dist/sports/bulk-entry.js +1 -1
- package/dist/sports/bulk-entry.js.map +1 -1
- package/dist/sports/contribution.d.ts +5 -5
- package/dist/sports/contribution.d.ts.map +1 -1
- package/dist/sports/contribution.js +4 -4
- package/dist/sports/contribution.js.map +1 -1
- package/dist/sports/curling-scoring.js +2 -2
- package/dist/sports/curling-scoring.js.map +1 -1
- package/dist/sports/football-scoring.js +2 -2
- package/dist/sports/football-scoring.js.map +1 -1
- package/dist/sports/index.js +1 -1
- package/dist/sports/index.js.map +1 -1
- package/dist/sports/ledger-continuity.js +1 -1
- package/dist/sports/ledger-continuity.js.map +1 -1
- package/dist/sports/registry.js +2 -2
- package/dist/sports/registry.js.map +1 -1
- package/dist/sports/replay.js +1 -1
- package/dist/sports/replay.js.map +1 -1
- package/dist/sports/rollup-check.js +1 -1
- package/dist/sports/rollup-check.js.map +1 -1
- package/dist/sports/score-mirror.js +1 -1
- package/dist/sports/score-mirror.js.map +1 -1
- package/dist/sports/scoring-display.d.ts +1 -1
- package/dist/sports/scoring-display.js +2 -2
- package/dist/sports/scoring-display.js.map +1 -1
- package/dist/sports/scoring.d.ts +5 -5
- package/dist/sports/scoring.d.ts.map +1 -1
- package/dist/sports/scoring.js +2 -2
- package/dist/sports/scoring.js.map +1 -1
- package/dist/sports/sport-adapter.d.ts +3 -3
- package/dist/sports/sport-adapter.d.ts.map +1 -1
- package/dist/sports/sport-adapter.js +2 -2
- package/dist/sports/sport-adapter.js.map +1 -1
- package/dist/sports/store-driver.d.ts +1 -1
- package/dist/sports/store-driver.d.ts.map +1 -1
- package/dist/sports/store-driver.js +2 -2
- package/dist/sports/store-driver.js.map +1 -1
- package/dist/types/audit.js +4 -4
- package/dist/types/audit.js.map +1 -1
- package/dist/types/claims.js +1 -1
- package/dist/types/claims.js.map +1 -1
- package/dist/types/picture-owners.js +1 -1
- package/dist/types/picture-owners.js.map +1 -1
- package/dist/types/schedules.js +1 -1
- package/dist/types/schedules.js.map +1 -1
- package/dist/types/sports.js +2 -2
- package/dist/types/sports.js.map +1 -1
- package/dist/types/stats.js +5 -5
- package/dist/types/stats.js.map +1 -1
- package/dist/types/unions.js +2 -2
- package/dist/types/unions.js.map +1 -1
- package/dist/types/uploads.js +1 -1
- package/dist/types/uploads.js.map +1 -1
- package/dist/upload-variables/sign-hardware-contract-variables.js +1 -1
- package/dist/upload-variables/sign-hardware-contract-variables.js.map +1 -1
- package/dist/upload-variables/sign-waiver-variables.js +1 -1
- package/dist/upload-variables/sign-waiver-variables.js.map +1 -1
- package/dist/utils/account-status.js +1 -1
- package/dist/utils/account-status.js.map +1 -1
- package/dist/utils/app-routes.js +1 -1
- package/dist/utils/app-routes.js.map +1 -1
- package/dist/utils/league-join-warning.js +1 -1
- package/dist/utils/league-join-warning.js.map +1 -1
- package/dist/utils/league-joining.js +1 -1
- package/dist/utils/league-joining.js.map +1 -1
- package/dist/utils/league-mode.js +1 -1
- package/dist/utils/league-mode.js.map +1 -1
- package/dist/utils/team-creation.js +1 -1
- package/dist/utils/team-creation.js.map +1 -1
- package/dist/utils/team-member-actions.js +1 -1
- package/dist/utils/team-member-actions.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Structural model of a Firestore Timestamp, so this package imports neither Firebase SDK.
|
|
2
2
|
//
|
|
3
|
-
// Every persisted moment is an epoch-millisecond number (ARCH-105), except a field a native-TTL
|
|
3
|
+
// Every persisted moment is an epoch-millisecond number (RULE-STD-ARCH-105), except a field a native-TTL
|
|
4
4
|
// policy reads — the admin notification documents' `expireAt` — which the service honours only on a
|
|
5
5
|
// real Timestamp.
|
|
6
6
|
import { z } from 'zod';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firestore-primitives.js","sourceRoot":"","sources":["../../src/doc-schemas/firestore-primitives.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"firestore-primitives.js","sourceRoot":"","sources":["../../src/doc-schemas/firestore-primitives.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,EAAE;AACF,yGAAyG;AACzG,oGAAoG;AACpG,kBAAkB;AAElB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,SAAS,oBAAoB,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAwC,CAAC;IAC3D,OAAO,CACL,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;QACrC,OAAO,SAAS,CAAC,WAAW,KAAK,QAAQ;QACzC,OAAO,SAAS,CAAC,QAAQ,KAAK,UAAU;QACxC,OAAO,SAAS,CAAC,MAAM,KAAK,UAAU,CACvC,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAyB,oBAAoB,EAAE;IAC7F,OAAO,EAAE,gCAAgC;CAC1C,CAAC,CAAC","sourcesContent":["// Structural model of a Firestore Timestamp, so this package imports neither Firebase SDK.\n//\n// Every persisted moment is an epoch-millisecond number (RULE-STD-ARCH-105), except a field a native-TTL\n// policy reads — the admin notification documents' `expireAt` — which the service honours only on a\n// real Timestamp.\n\nimport { z } from 'zod';\n\nexport interface FirestoreTimestampLike {\n seconds: number;\n nanoseconds: number;\n toMillis(): number;\n toDate(): Date;\n}\n\nfunction isFirestoreTimestamp(value: unknown): value is FirestoreTimestampLike {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<FirestoreTimestampLike>;\n return (\n typeof candidate.seconds === 'number' &&\n typeof candidate.nanoseconds === 'number' &&\n typeof candidate.toMillis === 'function' &&\n typeof candidate.toDate === 'function'\n );\n}\n\n// z.custom validates without transforming, so a real Timestamp passes through with its methods.\nexport const FirestoreTimestampSchema = z.custom<FirestoreTimestampLike>(isFirestoreTimestamp, {\n message: 'Expected a Firestore Timestamp',\n});\n"]}
|
|
@@ -12,7 +12,7 @@ export const GameLocationSchema = z.object({
|
|
|
12
12
|
subLocations: z.array(z.string()),
|
|
13
13
|
// Directions for players; absent when there are none.
|
|
14
14
|
memo: z.string().min(1).optional(),
|
|
15
|
-
// The venue's picture. Only the picture pipeline writes it (ARCH-001); the location callables
|
|
15
|
+
// The venue's picture. Only the picture pipeline writes it (RULE-STD-ARCH-001); the location callables
|
|
16
16
|
// write the fields above and never this one.
|
|
17
17
|
pictureId: pictureIdSchema.optional(),
|
|
18
18
|
createdAt: epochMsSchema,
|
|
@@ -21,7 +21,7 @@ export const GameLocationSchema = z.object({
|
|
|
21
21
|
// `_serverData/gameLocationRegistry` — the fence of the venue cap. Venue creation counts the venues
|
|
22
22
|
// in its transaction against MAX_GAME_LOCATIONS, and a query does not lock against an insert, so each
|
|
23
23
|
// creation advances `locationRevision` here: two creations at the cap conflict instead of both
|
|
24
|
-
// passing (BACKEND-116). Only its changing matters; absent until the first creation.
|
|
24
|
+
// passing (RULE-STD-BACKEND-116). Only its changing matters; absent until the first creation.
|
|
25
25
|
export const GameLocationRegistrySchema = z.strictObject({
|
|
26
26
|
locationRevision: countSchema,
|
|
27
27
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-location.js","sourceRoot":"","sources":["../../src/doc-schemas/game-location.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,iGAAiG;AACjG,oBAAoB;AAEpB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,6CAA6C;IAC7C,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,sDAAsD;IACtD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClC,
|
|
1
|
+
{"version":3,"file":"game-location.js","sourceRoot":"","sources":["../../src/doc-schemas/game-location.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,iGAAiG;AACjG,oBAAoB;AAEpB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,6CAA6C;IAC7C,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,sDAAsD;IACtD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClC,uGAAuG;IACvG,6CAA6C;IAC7C,SAAS,EAAE,eAAe,CAAC,QAAQ,EAAE;IACrC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC;AAGH,oGAAoG;AACpG,sGAAsG;AACtG,+FAA+F;AAC/F,8FAA8F;AAC9F,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,YAAY,CAAC;IACvD,gBAAgB,EAAE,WAAW;CAC9B,CAAC,CAAC","sourcesContent":["// `gameLocations/{gameLocationId}` — one venue games are played at. Games name it by id\n// (`gameLocationId`) beside their own free-text label, so a game still reads correctly after its\n// venue is deleted.\n\nimport { z } from 'zod';\nimport { countSchema, documentIdSegmentSchema, epochMsSchema } from './atoms.js';\nimport { pictureIdSchema } from './gallery.js';\n\nexport const gameLocationIdSchema = documentIdSegmentSchema;\n\nexport const GameLocationSchema = z.object({\n name: z.string().min(1),\n streetAddress: z.string(),\n // Courts, sheets, or rooms within the venue.\n subLocations: z.array(z.string()),\n // Directions for players; absent when there are none.\n memo: z.string().min(1).optional(),\n // The venue's picture. Only the picture pipeline writes it (RULE-STD-ARCH-001); the location callables\n // write the fields above and never this one.\n pictureId: pictureIdSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n});\nexport type GameLocation = z.infer<typeof GameLocationSchema>;\n\n// `_serverData/gameLocationRegistry` — the fence of the venue cap. Venue creation counts the venues\n// in its transaction against MAX_GAME_LOCATIONS, and a query does not lock against an insert, so each\n// creation advances `locationRevision` here: two creations at the cap conflict instead of both\n// passing (RULE-STD-BACKEND-116). Only its changing matters; absent until the first creation.\nexport const GameLocationRegistrySchema = z.strictObject({\n locationRevision: countSchema,\n});\nexport type GameLocationRegistry = z.infer<typeof GameLocationRegistrySchema>;\n"]}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// The first page also carries what finalize applied: the sport, the teams, and the participation
|
|
6
6
|
// snapshot, so the record alone reproduces a completed game's contribution without the game
|
|
7
|
-
// document a hand edit can reach (SPORT-
|
|
7
|
+
// document a hand edit can reach (RULE-QS-SPORT-924). It exists exactly while the game is `completed`.
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { STAT_HISTORY_PAGE_SIZE } from '../constants/sports.js';
|
|
10
10
|
import { gameRosterPlayerIdSchema, leagueTypeSchema, teamIdSchema } from './atoms.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-stat-history.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stat-history.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,qFAAqF;AACrF,6EAA6E;AAC7E,EAAE;AACF,iGAAiG;AACjG,4FAA4F;AAC5F
|
|
1
|
+
{"version":3,"file":"game-stat-history.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stat-history.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,qFAAqF;AACrF,6EAA6E;AAC7E,EAAE;AACF,iGAAiG;AACjG,4FAA4F;AAC5F,uGAAuG;AAEvG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,gBAAgB,GAAG,CAAC;KACvB,KAAK,CAAC,mBAAmB,CAAC;KAC1B,GAAG,CAAC,sBAAsB,CAAC;KAC3B,MAAM,CACL,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,EAC5F,4CAA4C,CAC7C,CAAC;AAEJ,yEAAyE;AACzE,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,YAAY,CAAC;IAC3D,MAAM,EAAE,gBAAgB;IACxB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACtC,UAAU,EAAE,gBAAgB;IAC5B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,mDAAmD;IACnD,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;IAClD,qBAAqB,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;IACxD,yBAAyB,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;CAC7D,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,CAAC,YAAY,CAAC;IAClE,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAC7B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAC7B,mCAAmC,CACpC;CACF,CAAC,CAAC;AAGH,mGAAmG;AACnG,+BAA+B;AAC/B,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC;IAC/C,8BAA8B;IAC9B,qCAAqC;CACtC,CAAC,CAAC","sourcesContent":["// `sportGames/{gameId}/gameStatHistory/{pageId}` — a completed game's finalized record: every event\n// document of the game, exactly as stored and in `sequence` order, in pages of up to\n// STAT_HISTORY_PAGE_SIZE, the page ids from `buildStatHistoryPageId(index)`.\n//\n// The first page also carries what finalize applied: the sport, the teams, and the participation\n// snapshot, so the record alone reproduces a completed game's contribution without the game\n// document a hand edit can reach (RULE-QS-SPORT-924). It exists exactly while the game is `completed`.\n\nimport { z } from 'zod';\nimport { STAT_HISTORY_PAGE_SIZE } from '../constants/sports.js';\nimport { gameRosterPlayerIdSchema, leagueTypeSchema, teamIdSchema } from './atoms.js';\nimport { GameStatEventSchema } from './game-stats.js';\n\nconst pageEventsSchema = z\n .array(GameStatEventSchema)\n .max(STAT_HISTORY_PAGE_SIZE)\n .refine(\n (events) =>\n events.every((event, index) => index === 0 || events[index - 1].sequence < event.sequence),\n 'A page holds its events in sequence order.',\n );\n\n// A game whose replay is its empty stats has a first page and no events.\nexport const GameStatHistoryFirstPageSchema = z.strictObject({\n events: pageEventsSchema,\n pageCount: z.number().int().positive(),\n leagueType: leagueTypeSchema,\n teamIds: z.array(teamIdSchema).min(1).max(2),\n // The participation snapshot, as roster-entry ids.\n rosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n});\nexport type GameStatHistoryFirstPage = z.infer<typeof GameStatHistoryFirstPageSchema>;\n\nexport const GameStatHistoryContinuationPageSchema = z.strictObject({\n events: pageEventsSchema.refine(\n (events) => events.length > 0,\n 'A continuation page holds events.',\n ),\n});\nexport type GameStatHistoryContinuationPage = z.infer<typeof GameStatHistoryContinuationPageSchema>;\n\n// Both page shapes are strict, so a first page missing part of its snapshot is refused rather than\n// read as a continuation page.\nexport const GameStatHistoryPageSchema = z.union([\n GameStatHistoryFirstPageSchema,\n GameStatHistoryContinuationPageSchema,\n]);\nexport type GameStatHistoryPage = z.infer<typeof GameStatHistoryPageSchema>;\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// The game tier of the stats model: the stat event documents, and the rollup the game document
|
|
2
|
-
// carries (SPORT-
|
|
2
|
+
// carries (RULE-QS-SPORT-901, RULE-QS-SPORT-911, RULE-QS-SPORT-912).
|
|
3
3
|
//
|
|
4
4
|
// Each stat event is its own document, `sportGames/{gameId}/gameStatEvents/{eventId}`, and the
|
|
5
5
|
// event documents are the source of truth: at every commit the game's `stats` equals their replay,
|
|
6
6
|
// in `sequence` order, from the sport's empty stats. The store is append-only — an undo appends a
|
|
7
7
|
// `STAT_VOID` and a correction a `STAT_CORRECTION`; no event is ever edited.
|
|
8
8
|
//
|
|
9
|
-
// An event names people by id; names resolve from `publicUsers` at read time (ARCH-103, SPORT-
|
|
9
|
+
// An event names people by id; names resolve from `publicUsers` at read time (RULE-STD-ARCH-103, RULE-QS-SPORT-914).
|
|
10
10
|
// `displayNameSnapshot` and `assistedByDisplayNameSnapshot` are carried only by copied events, as
|
|
11
11
|
// the audit record of the name the stat was entered under — never read as identity.
|
|
12
12
|
import { z } from 'zod';
|
|
@@ -20,7 +20,7 @@ import { basketballBulkStatKeySchema, countSchema, epochMsSchema, gameRosterPlay
|
|
|
20
20
|
// ---------------------------------------------------------------------------------------------
|
|
21
21
|
// Lines — one per team of the game, one per roster entry holding a non-zero player line
|
|
22
22
|
// ---------------------------------------------------------------------------------------------
|
|
23
|
-
// A line's counters: one field per key of the sport's declared stat-key set (SPORT-
|
|
23
|
+
// A line's counters: one field per key of the sport's declared stat-key set (RULE-QS-SPORT-907), so a key
|
|
24
24
|
// added to the set is a field of every line built from it.
|
|
25
25
|
function counterShape(keys) {
|
|
26
26
|
return Object.fromEntries(keys.map((key) => [key, countSchema]));
|
|
@@ -44,7 +44,7 @@ export const CurlingTeamStatsSchema = z.strictObject({
|
|
|
44
44
|
});
|
|
45
45
|
export const FootballTeamStatsSchema = z.strictObject(counterShape(FOOTBALL_TEAM_STAT_KEYS));
|
|
46
46
|
// ---------------------------------------------------------------------------------------------
|
|
47
|
-
// The payload — exactly two keys for every sport (SPORT-
|
|
47
|
+
// The payload — exactly two keys for every sport (RULE-QS-SPORT-901)
|
|
48
48
|
// ---------------------------------------------------------------------------------------------
|
|
49
49
|
// The one declaration of the game `stats` payload. Each sport's is built through it from that
|
|
50
50
|
// sport's game lines in `SPORT_LINE_SCHEMAS`, as `SPORT_GAME_STATS_SCHEMAS`. `playerStats` is keyed
|
|
@@ -60,7 +60,7 @@ export function gameStatsSchema(teamLine, playerLine) {
|
|
|
60
60
|
// Events
|
|
61
61
|
// ---------------------------------------------------------------------------------------------
|
|
62
62
|
// Every event: its id, its place in the replay, and when it was entered. The recorder facts are
|
|
63
|
-
// derived by the writing core, never taken from the payload (ARCH-106); a copied event has none.
|
|
63
|
+
// derived by the writing core, never taken from the payload (RULE-STD-ARCH-106); a copied event has none.
|
|
64
64
|
const statEventBase = {
|
|
65
65
|
eventId: statEventIdSchema,
|
|
66
66
|
sequence: z.number().int().positive(),
|
|
@@ -190,7 +190,7 @@ function recorderFactsAgree(event) {
|
|
|
190
190
|
}
|
|
191
191
|
// A name snapshot is the audit record a copied event keeps of the name its stat was entered under.
|
|
192
192
|
// Only the writing core stamps recorder facts, and it writes ids only, so an event carrying both a
|
|
193
|
-
// snapshot and a recorder fact is refused (ARCH-103, SPORT-
|
|
193
|
+
// snapshot and a recorder fact is refused (RULE-STD-ARCH-103, RULE-QS-SPORT-914).
|
|
194
194
|
function snapshotsOnlyOnCopiedEvents(event) {
|
|
195
195
|
const snapshot = event.displayNameSnapshot !== undefined || event.assistedByDisplayNameSnapshot !== undefined;
|
|
196
196
|
const recorded = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-stats.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stats.ts"],"names":[],"mappings":"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","sourcesContent":["// The game tier of the stats model: the stat event documents, and the rollup the game document\n// carries (SPORT-001, SPORT-101, SPORT-102).\n//\n// Each stat event is its own document, `sportGames/{gameId}/gameStatEvents/{eventId}`, and the\n// event documents are the source of truth: at every commit the game's `stats` equals their replay,\n// in `sequence` order, from the sport's empty stats. The store is append-only — an undo appends a\n// `STAT_VOID` and a correction a `STAT_CORRECTION`; no event is ever edited.\n//\n// An event names people by id; names resolve from `publicUsers` at read time (ARCH-103, SPORT-104).\n// `displayNameSnapshot` and `assistedByDisplayNameSnapshot` are carried only by copied events, as\n// the audit record of the name the stat was entered under — never read as identity.\n\nimport { z } from 'zod';\nimport { CURLING_ROUND_COUNT, MAX_CURLING_ROUND_POINTS } from '../constants/sports.js';\nimport { buildGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { buildStatCorrectionId, buildStatVoidId } from '../ids/stat-event-ids.js';\nimport {\n BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n BASKETBALL_BULK_EDIT_EVENT_TYPE,\n BASKETBALL_ONE_POINT_EVENT_TYPE,\n BASKETBALL_PLAYER_STAT_KEYS,\n BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n BASKETBALL_TEAM_STAT_KEYS,\n BASKETBALL_TIMEOUT_EVENT_TYPE,\n CURLING_ROUND_BLANK_EVENT_TYPE,\n CURLING_ROUND_SCORE_EVENT_TYPE,\n CURLING_TEAM_STAT_KEYS,\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n FOOTBALL_TEAM_STAT_KEYS,\n} from '../types/sports.js';\nimport type {\n BasketballStatEventType,\n CurlingStatEventType,\n FootballStatEventType,\n StatCounterKey,\n} from '../types/sports.js';\nimport {\n STAT_CORRECTION_EVENT_TYPE,\n STAT_CORRECTION_SOURCE_KEYS,\n STAT_EVENT_AUTHORITY_KEYS,\n STAT_VOID_EVENT_TYPE,\n} from '../types/stats.js';\nimport { CAPTAIN_TEAM_ROLES } from '../types/unions.js';\nimport {\n basketballBulkStatKeySchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n repairReasonSchema,\n statCounterKeySchema,\n statEventIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\n\n// ---------------------------------------------------------------------------------------------\n// Lines — one per team of the game, one per roster entry holding a non-zero player line\n// ---------------------------------------------------------------------------------------------\n\n// A line's counters: one field per key of the sport's declared stat-key set (SPORT-007), so a key\n// added to the set is a field of every line built from it.\nfunction counterShape<K extends StatCounterKey>(keys: readonly K[]): Record<K, typeof countSchema> {\n return Object.fromEntries(keys.map((key) => [key, countSchema])) as Record<K, typeof countSchema>;\n}\n\nexport const BasketballTeamStatsSchema = z.strictObject(counterShape(BASKETBALL_TEAM_STAT_KEYS));\nexport type BasketballTeamStats = z.infer<typeof BasketballTeamStatsSchema>;\n\n// `fantasyPoints` is `basketballFantasyPoints` of the line's own counters, on every basketball\n// player line — this game line and the totals lines built from it — stored so every table and\n// aggregate reads it rather than deriving it. FP is linear in the counters, so a totals line's FP\n// equals the sum of its games' FP.\nexport const BasketballPlayerStatsSchema = z.strictObject({\n ...counterShape(BASKETBALL_PLAYER_STAT_KEYS),\n fantasyPoints: z.number().nonnegative(),\n});\nexport type BasketballPlayerStats = z.infer<typeof BasketballPlayerStatsSchema>;\n\n// `roundScores[n]` is end n + 1: `null` until the end is recorded, `0` on both teams for a blank end.\n// `score` is the sum of the recorded ends.\nexport const CurlingTeamStatsSchema = z.strictObject({\n ...counterShape(CURLING_TEAM_STAT_KEYS),\n roundScores: z\n .array(z.number().int().min(0).max(MAX_CURLING_ROUND_POINTS).nullable())\n .length(CURLING_ROUND_COUNT),\n});\nexport type CurlingTeamStats = z.infer<typeof CurlingTeamStatsSchema>;\n\nexport const FootballTeamStatsSchema = z.strictObject(counterShape(FOOTBALL_TEAM_STAT_KEYS));\nexport type FootballTeamStats = z.infer<typeof FootballTeamStatsSchema>;\n\n// ---------------------------------------------------------------------------------------------\n// The payload — exactly two keys for every sport (SPORT-001)\n// ---------------------------------------------------------------------------------------------\n\n// The one declaration of the game `stats` payload. Each sport's is built through it from that\n// sport's game lines in `SPORT_LINE_SCHEMAS`, as `SPORT_GAME_STATS_SCHEMAS`. `playerStats` is keyed\n// by roster entry, never by person, so a person on both teams of a game holds a line on each side. A\n// sport with no player lines passes `z.never()`, so its map stays empty.\nexport function gameStatsSchema<TeamLine extends z.ZodType, PlayerLine extends z.ZodType>(\n teamLine: TeamLine,\n playerLine: PlayerLine,\n) {\n return z.strictObject({\n teamStats: z.record(teamIdSchema, teamLine),\n playerStats: z.record(gameRosterPlayerIdSchema, playerLine),\n });\n}\n\n// ---------------------------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------------------------\n\n// Every event: its id, its place in the replay, and when it was entered. The recorder facts are\n// derived by the writing core, never taken from the payload (ARCH-106); a copied event has none.\nconst statEventBase = {\n eventId: statEventIdSchema,\n sequence: z.number().int().positive(),\n recordedAt: epochMsSchema,\n acceptedAt: epochMsSchema.optional(),\n recordedByUserId: userIdSchema.optional(),\n recordedByAuthority: z.enum(STAT_EVENT_AUTHORITY_KEYS).optional(),\n // Only for the two team roles: the team whose authority the event was recorded under.\n recordedByTeamId: teamIdSchema.optional(),\n};\n\n// A player-attributed event names the roster entry `{teamId}_{playerUserId}` whose line it changes.\nconst playerEventFields = {\n teamId: teamIdSchema,\n playerUserId: userIdSchema,\n displayNameSnapshot: z.string().optional(),\n};\n\n// Basketball\n\nexport const BasketballOnePointEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_ONE_POINT_EVENT_TYPE),\n});\n\n// An assisted shot names its assister on the same event, which credits both players.\nexport const BasketballAssistableShotEventSchema = z\n .strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS),\n assistedByUserId: userIdSchema.optional(),\n assistedByDisplayNameSnapshot: z.string().optional(),\n })\n .refine(\n (event) =>\n event.assistedByDisplayNameSnapshot === undefined || event.assistedByUserId !== undefined,\n 'An assister snapshot needs the assister.',\n );\n\nexport const BasketballPlayerTallyEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS),\n});\n\nexport const BasketballTimeoutEventSchema = z.strictObject({\n ...statEventBase,\n teamId: teamIdSchema,\n type: z.literal(BASKETBALL_TIMEOUT_EVENT_TYPE),\n});\n\n// A scorer's edit: `value` is the signed change to `stat`, entered against `previousValue`, the\n// value the form showed. A copied bulk edit has no `previousValue`; nothing validates one again.\nexport const BasketballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_BULK_EDIT_EVENT_TYPE),\n stat: basketballBulkStatKeySchema,\n value: z.number().int(),\n previousValue: countSchema.optional(),\n});\n\n// Curling — an event names the end it records; it names no player.\n\nconst curlingRoundNumberSchema = z.number().int().min(1).max(CURLING_ROUND_COUNT);\n\nexport const CurlingRoundScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_SCORE_EVENT_TYPE),\n teamId: teamIdSchema,\n roundNumber: curlingRoundNumberSchema,\n points: z.number().int().min(1).max(MAX_CURLING_ROUND_POINTS),\n});\n\nexport const CurlingBlankEndEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_BLANK_EVENT_TYPE),\n roundNumber: curlingRoundNumberSchema,\n});\n\n// Football — team-attributed only.\n\nexport const FootballScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.enum(FOOTBALL_SCORE_EVENT_TYPE_KEYS),\n teamId: teamIdSchema,\n});\n\nexport const FootballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(FOOTBALL_BULK_EDIT_EVENT_TYPE),\n teamId: teamIdSchema,\n value: z.number().int(),\n previousValue: countSchema,\n});\n\n// The store-level events, members of every sport's union.\n\n// Undoes exactly the event it targets. Its id is derived from the target, so a target has at most\n// one void and a second undo of the same event is the same document.\nexport const StatVoidEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_VOID_EVENT_TYPE),\n targetEventId: statEventIdSchema,\n })\n .refine((event) => event.eventId === buildStatVoidId(event.targetEventId), {\n message: 'A void is stored at the id its target derives.',\n path: ['eventId'],\n });\n\n// Adds `value` to one correctable field of one line — the player line `{teamId}_{playerUserId}`\n// when it names a player, the team line of `teamId` when it does not. It never cascades: a player\n// correction does not move the team's score.\nexport const StatCorrectionEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_CORRECTION_EVENT_TYPE),\n teamId: teamIdSchema,\n playerUserId: userIdSchema.optional(),\n stat: statCounterKeySchema,\n value: z.number().int(),\n correctionSource: z.enum(STAT_CORRECTION_SOURCE_KEYS),\n operationId: statsOperationIdSchema,\n reason: repairReasonSchema,\n // The ghost a link re-keyed this correction's player line from. The id was derived from the\n // ghost's line when the correction was written, and a document id never changes.\n rekeyedFromGhostUserId: userIdSchema.optional(),\n })\n .refine(\n (event) =>\n event.eventId ===\n buildStatCorrectionId(\n event.operationId,\n event.playerUserId === undefined\n ? event.teamId\n : buildGameRosterPlayerId(\n event.teamId,\n event.rekeyedFromGhostUserId ?? event.playerUserId,\n ),\n event.stat,\n ),\n {\n message: 'A correction is stored at the id its operation, line, and field derive.',\n path: ['eventId'],\n },\n );\n\n// Recorder facts are all present or all absent, and a team is named exactly when the authority is a\n// team role.\nfunction recorderFactsAgree(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: (typeof STAT_EVENT_AUTHORITY_KEYS)[number];\n recordedByTeamId?: string;\n}): boolean {\n const facts = [event.acceptedAt, event.recordedByUserId, event.recordedByAuthority];\n const present = facts.filter((fact) => fact !== undefined).length;\n if (present !== 0 && present !== facts.length) return false;\n const teamAuthority = (CAPTAIN_TEAM_ROLES as readonly string[]).includes(\n event.recordedByAuthority ?? '',\n );\n return teamAuthority === (event.recordedByTeamId !== undefined);\n}\n\n// A name snapshot is the audit record a copied event keeps of the name its stat was entered under.\n// Only the writing core stamps recorder facts, and it writes ids only, so an event carrying both a\n// snapshot and a recorder fact is refused (ARCH-103, SPORT-104).\nfunction snapshotsOnlyOnCopiedEvents(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: string;\n recordedByTeamId?: string;\n displayNameSnapshot?: string;\n assistedByDisplayNameSnapshot?: string;\n}): boolean {\n const snapshot =\n event.displayNameSnapshot !== undefined || event.assistedByDisplayNameSnapshot !== undefined;\n const recorded = [\n event.acceptedAt,\n event.recordedByUserId,\n event.recordedByAuthority,\n event.recordedByTeamId,\n ].some((fact) => fact !== undefined);\n return !(snapshot && recorded);\n}\n\n// Event types are disjoint across sports, so `type` alone discriminates the whole union.\nexport const GameStatEventSchema = z\n .discriminatedUnion('type', [\n BasketballOnePointEventSchema,\n BasketballAssistableShotEventSchema,\n BasketballPlayerTallyEventSchema,\n BasketballTimeoutEventSchema,\n BasketballBulkEditEventSchema,\n CurlingRoundScoreEventSchema,\n CurlingBlankEndEventSchema,\n FootballScoreEventSchema,\n FootballBulkEditEventSchema,\n StatVoidEventSchema,\n StatCorrectionEventSchema,\n ])\n .refine(\n recorderFactsAgree,\n 'Recorder facts are all present, or all absent, with a team only for a team role.',\n )\n .refine(\n snapshotsOnlyOnCopiedEvents,\n 'A name snapshot belongs to a copied event, which carries no recorder facts.',\n );\nexport type GameStatEvent = z.infer<typeof GameStatEventSchema>;\nexport type GameStatEventType = GameStatEvent['type'];\n\nexport type BasketballStatEvent = Extract<GameStatEvent, { type: BasketballStatEventType }>;\nexport type CurlingRoundScoreEvent = z.infer<typeof CurlingRoundScoreEventSchema>;\nexport type CurlingBlankEndEvent = z.infer<typeof CurlingBlankEndEventSchema>;\nexport type CurlingStatEvent = Extract<GameStatEvent, { type: CurlingStatEventType }>;\nexport type FootballStatEvent = Extract<GameStatEvent, { type: FootballStatEventType }>;\nexport type StatVoidEvent = z.infer<typeof StatVoidEventSchema>;\nexport type StatCorrectionEvent = z.infer<typeof StatCorrectionEventSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"game-stats.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stats.ts"],"names":[],"mappings":"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","sourcesContent":["// The game tier of the stats model: the stat event documents, and the rollup the game document\n// carries (RULE-QS-SPORT-901, RULE-QS-SPORT-911, RULE-QS-SPORT-912).\n//\n// Each stat event is its own document, `sportGames/{gameId}/gameStatEvents/{eventId}`, and the\n// event documents are the source of truth: at every commit the game's `stats` equals their replay,\n// in `sequence` order, from the sport's empty stats. The store is append-only — an undo appends a\n// `STAT_VOID` and a correction a `STAT_CORRECTION`; no event is ever edited.\n//\n// An event names people by id; names resolve from `publicUsers` at read time (RULE-STD-ARCH-103, RULE-QS-SPORT-914).\n// `displayNameSnapshot` and `assistedByDisplayNameSnapshot` are carried only by copied events, as\n// the audit record of the name the stat was entered under — never read as identity.\n\nimport { z } from 'zod';\nimport { CURLING_ROUND_COUNT, MAX_CURLING_ROUND_POINTS } from '../constants/sports.js';\nimport { buildGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { buildStatCorrectionId, buildStatVoidId } from '../ids/stat-event-ids.js';\nimport {\n BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n BASKETBALL_BULK_EDIT_EVENT_TYPE,\n BASKETBALL_ONE_POINT_EVENT_TYPE,\n BASKETBALL_PLAYER_STAT_KEYS,\n BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n BASKETBALL_TEAM_STAT_KEYS,\n BASKETBALL_TIMEOUT_EVENT_TYPE,\n CURLING_ROUND_BLANK_EVENT_TYPE,\n CURLING_ROUND_SCORE_EVENT_TYPE,\n CURLING_TEAM_STAT_KEYS,\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n FOOTBALL_TEAM_STAT_KEYS,\n} from '../types/sports.js';\nimport type {\n BasketballStatEventType,\n CurlingStatEventType,\n FootballStatEventType,\n StatCounterKey,\n} from '../types/sports.js';\nimport {\n STAT_CORRECTION_EVENT_TYPE,\n STAT_CORRECTION_SOURCE_KEYS,\n STAT_EVENT_AUTHORITY_KEYS,\n STAT_VOID_EVENT_TYPE,\n} from '../types/stats.js';\nimport { CAPTAIN_TEAM_ROLES } from '../types/unions.js';\nimport {\n basketballBulkStatKeySchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n repairReasonSchema,\n statCounterKeySchema,\n statEventIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\n\n// ---------------------------------------------------------------------------------------------\n// Lines — one per team of the game, one per roster entry holding a non-zero player line\n// ---------------------------------------------------------------------------------------------\n\n// A line's counters: one field per key of the sport's declared stat-key set (RULE-QS-SPORT-907), so a key\n// added to the set is a field of every line built from it.\nfunction counterShape<K extends StatCounterKey>(keys: readonly K[]): Record<K, typeof countSchema> {\n return Object.fromEntries(keys.map((key) => [key, countSchema])) as Record<K, typeof countSchema>;\n}\n\nexport const BasketballTeamStatsSchema = z.strictObject(counterShape(BASKETBALL_TEAM_STAT_KEYS));\nexport type BasketballTeamStats = z.infer<typeof BasketballTeamStatsSchema>;\n\n// `fantasyPoints` is `basketballFantasyPoints` of the line's own counters, on every basketball\n// player line — this game line and the totals lines built from it — stored so every table and\n// aggregate reads it rather than deriving it. FP is linear in the counters, so a totals line's FP\n// equals the sum of its games' FP.\nexport const BasketballPlayerStatsSchema = z.strictObject({\n ...counterShape(BASKETBALL_PLAYER_STAT_KEYS),\n fantasyPoints: z.number().nonnegative(),\n});\nexport type BasketballPlayerStats = z.infer<typeof BasketballPlayerStatsSchema>;\n\n// `roundScores[n]` is end n + 1: `null` until the end is recorded, `0` on both teams for a blank end.\n// `score` is the sum of the recorded ends.\nexport const CurlingTeamStatsSchema = z.strictObject({\n ...counterShape(CURLING_TEAM_STAT_KEYS),\n roundScores: z\n .array(z.number().int().min(0).max(MAX_CURLING_ROUND_POINTS).nullable())\n .length(CURLING_ROUND_COUNT),\n});\nexport type CurlingTeamStats = z.infer<typeof CurlingTeamStatsSchema>;\n\nexport const FootballTeamStatsSchema = z.strictObject(counterShape(FOOTBALL_TEAM_STAT_KEYS));\nexport type FootballTeamStats = z.infer<typeof FootballTeamStatsSchema>;\n\n// ---------------------------------------------------------------------------------------------\n// The payload — exactly two keys for every sport (RULE-QS-SPORT-901)\n// ---------------------------------------------------------------------------------------------\n\n// The one declaration of the game `stats` payload. Each sport's is built through it from that\n// sport's game lines in `SPORT_LINE_SCHEMAS`, as `SPORT_GAME_STATS_SCHEMAS`. `playerStats` is keyed\n// by roster entry, never by person, so a person on both teams of a game holds a line on each side. A\n// sport with no player lines passes `z.never()`, so its map stays empty.\nexport function gameStatsSchema<TeamLine extends z.ZodType, PlayerLine extends z.ZodType>(\n teamLine: TeamLine,\n playerLine: PlayerLine,\n) {\n return z.strictObject({\n teamStats: z.record(teamIdSchema, teamLine),\n playerStats: z.record(gameRosterPlayerIdSchema, playerLine),\n });\n}\n\n// ---------------------------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------------------------\n\n// Every event: its id, its place in the replay, and when it was entered. The recorder facts are\n// derived by the writing core, never taken from the payload (RULE-STD-ARCH-106); a copied event has none.\nconst statEventBase = {\n eventId: statEventIdSchema,\n sequence: z.number().int().positive(),\n recordedAt: epochMsSchema,\n acceptedAt: epochMsSchema.optional(),\n recordedByUserId: userIdSchema.optional(),\n recordedByAuthority: z.enum(STAT_EVENT_AUTHORITY_KEYS).optional(),\n // Only for the two team roles: the team whose authority the event was recorded under.\n recordedByTeamId: teamIdSchema.optional(),\n};\n\n// A player-attributed event names the roster entry `{teamId}_{playerUserId}` whose line it changes.\nconst playerEventFields = {\n teamId: teamIdSchema,\n playerUserId: userIdSchema,\n displayNameSnapshot: z.string().optional(),\n};\n\n// Basketball\n\nexport const BasketballOnePointEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_ONE_POINT_EVENT_TYPE),\n});\n\n// An assisted shot names its assister on the same event, which credits both players.\nexport const BasketballAssistableShotEventSchema = z\n .strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS),\n assistedByUserId: userIdSchema.optional(),\n assistedByDisplayNameSnapshot: z.string().optional(),\n })\n .refine(\n (event) =>\n event.assistedByDisplayNameSnapshot === undefined || event.assistedByUserId !== undefined,\n 'An assister snapshot needs the assister.',\n );\n\nexport const BasketballPlayerTallyEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS),\n});\n\nexport const BasketballTimeoutEventSchema = z.strictObject({\n ...statEventBase,\n teamId: teamIdSchema,\n type: z.literal(BASKETBALL_TIMEOUT_EVENT_TYPE),\n});\n\n// A scorer's edit: `value` is the signed change to `stat`, entered against `previousValue`, the\n// value the form showed. A copied bulk edit has no `previousValue`; nothing validates one again.\nexport const BasketballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_BULK_EDIT_EVENT_TYPE),\n stat: basketballBulkStatKeySchema,\n value: z.number().int(),\n previousValue: countSchema.optional(),\n});\n\n// Curling — an event names the end it records; it names no player.\n\nconst curlingRoundNumberSchema = z.number().int().min(1).max(CURLING_ROUND_COUNT);\n\nexport const CurlingRoundScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_SCORE_EVENT_TYPE),\n teamId: teamIdSchema,\n roundNumber: curlingRoundNumberSchema,\n points: z.number().int().min(1).max(MAX_CURLING_ROUND_POINTS),\n});\n\nexport const CurlingBlankEndEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_BLANK_EVENT_TYPE),\n roundNumber: curlingRoundNumberSchema,\n});\n\n// Football — team-attributed only.\n\nexport const FootballScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.enum(FOOTBALL_SCORE_EVENT_TYPE_KEYS),\n teamId: teamIdSchema,\n});\n\nexport const FootballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(FOOTBALL_BULK_EDIT_EVENT_TYPE),\n teamId: teamIdSchema,\n value: z.number().int(),\n previousValue: countSchema,\n});\n\n// The store-level events, members of every sport's union.\n\n// Undoes exactly the event it targets. Its id is derived from the target, so a target has at most\n// one void and a second undo of the same event is the same document.\nexport const StatVoidEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_VOID_EVENT_TYPE),\n targetEventId: statEventIdSchema,\n })\n .refine((event) => event.eventId === buildStatVoidId(event.targetEventId), {\n message: 'A void is stored at the id its target derives.',\n path: ['eventId'],\n });\n\n// Adds `value` to one correctable field of one line — the player line `{teamId}_{playerUserId}`\n// when it names a player, the team line of `teamId` when it does not. It never cascades: a player\n// correction does not move the team's score.\nexport const StatCorrectionEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_CORRECTION_EVENT_TYPE),\n teamId: teamIdSchema,\n playerUserId: userIdSchema.optional(),\n stat: statCounterKeySchema,\n value: z.number().int(),\n correctionSource: z.enum(STAT_CORRECTION_SOURCE_KEYS),\n operationId: statsOperationIdSchema,\n reason: repairReasonSchema,\n // The ghost a link re-keyed this correction's player line from. The id was derived from the\n // ghost's line when the correction was written, and a document id never changes.\n rekeyedFromGhostUserId: userIdSchema.optional(),\n })\n .refine(\n (event) =>\n event.eventId ===\n buildStatCorrectionId(\n event.operationId,\n event.playerUserId === undefined\n ? event.teamId\n : buildGameRosterPlayerId(\n event.teamId,\n event.rekeyedFromGhostUserId ?? event.playerUserId,\n ),\n event.stat,\n ),\n {\n message: 'A correction is stored at the id its operation, line, and field derive.',\n path: ['eventId'],\n },\n );\n\n// Recorder facts are all present or all absent, and a team is named exactly when the authority is a\n// team role.\nfunction recorderFactsAgree(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: (typeof STAT_EVENT_AUTHORITY_KEYS)[number];\n recordedByTeamId?: string;\n}): boolean {\n const facts = [event.acceptedAt, event.recordedByUserId, event.recordedByAuthority];\n const present = facts.filter((fact) => fact !== undefined).length;\n if (present !== 0 && present !== facts.length) return false;\n const teamAuthority = (CAPTAIN_TEAM_ROLES as readonly string[]).includes(\n event.recordedByAuthority ?? '',\n );\n return teamAuthority === (event.recordedByTeamId !== undefined);\n}\n\n// A name snapshot is the audit record a copied event keeps of the name its stat was entered under.\n// Only the writing core stamps recorder facts, and it writes ids only, so an event carrying both a\n// snapshot and a recorder fact is refused (RULE-STD-ARCH-103, RULE-QS-SPORT-914).\nfunction snapshotsOnlyOnCopiedEvents(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: string;\n recordedByTeamId?: string;\n displayNameSnapshot?: string;\n assistedByDisplayNameSnapshot?: string;\n}): boolean {\n const snapshot =\n event.displayNameSnapshot !== undefined || event.assistedByDisplayNameSnapshot !== undefined;\n const recorded = [\n event.acceptedAt,\n event.recordedByUserId,\n event.recordedByAuthority,\n event.recordedByTeamId,\n ].some((fact) => fact !== undefined);\n return !(snapshot && recorded);\n}\n\n// Event types are disjoint across sports, so `type` alone discriminates the whole union.\nexport const GameStatEventSchema = z\n .discriminatedUnion('type', [\n BasketballOnePointEventSchema,\n BasketballAssistableShotEventSchema,\n BasketballPlayerTallyEventSchema,\n BasketballTimeoutEventSchema,\n BasketballBulkEditEventSchema,\n CurlingRoundScoreEventSchema,\n CurlingBlankEndEventSchema,\n FootballScoreEventSchema,\n FootballBulkEditEventSchema,\n StatVoidEventSchema,\n StatCorrectionEventSchema,\n ])\n .refine(\n recorderFactsAgree,\n 'Recorder facts are all present, or all absent, with a team only for a team role.',\n )\n .refine(\n snapshotsOnlyOnCopiedEvents,\n 'A name snapshot belongs to a copied event, which carries no recorder facts.',\n );\nexport type GameStatEvent = z.infer<typeof GameStatEventSchema>;\nexport type GameStatEventType = GameStatEvent['type'];\n\nexport type BasketballStatEvent = Extract<GameStatEvent, { type: BasketballStatEventType }>;\nexport type CurlingRoundScoreEvent = z.infer<typeof CurlingRoundScoreEventSchema>;\nexport type CurlingBlankEndEvent = z.infer<typeof CurlingBlankEndEventSchema>;\nexport type CurlingStatEvent = Extract<GameStatEvent, { type: CurlingStatEventType }>;\nexport type FootballStatEvent = Extract<GameStatEvent, { type: FootballStatEventType }>;\nexport type StatVoidEvent = z.infer<typeof StatVoidEventSchema>;\nexport type StatCorrectionEvent = z.infer<typeof StatCorrectionEventSchema>;\n"]}
|
|
@@ -25,26 +25,26 @@ export const LeagueTeamSchema = z.object({
|
|
|
25
25
|
// Advanced by every membership change on the team after its creation; absent until the first.
|
|
26
26
|
// Only its changing matters: game creation and a game's team replacement read this document in the
|
|
27
27
|
// transaction that seeds a roster from the team's memberships, so a membership change landing at
|
|
28
|
-
// the same moment conflicts with them instead of missing the new game (BACKEND-116).
|
|
28
|
+
// the same moment conflicts with them instead of missing the new game (RULE-STD-BACKEND-116).
|
|
29
29
|
membershipRevision: countSchema.optional(),
|
|
30
30
|
// Advanced by every invitation and join request a door creates for the team; absent until the
|
|
31
31
|
// first. Only its changing matters: a pending invitation or request between one team and one
|
|
32
32
|
// person is unique, and a membership on the team closes both, but a transaction does not lock a
|
|
33
33
|
// query against an insert — so each creation writes this document, which every competing
|
|
34
34
|
// creation and every membership door also writes, and two racers conflict instead of both passing
|
|
35
|
-
// their checks (BACKEND-116).
|
|
35
|
+
// their checks (RULE-STD-BACKEND-116).
|
|
36
36
|
joinRevision: countSchema.optional(),
|
|
37
37
|
// Advanced by every game creation and every game edit that brings the team in; absent until the
|
|
38
38
|
// first. Only its changing matters: a team's deletion re-reads the team's games in the transaction
|
|
39
39
|
// that deletes the team document, and a query does not lock against an insert — so a game created
|
|
40
40
|
// for the team writes this document, which the deletion also writes, and the two conflict instead of
|
|
41
|
-
// leaving a game naming a deleted team (BACKEND-116).
|
|
41
|
+
// leaving a game naming a deleted team (RULE-STD-BACKEND-116).
|
|
42
42
|
scheduleRevision: countSchema.optional(),
|
|
43
43
|
createdAt: epochMsSchema,
|
|
44
44
|
updatedAt: epochMsSchema,
|
|
45
45
|
});
|
|
46
46
|
// `leagueTeamContacts/{teamId}` — how the league reaches the team. Admin read only: the team
|
|
47
|
-
// document is public, so the phone lives here, never on it (BACKEND-105). `leagueId` is what lists a
|
|
47
|
+
// document is public, so the phone lives here, never on it (RULE-STD-BACKEND-105). `leagueId` is what lists a
|
|
48
48
|
// league's contacts in one query. Written with the team by team creation.
|
|
49
49
|
export const LeagueTeamContactSchema = z.object({
|
|
50
50
|
leagueId: leagueIdSchema,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"league-team.js","sourceRoot":"","sources":["../../src/doc-schemas/league-team.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,EACL,WAAW,EACX,aAAa,EACb,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,cAAc;IACxB,qDAAqD;IACrD,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,+FAA+F;IAC/F,kEAAkE;IAClE,eAAe,EAAE,YAAY;IAC7B,uEAAuE;IACvE,WAAW,EAAE,qBAAqB,CAAC,QAAQ,EAAE;IAC7C,kGAAkG;IAClG,0FAA0F;IAC1F,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAC,QAAQ,EAAE;IAC7E,gGAAgG;IAChG,uDAAuD;IACvD,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,8FAA8F;IAC9F,mGAAmG;IACnG,iGAAiG;IACjG,
|
|
1
|
+
{"version":3,"file":"league-team.js","sourceRoot":"","sources":["../../src/doc-schemas/league-team.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,EACL,WAAW,EACX,aAAa,EACb,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,cAAc;IACxB,qDAAqD;IACrD,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,+FAA+F;IAC/F,kEAAkE;IAClE,eAAe,EAAE,YAAY;IAC7B,uEAAuE;IACvE,WAAW,EAAE,qBAAqB,CAAC,QAAQ,EAAE;IAC7C,kGAAkG;IAClG,0FAA0F;IAC1F,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAC,QAAQ,EAAE;IAC7E,gGAAgG;IAChG,uDAAuD;IACvD,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,8FAA8F;IAC9F,mGAAmG;IACnG,iGAAiG;IACjG,8FAA8F;IAC9F,kBAAkB,EAAE,WAAW,CAAC,QAAQ,EAAE;IAC1C,8FAA8F;IAC9F,6FAA6F;IAC7F,gGAAgG;IAChG,yFAAyF;IACzF,kGAAkG;IAClG,uCAAuC;IACvC,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,gGAAgG;IAChG,mGAAmG;IACnG,kGAAkG;IAClG,qGAAqG;IACrG,+DAA+D;IAC/D,gBAAgB,EAAE,WAAW,CAAC,QAAQ,EAAE;IACxC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC;AAGH,6FAA6F;AAC7F,8GAA8G;AAC9G,0EAA0E;AAC1E,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,QAAQ,EAAE,cAAc;IACxB,6FAA6F;IAC7F,YAAY,EAAE,eAAe,CAAC,0BAA0B,CAAC;IACzD,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC","sourcesContent":["// `leagueTeams/{teamId}` — one team in one league, with its derived season rollups. Who is on the\n// team, and who captains it, is `teamMemberships` — never a field here.\n\nimport { z } from 'zod';\nimport { CONTACT_PHONE_INPUT_FORMAT } from '../input-formats/input-formats.js';\nimport {\n countSchema,\n epochMsSchema,\n leagueIdSchema,\n teamIconSchema,\n teamTypeSchema,\n textFieldSchema,\n userIdSchema,\n} from './atoms.js';\nimport { PlayerSeasonStatsSchema, TeamSeasonStatsSchema } from './aggregate-stats.js';\nimport { GallerySummarySchema } from './gallery.js';\n\nexport const LeagueTeamSchema = z.object({\n name: z.string(),\n icon: teamIconSchema,\n leagueId: leagueIdSchema,\n // Present only in a Competitive/Recreational league.\n teamType: teamTypeSchema.optional(),\n // The account that created the team — the new captain, or the admin who created it for a ghost\n // captain. A creation record, never a membership or an authority.\n createdByUserId: userIdSchema,\n // The team's standings line, absent until its first game is finalized.\n seasonStats: TeamSeasonStatsSchema.optional(),\n // The league player stats: one row per person the team's games credited, keyed by userId (real or\n // ghost). The team's league, the team, and the key are the membership the row belongs to.\n playerSeasonStats: z.record(userIdSchema, PlayerSeasonStatsSchema).optional(),\n // The team's picture gallery summary — absent is an empty gallery. Only the picture core writes\n // it; the team callables and the copy action never do.\n pictures: GallerySummarySchema.optional(),\n // Advanced by every membership change on the team after its creation; absent until the first.\n // Only its changing matters: game creation and a game's team replacement read this document in the\n // transaction that seeds a roster from the team's memberships, so a membership change landing at\n // the same moment conflicts with them instead of missing the new game (RULE-STD-BACKEND-116).\n membershipRevision: countSchema.optional(),\n // Advanced by every invitation and join request a door creates for the team; absent until the\n // first. Only its changing matters: a pending invitation or request between one team and one\n // person is unique, and a membership on the team closes both, but a transaction does not lock a\n // query against an insert — so each creation writes this document, which every competing\n // creation and every membership door also writes, and two racers conflict instead of both passing\n // their checks (RULE-STD-BACKEND-116).\n joinRevision: countSchema.optional(),\n // Advanced by every game creation and every game edit that brings the team in; absent until the\n // first. Only its changing matters: a team's deletion re-reads the team's games in the transaction\n // that deletes the team document, and a query does not lock against an insert — so a game created\n // for the team writes this document, which the deletion also writes, and the two conflict instead of\n // leaving a game naming a deleted team (RULE-STD-BACKEND-116).\n scheduleRevision: countSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n});\nexport type LeagueTeam = z.infer<typeof LeagueTeamSchema>;\n\n// `leagueTeamContacts/{teamId}` — how the league reaches the team. Admin read only: the team\n// document is public, so the phone lives here, never on it (RULE-STD-BACKEND-105). `leagueId` is what lists a\n// league's contacts in one query. Written with the team by team creation.\nexport const LeagueTeamContactSchema = z.object({\n leagueId: leagueIdSchema,\n // The number as the contact callables kept it, held to the same declaration they parse with.\n contactPhone: textFieldSchema(CONTACT_PHONE_INPUT_FORMAT),\n updatedAt: epochMsSchema,\n});\nexport type LeagueTeamContact = z.infer<typeof LeagueTeamContactSchema>;\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// `membershipRosterSyncs/{teamMembershipId}` — one team membership's durable obligation to bring
|
|
2
|
-
// its team's open game rosters in line with it (QUALITY-104, BACKEND-302).
|
|
2
|
+
// its team's open game rosters in line with it (RULE-STD-QUALITY-104, RULE-STD-BACKEND-302).
|
|
3
3
|
//
|
|
4
4
|
// Every change to a membership — made, removed, or linked — advances `rosterRevision` in the same
|
|
5
5
|
// transaction as the change, so a committed membership can never lose its roster work. The roster
|
|
6
6
|
// core raises `reconciledRosterRevision` to the revision it finished; the obligation is outstanding
|
|
7
7
|
// while it is lower. The document outlives a deleted membership, so a removal is still reconciled,
|
|
8
|
-
// and it is keyed by the membership id, so no second id format exists (ARCH-109). Backend-only.
|
|
8
|
+
// and it is keyed by the membership id, so no second id format exists (RULE-STD-ARCH-109). Backend-only.
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH } from '../constants/business.js';
|
|
11
11
|
import { FAILED_ROSTER_SYNC_STATUS, RECONCILED_ROSTER_SYNC_STATUS } from '../types/unions.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership-roster-sync.js","sourceRoot":"","sources":["../../src/doc-schemas/membership-roster-sync.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,
|
|
1
|
+
{"version":3,"file":"membership-roster-sync.js","sourceRoot":"","sources":["../../src/doc-schemas/membership-roster-sync.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,6FAA6F;AAC7F,EAAE;AACF,kGAAkG;AAClG,kGAAkG;AAClG,oGAAoG;AACpG,mGAAmG;AACnG,yGAAyG;AAEzG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sCAAsC,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,gCAAgC,EAChC,cAAc,EACd,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,mGAAmG;AACnG,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC,YAAY,CAAC;IAC7D,cAAc,EAAE,cAAc;IAC9B,WAAW,EAAE,YAAY;CAC1B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC;KACxC,MAAM,CAAC;IACN,QAAQ,EAAE,cAAc;IACxB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,yEAAyE;IACzE,cAAc,EAAE,cAAc;IAC9B,yEAAyE;IACzE,wBAAwB,EAAE,qBAAqB;IAC/C,UAAU,EAAE,gCAAgC;IAC5C,sEAAsE;IACtE,YAAY,EAAE,WAAW;IACzB,6DAA6D;IAC7D,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,gCAAgC,CAAC,QAAQ,EAAE;IACnD,4EAA4E;IAC5E,QAAQ,EAAE,aAAa,CAAC,QAAQ,EAAE;IAClC,iGAAiG;IACjG,8BAA8B;IAC9B,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,QAAQ,EAAE;IACrF,SAAS,EAAE,aAAa;CACzB,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,wBAAwB,IAAI,IAAI,CAAC,cAAc,EAAE;IACtE,OAAO,EAAE,iEAAiE;IAC1E,IAAI,EAAE,CAAC,0BAA0B,CAAC;CACnC,CAAC;KACD,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,IAAI,CAAC,UAAU,KAAK,6BAA6B,CAAC;IACnD,CAAC,IAAI,CAAC,wBAAwB,KAAK,IAAI,CAAC,cAAc,CAAC,EACzD;IACE,OAAO,EAAE,mFAAmF;IAC5F,IAAI,EAAE,CAAC,YAAY,CAAC;CACrB,CACF;KACA,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,KAAK,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,EAC3F;IACE,OAAO,EAAE,8DAA8D;IACvE,IAAI,EAAE,CAAC,UAAU,CAAC;CACnB,CACF,CAAC","sourcesContent":["// `membershipRosterSyncs/{teamMembershipId}` — one team membership's durable obligation to bring\n// its team's open game rosters in line with it (RULE-STD-QUALITY-104, RULE-STD-BACKEND-302).\n//\n// Every change to a membership — made, removed, or linked — advances `rosterRevision` in the same\n// transaction as the change, so a committed membership can never lose its roster work. The roster\n// core raises `reconciledRosterRevision` to the revision it finished; the obligation is outstanding\n// while it is lower. The document outlives a deleted membership, so a removal is still reconciled,\n// and it is keyed by the membership id, so no second id format exists (RULE-STD-ARCH-109). Backend-only.\n\nimport { z } from 'zod';\nimport { MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH } from '../constants/business.js';\nimport { FAILED_ROSTER_SYNC_STATUS, RECONCILED_ROSTER_SYNC_STATUS } from '../types/unions.js';\nimport {\n appliedRevisionSchema,\n countSchema,\n epochMsSchema,\n gameIdSchema,\n leagueIdSchema,\n membershipRosterSyncStatusSchema,\n revisionSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\n\n// Where a run that stopped part-way resumes: the last game it finished for this revision. A cursor\n// for an older revision is ignored, because a newer change re-reconciles every open game.\nexport const MembershipRosterSyncCursorSchema = z.strictObject({\n rosterRevision: revisionSchema,\n afterGameId: gameIdSchema,\n});\nexport type MembershipRosterSyncCursor = z.infer<typeof MembershipRosterSyncCursorSchema>;\n\nexport const MembershipRosterSyncSchema = z\n .object({\n leagueId: leagueIdSchema,\n teamId: teamIdSchema,\n userId: userIdSchema,\n // Advanced by every change to the membership; the first change writes 1.\n rosterRevision: revisionSchema,\n // The revision the roster core last finished; 0 before the first finish.\n reconciledRosterRevision: appliedRevisionSchema,\n syncStatus: membershipRosterSyncStatusSchema,\n // Runs of the worker at the current revision that ended in a failure.\n attemptCount: countSchema,\n // The earliest moment the worker takes the obligation again.\n nextAttemptAt: epochMsSchema,\n cursor: MembershipRosterSyncCursorSchema.optional(),\n // When its retries ran out: present exactly while the obligation is failed.\n failedAt: epochMsSchema.optional(),\n // The last failed run's message, kept while it retries and while it is failed; a finished run or\n // an admin's retry clears it.\n lastFailureMessage: z.string().max(MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH).optional(),\n updatedAt: epochMsSchema,\n })\n .refine((sync) => sync.reconciledRosterRevision <= sync.rosterRevision, {\n message: 'A roster obligation cannot be reconciled past its own revision.',\n path: ['reconciledRosterRevision'],\n })\n .refine(\n (sync) =>\n (sync.syncStatus === RECONCILED_ROSTER_SYNC_STATUS) ===\n (sync.reconciledRosterRevision === sync.rosterRevision),\n {\n message: 'An obligation is reconciled exactly when its reconciled revision is its revision.',\n path: ['syncStatus'],\n },\n )\n .refine(\n (sync) => (sync.syncStatus === FAILED_ROSTER_SYNC_STATUS) === (sync.failedAt !== undefined),\n {\n message: 'An obligation carries `failedAt` exactly while it is failed.',\n path: ['failedAt'],\n },\n );\nexport type MembershipRosterSync = z.infer<typeof MembershipRosterSyncSchema>;\n"]}
|
|
@@ -440,6 +440,9 @@ export declare const PendingMediaPendingSchema: z.ZodObject<{
|
|
|
440
440
|
}, z.core.$strict>;
|
|
441
441
|
export declare const PendingMediaProcessingSchema: z.ZodObject<{
|
|
442
442
|
status: z.ZodLiteral<"processing">;
|
|
443
|
+
processingStartedAt: z.ZodNumber;
|
|
444
|
+
processingAttemptCount: z.ZodNumber;
|
|
445
|
+
processingLeaseExpiresAt: z.ZodNumber;
|
|
443
446
|
id: z.ZodString;
|
|
444
447
|
userId: z.ZodString;
|
|
445
448
|
fileOrigin: z.ZodEnum<{
|
|
@@ -484,9 +487,6 @@ export declare const PendingMediaProcessingSchema: z.ZodObject<{
|
|
|
484
487
|
moderationOverride: z.ZodOptional<z.ZodString>;
|
|
485
488
|
createdAt: z.ZodNumber;
|
|
486
489
|
updatedAt: z.ZodNumber;
|
|
487
|
-
processingStartedAt: z.ZodOptional<z.ZodNumber>;
|
|
488
|
-
processingAttemptCount: z.ZodOptional<z.ZodNumber>;
|
|
489
|
-
processingLeaseExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
490
490
|
terminalAt: z.ZodOptional<z.ZodNumber>;
|
|
491
491
|
}, z.core.$strict>;
|
|
492
492
|
export declare const PendingMediaCompletedSchema: z.ZodObject<{
|
|
@@ -1382,6 +1382,9 @@ export declare const PendingMediaSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1382
1382
|
terminalAt: z.ZodOptional<z.ZodNumber>;
|
|
1383
1383
|
}, z.core.$strict>, z.ZodObject<{
|
|
1384
1384
|
status: z.ZodLiteral<"processing">;
|
|
1385
|
+
processingStartedAt: z.ZodNumber;
|
|
1386
|
+
processingAttemptCount: z.ZodNumber;
|
|
1387
|
+
processingLeaseExpiresAt: z.ZodNumber;
|
|
1385
1388
|
id: z.ZodString;
|
|
1386
1389
|
userId: z.ZodString;
|
|
1387
1390
|
fileOrigin: z.ZodEnum<{
|
|
@@ -1426,9 +1429,6 @@ export declare const PendingMediaSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1426
1429
|
moderationOverride: z.ZodOptional<z.ZodString>;
|
|
1427
1430
|
createdAt: z.ZodNumber;
|
|
1428
1431
|
updatedAt: z.ZodNumber;
|
|
1429
|
-
processingStartedAt: z.ZodOptional<z.ZodNumber>;
|
|
1430
|
-
processingAttemptCount: z.ZodOptional<z.ZodNumber>;
|
|
1431
|
-
processingLeaseExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
1432
1432
|
terminalAt: z.ZodOptional<z.ZodNumber>;
|
|
1433
1433
|
}, z.core.$strict>, z.ZodObject<{
|
|
1434
1434
|
status: z.ZodLiteral<"completed">;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// `publicUsers/{uid}` — display identity for real users AND ghosts, resolved at read time by
|
|
2
|
-
// everything that shows a name (ARCH-103). `displayName_lowercase` powers prefix search.
|
|
2
|
+
// everything that shows a name (RULE-STD-ARCH-103). `displayName_lowercase` powers prefix search.
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { epochMsSchema } from './atoms.js';
|
|
5
5
|
export const PublicUserSchema = z.object({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publicUser.js","sourceRoot":"","sources":["../../src/doc-schemas/publicUser.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,
|
|
1
|
+
{"version":3,"file":"publicUser.js","sourceRoot":"","sources":["../../src/doc-schemas/publicUser.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,kGAAkG;AAElG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE;IACjC,0FAA0F;IAC1F,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,8DAA8D;IAC9D,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,sDAAsD;IACtD,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC;QACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KACjC,CAAC;IACF,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC","sourcesContent":["// `publicUsers/{uid}` — display identity for real users AND ghosts, resolved at read time by\n// everything that shows a name (RULE-STD-ARCH-103). `displayName_lowercase` powers prefix search.\n\nimport { z } from 'zod';\nimport { epochMsSchema } from './atoms.js';\n\nexport const PublicUserSchema = z.object({\n uid: z.string(),\n displayName: z.string(),\n displayName_lowercase: z.string(),\n // True for a `ghost_*` placeholder player; derived from the id, stored for query filters.\n isGhost: z.boolean(),\n // True while the account is banned; always false for a ghost.\n disabled: z.boolean(),\n // Award ids the user chose to show beside their name.\n selectedBadges: z.object({\n primary: z.string().optional(),\n secondary: z.string().optional(),\n }),\n updatedAt: epochMsSchema,\n});\nexport type PublicUser = z.infer<typeof PublicUserSchema>;\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// `createRegistrationStart` right after the login is created, before `registerUser`. It is the
|
|
3
3
|
// orphan-login cleanup's work list: a login whose registration started more than the grace window
|
|
4
4
|
// ago and still holds no profile was abandoned. Backend-only; no client reads or writes it. Its
|
|
5
|
-
// `expireAt` is the native-TTL field that removes it (ARCH-105's exception).
|
|
5
|
+
// `expireAt` is the native-TTL field that removes it (RULE-STD-ARCH-105's exception).
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { epochMsSchema, userIdSchema } from './atoms.js';
|
|
8
8
|
import { FirestoreTimestampSchema } from './firestore-primitives.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration-start.js","sourceRoot":"","sources":["../../src/doc-schemas/registration-start.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,
|
|
1
|
+
{"version":3,"file":"registration-start.js","sourceRoot":"","sources":["../../src/doc-schemas/registration-start.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,sFAAsF;AAEtF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC;IACN,MAAM,EAAE,YAAY;IACpB,SAAS,EAAE,aAAa;IACxB,QAAQ,EAAE,wBAAwB;CACnC,CAAC;KACD,MAAM,EAAE,CAAC","sourcesContent":["// `registrationStarts/{userId}` — the record that a login's registration started: written by\n// `createRegistrationStart` right after the login is created, before `registerUser`. It is the\n// orphan-login cleanup's work list: a login whose registration started more than the grace window\n// ago and still holds no profile was abandoned. Backend-only; no client reads or writes it. Its\n// `expireAt` is the native-TTL field that removes it (RULE-STD-ARCH-105's exception).\n\nimport { z } from 'zod';\nimport { epochMsSchema, userIdSchema } from './atoms.js';\nimport { FirestoreTimestampSchema } from './firestore-primitives.js';\n\nexport const RegistrationStartSchema = z\n .object({\n userId: userIdSchema,\n startedAt: epochMsSchema,\n expireAt: FirestoreTimestampSchema,\n })\n .strict();\nexport type RegistrationStart = z.infer<typeof RegistrationStartSchema>;\n"]}
|
|
@@ -1626,6 +1626,9 @@ export declare const COLLECTION_SCHEMAS: {
|
|
|
1626
1626
|
terminalAt: z.ZodOptional<z.ZodNumber>;
|
|
1627
1627
|
}, z.core.$strict>, z.ZodObject<{
|
|
1628
1628
|
status: z.ZodLiteral<"processing">;
|
|
1629
|
+
processingStartedAt: z.ZodNumber;
|
|
1630
|
+
processingAttemptCount: z.ZodNumber;
|
|
1631
|
+
processingLeaseExpiresAt: z.ZodNumber;
|
|
1629
1632
|
id: z.ZodString;
|
|
1630
1633
|
userId: z.ZodString;
|
|
1631
1634
|
fileOrigin: z.ZodEnum<{
|
|
@@ -1670,9 +1673,6 @@ export declare const COLLECTION_SCHEMAS: {
|
|
|
1670
1673
|
moderationOverride: z.ZodOptional<z.ZodString>;
|
|
1671
1674
|
createdAt: z.ZodNumber;
|
|
1672
1675
|
updatedAt: z.ZodNumber;
|
|
1673
|
-
processingStartedAt: z.ZodOptional<z.ZodNumber>;
|
|
1674
|
-
processingAttemptCount: z.ZodOptional<z.ZodNumber>;
|
|
1675
|
-
processingLeaseExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
1676
1676
|
terminalAt: z.ZodOptional<z.ZodNumber>;
|
|
1677
1677
|
}, z.core.$strict>, z.ZodObject<{
|
|
1678
1678
|
status: z.ZodLiteral<"completed">;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// `sportGames/{gameId}` — one scheduled or played game — and its `gameRosterPlayers` subcollection.
|
|
2
|
-
// The game is discriminated on `leagueType`, which selects its `stats` shape (SPORT-
|
|
2
|
+
// The game is discriminated on `leagueType`, which selects its `stats` shape (RULE-QS-SPORT-902).
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { parseGameRosterPlayerId } from '../ids/compound-ids.js';
|
|
5
5
|
import { SPORT_LINE_SCHEMAS } from './aggregate-stats.js';
|
|
6
6
|
import { gameStatsSchema } from './game-stats.js';
|
|
7
7
|
import { calendarDateSchema, countSchema, epochMsSchema, gameRosterPlayerIdSchema, gameStatusSchema, leagueIdSchema, scheduleWeekIdSchema, statsOperationIdSchema, teamIdSchema, userIdSchema, } from './atoms.js';
|
|
8
8
|
import { gameLocationIdSchema } from './game-location.js';
|
|
9
|
-
// Each sport's game `stats`: the two-key payload (SPORT-
|
|
9
|
+
// Each sport's game `stats`: the two-key payload (RULE-QS-SPORT-901) over that sport's game lines, taken
|
|
10
10
|
// from `SPORT_LINE_SCHEMAS` — the one pairing of a sport with its lines. Spelled per sport so each
|
|
11
11
|
// payload keeps its exact type; `satisfies` fails the build when a sport is missing.
|
|
12
12
|
export const SPORT_GAME_STATS_SCHEMAS = {
|
|
@@ -38,7 +38,7 @@ const sportGameBase = {
|
|
|
38
38
|
// The real (non-bye) team ids, for `array-contains` queries.
|
|
39
39
|
teamIds: z.array(teamIdSchema).min(1).max(2),
|
|
40
40
|
// Each team's score, mirrored from its `stats.teamStats` line by the same write that changes it
|
|
41
|
-
// (SPORT-
|
|
41
|
+
// (RULE-QS-SPORT-917), so a schedule or list reads a score without the stats.
|
|
42
42
|
score: z.record(teamIdSchema, countSchema),
|
|
43
43
|
// The number of this game's stat event documents, which is also the last `sequence` assigned.
|
|
44
44
|
statEventSequence: countSchema,
|
|
@@ -47,7 +47,7 @@ const sportGameBase = {
|
|
|
47
47
|
// Absence is per roster entry: a person on both teams can be absent on one side only.
|
|
48
48
|
absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),
|
|
49
49
|
// The roster entries finalize credited no game played — the adapter's decision, stored so every
|
|
50
|
-
// later path reverses what was applied rather than deciding again (SPORT-
|
|
50
|
+
// later path reverses what was applied rather than deciding again (RULE-QS-SPORT-924). Present only while
|
|
51
51
|
// the game is completed.
|
|
52
52
|
nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema).optional(),
|
|
53
53
|
// Set while a multi-page stats operation holds the game; every other writer of its stats, roster,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sport-game.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-game.ts"],"names":[],"mappings":"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","sourcesContent":["// `sportGames/{gameId}` — one scheduled or played game — and its `gameRosterPlayers` subcollection.\n// The game is discriminated on `leagueType`, which selects its `stats` shape (SPORT-002).\n\nimport { z } from 'zod';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport type { LeagueType } from '../types/sports.js';\nimport { SPORT_LINE_SCHEMAS } from './aggregate-stats.js';\nimport { gameStatsSchema } from './game-stats.js';\nimport {\n calendarDateSchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n gameStatusSchema,\n leagueIdSchema,\n scheduleWeekIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\nimport { gameLocationIdSchema } from './game-location.js';\n\n// Each sport's game `stats`: the two-key payload (SPORT-001) over that sport's game lines, taken\n// from `SPORT_LINE_SCHEMAS` — the one pairing of a sport with its lines. Spelled per sport so each\n// payload keeps its exact type; `satisfies` fails the build when a sport is missing.\nexport const SPORT_GAME_STATS_SCHEMAS = {\n Basketball: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Basketball.gameTeam,\n SPORT_LINE_SCHEMAS.Basketball.gamePlayer,\n ),\n Curling: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Curling.gameTeam,\n SPORT_LINE_SCHEMAS.Curling.gamePlayer,\n ),\n Football: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Football.gameTeam,\n SPORT_LINE_SCHEMAS.Football.gamePlayer,\n ),\n} as const satisfies Record<LeagueType, z.ZodType>;\n\nexport type BasketballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Basketball>;\nexport type CurlingGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Curling>;\nexport type FootballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Football>;\n\n// Which sport's payload a game holds is decided by its `leagueType`, never by the payload's shape\n// (SPORT-002).\nexport type GameStats = z.infer<(typeof SPORT_GAME_STATS_SCHEMAS)[LeagueType]>;\n\n// One of a game's two team slots. `teamId` is a leagueTeams id, or BYE_TEAM_ID for a bye week.\nexport const GameTeamSchema = z.object({\n teamId: teamIdSchema,\n});\nexport type GameTeam = z.infer<typeof GameTeamSchema>;\n\nconst sportGameBase = {\n leagueId: leagueIdSchema,\n // The week this game belongs to; weekly schedules are reconstructed by grouping on `weekId`.\n weekId: scheduleWeekIdSchema,\n weekTitle: z.string(),\n weekLocation: z.string(),\n weekStartDate: epochMsSchema,\n startTime: epochMsSchema,\n // The game's local calendar date in LEAGUE_TIME_ZONE.\n dayKey: calendarDateSchema,\n // The venue as shown, e.g. \"Main Gym - Court 2\". Free text; `gameLocationId` is the reference.\n locationLabel: z.string(),\n gameLocationId: gameLocationIdSchema.optional(),\n subLocation: z.string().optional(),\n status: gameStatusSchema,\n // Positional: teams[0] is team one, teams[1] is team two.\n teams: z.tuple([GameTeamSchema, GameTeamSchema]),\n // The real (non-bye) team ids, for `array-contains` queries.\n teamIds: z.array(teamIdSchema).min(1).max(2),\n // Each team's score, mirrored from its `stats.teamStats` line by the same write that changes it\n // (SPORT-107), so a schedule or list reads a score without the stats.\n score: z.record(teamIdSchema, countSchema),\n // The number of this game's stat event documents, which is also the last `sequence` assigned.\n statEventSequence: countSchema,\n // A scorer's note per person, keyed by userId (real or ghost).\n memos: z.record(z.string(), z.string()).optional(),\n // Absence is per roster entry: a person on both teams can be absent on one side only.\n absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n // The roster entries finalize credited no game played — the adapter's decision, stored so every\n // later path reverses what was applied rather than deciding again (SPORT-204). Present only while\n // the game is completed.\n nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema).optional(),\n // Set while a multi-page stats operation holds the game; every other writer of its stats, roster,\n // absence, memos, teams, or existence refuses it until the operation clears it.\n statsOperationId: statsOperationIdSchema.optional(),\n finalizedAt: epochMsSchema.optional(),\n // When reopen last opened the finalized game again; set by reopen and never cleared. Its roster is\n // then the record of who played it, so a membership change no longer reaches it — only a scorer\n // adding a ghost to this game does.\n reopenedAt: epochMsSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n};\n\n// The rollup holds a line for each of the game's teams and no other, the score mirror names the same\n// teams, and every player line is keyed to one of them.\nfunction rollupMatchesTeams(game: {\n teamIds: string[];\n score: Record<string, number>;\n stats: { teamStats: Record<string, unknown>; playerStats: Record<string, unknown> };\n}): boolean {\n const teams = new Set(game.teamIds);\n const namesEveryTeam = (keys: string[]) =>\n keys.length === teams.size && keys.every((key) => teams.has(key));\n return (\n namesEveryTeam(Object.keys(game.stats.teamStats)) &&\n namesEveryTeam(Object.keys(game.score)) &&\n Object.keys(game.stats.playerStats).every((key) =>\n teams.has(parseGameRosterPlayerId(key)?.teamId ?? ''),\n )\n );\n}\nconst ROLLUP_MISMATCH = \"The stats rollup and score mirror hold exactly the game's teams.\";\n\nexport const BasketballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Basketball' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Basketball,\n // Which team holds the possession arrow.\n jumpBallTeamId: teamIdSchema.optional(),\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type BasketballGame = z.infer<typeof BasketballGameSchema>;\n\nexport const CurlingGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Curling' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Curling,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type CurlingGame = z.infer<typeof CurlingGameSchema>;\n\nexport const FootballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Football' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Football,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type FootballGame = z.infer<typeof FootballGameSchema>;\n\nexport const SportGameSchema = z.discriminatedUnion('leagueType', [\n BasketballGameSchema,\n CurlingGameSchema,\n FootballGameSchema,\n]);\nexport type SportGame = z.infer<typeof SportGameSchema>;\n\n// `sportGames/{gameId}/gameRosterPlayers/{teamId}_{userId}` — one entry per team per person, the id\n// from `buildGameRosterPlayerId`. The roster is the participation authority finalize iterates.\nexport const GameRosterPlayerSchema = z.object({\n userId: userIdSchema,\n teamId: teamIdSchema,\n isGhost: z.boolean(),\n syncedAt: epochMsSchema,\n});\nexport type GameRosterPlayer = z.infer<typeof GameRosterPlayerSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"sport-game.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-game.ts"],"names":[],"mappings":"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","sourcesContent":["// `sportGames/{gameId}` — one scheduled or played game — and its `gameRosterPlayers` subcollection.\n// The game is discriminated on `leagueType`, which selects its `stats` shape (RULE-QS-SPORT-902).\n\nimport { z } from 'zod';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport type { LeagueType } from '../types/sports.js';\nimport { SPORT_LINE_SCHEMAS } from './aggregate-stats.js';\nimport { gameStatsSchema } from './game-stats.js';\nimport {\n calendarDateSchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n gameStatusSchema,\n leagueIdSchema,\n scheduleWeekIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\nimport { gameLocationIdSchema } from './game-location.js';\n\n// Each sport's game `stats`: the two-key payload (RULE-QS-SPORT-901) over that sport's game lines, taken\n// from `SPORT_LINE_SCHEMAS` — the one pairing of a sport with its lines. Spelled per sport so each\n// payload keeps its exact type; `satisfies` fails the build when a sport is missing.\nexport const SPORT_GAME_STATS_SCHEMAS = {\n Basketball: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Basketball.gameTeam,\n SPORT_LINE_SCHEMAS.Basketball.gamePlayer,\n ),\n Curling: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Curling.gameTeam,\n SPORT_LINE_SCHEMAS.Curling.gamePlayer,\n ),\n Football: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Football.gameTeam,\n SPORT_LINE_SCHEMAS.Football.gamePlayer,\n ),\n} as const satisfies Record<LeagueType, z.ZodType>;\n\nexport type BasketballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Basketball>;\nexport type CurlingGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Curling>;\nexport type FootballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Football>;\n\n// Which sport's payload a game holds is decided by its `leagueType`, never by the payload's shape\n// (RULE-QS-SPORT-902).\nexport type GameStats = z.infer<(typeof SPORT_GAME_STATS_SCHEMAS)[LeagueType]>;\n\n// One of a game's two team slots. `teamId` is a leagueTeams id, or BYE_TEAM_ID for a bye week.\nexport const GameTeamSchema = z.object({\n teamId: teamIdSchema,\n});\nexport type GameTeam = z.infer<typeof GameTeamSchema>;\n\nconst sportGameBase = {\n leagueId: leagueIdSchema,\n // The week this game belongs to; weekly schedules are reconstructed by grouping on `weekId`.\n weekId: scheduleWeekIdSchema,\n weekTitle: z.string(),\n weekLocation: z.string(),\n weekStartDate: epochMsSchema,\n startTime: epochMsSchema,\n // The game's local calendar date in LEAGUE_TIME_ZONE.\n dayKey: calendarDateSchema,\n // The venue as shown, e.g. \"Main Gym - Court 2\". Free text; `gameLocationId` is the reference.\n locationLabel: z.string(),\n gameLocationId: gameLocationIdSchema.optional(),\n subLocation: z.string().optional(),\n status: gameStatusSchema,\n // Positional: teams[0] is team one, teams[1] is team two.\n teams: z.tuple([GameTeamSchema, GameTeamSchema]),\n // The real (non-bye) team ids, for `array-contains` queries.\n teamIds: z.array(teamIdSchema).min(1).max(2),\n // Each team's score, mirrored from its `stats.teamStats` line by the same write that changes it\n // (RULE-QS-SPORT-917), so a schedule or list reads a score without the stats.\n score: z.record(teamIdSchema, countSchema),\n // The number of this game's stat event documents, which is also the last `sequence` assigned.\n statEventSequence: countSchema,\n // A scorer's note per person, keyed by userId (real or ghost).\n memos: z.record(z.string(), z.string()).optional(),\n // Absence is per roster entry: a person on both teams can be absent on one side only.\n absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n // The roster entries finalize credited no game played — the adapter's decision, stored so every\n // later path reverses what was applied rather than deciding again (RULE-QS-SPORT-924). Present only while\n // the game is completed.\n nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema).optional(),\n // Set while a multi-page stats operation holds the game; every other writer of its stats, roster,\n // absence, memos, teams, or existence refuses it until the operation clears it.\n statsOperationId: statsOperationIdSchema.optional(),\n finalizedAt: epochMsSchema.optional(),\n // When reopen last opened the finalized game again; set by reopen and never cleared. Its roster is\n // then the record of who played it, so a membership change no longer reaches it — only a scorer\n // adding a ghost to this game does.\n reopenedAt: epochMsSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n};\n\n// The rollup holds a line for each of the game's teams and no other, the score mirror names the same\n// teams, and every player line is keyed to one of them.\nfunction rollupMatchesTeams(game: {\n teamIds: string[];\n score: Record<string, number>;\n stats: { teamStats: Record<string, unknown>; playerStats: Record<string, unknown> };\n}): boolean {\n const teams = new Set(game.teamIds);\n const namesEveryTeam = (keys: string[]) =>\n keys.length === teams.size && keys.every((key) => teams.has(key));\n return (\n namesEveryTeam(Object.keys(game.stats.teamStats)) &&\n namesEveryTeam(Object.keys(game.score)) &&\n Object.keys(game.stats.playerStats).every((key) =>\n teams.has(parseGameRosterPlayerId(key)?.teamId ?? ''),\n )\n );\n}\nconst ROLLUP_MISMATCH = \"The stats rollup and score mirror hold exactly the game's teams.\";\n\nexport const BasketballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Basketball' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Basketball,\n // Which team holds the possession arrow.\n jumpBallTeamId: teamIdSchema.optional(),\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type BasketballGame = z.infer<typeof BasketballGameSchema>;\n\nexport const CurlingGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Curling' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Curling,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type CurlingGame = z.infer<typeof CurlingGameSchema>;\n\nexport const FootballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Football' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Football,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type FootballGame = z.infer<typeof FootballGameSchema>;\n\nexport const SportGameSchema = z.discriminatedUnion('leagueType', [\n BasketballGameSchema,\n CurlingGameSchema,\n FootballGameSchema,\n]);\nexport type SportGame = z.infer<typeof SportGameSchema>;\n\n// `sportGames/{gameId}/gameRosterPlayers/{teamId}_{userId}` — one entry per team per person, the id\n// from `buildGameRosterPlayerId`. The roster is the participation authority finalize iterates.\nexport const GameRosterPlayerSchema = z.object({\n userId: userIdSchema,\n teamId: teamIdSchema,\n isGhost: z.boolean(),\n syncedAt: epochMsSchema,\n});\nexport type GameRosterPlayer = z.infer<typeof GameRosterPlayerSchema>;\n"]}
|
|
@@ -61,7 +61,7 @@ export const SportLeagueSchema = z
|
|
|
61
61
|
// Advanced by every team created in the league; absent until the first. Only its changing
|
|
62
62
|
// matters: team creation counts the league's teams in its transaction against
|
|
63
63
|
// MAX_TEAMS_PER_LEAGUE, and a query does not lock against an insert — so each creation writes this
|
|
64
|
-
// document, and two creations at the cap conflict instead of both passing (BACKEND-116).
|
|
64
|
+
// document, and two creations at the cap conflict instead of both passing (RULE-STD-BACKEND-116).
|
|
65
65
|
teamRevision: countSchema.optional(),
|
|
66
66
|
// Advanced by every game created in the league, a schedule week's included; absent until the
|
|
67
67
|
// first. Only its changing matters, the same way: game creation counts the league's games in its
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sport-league.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-league.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,iGAAiG;AACjG,gGAAgG;AAChG,0BAA0B;AAE1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,CAAC,MAAM,iCAAiC,GAAG,yCAAyC,CAAC;AAC3F,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wFAAwF,CAAC;AAE3F;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAAC,MAG9C;IACC,OAAO,MAAM,CAAC,cAAc,KAAK,CAAC,MAAM,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAOlC,MAAS,EAAE,GAA6B;IACxC,IAAI,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QAClD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,eAAe,CAAC;YACvB,OAAO,EAAE,iCAAiC;SAC3C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,8BAA8B,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,kBAAkB,CAAC;YAC1B,OAAO,EAAE,mCAAmC;SAC7C,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,UAAU,EAAE,gBAAgB;IAC5B,UAAU,EAAE,gBAAgB;IAC5B,eAAe,EAAE,kBAAkB;IACnC,aAAa,EAAE,kBAAkB;IACjC,MAAM,EAAE,kBAAkB;IAC1B,8FAA8F;IAC9F,gGAAgG;IAChG,qCAAqC;IACrC,aAAa,EAAE,wBAAwB;IACvC,iBAAiB,EAAE,wBAAwB;IAC3C,gBAAgB,EAAE,wBAAwB;IAC1C,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;IAC3B,gBAAgB,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IACnD,2FAA2F;IAC3F,gEAAgE;IAChE,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,0FAA0F;IAC1F,8EAA8E;IAC9E,mGAAmG;IACnG,
|
|
1
|
+
{"version":3,"file":"sport-league.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-league.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,iGAAiG;AACjG,gGAAgG;AAChG,0BAA0B;AAE1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,CAAC,MAAM,iCAAiC,GAAG,yCAAyC,CAAC;AAC3F,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wFAAwF,CAAC;AAE3F;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAAC,MAG9C;IACC,OAAO,MAAM,CAAC,cAAc,KAAK,CAAC,MAAM,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAOlC,MAAS,EAAE,GAA6B;IACxC,IAAI,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QAClD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,eAAe,CAAC;YACvB,OAAO,EAAE,iCAAiC;SAC3C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,8BAA8B,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,kBAAkB,CAAC;YAC1B,OAAO,EAAE,mCAAmC;SAC7C,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,UAAU,EAAE,gBAAgB;IAC5B,UAAU,EAAE,gBAAgB;IAC5B,eAAe,EAAE,kBAAkB;IACnC,aAAa,EAAE,kBAAkB;IACjC,MAAM,EAAE,kBAAkB;IAC1B,8FAA8F;IAC9F,gGAAgG;IAChG,qCAAqC;IACrC,aAAa,EAAE,wBAAwB;IACvC,iBAAiB,EAAE,wBAAwB;IAC3C,gBAAgB,EAAE,wBAAwB;IAC1C,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;IAC3B,gBAAgB,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IACnD,2FAA2F;IAC3F,gEAAgE;IAChE,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,0FAA0F;IAC1F,8EAA8E;IAC9E,mGAAmG;IACnG,kGAAkG;IAClG,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,6FAA6F;IAC7F,iGAAiG;IACjG,4CAA4C;IAC5C,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC;KACD,WAAW,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["// `sportLeagues/{leagueId}` — one league: its sport, mode, dates, status, playoff cutoffs, and waiver\n// requirement. The admin league callables write its settings, status, and mode; the picture core\n// writes `pictures`. Teams, games, and player stats live in their own collections and are never\n// copied onto the league.\n\nimport { z } from 'zod';\nimport {\n calendarDateSchema,\n countSchema,\n epochMsSchema,\n leagueModeSchema,\n leagueStatusSchema,\n leagueTypeSchema,\n} from './atoms.js';\nimport { GallerySummarySchema } from './gallery.js';\nimport { waiverTemplateIdSchema } from './waiver-template.js';\n\nexport const LEAGUE_DATES_OUT_OF_ORDER_MESSAGE = 'The league cannot end before it starts.';\nexport const WAIVER_REQUIREMENT_MISMATCH_MESSAGE =\n 'Choose a waiver template when the league requires a waiver, and none when it does not.';\n\n/**\n * A league names a waiver template exactly when it requires a waiver, so no reader needs a\n * sentinel for \"no waiver\" and no stale template id survives turning the requirement off.\n */\nexport function hasConsistentWaiverRequirement(league: {\n requiresWaiver: boolean;\n waiverTemplateId?: string | undefined;\n}): boolean {\n return league.requiresWaiver === (league.waiverTemplateId !== undefined);\n}\n\n/**\n * The cross-field rules every league settings shape obeys — the stored document and both callable\n * inputs — so the rule a form shows, the callable enforces, and the copy action validates is one\n * rule.\n */\nexport function refineLeagueSettings<\n T extends {\n leagueStartDate: string;\n leagueEndDate: string;\n requiresWaiver: boolean;\n waiverTemplateId?: string | undefined;\n },\n>(league: T, ctx: z.core.$RefinementCtx<T>): void {\n if (league.leagueEndDate < league.leagueStartDate) {\n ctx.addIssue({\n code: 'custom',\n path: ['leagueEndDate'],\n message: LEAGUE_DATES_OUT_OF_ORDER_MESSAGE,\n });\n }\n if (!hasConsistentWaiverRequirement(league)) {\n ctx.addIssue({\n code: 'custom',\n path: ['waiverTemplateId'],\n message: WAIVER_REQUIREMENT_MISMATCH_MESSAGE,\n });\n }\n}\n\n// How many teams reach the playoffs is a count; the callable inputs bound it with `MAX_PLAYOFF_CUTOFF`.\nexport const playoffCutoffCountSchema = countSchema;\n\nexport const SportLeagueSchema = z\n .object({\n leagueTitle: z.string(),\n description: z.string(),\n leagueType: leagueTypeSchema,\n leagueMode: leagueModeSchema,\n leagueStartDate: calendarDateSchema,\n leagueEndDate: calendarDateSchema,\n status: leagueStatusSchema,\n // How many teams reach the playoffs: `playoffCutoff` in a Regular league, the Competitive and\n // Recreational pools' own cutoffs in a Competitive/Recreational one. All three are kept in both\n // modes so a mode switch loses none.\n playoffCutoff: playoffCutoffCountSchema,\n playoffCutoffComp: playoffCutoffCountSchema,\n playoffCutoffRec: playoffCutoffCountSchema,\n requiresWaiver: z.boolean(),\n waiverTemplateId: waiverTemplateIdSchema.optional(),\n // The league's picture gallery summary — absent is an empty gallery. Only the picture core\n // writes it; the league callables and the copy action never do.\n pictures: GallerySummarySchema.optional(),\n // Advanced by every team created in the league; absent until the first. Only its changing\n // matters: team creation counts the league's teams in its transaction against\n // MAX_TEAMS_PER_LEAGUE, and a query does not lock against an insert — so each creation writes this\n // document, and two creations at the cap conflict instead of both passing (RULE-STD-BACKEND-116).\n teamRevision: countSchema.optional(),\n // Advanced by every game created in the league, a schedule week's included; absent until the\n // first. Only its changing matters, the same way: game creation counts the league's games in its\n // transaction against MAX_GAMES_PER_LEAGUE.\n gameRevision: countSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n })\n .superRefine(refineLeagueSettings);\nexport type SportLeague = z.infer<typeof SportLeagueSchema>;\n"]}
|