Heal the layer break around the Id<> type

This commit is contained in:
Tau 2019-10-27 11:27:51 -04:00
parent 3a71f5054c
commit 6a4ac795b2
24 changed files with 96 additions and 60 deletions

View File

@ -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", {

View File

@ -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,
});

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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) {}

View File

@ -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,

View File

@ -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", {

View File

@ -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>,
]
);
}

View File

@ -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")

View File

@ -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 {

View File

@ -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

View File

@ -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,

View File

@ -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,
});

View File

@ -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) {}

View File

@ -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.

View File

@ -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,

View File

@ -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 */

View File

@ -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>;

View File

@ -1,3 +1,2 @@
export * from "./api";
export * from "./sqlite";
export * from "./util";

View File

@ -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);

View File

@ -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);
}