Skip to content

Commit

Permalink
Create list and set writers
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Le Cam committed Apr 15, 2024
1 parent 4f7821b commit ad6bbb6
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 0 deletions.
6 changes: 6 additions & 0 deletions composites/points/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ import { definition } from './definition.js'
export { definition }
export const SimplePointsAggregationID = definition.models.SimplePointsAggregation.id
export const SimplePointsAllocationID = definition.models.SimplePointsAllocation.id

export type PointsContent = {
issuer: string // DID
recipient: string // DID
points: number
}
60 changes: 60 additions & 0 deletions libraries/points/src/base-reader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { BaseQuery } from '@ceramicnetwork/common'
import { DocumentLoader } from '@composedb/loader'
import type { CeramicAPI } from '@composedb/types'
import type { PointsContent } from '@ceramic-solutions/points-composite'

import { getCeramic } from './ceramic.js'
import { getQueryForRecipient, queryConnection } from './query.js'
import type { QueryDocumentsOptions, QueryDocumentsResult } from './types.js'

export type PointsBaseReaderParams = {
issuer: string
modelID: string
ceramic?: CeramicAPI | string
loader?: DocumentLoader
}

export class PointsBaseReader<Content extends PointsContent = PointsContent> {
#baseQuery: BaseQuery
#issuer: string
#ceramic: CeramicAPI
#loader: DocumentLoader
#modelID: string

constructor(params: PointsBaseReaderParams) {
const ceramic = getCeramic(params.ceramic)
this.#baseQuery = { account: params.issuer, models: [params.modelID] }
this.#modelID = params.modelID
this.#ceramic = ceramic
this.#issuer = params.issuer
this.#loader = params.loader ?? new DocumentLoader({ ceramic })
}

get issuer(): string {
return this.#issuer
}

get modelID(): string {
return this.#modelID
}

get ceramic(): CeramicAPI {
return this.#ceramic
}

get loader(): DocumentLoader {
return this.#loader
}

async queryDocuments(options?: QueryDocumentsOptions): Promise<QueryDocumentsResult<Content>> {
return await queryConnection(this.#loader, this.#baseQuery, options)
}

async queryDocumentsFor(
did: string,
options?: QueryDocumentsOptions,
): Promise<QueryDocumentsResult<Content>> {
const query = getQueryForRecipient(this.#baseQuery, did)
return await queryConnection(this.#loader, query, options)
}
}
12 changes: 12 additions & 0 deletions libraries/points/src/ceramic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { CeramicClient } from '@ceramicnetwork/http-client'
import type { CeramicAPI } from '@composedb/types'

import { getAuthenticatedDID } from './did.js'

export function getCeramic(ceramic?: CeramicAPI | string): CeramicAPI {
return ceramic == null || typeof ceramic === 'string' ? new CeramicClient(ceramic) : ceramic
}

export async function getAuthenticatedCeramic(
seed: Uint8Array,
ceramicClientOrURL?: CeramicAPI | string,
): Promise<CeramicAPI> {
const ceramic = getCeramic(ceramicClientOrURL)
const did = await getAuthenticatedDID(seed)
ceramic.did = did
return ceramic
}
43 changes: 43 additions & 0 deletions libraries/points/src/list-writer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { CeramicAPI, ModelInstanceDocument } from '@composedb/types'
import type { PointsContent } from '@ceramic-solutions/points-composite'

import { getAuthenticatedCeramic } from './ceramic.js'
import { PointsBaseReader, type PointsBaseReaderParams } from './base-reader.js'

export type PointsListWriterFromSeedParams = PointsBaseReaderParams & {
seed: Uint8Array
}

export type PointsListWriterParams = Omit<PointsBaseReaderParams, 'ceramic'> & {
ceramic: CeramicAPI
}

export class PointsListWriter<
Content extends PointsContent = PointsContent,
> extends PointsBaseReader<Content> {
static async fromSeed<Content extends PointsContent = PointsContent>(
params: PointsListWriterFromSeedParams,
): Promise<PointsListWriter<Content>> {
const ceramic = await getAuthenticatedCeramic(params.seed, params.ceramic)
return new PointsListWriter({ ...params, ceramic })
}

constructor(params: PointsListWriterParams) {
if (!params.ceramic.did?.authenticated) {
throw new Error(`An authenticated DID instance must be set on the Ceramic client`)
}
super({ ...params, issuer: params.ceramic.did.id })
}

async createDocument(content: Content): Promise<ModelInstanceDocument<Content>> {
return await this.loader.create(this.modelID, content)
}

async removeDocument(id: string): Promise<void> {
const doc = await this.loader.load({ id })
if (doc.metadata.model.toString() !== this.modelID) {
throw new Error(`Document ${id} is not using the expected model ${this.modelID}`)
}
await doc.shouldIndex(false)
}
}
33 changes: 33 additions & 0 deletions libraries/points/src/set-reader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { DeterministicLoadOptions } from '@composedb/loader'
import type { ModelInstanceDocument } from '@composedb/types'
import type { PointsContent } from '@ceramic-solutions/points-composite'

import { PointsBaseReader, type PointsBaseReaderParams } from './base-reader.js'

export function toUniqueArg(value: string | Array<string>): Array<string> {
return Array.isArray(value) ? value : [value]
}

export type PointsSetReaderParams = PointsBaseReaderParams

export class PointsSetReader<
Content extends PointsContent = PointsContent,
> extends PointsBaseReader<Content> {
async loadDocumentFor(
didOrValues: string | Array<string>,
options: DeterministicLoadOptions = {},
): Promise<ModelInstanceDocument<Content> | null> {
return await this.loader.loadSet(this.issuer, this.modelID, toUniqueArg(didOrValues), {
ignoreEmpty: true,
...options,
})
}

async loadPointsFor(
didOrValues: string | Array<string>,
options?: DeterministicLoadOptions,
): Promise<number> {
const doc = await this.loadDocumentFor(didOrValues, options)
return doc?.content?.points ?? 0
}
}
67 changes: 67 additions & 0 deletions libraries/points/src/set-writer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { CeramicAPI, ModelInstanceDocument } from '@composedb/types'
import type { PointsContent } from '@ceramic-solutions/points-composite'

import { getAuthenticatedCeramic } from './ceramic.js'
import { PointsSetReader, type PointsSetReaderParams, toUniqueArg } from './set-reader.js'

export type PointsSetWriterFromSeedParams = PointsSetReaderParams & {
seed: Uint8Array
}

export type PointsSetWriterParams = Omit<PointsSetReaderParams, 'ceramic'> & {
ceramic: CeramicAPI
}

export class PointsSetWriter<
Content extends PointsContent = PointsContent,
> extends PointsSetReader<Content> {
static async fromSeed<Content extends PointsContent = PointsContent>(
params: PointsSetWriterFromSeedParams,
): Promise<PointsSetWriter<Content>> {
const ceramic = await getAuthenticatedCeramic(params.seed, params.ceramic)
return new PointsSetWriter({ ...params, ceramic })
}

constructor(params: PointsSetWriterParams) {
if (!params.ceramic.did?.authenticated) {
throw new Error(`An authenticated DID instance must be set on the Ceramic client`)
}
super({ ...params, issuer: params.ceramic.did.id })
}

async _loadDocumentFor(
didOrValues: string | Array<string>,
): Promise<ModelInstanceDocument<Content>> {
const doc = await this.loadDocumentFor(didOrValues, { ignoreEmpty: false, onlyIndexed: false })
return doc!
}

async setDocumentFor(
didOrValues: string | Array<string>,
updateContent: (content: Content | null) => Partial<Content>,
): Promise<ModelInstanceDocument<Content>> {
const unique = toUniqueArg(didOrValues)
const doc = await this._loadDocumentFor(didOrValues)
const content = doc!.content
await doc!.replace({
// Copy existing content or set recipient (assuming it's the first value)
...(content ?? { recipient: unique[0] }),
// Apply content update
...updateContent(content),
} as Content)
return doc!
}

async removeDocument(id: string): Promise<void> {
const doc = await this.loader.load({ id })
if (doc.metadata.model.toString() !== this.modelID) {
throw new Error(`Document ${id} is not using the expected model ${this.modelID}`)
}
await doc.shouldIndex(false)
}

async removeDocumentFor(didOrValues: string | Array<string>): Promise<void> {
const doc = await this._loadDocumentFor(didOrValues)
await doc.shouldIndex(false)
}
}

0 comments on commit ad6bbb6

Please sign in to comment.