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

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

Rework Database

-1
.gitignore
···
.idea
packages/*/coverage
.vscode/
-
test.sqlite
.DS_Store
*.log
tsconfig.build.tsbuildinfo
-1
packages/lib/package.json
···
"zod": "^3.14.2"
},
"devDependencies": {
-
"@types/pg": "^8.6.5",
"eslint-plugin-prettier": "^4.2.1"
}
}
+1 -1
packages/server/bin/migration-create.ts
···
)
}
const filename = `${prefix}-${name}`
-
const dir = path.join(__dirname, '..', 'src', 'server', 'migrations')
+
const dir = path.join(__dirname, '..', 'src', 'migrations')
await fs.writeFile(path.join(dir, `${filename}.ts`), template, { flag: 'wx' })
await fs.writeFile(
+1 -2
packages/server/build.js
···
entryPoints: [
'src/index.ts',
'src/bin.ts',
-
'src/db.ts',
+
'src/db/index.ts',
],
bundle: true,
sourcemap: true,
···
assetNames: 'src/static',
plugins: buildShallow ? [nodeExternalsPlugin()] : [],
external: [
-
'better-sqlite3',
// Referenced in pg driver, but optional and we don't use it
'pg-native',
],
+1 -1
packages/server/example.dev.env
···
-
DB_POSTGRES_URL="postgres://bsky:yksb@localhost/plc_dev"
+
DATABASE_URL="postgres://bsky:yksb@localhost/plc_dev"
DEBUG_MODE=1
LOG_ENABLED="true"
LOG_LEVEL=debug
+1 -3
packages/server/package.json
···
"license": "MIT",
"scripts": {
"start": "node dist/bin.js",
-
"test": "jest",
-
"test:pg": "./pg/with-test-db.sh jest",
+
"test": "./pg/with-test-db.sh jest",
"test:log": "cat test.log | pino-pretty",
"prettier": "prettier --check src/",
"prettier:fix": "prettier --write src/",
···
"@atproto/crypto": "0.1.0",
"@did-plc/lib": "*",
"axios": "^0.27.2",
-
"better-sqlite3": "^8.1.0",
"cors": "^2.8.5",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
+1 -1
packages/server/pg/README.md
···
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.
-
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.
+
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.
**Example**
+1 -1
packages/server/pg/with-test-db.sh
···
export PGUSER=pg
export PGPASSWORD=password
export PGDATABASE=postgres
-
export DB_POSTGRES_URL="postgresql://pg:password@localhost:5433/postgres"
+
export DATABASE_URL="postgresql://pg:password@localhost:5433/postgres"
"$@"
code=$?
+8 -11
packages/server/src/bin.ts
···
import './env'
-
import { Database } from './db'
+
import { Database, PlcDatabase } from './db'
import PlcServer from '.'
const run = async () => {
-
const dbLoc = process.env.DATABASE_LOC
-
const dbPostgresUrl = process.env.DB_POSTGRES_URL
+
const dbUrl = process.env.DATABASE_URL
-
let db: Database
-
if (dbPostgresUrl) {
-
db = Database.postgres({ url: dbPostgresUrl })
-
} else if (dbLoc) {
-
db = Database.sqlite(dbLoc)
+
let db: PlcDatabase
+
if (dbUrl) {
+
const pgDb = Database.postgres({ url: dbUrl })
+
await pgDb.migrateToLatestOrThrow()
+
db = pgDb
} else {
-
db = Database.memory()
+
db = Database.mock()
}
-
-
await db.migrateToLatestOrThrow()
const envPort = parseInt(process.env.PORT || '')
const port = isNaN(envPort) ? 2582 : envPort
+2 -2
packages/server/src/context.ts
···
-
import { Database } from './db'
+
import { PlcDatabase } from './db'
export class AppContext {
constructor(
private opts: {
-
db: Database
+
db: PlcDatabase
version: string
port?: number
},
+59 -60
packages/server/src/db.ts packages/server/src/db/index.ts
···
-
import { Kysely, Migrator, PostgresDialect, SqliteDialect } from 'kysely'
-
import SqliteDB from 'better-sqlite3'
+
import { Generated, Kysely, Migrator, PostgresDialect, sql } from 'kysely'
import { Pool as PgPool, types as pgTypes } from 'pg'
import { CID } from 'multiformats/cid'
import { cidForCbor, check } from '@atproto/common'
import * as plc from '@did-plc/lib'
-
import { ServerError } from './error'
-
import * as migrations from './migrations'
+
import { ServerError } from '../error'
+
import * as migrations from '../migrations'
+
import { OpLogExport, PlcDatabase } from './types'
+
import MockDatabase from './mock'
+
+
export * from './mock'
+
export * from './types'
-
export class Database {
+
export class Database implements PlcDatabase {
migrator: Migrator
-
constructor(
-
public db: Kysely<DatabaseSchema>,
-
public dialect: Dialect,
-
public schema?: string,
-
) {
+
constructor(public db: Kysely<DatabaseSchema>, public schema?: string) {
this.migrator = new Migrator({
db,
migrationTableSchema: schema,
···
},
},
})
-
}
-
-
static sqlite(location: string): Database {
-
const db = new Kysely<DatabaseSchema>({
-
dialect: new SqliteDialect({
-
database: new SqliteDB(location),
-
}),
-
})
-
return new Database(db, 'sqlite')
}
static postgres(opts: { url: string; schema?: string }): Database {
···
dialect: new PostgresDialect({ pool }),
})
-
return new Database(db, 'pg', schema)
+
return new Database(db, schema)
}
-
static memory(): Database {
-
return Database.sqlite(':memory:')
+
static mock(): MockDatabase {
+
return new MockDatabase()
}
async close(): Promise<void> {
await this.db.destroy()
}
+
async healthCheck(): Promise<void> {
+
await sql`select 1`.execute(this.db)
+
}
+
+
async migrateToOrThrow(migration: string) {
+
if (this.schema !== undefined) {
+
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
+
}
+
const { error, results } = await this.migrator.migrateTo(migration)
+
if (error) {
+
throw error
+
}
+
if (!results) {
+
throw new Error('An unknown failure occurred while migrating')
+
}
+
return results
+
}
+
async migrateToLatestOrThrow() {
if (this.schema !== undefined) {
await this.db.schema.createSchema(this.schema).ifNotExists().execute()
···
.insertInto('operations')
.values({
did,
-
operation: JSON.stringify(proposed),
+
operation: proposed,
cid: cid.toString(),
-
nullified: 0,
-
createdAt: new Date().toISOString(),
+
nullified: false,
})
.execute()
···
const nullfiedStrs = nullified.map((cid) => cid.toString())
await tx
.updateTable('operations')
-
.set({ nullified: 1 })
+
.set({ nullified: true })
.where('did', '=', did)
.where('cid', 'in', nullfiedStrs)
.execute()
···
.selectFrom('operations')
.select('cid')
.where('did', '=', did)
-
.where('nullified', '=', 0)
+
.where('nullified', '=', false)
.orderBy('createdAt', 'desc')
.limit(2)
.execute()
···
.selectFrom('operations')
.select('cid')
.where('did', '=', did)
-
.where('nullified', '=', 0)
+
.where('nullified', '=', false)
.where('cid', 'not in', notIncludedStr)
.orderBy('createdAt', 'desc')
.executeTakeFirst()
···
.selectFrom('operations')
.selectAll()
.where('did', '=', did)
-
.where('nullified', '=', 0)
+
.where('nullified', '=', false)
.orderBy('createdAt', 'asc')
.execute()
return res.map((row) => ({
did: row.did,
-
operation: JSON.parse(row.operation),
+
operation: row.operation,
cid: CID.parse(row.cid),
-
nullified: row.nullified === 1,
-
createdAt: new Date(row.createdAt),
+
nullified: row.nullified,
+
createdAt: row.createdAt,
}))
}
async fullExport(): Promise<Record<string, OpLogExport>> {
-
const res = await this.db
-
.selectFrom('operations')
-
.selectAll()
-
.orderBy('did')
-
.orderBy('createdAt')
-
.execute()
-
return res.reduce((acc, cur) => {
-
acc[cur.did] ??= []
-
acc[cur.did].push({
-
op: JSON.parse(cur.operation),
-
nullified: cur.nullified === 1,
-
createdAt: cur.createdAt,
-
})
-
return acc
-
}, {} as Record<string, OpLogExport>)
+
return {}
+
// const res = await this.db
+
// .selectFrom('operations')
+
// .selectAll()
+
// .orderBy('did')
+
// .orderBy('createdAt')
+
// .execute()
+
// return res.reduce((acc, cur) => {
+
// acc[cur.did] ??= []
+
// acc[cur.did].push({
+
// op: cur.operation),
+
// nullified: cur.nullified === 1,
+
// createdAt: cur.createdAt,
+
// })
+
// return acc
+
// }, {} as Record<string, OpLogExport>)
}
}
export default Database
-
-
export type Dialect = 'pg' | 'sqlite'
interface OperationsTable {
did: string
-
operation: string
+
operation: plc.CompatibleOpOrTombstone
cid: string
-
nullified: 0 | 1
-
createdAt: string
+
nullified: boolean
+
createdAt: Generated<Date>
}
interface DatabaseSchema {
operations: OperationsTable
}
-
-
type OpLogExport = OpExport[]
-
-
type OpExport = {
-
op: Record<string, unknown>
-
nullified: boolean
-
createdAt: string
-
}
+65
packages/server/src/db/mock.ts
···
+
import { cidForCbor, check } from '@atproto/common'
+
import * as plc from '@did-plc/lib'
+
import { ServerError } from '../error'
+
import { OpLogExport, PlcDatabase } from './types'
+
+
type Contents = Record<string, plc.IndexedOperation[]>
+
+
export class MockDatabase implements PlcDatabase {
+
contents: Contents = {}
+
+
async close(): Promise<void> {}
+
async healthCheck(): Promise<void> {}
+
+
async validateAndAddOp(did: string, proposed: plc.Operation): Promise<void> {
+
this.contents[did] ??= []
+
const opsBefore = this.contents[did]
+
// throws if invalid
+
const { nullified } = await plc.assureValidNextOp(did, opsBefore, proposed)
+
const cid = await cidForCbor(proposed)
+
if (this.contents[did] !== opsBefore) {
+
throw new ServerError(
+
409,
+
`Proposed prev does not match the most recent operation`,
+
)
+
}
+
this.contents[did].push({
+
did,
+
operation: proposed,
+
cid,
+
nullified: false,
+
createdAt: new Date(),
+
})
+
+
if (nullified.length > 0) {
+
for (let i = 0; i < this.contents[did].length; i++) {
+
const cid = this.contents[did][i].cid
+
for (const toCheck of nullified) {
+
if (toCheck.equals(cid)) {
+
this.contents[did][i].nullified = true
+
}
+
}
+
}
+
}
+
}
+
+
async opsForDid(did: string): Promise<plc.OpOrTombstone[]> {
+
const ops = await this._opsForDid(did)
+
return ops.map((op) => {
+
if (check.is(op.operation, plc.def.createOpV1)) {
+
return plc.normalizeOp(op.operation)
+
}
+
return op.operation
+
})
+
}
+
+
async _opsForDid(did: string): Promise<plc.IndexedOperation[]> {
+
return this.contents[did] ?? []
+
}
+
+
async fullExport(): Promise<Record<string, OpLogExport>> {
+
return {}
+
}
+
}
+
+
export default MockDatabase
+18
packages/server/src/db/types.ts
···
+
import * as plc from '@did-plc/lib'
+
+
export interface PlcDatabase {
+
close(): Promise<void>
+
healthCheck(): Promise<void>
+
validateAndAddOp(did: string, proposed: plc.Operation): Promise<void>
+
opsForDid(did: string): Promise<plc.OpOrTombstone[]>
+
_opsForDid(did: string): Promise<plc.IndexedOperation[]>
+
fullExport(): Promise<Record<string, OpLogExport>>
+
}
+
+
export type OpLogExport = OpExport[]
+
+
export type OpExport = {
+
op: Record<string, unknown>
+
nullified: boolean
+
createdAt: string
+
}
+2 -2
packages/server/src/index.ts
···
import cors from 'cors'
import http from 'http'
import events from 'events'
-
import { Database } from './db'
import * as error from './error'
import createRouter from './routes'
import { loggerMiddleware } from './logger'
import AppContext from './context'
import { createHttpTerminator, HttpTerminator } from 'http-terminator'
+
import { PlcDatabase } from './db/types'
export * from './db'
export * from './context'
···
}
static create(opts: {
-
db: Database
+
db: PlcDatabase
port?: number
version?: string
}): PlcServer {
+66
packages/server/src/migrations/20230223T215019669Z-refactor.ts
···
+
import { Kysely, sql } from 'kysely'
+
+
export async function up(db: Kysely<any>): Promise<void> {
+
await db.schema
+
.createTable('operations_new')
+
.addColumn('did', 'text', (col) => col.notNull())
+
.addColumn('operation', 'jsonb', (col) => col.notNull())
+
.addColumn('cid', 'text', (col) => col.notNull())
+
.addColumn('nullified', 'boolean', (col) => col.notNull())
+
.addColumn('createdAt', 'timestamptz', (col) =>
+
col.notNull().defaultTo(sql`current_timestamp`),
+
)
+
.addPrimaryKeyConstraint('operations_primary_key', ['did', 'cid'])
+
.execute()
+
+
const dump = await db.selectFrom('operations').selectAll().execute()
+
const vals = dump.map((row) => ({
+
did: row.did,
+
operation: row.operation,
+
cid: row.cid,
+
nullified: row.nullified === 1 ? true : false,
+
createdAt: row.createdAt,
+
}))
+
+
if (vals.length > 0) {
+
await db.insertInto('operations_new').values(vals).execute()
+
}
+
+
await db.schema.dropTable('operations').execute()
+
+
await db.schema.alterTable('operations_new').renameTo('operations').execute()
+
+
await db.schema
+
.createIndex('operations_createdAt_index')
+
.on('operations')
+
.column('createdAt')
+
.execute()
+
}
+
+
export async function down(db: Kysely<any>): Promise<void> {
+
await db.schema
+
.createTable('operations_new')
+
.addColumn('did', 'varchar', (col) => col.notNull())
+
.addColumn('operation', 'text', (col) => col.notNull())
+
.addColumn('cid', 'varchar', (col) => col.notNull())
+
.addColumn('nullified', 'int2', (col) => col.defaultTo(0))
+
.addColumn('createdAt', 'varchar', (col) => col.notNull())
+
.addPrimaryKeyConstraint('primary_key', ['did', 'cid'])
+
.execute()
+
+
const dump = await db.selectFrom('operations').selectAll().execute()
+
const vals = dump.map((row) => ({
+
did: row.did,
+
operation: JSON.stringify(row.operation),
+
cid: row.cid,
+
nullified: row.nullified ? 1 : 0,
+
createdAt: row.createdAt.toISOString(),
+
}))
+
+
await db.insertInto('operations_new').values(vals).execute()
+
+
await db.schema.dropIndex('operations_createdAt_index').execute()
+
await db.schema.dropTable('operations').execute()
+
+
await db.schema.alterTable('operations_new').renameTo('operations').execute()
+
}
+1
packages/server/src/migrations/index.ts
···
// this with kysely's FileMigrationProvider, but it doesn't play nicely with the build process.
export * as _20221020T204908820Z from './20221020T204908820Z-operations-init'
+
export * as _20230223T215019669Z from './20230223T215019669Z-refactor'
+1 -2
packages/server/src/routes.ts
···
import express from 'express'
-
import { sql } from 'kysely'
import { check } from '@atproto/common'
import * as plc from '@did-plc/lib'
import { ServerError } from './error'
···
router.get('/_health', async function (req, res) {
const { db, version } = ctx
try {
-
await sql`select 1`.execute(db.db)
+
await db.healthCheck()
} catch (err) {
req.log.error(err, 'failed health check')
return res.status(503).send({ version, error: 'Service Unavailable' })
+12 -11
packages/server/tests/_util.ts
···
export type TestServerInfo = {
ctx: AppContext
url: string
+
db: Database
close: CloseFn
}
export const runTestServer = async (opts: {
-
dbPostgresSchema: string
+
dbSchema: string
}): Promise<TestServerInfo> => {
-
const { dbPostgresSchema } = opts
-
const dbPostgresUrl = process.env.DB_POSTGRES_URL || undefined
+
const { dbSchema } = opts
+
const dbUrl = process.env.DATABASE_URL
+
if (!dbUrl) {
+
throw new Error('No postgres url provided')
+
}
-
const db =
-
dbPostgresUrl !== undefined
-
? Database.postgres({
-
url: dbPostgresUrl,
-
schema: dbPostgresSchema,
-
})
-
: Database.memory()
-
+
const db = Database.postgres({
+
url: dbUrl,
+
schema: dbSchema,
+
})
await db.migrateToLatestOrThrow()
const plc = PlcServer.create({ db })
···
return {
ctx: plc.ctx,
url: `http://localhost:${port}`,
+
db,
close: async () => {
await plc.destroy()
},
+111
packages/server/tests/migrations/refactor.test.ts
···
+
import { cidForCbor, DAY } from '@atproto/common'
+
import { Secp256k1Keypair } from '@atproto/crypto'
+
import * as plc from '@did-plc/lib'
+
import { Kysely } from 'kysely'
+
import { Database } from '../../src'
+
+
describe('refactor migration', () => {
+
let db: Database
+
let rawDb: Kysely<any>
+
+
beforeAll(async () => {
+
const dbUrl = process.env.DATABASE_URL
+
if (!dbUrl) {
+
throw new Error('No postgres url provided')
+
}
+
db = Database.postgres({
+
url: dbUrl,
+
schema: 'migration_refactor',
+
})
+
+
await db.migrateToOrThrow('_20221020T204908820Z')
+
rawDb = db.db
+
})
+
+
afterAll(async () => {
+
await db.close()
+
})
+
+
const ops: any[] = []
+
let before: any[]
+
+
it('fills the database with some operations', async () => {
+
for (let i = 0; i < 100; i++) {
+
const signingKey = await Secp256k1Keypair.create()
+
const recoveryKey = await Secp256k1Keypair.create()
+
const op = await plc.deprecatedSignCreate(
+
{
+
type: 'create',
+
signingKey: signingKey.did(),
+
recoveryKey: recoveryKey.did(),
+
handle: `user${i}.test`,
+
service: 'https://example.com',
+
prev: null,
+
},
+
signingKey,
+
)
+
const did = await plc.didForCreateOp(op)
+
const cid = await cidForCbor(op)
+
const randomOffset = Math.floor(Math.random() * DAY * 60)
+
const time = new Date(Date.now() - randomOffset).toISOString()
+
ops.push({
+
did,
+
operation: JSON.stringify(op),
+
cid: cid.toString(),
+
nullified: 0,
+
createdAt: time,
+
})
+
}
+
await rawDb.insertInto('operations').values(ops).execute()
+
+
before = await rawDb
+
.selectFrom('operations')
+
.selectAll()
+
.orderBy('did', 'asc')
+
.execute()
+
})
+
+
it('migrates', async () => {
+
await db.migrateToOrThrow('_20230223T215019669Z')
+
})
+
+
it('correctly migrated all data', async () => {
+
const migrated = await rawDb
+
.selectFrom('operations')
+
.selectAll()
+
.orderBy('did', 'asc')
+
.execute()
+
const ordered = ops.sort((a, b) => a.did.localeCompare(b.did))
+
expect(migrated.length).toBe(ordered.length)
+
for (let i = 0; i < migrated.length; i++) {
+
expect(migrated[i].did).toBe(ordered[i].did)
+
expect(migrated[i].operation).toEqual(JSON.parse(ordered[i].operation))
+
expect(migrated[i].cid).toBe(ordered[i].cid)
+
expect(migrated[i].nullified).toBe(
+
ordered[i].nullified === 1 ? true : false,
+
)
+
expect(migrated[i].createdAt).toEqual(new Date(ordered[i].createdAt))
+
}
+
})
+
+
it('migrates down', async () => {
+
await db.migrateToOrThrow('_20221020T204908820Z')
+
const migratedBack = await rawDb
+
.selectFrom('operations')
+
.selectAll()
+
.orderBy('did', 'asc')
+
.execute()
+
expect(migratedBack.length).toBe(before.length)
+
// normalize json
+
const beforeNormalized = before.map((row) => ({
+
...row,
+
operation: JSON.parse(row.operation),
+
}))
+
const migratedNormalized = migratedBack.map((row) => ({
+
...row,
+
operation: JSON.parse(row.operation),
+
}))
+
+
expect(migratedNormalized).toEqual(beforeNormalized)
+
})
+
})
+5 -5
packages/server/tests/server.test.ts
···
beforeAll(async () => {
const server = await runTestServer({
-
dbPostgresSchema: 'server',
+
dbSchema: 'server',
})
-
db = server.ctx.db
+
db = server.db
close = server.close
client = new plc.Client(server.url)
signingKey = await EcdsaKeypair.create()
···
})
it('handles concurrent requests to many docs', async () => {
-
const COUNT = 100
+
const COUNT = 50
const keys: EcdsaKeypair[] = []
for (let i = 0; i < COUNT; i++) {
keys.push(await EcdsaKeypair.create())
···
})
it('resolves races into a coherent history with no forks', async () => {
-
const COUNT = 100
+
const COUNT = 50
const keys: EcdsaKeypair[] = []
for (let i = 0; i < COUNT; i++) {
keys.push(await EcdsaKeypair.create())
···
}),
)
expect(successes).toBe(1)
-
expect(failures).toBe(99)
+
expect(failures).toBe(49)
const ops = await client.getOperationLog(did)
await plc.validateOperationLog(did, ops)
+6 -170
yarn.lock
···
resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz"
integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
-
better-sqlite3@^8.1.0:
-
version "8.1.0"
-
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-8.1.0.tgz#a0039c5dfdc04b733cac3c8dbe1b71f3e5fc62d3"
-
integrity sha512-p1m09H+Oi8R9TPj810pdNswMFuVgRNgCJEWypp6jlkOgSwMIrNyuj3hW78xEuBRGok5RzeaUW8aBtTWF3l/TQA==
-
dependencies:
-
bindings "^1.5.0"
-
prebuild-install "^7.1.0"
-
big-integer@^1.6.51:
version "1.6.51"
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz"
integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
-
-
bindings@^1.5.0:
-
version "1.5.0"
-
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
-
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
-
dependencies:
-
file-uri-to-path "1.0.0"
-
-
bl@^4.0.3:
-
version "4.1.0"
-
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
-
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
-
dependencies:
-
buffer "^5.5.0"
-
inherits "^2.0.4"
-
readable-stream "^3.4.0"
body-parser@1.20.1:
version "1.20.1"
···
version "2.0.0"
resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04"
integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==
-
-
buffer@^5.5.0:
-
version "5.7.1"
-
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
-
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
-
dependencies:
-
base64-js "^1.3.1"
-
ieee754 "^1.1.13"
buffer@^6.0.3:
version "6.0.3"
···
resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
-
chownr@^1.1.1, chownr@^1.1.4:
+
chownr@^1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
···
resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz"
integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
-
decompress-response@^6.0.0:
-
version "6.0.0"
-
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
-
integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
-
dependencies:
-
mimic-response "^3.1.0"
-
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz"
integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==
-
deep-extend@^0.6.0:
-
version "0.6.0"
-
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
-
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
-
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
···
version "6.1.0"
resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz"
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
-
-
detect-libc@^2.0.0:
-
version "2.0.1"
-
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
-
integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
detect-newline@^3.0.0:
version "3.1.0"
···
dependencies:
iconv-lite "^0.6.2"
-
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+
end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
···
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
-
expand-template@^2.0.3:
-
version "2.0.3"
-
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
-
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
-
expect@^28.0.0, expect@^28.1.3:
version "28.1.3"
resolved "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz"
···
dependencies:
flat-cache "^3.0.4"
-
file-uri-to-path@1.0.0:
-
version "1.0.0"
-
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
-
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
-
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
···
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
-
fs-constants@^1.0.0:
-
version "1.0.0"
-
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
-
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
-
fs-extra@^10.0.1:
version "10.1.0"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz"
···
integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==
dependencies:
ini "^1.3.2"
-
-
github-from-package@0.0.0:
-
version "0.0.0"
-
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
-
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
glob-parent@^5.1.1, glob-parent@^5.1.2:
version "5.1.2"
···
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
-
ieee754@^1.1.13, ieee754@^1.2.1:
+
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
···
once "^1.3.0"
wrappy "1"
-
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
+
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
+
ini@^1.3.2, ini@^1.3.4:
version "1.3.8"
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
···
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
-
mimic-response@^3.1.0:
-
version "3.1.0"
-
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
-
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
-
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz"
···
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
-
minimist@^1.2.3:
-
version "1.2.8"
-
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
-
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-
minipass-collect@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz"
···
minipass "^3.0.0"
yallist "^4.0.0"
-
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
-
version "0.5.3"
-
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
-
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
-
mkdirp-infer-owner@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz"
···
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
-
napi-build-utils@^1.0.1:
-
version "1.0.2"
-
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
-
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
-
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
···
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
-
-
node-abi@^3.3.0:
-
version "3.33.0"
-
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.33.0.tgz#8b23a0cec84e1c5f5411836de6a9b84bccf26e7f"
-
integrity sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==
-
dependencies:
-
semver "^7.3.5"
node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.6.7"
···
dependencies:
xtend "^4.0.0"
-
prebuild-install@^7.1.0:
-
version "7.1.1"
-
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
-
integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==
-
dependencies:
-
detect-libc "^2.0.0"
-
expand-template "^2.0.3"
-
github-from-package "0.0.0"
-
minimist "^1.2.3"
-
mkdirp-classic "^0.5.3"
-
napi-build-utils "^1.0.1"
-
node-abi "^3.3.0"
-
pump "^3.0.0"
-
rc "^1.2.7"
-
simple-get "^4.0.0"
-
tar-fs "^2.0.0"
-
tunnel-agent "^0.6.0"
-
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
···
iconv-lite "0.4.24"
unpipe "1.0.0"
-
rc@^1.2.7:
-
version "1.2.8"
-
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
-
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
-
dependencies:
-
deep-extend "^0.6.0"
-
ini "~1.3.0"
-
minimist "^1.2.0"
-
strip-json-comments "~2.0.1"
-
react-is@^18.0.0:
version "18.2.0"
resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz"
···
dependencies:
mute-stream "~0.0.4"
-
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:
+
readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
···
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
-
simple-concat@^1.0.0:
-
version "1.0.1"
-
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
-
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
-
-
simple-get@^4.0.0:
-
version "4.0.1"
-
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
-
integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
-
dependencies:
-
decompress-response "^6.0.0"
-
once "^1.3.1"
-
simple-concat "^1.0.0"
-
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz"
···
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-
strip-json-comments@~2.0.1:
-
version "2.0.1"
-
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
-
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
-
strong-log-transformer@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz"
···
version "1.0.0"
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
-
-
tar-fs@^2.0.0:
-
version "2.1.1"
-
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
-
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
-
dependencies:
-
chownr "^1.1.1"
-
mkdirp-classic "^0.5.2"
-
pump "^3.0.0"
-
tar-stream "^2.1.4"
-
-
tar-stream@^2.1.4:
-
version "2.2.0"
-
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
-
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
-
dependencies:
-
bl "^4.0.3"
-
end-of-stream "^1.4.1"
-
fs-constants "^1.0.0"
-
inherits "^2.0.3"
-
readable-stream "^3.1.1"
tar@^4.4.12:
version "4.4.19"