mirror of
https://github.com/djhackersdev/minime.git
synced 2026-04-23 09:47:12 -05:00
Heal the layer break around the Id<> type
This commit is contained in:
parent
3a71f5054c
commit
6a4ac795b2
|
|
@ -2,7 +2,7 @@ import sql from "sql-bricks-postgres";
|
|||
|
||||
import { CardRepository, Repositories } from "./repo";
|
||||
import { AimeId, generateExtId } from "../model";
|
||||
import { Transaction, generateId } from "../sql";
|
||||
import { Transaction } from "../sql";
|
||||
|
||||
class CardRepositoryImpl implements CardRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -34,8 +34,8 @@ class CardRepositoryImpl implements CardRepository {
|
|||
}
|
||||
|
||||
async register(luid: string, now: Date): Promise<AimeId> {
|
||||
const playerId = generateId();
|
||||
const cardId = generateId();
|
||||
const playerId = this._txn.generateId();
|
||||
const cardId = this._txn.generateId();
|
||||
const aimeId = generateExtId() as AimeId;
|
||||
|
||||
const playerSql = sql.insert("aime_player", {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { BackgroundCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlBackgroundsRepository
|
||||
implements FlagRepository<BackgroundCode> {
|
||||
|
|
@ -38,7 +39,7 @@ export class SqlBackgroundsRepository
|
|||
}
|
||||
|
||||
const saveSql = sql.insert("idz_background_unlock", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
background_no: flag,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { Car, CarSelector } from "../model/car";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CarRepository } from "../repo";
|
||||
import { Id, Row, Transaction, generateId } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
function _extractRow(row: Row): Car {
|
||||
return {
|
||||
|
|
@ -69,7 +70,7 @@ export class SqlCarRepository implements CarRepository {
|
|||
async saveCar(profileId: Id<Profile>, car: Car): Promise<void> {
|
||||
const saveSql = sql
|
||||
.insert("idz_car", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
selector: car.selector,
|
||||
field_00: car.field_00,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { BackgroundCode, TitleCode } from "../model/base";
|
|||
import { Chara, Gender } from "../model/chara";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id, Row, Transaction } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
export function _extractChara(row: Row): Chara {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { CourseNo } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { CoursePlaysRepository } from "../repo";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -34,7 +35,7 @@ export class SqlCoursePlaysRepository implements CoursePlaysRepository {
|
|||
for (const [k, v] of plays) {
|
||||
const saveSql = sql
|
||||
.insert("idz_course_plays", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
course_no: k,
|
||||
count: v,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { MissionGrid, MissionState } from "../model/mission";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -59,7 +60,7 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
|
|||
|
||||
const saveSql = sql
|
||||
.insert("idz_solo_mission_state", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
grid_no: i,
|
||||
cell_no: j,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import sql from "sql-bricks-postgres";
|
|||
|
||||
import { Profile } from "../model/profile";
|
||||
import { ProfileRepository } from "../repo";
|
||||
import { AimeId } from "../../model";
|
||||
import { Id, Row, Transaction, generateId } from "../../sql";
|
||||
import { AimeId, Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
export function _extractProfile(row: Row): Profile {
|
||||
return {
|
||||
|
|
@ -45,7 +45,7 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return BigInt(row.id) as Id<Profile>;
|
||||
return row.id as Id<Profile>;
|
||||
}
|
||||
|
||||
async load(id: Id<Profile>): Promise<Profile> {
|
||||
|
|
@ -91,7 +91,7 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
throw new Error("Aime ID not found");
|
||||
}
|
||||
|
||||
const id = generateId();
|
||||
const id = this._txn.generateId<Profile>();
|
||||
const playerId = row.id;
|
||||
|
||||
const createSql = sql.insert("idz_profile", {
|
||||
|
|
@ -109,6 +109,6 @@ export class SqlProfileRepository implements ProfileRepository {
|
|||
|
||||
await this._txn.modify(createSql);
|
||||
|
||||
return id as Id<Profile>;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { Settings } from "../model/settings";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlSettingsRepository implements FacetRepository<Settings> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { Profile } from "../model/profile";
|
||||
import { Story, StoryRow, StoryCell } from "../model/story";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlStoryRepository implements FacetRepository<Story> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -81,7 +82,7 @@ export class SqlStoryRepository implements FacetRepository<Story> {
|
|||
|
||||
const cellSql = sql
|
||||
.insert("idz_story_cell_state", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
row_no: i,
|
||||
col_no: j,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { ExtId } from "../model/base";
|
||||
import { Team } from "../model/team";
|
||||
import { TeamSpec, TeamRepository } from "../repo";
|
||||
import { generateExtId } from "../../model";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id, generateExtId } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamRepository implements TeamRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -21,7 +21,7 @@ export class SqlTeamRepository implements TeamRepository {
|
|||
throw new Error(`Team not found for ExtID ${extId}`);
|
||||
}
|
||||
|
||||
return BigInt(row.id) as Id<Team>;
|
||||
return row.id as Id<Team>;
|
||||
}
|
||||
|
||||
async load(id: Id<Team>): Promise<Team> {
|
||||
|
|
@ -57,7 +57,7 @@ export class SqlTeamRepository implements TeamRepository {
|
|||
}
|
||||
|
||||
async create(team: TeamSpec): Promise<[Id<Team>, ExtId<Team>]> {
|
||||
const id = generateId() as Id<Team>;
|
||||
const id = this._txn.generateId<Team>();
|
||||
const extId = generateExtId() as ExtId<Team>;
|
||||
|
||||
const createSql = sql.insert("idz_team", {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import sql from "sql-bricks-postgres";
|
|||
|
||||
import { Team, TeamAuto } from "../model/team";
|
||||
import { TeamAutoRepository } from "../repo";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamAutoRepository implements TeamAutoRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -22,7 +23,7 @@ export class SqlTeamAutoRepository implements TeamAutoRepository {
|
|||
serialNo: parseInt(row.serial_no),
|
||||
nameIdx: parseInt(row.name_idx),
|
||||
},
|
||||
BigInt(row.id) as Id<Team>,
|
||||
row.id as Id<Team>,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { Team, TeamMember } from "../model/team";
|
|||
import { TeamMemberRepository } from "../repo";
|
||||
import { _extractProfile } from "./profile";
|
||||
import { _extractChara } from "./chara";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamMemberRepository implements TeamMemberRepository {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -22,7 +23,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return BigInt(row.team_id) as Id<Team>;
|
||||
return row.team_id as Id<Team>;
|
||||
}
|
||||
|
||||
async findLeader(teamId: Id<Team>): Promise<Id<Profile> | undefined> {
|
||||
|
|
@ -38,7 +39,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return BigInt(row.id) as Id<Profile>;
|
||||
return row.id as Id<Profile>;
|
||||
}
|
||||
|
||||
async loadRoster(teamId: Id<Team>): Promise<TeamMember[]> {
|
||||
|
|
@ -65,7 +66,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
|
|||
profileId: Id<Profile>,
|
||||
timestamp: Date
|
||||
): Promise<void> {
|
||||
// Lock the team record to avoid race conditions. This way
|
||||
// Lock the team record to avoid race conditions.
|
||||
|
||||
const lockSql = sql
|
||||
.select("id")
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import sql from "sql-bricks-postgres";
|
|||
|
||||
import { Team } from "../model/team";
|
||||
import { TeamReservationRepository } from "../repo";
|
||||
import { AimeId } from "../../model";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
import { AimeId, Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTeamReservationRepository
|
||||
implements TeamReservationRepository {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { Profile } from "../model/profile";
|
||||
import { Tickets } from "../model/tickets";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
// TODO free continue
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { CarSelector } from "../model/car";
|
|||
import { Profile } from "../model/profile";
|
||||
import { TimeAttackScore } from "../model/timeAttack";
|
||||
import { TimeAttackRepository, TopTenResult } from "../repo";
|
||||
import { Id, Row, Transaction, generateId } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Row, Transaction } from "../../sql";
|
||||
|
||||
function _extractRow(row: Row): TimeAttackScore {
|
||||
return {
|
||||
|
|
@ -56,7 +57,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
|
||||
async save(profileId: Id<Profile>, score: TimeAttackScore): Promise<void> {
|
||||
const logSql = sql.insert("idz_ta_result", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
route_no: score.routeNo,
|
||||
total_time: score.totalTime,
|
||||
|
|
@ -79,7 +80,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
|
|||
|
||||
if (row === undefined) {
|
||||
const insertSql = sql.insert("idz_ta_best", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
route_no: score.routeNo,
|
||||
total_time: score.totalTime,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { TitleCode } from "../model/base";
|
||||
import { Profile } from "../model/profile";
|
||||
import { FlagRepository } from "../repo";
|
||||
import { Id, Transaction, generateId } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
@ -33,7 +34,7 @@ export class SqlTitlesRepository implements FlagRepository<TitleCode> {
|
|||
}
|
||||
|
||||
const saveSql = sql.insert("idz_title_unlock", {
|
||||
id: generateId(),
|
||||
id: this._txn.generateId(),
|
||||
profile_id: profileId,
|
||||
title_no: flag,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import sql from "sql-bricks-postgres";
|
|||
import { Profile } from "../model/profile";
|
||||
import { Unlocks } from "../model/unlocks";
|
||||
import { FacetRepository } from "../repo";
|
||||
import { Id, Transaction } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
import { Transaction } from "../../sql";
|
||||
|
||||
export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
|
||||
constructor(private readonly _txn: Transaction) {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Team } from "../model/team";
|
||||
import { Repositories } from "../repo";
|
||||
import { Id } from "../../sql";
|
||||
import { Id } from "../../model";
|
||||
|
||||
// Bleh. This factorization is kind of messy.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import { Subtract } from "utility-types";
|
||||
|
||||
import * as Model from "./model";
|
||||
import { AimeId } from "../model";
|
||||
import { Id } from "../sql";
|
||||
|
||||
// Id<> is a layer break here... need to find a better way to deal with this.
|
||||
import { AimeId, Id } from "../model";
|
||||
|
||||
export type TeamSpec = Subtract<
|
||||
Model.Team,
|
||||
|
|
|
|||
17
src/model.ts
17
src/model.ts
|
|
@ -1,5 +1,22 @@
|
|||
import { randomBytes } from "crypto";
|
||||
|
||||
/**
|
||||
* An internal database id.
|
||||
*
|
||||
* We don't have any say over the protocols that we are implementing, and we
|
||||
* would also like our internal data store to adhere to a consistent set of
|
||||
* design principles. This type defines a fully opaque primary key data type
|
||||
* for database records which should not be exposed to external clients under
|
||||
* normal circumstances. Clients outside the SQL DB (or other persistent data
|
||||
* storage) driver should make no assumptions about its structure or actual
|
||||
* underlying data type.
|
||||
*
|
||||
* Database entities presented to external clients must be identified using
|
||||
* alternative external identifiers in formats that are acceptable to those
|
||||
* external systems.
|
||||
*/
|
||||
export type Id<T> = string & { __type: T };
|
||||
|
||||
export type AimeId = number & { __aimeId: null };
|
||||
|
||||
/** Generate a random 32-bit ID for use in external protocol messages */
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
export type Id<T> = bigint & { __id: T };
|
||||
import { Id } from "../model";
|
||||
|
||||
export interface Row {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
/**
|
||||
* Generate a new random primary key.
|
||||
*
|
||||
* On SQLite this is a random 63-bit integer (the high bit is always zero
|
||||
* so that all IDs are positive, mostly for aesthetic reasons although this
|
||||
* may also usefully reserve a namespace for automated testing).
|
||||
*
|
||||
* On Postgres this might generate UUIDv4s instead.
|
||||
*/
|
||||
generateId<T>(): Id<T>;
|
||||
|
||||
modify(stmt: sql.Statement): Promise<void>;
|
||||
|
||||
fetchRow(stmt: sql.SelectStatement): Promise<Row | undefined>;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
export * from "./api";
|
||||
export * from "./sqlite";
|
||||
export * from "./util";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import Database from "better-sqlite3";
|
||||
import { randomBytes } from "crypto";
|
||||
import * as sql from "sql-bricks-postgres";
|
||||
|
||||
import { DataSource, Row, Transaction } from "./api";
|
||||
import { Id } from "../model";
|
||||
|
||||
type MixedRow = {
|
||||
[key: string]: any;
|
||||
|
|
@ -52,6 +54,17 @@ function _postprocess(obj: MixedRow): Row {
|
|||
class SqliteTransaction implements Transaction {
|
||||
constructor(private readonly _db: Database.Database) {}
|
||||
|
||||
generateId<T>(): Id<T> {
|
||||
const buf = randomBytes(8);
|
||||
|
||||
buf[0] &= 0x7f; // Force number to be non-negative
|
||||
|
||||
const val = buf.readBigUInt64BE(0);
|
||||
const str = val.toString();
|
||||
|
||||
return str as Id<T>;
|
||||
}
|
||||
|
||||
modify(stmt: sql.Statement): Promise<void> {
|
||||
const params = _preprocess(stmt);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import { randomBytes } from "crypto";
|
||||
|
||||
export function generateId(): bigint {
|
||||
const buf = randomBytes(8);
|
||||
|
||||
buf[0] &= 0x7f; // Force number to be non-negative
|
||||
|
||||
// Let's not depend on Node v12 for the sake of 3 LoC just yet.
|
||||
|
||||
const hi = buf.readUInt32BE(0);
|
||||
const lo = buf.readUInt32BE(4);
|
||||
|
||||
return (BigInt(hi) << 32n) | BigInt(lo);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user