Fork of github.com/did-method-plc/did-method-plc
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #3 from bluesky-social/db-rework

Rework Database

authored by

Daniel Holmgren and committed by
GitHub
3be04b95 0e771943

+362 -274
-1
.gitignore
··· 6 6 .idea 7 7 packages/*/coverage 8 8 .vscode/ 9 - test.sqlite 10 9 .DS_Store 11 10 *.log 12 11 tsconfig.build.tsbuildinfo
-1
packages/lib/package.json
··· 27 27 "zod": "^3.14.2" 28 28 }, 29 29 "devDependencies": { 30 - "@types/pg": "^8.6.5", 31 30 "eslint-plugin-prettier": "^4.2.1" 32 31 } 33 32 }
+1 -1
packages/server/bin/migration-create.ts
··· 14 14 ) 15 15 } 16 16 const filename = `${prefix}-${name}` 17 - const dir = path.join(__dirname, '..', 'src', 'server', 'migrations') 17 + const dir = path.join(__dirname, '..', 'src', 'migrations') 18 18 19 19 await fs.writeFile(path.join(dir, `${filename}.ts`), template, { flag: 'wx' }) 20 20 await fs.writeFile(
+1 -2
packages/server/build.js
··· 16 16 entryPoints: [ 17 17 'src/index.ts', 18 18 'src/bin.ts', 19 - 'src/db.ts', 19 + 'src/db/index.ts', 20 20 ], 21 21 bundle: true, 22 22 sourcemap: true, ··· 26 26 assetNames: 'src/static', 27 27 plugins: buildShallow ? [nodeExternalsPlugin()] : [], 28 28 external: [ 29 - 'better-sqlite3', 30 29 // Referenced in pg driver, but optional and we don't use it 31 30 'pg-native', 32 31 ],
+1 -1
packages/server/example.dev.env
··· 1 - DB_POSTGRES_URL="postgres://bsky:yksb@localhost/plc_dev" 1 + DATABASE_URL="postgres://bsky:yksb@localhost/plc_dev" 2 2 DEBUG_MODE=1 3 3 LOG_ENABLED="true" 4 4 LOG_LEVEL=debug
+1 -3
packages/server/package.json
··· 5 5 "license": "MIT", 6 6 "scripts": { 7 7 "start": "node dist/bin.js", 8 - "test": "jest", 9 - "test:pg": "./pg/with-test-db.sh jest", 8 + "test": "./pg/with-test-db.sh jest", 10 9 "test:log": "cat test.log | pino-pretty", 11 10 "prettier": "prettier --check src/", 12 11 "prettier:fix": "prettier --write src/", ··· 28 27 "@atproto/crypto": "0.1.0", 29 28 "@did-plc/lib": "*", 30 29 "axios": "^0.27.2", 31 - "better-sqlite3": "^8.1.0", 32 30 "cors": "^2.8.5", 33 31 "express": "^4.18.2", 34 32 "express-async-errors": "^3.1.1",
+1 -1
packages/server/pg/README.md
··· 8 8 9 9 This script allows you to run any command with a fresh, ephemeral/single-use postgres database available. When the script starts a Dockerized postgres container starts-up, and when the script completes that container is removed. 10 10 11 - The environment variable `DB_POSTGRES_URL` will be set with a connection string that can be used to connect to the database. The [`PG*` environment variables](https://www.postgresql.org/docs/current/libpq-envars.html) that are recognized by libpq (i.e. used by the `psql` client) are also set. 11 + The environment variable `DATABASE_URL` will be set with a connection string that can be used to connect to the database. The [`PG*` environment variables](https://www.postgresql.org/docs/current/libpq-envars.html) that are recognized by libpq (i.e. used by the `psql` client) are also set. 12 12 13 13 **Example** 14 14
+1 -1
packages/server/pg/with-test-db.sh
··· 22 22 export PGUSER=pg 23 23 export PGPASSWORD=password 24 24 export PGDATABASE=postgres 25 - export DB_POSTGRES_URL="postgresql://pg:password@localhost:5433/postgres" 25 + export DATABASE_URL="postgresql://pg:password@localhost:5433/postgres" 26 26 "$@" 27 27 code=$? 28 28
+8 -11
packages/server/src/bin.ts
··· 1 1 import './env' 2 - import { Database } from './db' 2 + import { Database, PlcDatabase } from './db' 3 3 import PlcServer from '.' 4 4 5 5 const run = async () => { 6 - const dbLoc = process.env.DATABASE_LOC 7 - const dbPostgresUrl = process.env.DB_POSTGRES_URL 6 + const dbUrl = process.env.DATABASE_URL 8 7 9 - let db: Database 10 - if (dbPostgresUrl) { 11 - db = Database.postgres({ url: dbPostgresUrl }) 12 - } else if (dbLoc) { 13 - db = Database.sqlite(dbLoc) 8 + let db: PlcDatabase 9 + if (dbUrl) { 10 + const pgDb = Database.postgres({ url: dbUrl }) 11 + await pgDb.migrateToLatestOrThrow() 12 + db = pgDb 14 13 } else { 15 - db = Database.memory() 14 + db = Database.mock() 16 15 } 17 - 18 - await db.migrateToLatestOrThrow() 19 16 20 17 const envPort = parseInt(process.env.PORT || '') 21 18 const port = isNaN(envPort) ? 2582 : envPort
+2 -2
packages/server/src/context.ts
··· 1 - import { Database } from './db' 1 + import { PlcDatabase } from './db' 2 2 3 3 export class AppContext { 4 4 constructor( 5 5 private opts: { 6 - db: Database 6 + db: PlcDatabase 7 7 version: string 8 8 port?: number 9 9 },
+59 -60
packages/server/src/db.ts packages/server/src/db/index.ts
··· 1 - import { Kysely, Migrator, PostgresDialect, SqliteDialect } from 'kysely' 2 - import SqliteDB from 'better-sqlite3' 1 + import { Generated, Kysely, Migrator, PostgresDialect, sql } from 'kysely' 3 2 import { Pool as PgPool, types as pgTypes } from 'pg' 4 3 import { CID } from 'multiformats/cid' 5 4 import { cidForCbor, check } from '@atproto/common' 6 5 import * as plc from '@did-plc/lib' 7 - import { ServerError } from './error' 8 - import * as migrations from './migrations' 6 + import { ServerError } from '../error' 7 + import * as migrations from '../migrations' 8 + import { OpLogExport, PlcDatabase } from './types' 9 + import MockDatabase from './mock' 10 + 11 + export * from './mock' 12 + export * from './types' 9 13 10 - export class Database { 14 + export class Database implements PlcDatabase { 11 15 migrator: Migrator 12 - constructor( 13 - public db: Kysely<DatabaseSchema>, 14 - public dialect: Dialect, 15 - public schema?: string, 16 - ) { 16 + constructor(public db: Kysely<DatabaseSchema>, public schema?: string) { 17 17 this.migrator = new Migrator({ 18 18 db, 19 19 migrationTableSchema: schema, ··· 23 23 }, 24 24 }, 25 25 }) 26 - } 27 - 28 - static sqlite(location: string): Database { 29 - const db = new Kysely<DatabaseSchema>({ 30 - dialect: new SqliteDialect({ 31 - database: new SqliteDB(location), 32 - }), 33 - }) 34 - return new Database(db, 'sqlite') 35 26 } 36 27 37 28 static postgres(opts: { url: string; schema?: string }): Database { ··· 58 49 dialect: new PostgresDialect({ pool }), 59 50 }) 60 51 61 - return new Database(db, 'pg', schema) 52 + return new Database(db, schema) 62 53 } 63 54 64 - static memory(): Database { 65 - return Database.sqlite(':memory:') 55 + static mock(): MockDatabase { 56 + return new MockDatabase() 66 57 } 67 58 68 59 async close(): Promise<void> { 69 60 await this.db.destroy() 70 61 } 71 62 63 + async healthCheck(): Promise<void> { 64 + await sql`select 1`.execute(this.db) 65 + } 66 + 67 + async migrateToOrThrow(migration: string) { 68 + if (this.schema !== undefined) { 69 + await this.db.schema.createSchema(this.schema).ifNotExists().execute() 70 + } 71 + const { error, results } = await this.migrator.migrateTo(migration) 72 + if (error) { 73 + throw error 74 + } 75 + if (!results) { 76 + throw new Error('An unknown failure occurred while migrating') 77 + } 78 + return results 79 + } 80 + 72 81 async migrateToLatestOrThrow() { 73 82 if (this.schema !== undefined) { 74 83 await this.db.schema.createSchema(this.schema).ifNotExists().execute() ··· 97 106 .insertInto('operations') 98 107 .values({ 99 108 did, 100 - operation: JSON.stringify(proposed), 109 + operation: proposed, 101 110 cid: cid.toString(), 102 - nullified: 0, 103 - createdAt: new Date().toISOString(), 111 + nullified: false, 104 112 }) 105 113 .execute() 106 114 ··· 108 116 const nullfiedStrs = nullified.map((cid) => cid.toString()) 109 117 await tx 110 118 .updateTable('operations') 111 - .set({ nullified: 1 }) 119 + .set({ nullified: true }) 112 120 .where('did', '=', did) 113 121 .where('cid', 'in', nullfiedStrs) 114 122 .execute() ··· 120 128 .selectFrom('operations') 121 129 .select('cid') 122 130 .where('did', '=', did) 123 - .where('nullified', '=', 0) 131 + .where('nullified', '=', false) 124 132 .orderBy('createdAt', 'desc') 125 133 .limit(2) 126 134 .execute() ··· 143 151 .selectFrom('operations') 144 152 .select('cid') 145 153 .where('did', '=', did) 146 - .where('nullified', '=', 0) 154 + .where('nullified', '=', false) 147 155 .where('cid', 'not in', notIncludedStr) 148 156 .orderBy('createdAt', 'desc') 149 157 .executeTakeFirst() ··· 165 173 .selectFrom('operations') 166 174 .selectAll() 167 175 .where('did', '=', did) 168 - .where('nullified', '=', 0) 176 + .where('nullified', '=', false) 169 177 .orderBy('createdAt', 'asc') 170 178 .execute() 171 179 172 180 return res.map((row) => ({ 173 181 did: row.did, 174 - operation: JSON.parse(row.operation), 182 + operation: row.operation, 175 183 cid: CID.parse(row.cid), 176 - nullified: row.nullified === 1, 177 - createdAt: new Date(row.createdAt), 184 + nullified: row.nullified, 185 + createdAt: row.createdAt, 178 186 })) 179 187 } 180 188 181 189 async fullExport(): Promise<Record<string, OpLogExport>> { 182 - const res = await this.db 183 - .selectFrom('operations') 184 - .selectAll() 185 - .orderBy('did') 186 - .orderBy('createdAt') 187 - .execute() 188 - return res.reduce((acc, cur) => { 189 - acc[cur.did] ??= [] 190 - acc[cur.did].push({ 191 - op: JSON.parse(cur.operation), 192 - nullified: cur.nullified === 1, 193 - createdAt: cur.createdAt, 194 - }) 195 - return acc 196 - }, {} as Record<string, OpLogExport>) 190 + return {} 191 + // const res = await this.db 192 + // .selectFrom('operations') 193 + // .selectAll() 194 + // .orderBy('did') 195 + // .orderBy('createdAt') 196 + // .execute() 197 + // return res.reduce((acc, cur) => { 198 + // acc[cur.did] ??= [] 199 + // acc[cur.did].push({ 200 + // op: cur.operation), 201 + // nullified: cur.nullified === 1, 202 + // createdAt: cur.createdAt, 203 + // }) 204 + // return acc 205 + // }, {} as Record<string, OpLogExport>) 197 206 } 198 207 } 199 208 200 209 export default Database 201 - 202 - export type Dialect = 'pg' | 'sqlite' 203 210 204 211 interface OperationsTable { 205 212 did: string 206 - operation: string 213 + operation: plc.CompatibleOpOrTombstone 207 214 cid: string 208 - nullified: 0 | 1 209 - createdAt: string 215 + nullified: boolean 216 + createdAt: Generated<Date> 210 217 } 211 218 212 219 interface DatabaseSchema { 213 220 operations: OperationsTable 214 221 } 215 - 216 - type OpLogExport = OpExport[] 217 - 218 - type OpExport = { 219 - op: Record<string, unknown> 220 - nullified: boolean 221 - createdAt: string 222 - }
+65
packages/server/src/db/mock.ts
··· 1 + import { cidForCbor, check } from '@atproto/common' 2 + import * as plc from '@did-plc/lib' 3 + import { ServerError } from '../error' 4 + import { OpLogExport, PlcDatabase } from './types' 5 + 6 + type Contents = Record<string, plc.IndexedOperation[]> 7 + 8 + export class MockDatabase implements PlcDatabase { 9 + contents: Contents = {} 10 + 11 + async close(): Promise<void> {} 12 + async healthCheck(): Promise<void> {} 13 + 14 + async validateAndAddOp(did: string, proposed: plc.Operation): Promise<void> { 15 + this.contents[did] ??= [] 16 + const opsBefore = this.contents[did] 17 + // throws if invalid 18 + const { nullified } = await plc.assureValidNextOp(did, opsBefore, proposed) 19 + const cid = await cidForCbor(proposed) 20 + if (this.contents[did] !== opsBefore) { 21 + throw new ServerError( 22 + 409, 23 + `Proposed prev does not match the most recent operation`, 24 + ) 25 + } 26 + this.contents[did].push({ 27 + did, 28 + operation: proposed, 29 + cid, 30 + nullified: false, 31 + createdAt: new Date(), 32 + }) 33 + 34 + if (nullified.length > 0) { 35 + for (let i = 0; i < this.contents[did].length; i++) { 36 + const cid = this.contents[did][i].cid 37 + for (const toCheck of nullified) { 38 + if (toCheck.equals(cid)) { 39 + this.contents[did][i].nullified = true 40 + } 41 + } 42 + } 43 + } 44 + } 45 + 46 + async opsForDid(did: string): Promise<plc.OpOrTombstone[]> { 47 + const ops = await this._opsForDid(did) 48 + return ops.map((op) => { 49 + if (check.is(op.operation, plc.def.createOpV1)) { 50 + return plc.normalizeOp(op.operation) 51 + } 52 + return op.operation 53 + }) 54 + } 55 + 56 + async _opsForDid(did: string): Promise<plc.IndexedOperation[]> { 57 + return this.contents[did] ?? [] 58 + } 59 + 60 + async fullExport(): Promise<Record<string, OpLogExport>> { 61 + return {} 62 + } 63 + } 64 + 65 + export default MockDatabase
+18
packages/server/src/db/types.ts
··· 1 + import * as plc from '@did-plc/lib' 2 + 3 + export interface PlcDatabase { 4 + close(): Promise<void> 5 + healthCheck(): Promise<void> 6 + validateAndAddOp(did: string, proposed: plc.Operation): Promise<void> 7 + opsForDid(did: string): Promise<plc.OpOrTombstone[]> 8 + _opsForDid(did: string): Promise<plc.IndexedOperation[]> 9 + fullExport(): Promise<Record<string, OpLogExport>> 10 + } 11 + 12 + export type OpLogExport = OpExport[] 13 + 14 + export type OpExport = { 15 + op: Record<string, unknown> 16 + nullified: boolean 17 + createdAt: string 18 + }
+2 -2
packages/server/src/index.ts
··· 8 8 import cors from 'cors' 9 9 import http from 'http' 10 10 import events from 'events' 11 - import { Database } from './db' 12 11 import * as error from './error' 13 12 import createRouter from './routes' 14 13 import { loggerMiddleware } from './logger' 15 14 import AppContext from './context' 16 15 import { createHttpTerminator, HttpTerminator } from 'http-terminator' 16 + import { PlcDatabase } from './db/types' 17 17 18 18 export * from './db' 19 19 export * from './context' ··· 30 30 } 31 31 32 32 static create(opts: { 33 - db: Database 33 + db: PlcDatabase 34 34 port?: number 35 35 version?: string 36 36 }): PlcServer {
+66
packages/server/src/migrations/20230223T215019669Z-refactor.ts
··· 1 + import { Kysely, sql } from 'kysely' 2 + 3 + export async function up(db: Kysely<any>): Promise<void> { 4 + await db.schema 5 + .createTable('operations_new') 6 + .addColumn('did', 'text', (col) => col.notNull()) 7 + .addColumn('operation', 'jsonb', (col) => col.notNull()) 8 + .addColumn('cid', 'text', (col) => col.notNull()) 9 + .addColumn('nullified', 'boolean', (col) => col.notNull()) 10 + .addColumn('createdAt', 'timestamptz', (col) => 11 + col.notNull().defaultTo(sql`current_timestamp`), 12 + ) 13 + .addPrimaryKeyConstraint('operations_primary_key', ['did', 'cid']) 14 + .execute() 15 + 16 + const dump = await db.selectFrom('operations').selectAll().execute() 17 + const vals = dump.map((row) => ({ 18 + did: row.did, 19 + operation: row.operation, 20 + cid: row.cid, 21 + nullified: row.nullified === 1 ? true : false, 22 + createdAt: row.createdAt, 23 + })) 24 + 25 + if (vals.length > 0) { 26 + await db.insertInto('operations_new').values(vals).execute() 27 + } 28 + 29 + await db.schema.dropTable('operations').execute() 30 + 31 + await db.schema.alterTable('operations_new').renameTo('operations').execute() 32 + 33 + await db.schema 34 + .createIndex('operations_createdAt_index') 35 + .on('operations') 36 + .column('createdAt') 37 + .execute() 38 + } 39 + 40 + export async function down(db: Kysely<any>): Promise<void> { 41 + await db.schema 42 + .createTable('operations_new') 43 + .addColumn('did', 'varchar', (col) => col.notNull()) 44 + .addColumn('operation', 'text', (col) => col.notNull()) 45 + .addColumn('cid', 'varchar', (col) => col.notNull()) 46 + .addColumn('nullified', 'int2', (col) => col.defaultTo(0)) 47 + .addColumn('createdAt', 'varchar', (col) => col.notNull()) 48 + .addPrimaryKeyConstraint('primary_key', ['did', 'cid']) 49 + .execute() 50 + 51 + const dump = await db.selectFrom('operations').selectAll().execute() 52 + const vals = dump.map((row) => ({ 53 + did: row.did, 54 + operation: JSON.stringify(row.operation), 55 + cid: row.cid, 56 + nullified: row.nullified ? 1 : 0, 57 + createdAt: row.createdAt.toISOString(), 58 + })) 59 + 60 + await db.insertInto('operations_new').values(vals).execute() 61 + 62 + await db.schema.dropIndex('operations_createdAt_index').execute() 63 + await db.schema.dropTable('operations').execute() 64 + 65 + await db.schema.alterTable('operations_new').renameTo('operations').execute() 66 + }
+1
packages/server/src/migrations/index.ts
··· 3 3 // this with kysely's FileMigrationProvider, but it doesn't play nicely with the build process. 4 4 5 5 export * as _20221020T204908820Z from './20221020T204908820Z-operations-init' 6 + export * as _20230223T215019669Z from './20230223T215019669Z-refactor'
+1 -2
packages/server/src/routes.ts
··· 1 1 import express from 'express' 2 - import { sql } from 'kysely' 3 2 import { check } from '@atproto/common' 4 3 import * as plc from '@did-plc/lib' 5 4 import { ServerError } from './error' ··· 11 10 router.get('/_health', async function (req, res) { 12 11 const { db, version } = ctx 13 12 try { 14 - await sql`select 1`.execute(db.db) 13 + await db.healthCheck() 15 14 } catch (err) { 16 15 req.log.error(err, 'failed health check') 17 16 return res.status(503).send({ version, error: 'Service Unavailable' })
+12 -11
packages/server/tests/_util.ts
··· 6 6 export type TestServerInfo = { 7 7 ctx: AppContext 8 8 url: string 9 + db: Database 9 10 close: CloseFn 10 11 } 11 12 12 13 export const runTestServer = async (opts: { 13 - dbPostgresSchema: string 14 + dbSchema: string 14 15 }): Promise<TestServerInfo> => { 15 - const { dbPostgresSchema } = opts 16 - const dbPostgresUrl = process.env.DB_POSTGRES_URL || undefined 16 + const { dbSchema } = opts 17 + const dbUrl = process.env.DATABASE_URL 18 + if (!dbUrl) { 19 + throw new Error('No postgres url provided') 20 + } 17 21 18 - const db = 19 - dbPostgresUrl !== undefined 20 - ? Database.postgres({ 21 - url: dbPostgresUrl, 22 - schema: dbPostgresSchema, 23 - }) 24 - : Database.memory() 25 - 22 + const db = Database.postgres({ 23 + url: dbUrl, 24 + schema: dbSchema, 25 + }) 26 26 await db.migrateToLatestOrThrow() 27 27 28 28 const plc = PlcServer.create({ db }) ··· 32 32 return { 33 33 ctx: plc.ctx, 34 34 url: `http://localhost:${port}`, 35 + db, 35 36 close: async () => { 36 37 await plc.destroy() 37 38 },
+111
packages/server/tests/migrations/refactor.test.ts
··· 1 + import { cidForCbor, DAY } from '@atproto/common' 2 + import { Secp256k1Keypair } from '@atproto/crypto' 3 + import * as plc from '@did-plc/lib' 4 + import { Kysely } from 'kysely' 5 + import { Database } from '../../src' 6 + 7 + describe('refactor migration', () => { 8 + let db: Database 9 + let rawDb: Kysely<any> 10 + 11 + beforeAll(async () => { 12 + const dbUrl = process.env.DATABASE_URL 13 + if (!dbUrl) { 14 + throw new Error('No postgres url provided') 15 + } 16 + db = Database.postgres({ 17 + url: dbUrl, 18 + schema: 'migration_refactor', 19 + }) 20 + 21 + await db.migrateToOrThrow('_20221020T204908820Z') 22 + rawDb = db.db 23 + }) 24 + 25 + afterAll(async () => { 26 + await db.close() 27 + }) 28 + 29 + const ops: any[] = [] 30 + let before: any[] 31 + 32 + it('fills the database with some operations', async () => { 33 + for (let i = 0; i < 100; i++) { 34 + const signingKey = await Secp256k1Keypair.create() 35 + const recoveryKey = await Secp256k1Keypair.create() 36 + const op = await plc.deprecatedSignCreate( 37 + { 38 + type: 'create', 39 + signingKey: signingKey.did(), 40 + recoveryKey: recoveryKey.did(), 41 + handle: `user${i}.test`, 42 + service: 'https://example.com', 43 + prev: null, 44 + }, 45 + signingKey, 46 + ) 47 + const did = await plc.didForCreateOp(op) 48 + const cid = await cidForCbor(op) 49 + const randomOffset = Math.floor(Math.random() * DAY * 60) 50 + const time = new Date(Date.now() - randomOffset).toISOString() 51 + ops.push({ 52 + did, 53 + operation: JSON.stringify(op), 54 + cid: cid.toString(), 55 + nullified: 0, 56 + createdAt: time, 57 + }) 58 + } 59 + await rawDb.insertInto('operations').values(ops).execute() 60 + 61 + before = await rawDb 62 + .selectFrom('operations') 63 + .selectAll() 64 + .orderBy('did', 'asc') 65 + .execute() 66 + }) 67 + 68 + it('migrates', async () => { 69 + await db.migrateToOrThrow('_20230223T215019669Z') 70 + }) 71 + 72 + it('correctly migrated all data', async () => { 73 + const migrated = await rawDb 74 + .selectFrom('operations') 75 + .selectAll() 76 + .orderBy('did', 'asc') 77 + .execute() 78 + const ordered = ops.sort((a, b) => a.did.localeCompare(b.did)) 79 + expect(migrated.length).toBe(ordered.length) 80 + for (let i = 0; i < migrated.length; i++) { 81 + expect(migrated[i].did).toBe(ordered[i].did) 82 + expect(migrated[i].operation).toEqual(JSON.parse(ordered[i].operation)) 83 + expect(migrated[i].cid).toBe(ordered[i].cid) 84 + expect(migrated[i].nullified).toBe( 85 + ordered[i].nullified === 1 ? true : false, 86 + ) 87 + expect(migrated[i].createdAt).toEqual(new Date(ordered[i].createdAt)) 88 + } 89 + }) 90 + 91 + it('migrates down', async () => { 92 + await db.migrateToOrThrow('_20221020T204908820Z') 93 + const migratedBack = await rawDb 94 + .selectFrom('operations') 95 + .selectAll() 96 + .orderBy('did', 'asc') 97 + .execute() 98 + expect(migratedBack.length).toBe(before.length) 99 + // normalize json 100 + const beforeNormalized = before.map((row) => ({ 101 + ...row, 102 + operation: JSON.parse(row.operation), 103 + })) 104 + const migratedNormalized = migratedBack.map((row) => ({ 105 + ...row, 106 + operation: JSON.parse(row.operation), 107 + })) 108 + 109 + expect(migratedNormalized).toEqual(beforeNormalized) 110 + }) 111 + })
+5 -5
packages/server/tests/server.test.ts
··· 22 22 23 23 beforeAll(async () => { 24 24 const server = await runTestServer({ 25 - dbPostgresSchema: 'server', 25 + dbSchema: 'server', 26 26 }) 27 27 28 - db = server.ctx.db 28 + db = server.db 29 29 close = server.close 30 30 client = new plc.Client(server.url) 31 31 signingKey = await EcdsaKeypair.create() ··· 166 166 }) 167 167 168 168 it('handles concurrent requests to many docs', async () => { 169 - const COUNT = 100 169 + const COUNT = 50 170 170 const keys: EcdsaKeypair[] = [] 171 171 for (let i = 0; i < COUNT; i++) { 172 172 keys.push(await EcdsaKeypair.create()) ··· 189 189 }) 190 190 191 191 it('resolves races into a coherent history with no forks', async () => { 192 - const COUNT = 100 192 + const COUNT = 50 193 193 const keys: EcdsaKeypair[] = [] 194 194 for (let i = 0; i < COUNT; i++) { 195 195 keys.push(await EcdsaKeypair.create()) ··· 213 213 }), 214 214 ) 215 215 expect(successes).toBe(1) 216 - expect(failures).toBe(99) 216 + expect(failures).toBe(49) 217 217 218 218 const ops = await client.getOperationLog(did) 219 219 await plc.validateOperationLog(did, ops)
+6 -170
yarn.lock
··· 2780 2780 resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz" 2781 2781 integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== 2782 2782 2783 - better-sqlite3@^8.1.0: 2784 - version "8.1.0" 2785 - resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-8.1.0.tgz#a0039c5dfdc04b733cac3c8dbe1b71f3e5fc62d3" 2786 - integrity sha512-p1m09H+Oi8R9TPj810pdNswMFuVgRNgCJEWypp6jlkOgSwMIrNyuj3hW78xEuBRGok5RzeaUW8aBtTWF3l/TQA== 2787 - dependencies: 2788 - bindings "^1.5.0" 2789 - prebuild-install "^7.1.0" 2790 - 2791 2783 big-integer@^1.6.51: 2792 2784 version "1.6.51" 2793 2785 resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" 2794 2786 integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== 2795 - 2796 - bindings@^1.5.0: 2797 - version "1.5.0" 2798 - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 2799 - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 2800 - dependencies: 2801 - file-uri-to-path "1.0.0" 2802 - 2803 - bl@^4.0.3: 2804 - version "4.1.0" 2805 - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" 2806 - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== 2807 - dependencies: 2808 - buffer "^5.5.0" 2809 - inherits "^2.0.4" 2810 - readable-stream "^3.4.0" 2811 2787 2812 2788 body-parser@1.20.1: 2813 2789 version "1.20.1" ··· 2887 2863 version "2.0.0" 2888 2864 resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" 2889 2865 integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== 2890 - 2891 - buffer@^5.5.0: 2892 - version "5.7.1" 2893 - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 2894 - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== 2895 - dependencies: 2896 - base64-js "^1.3.1" 2897 - ieee754 "^1.1.13" 2898 2866 2899 2867 buffer@^6.0.3: 2900 2868 version "6.0.3" ··· 3022 2990 resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" 3023 2991 integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== 3024 2992 3025 - chownr@^1.1.1, chownr@^1.1.4: 2993 + chownr@^1.1.4: 3026 2994 version "1.1.4" 3027 2995 resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" 3028 2996 integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== ··· 3420 3388 resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz" 3421 3389 integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== 3422 3390 3423 - decompress-response@^6.0.0: 3424 - version "6.0.0" 3425 - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" 3426 - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== 3427 - dependencies: 3428 - mimic-response "^3.1.0" 3429 - 3430 3391 dedent@^0.7.0: 3431 3392 version "0.7.0" 3432 3393 resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" 3433 3394 integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== 3434 3395 3435 - deep-extend@^0.6.0: 3436 - version "0.6.0" 3437 - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 3438 - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 3439 - 3440 3396 deep-is@^0.1.3: 3441 3397 version "0.1.4" 3442 3398 resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" ··· 3506 3462 version "6.1.0" 3507 3463 resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz" 3508 3464 integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== 3509 - 3510 - detect-libc@^2.0.0: 3511 - version "2.0.1" 3512 - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" 3513 - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== 3514 3465 3515 3466 detect-newline@^3.0.0: 3516 3467 version "3.1.0" ··· 3613 3564 dependencies: 3614 3565 iconv-lite "^0.6.2" 3615 3566 3616 - end-of-stream@^1.1.0, end-of-stream@^1.4.1: 3567 + end-of-stream@^1.1.0: 3617 3568 version "1.4.4" 3618 3569 resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" 3619 3570 integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== ··· 4063 4014 resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" 4064 4015 integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== 4065 4016 4066 - expand-template@^2.0.3: 4067 - version "2.0.3" 4068 - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" 4069 - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== 4070 - 4071 4017 expect@^28.0.0, expect@^28.1.3: 4072 4018 version "28.1.3" 4073 4019 resolved "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz" ··· 4236 4182 dependencies: 4237 4183 flat-cache "^3.0.4" 4238 4184 4239 - file-uri-to-path@1.0.0: 4240 - version "1.0.0" 4241 - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 4242 - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== 4243 - 4244 4185 fill-range@^7.0.1: 4245 4186 version "7.0.1" 4246 4187 resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" ··· 4340 4281 resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 4341 4282 integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== 4342 4283 4343 - fs-constants@^1.0.0: 4344 - version "1.0.0" 4345 - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" 4346 - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== 4347 - 4348 4284 fs-extra@^10.0.1: 4349 4285 version "10.1.0" 4350 4286 resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" ··· 4529 4465 integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== 4530 4466 dependencies: 4531 4467 ini "^1.3.2" 4532 - 4533 - github-from-package@0.0.0: 4534 - version "0.0.0" 4535 - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" 4536 - integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== 4537 4468 4538 4469 glob-parent@^5.1.1, glob-parent@^5.1.2: 4539 4470 version "5.1.2" ··· 4799 4730 dependencies: 4800 4731 safer-buffer ">= 2.1.2 < 3.0.0" 4801 4732 4802 - ieee754@^1.1.13, ieee754@^1.2.1: 4733 + ieee754@^1.2.1: 4803 4734 version "1.2.1" 4804 4735 resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" 4805 4736 integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ··· 4855 4786 once "^1.3.0" 4856 4787 wrappy "1" 4857 4788 4858 - inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: 4789 + inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: 4859 4790 version "2.0.4" 4860 4791 resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" 4861 4792 integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 4862 4793 4863 - ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: 4794 + ini@^1.3.2, ini@^1.3.4: 4864 4795 version "1.3.8" 4865 4796 resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" 4866 4797 integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ··· 5963 5894 resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" 5964 5895 integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== 5965 5896 5966 - mimic-response@^3.1.0: 5967 - version "3.1.0" 5968 - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" 5969 - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== 5970 - 5971 5897 min-indent@^1.0.0: 5972 5898 version "1.0.1" 5973 5899 resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" ··· 6001 5927 resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" 6002 5928 integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 6003 5929 6004 - minimist@^1.2.3: 6005 - version "1.2.8" 6006 - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" 6007 - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== 6008 - 6009 5930 minipass-collect@^1.0.2: 6010 5931 version "1.0.2" 6011 5932 resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz" ··· 6083 6004 minipass "^3.0.0" 6084 6005 yallist "^4.0.0" 6085 6006 6086 - mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: 6087 - version "0.5.3" 6088 - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" 6089 - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== 6090 - 6091 6007 mkdirp-infer-owner@^2.0.0: 6092 6008 version "2.0.0" 6093 6009 resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz" ··· 6150 6066 resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" 6151 6067 integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== 6152 6068 6153 - napi-build-utils@^1.0.1: 6154 - version "1.0.2" 6155 - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" 6156 - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== 6157 - 6158 6069 natural-compare@^1.4.0: 6159 6070 version "1.4.0" 6160 6071 resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" ··· 6174 6085 version "1.0.5" 6175 6086 resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" 6176 6087 integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== 6177 - 6178 - node-abi@^3.3.0: 6179 - version "3.33.0" 6180 - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.33.0.tgz#8b23a0cec84e1c5f5411836de6a9b84bccf26e7f" 6181 - integrity sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog== 6182 - dependencies: 6183 - semver "^7.3.5" 6184 6088 6185 6089 node-fetch@^2.6.1, node-fetch@^2.6.7: 6186 6090 version "2.6.7" ··· 6945 6849 dependencies: 6946 6850 xtend "^4.0.0" 6947 6851 6948 - prebuild-install@^7.1.0: 6949 - version "7.1.1" 6950 - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" 6951 - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== 6952 - dependencies: 6953 - detect-libc "^2.0.0" 6954 - expand-template "^2.0.3" 6955 - github-from-package "0.0.0" 6956 - minimist "^1.2.3" 6957 - mkdirp-classic "^0.5.3" 6958 - napi-build-utils "^1.0.1" 6959 - node-abi "^3.3.0" 6960 - pump "^3.0.0" 6961 - rc "^1.2.7" 6962 - simple-get "^4.0.0" 6963 - tar-fs "^2.0.0" 6964 - tunnel-agent "^0.6.0" 6965 - 6966 6852 prelude-ls@^1.2.1: 6967 6853 version "1.2.1" 6968 6854 resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" ··· 7136 7022 iconv-lite "0.4.24" 7137 7023 unpipe "1.0.0" 7138 7024 7139 - rc@^1.2.7: 7140 - version "1.2.8" 7141 - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 7142 - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== 7143 - dependencies: 7144 - deep-extend "^0.6.0" 7145 - ini "~1.3.0" 7146 - minimist "^1.2.0" 7147 - strip-json-comments "~2.0.1" 7148 - 7149 7025 react-is@^18.0.0: 7150 7026 version "18.2.0" 7151 7027 resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" ··· 7246 7122 dependencies: 7247 7123 mute-stream "~0.0.4" 7248 7124 7249 - readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: 7125 + readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.6.0: 7250 7126 version "3.6.0" 7251 7127 resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" 7252 7128 integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== ··· 7643 7519 resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" 7644 7520 integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== 7645 7521 7646 - simple-concat@^1.0.0: 7647 - version "1.0.1" 7648 - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" 7649 - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== 7650 - 7651 - simple-get@^4.0.0: 7652 - version "4.0.1" 7653 - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" 7654 - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== 7655 - dependencies: 7656 - decompress-response "^6.0.0" 7657 - once "^1.3.1" 7658 - simple-concat "^1.0.0" 7659 - 7660 7522 sisteransi@^1.0.5: 7661 7523 version "1.0.5" 7662 7524 resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" ··· 7962 7824 resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" 7963 7825 integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 7964 7826 7965 - strip-json-comments@~2.0.1: 7966 - version "2.0.1" 7967 - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 7968 - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== 7969 - 7970 7827 strong-log-transformer@^2.1.0: 7971 7828 version "2.1.0" 7972 7829 resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz" ··· 8009 7866 version "1.0.0" 8010 7867 resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" 8011 7868 integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 8012 - 8013 - tar-fs@^2.0.0: 8014 - version "2.1.1" 8015 - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" 8016 - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== 8017 - dependencies: 8018 - chownr "^1.1.1" 8019 - mkdirp-classic "^0.5.2" 8020 - pump "^3.0.0" 8021 - tar-stream "^2.1.4" 8022 - 8023 - tar-stream@^2.1.4: 8024 - version "2.2.0" 8025 - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" 8026 - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== 8027 - dependencies: 8028 - bl "^4.0.3" 8029 - end-of-stream "^1.4.1" 8030 - fs-constants "^1.0.0" 8031 - inherits "^2.0.3" 8032 - readable-stream "^3.1.1" 8033 7869 8034 7870 tar@^4.4.12: 8035 7871 version "4.4.19"