@ceramicnetwork/stream-model-instance
Version:
Ceramic Model Instance Document stream type
217 lines • 8.89 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var ModelInstanceDocument_1;
import jsonpatch from 'fast-json-patch';
import * as dagCbor from '@ipld/dag-cbor';
import { randomBytes } from '@stablelib/random';
import sizeof from 'object-sizeof';
import { Stream, StreamStatic, SyncOptions, CeramicSigner, } from '@ceramicnetwork/common';
import { StreamRef } from '@ceramicnetwork/streamid';
import { fromString } from 'uint8arrays';
const DEFAULT_CREATE_OPTS = {
anchor: true,
publish: true,
sync: SyncOptions.NEVER_SYNC,
syncTimeoutSeconds: 0,
};
const DEFAULT_DETERMINISTIC_OPTS = {
anchor: false,
publish: false,
sync: SyncOptions.PREFER_CACHE,
};
const DEFAULT_LOAD_OPTS = { sync: SyncOptions.PREFER_CACHE };
const DEFAULT_UPDATE_OPTS = { anchor: true, publish: true };
async function throwReadOnlyError() {
throw new Error('Historical stream commits cannot be modified. Load the stream without specifying a commit to make updates.');
}
let ModelInstanceDocument = ModelInstanceDocument_1 = class ModelInstanceDocument extends Stream {
constructor() {
super(...arguments);
this._isReadOnly = false;
}
get content() {
return super.content;
}
get metadata() {
const metadata = this.state$.value.metadata;
return {
controller: metadata.controllers[0],
model: metadata.model,
unique: metadata.unique,
context: metadata.context,
shouldIndex: metadata.shouldIndex,
};
}
static async create(ceramic, content, metadata, opts = {}) {
opts = { ...DEFAULT_CREATE_OPTS, ...opts };
const signer = opts.asDID
? CeramicSigner.fromDID(opts.asDID)
: opts.signer || ceramic.signer;
const commit = await ModelInstanceDocument_1.makeGenesis(signer, content, metadata);
return ceramic.createStreamFromGenesis(ModelInstanceDocument_1.STREAM_TYPE_ID, commit, opts);
}
static async single(ceramic, metadata, opts = {}) {
opts = { ...DEFAULT_DETERMINISTIC_OPTS, ...opts };
const signer = opts.asDID
? CeramicSigner.fromDID(opts.asDID)
: opts.signer || ceramic.signer;
metadata = { ...metadata, deterministic: true };
const commit = await ModelInstanceDocument_1.makeGenesis(signer, null, metadata);
return ceramic.createStreamFromGenesis(ModelInstanceDocument_1.STREAM_TYPE_ID, commit, opts);
}
static async set(ceramic, metadata, unique, opts = {}) {
opts = { ...DEFAULT_DETERMINISTIC_OPTS, ...opts };
const signer = opts.asDID
? CeramicSigner.fromDID(opts.asDID)
: opts.signer || ceramic.signer;
metadata = { ...metadata, deterministic: true };
const commit = await ModelInstanceDocument_1.makeGenesis(signer, null, metadata, unique);
return ceramic.createStreamFromGenesis(ModelInstanceDocument_1.STREAM_TYPE_ID, commit, opts);
}
static async load(reader, streamId, opts = {}) {
opts = { ...DEFAULT_LOAD_OPTS, ...opts };
const streamRef = StreamRef.from(streamId);
if (streamRef.type != ModelInstanceDocument_1.STREAM_TYPE_ID) {
throw new Error(`StreamID ${streamRef.toString()} does not refer to a '${ModelInstanceDocument_1.STREAM_TYPE_NAME}' stream, but to a ${streamRef.typeName}`);
}
return reader.loadStream(streamRef, opts);
}
async replace(content, metadata = undefined, opts = {}) {
opts = { ...DEFAULT_UPDATE_OPTS, ...opts };
validateContentLength(content);
const signer = opts.asDID
? CeramicSigner.fromDID(opts.asDID)
: opts.signer || this.api.signer;
let header = undefined;
if (metadata && metadata.shouldIndex != null) {
header = {
shouldIndex: metadata.shouldIndex,
};
}
const updateCommit = await ModelInstanceDocument_1.makeUpdateCommit(signer, this.commitId, this.content, content, header);
const updated = await this.api.applyCommit(this.id, updateCommit, opts);
this.state$.next(updated.state);
}
async patch(jsonPatch, metadata = undefined, opts = {}) {
opts = { ...DEFAULT_UPDATE_OPTS, ...opts };
const signer = opts.asDID
? CeramicSigner.fromDID(opts.asDID)
: opts.signer || this.api.signer;
jsonPatch.forEach((patch) => {
switch (patch.op) {
case 'add': {
validateContentLength(patch.value);
break;
}
case 'replace': {
validateContentLength(patch.value);
break;
}
default: {
break;
}
}
});
const rawCommit = {
data: jsonPatch,
prev: this.tip,
id: this.id.cid,
};
if (metadata?.shouldIndex != null) {
rawCommit.header = {
shouldIndex: metadata.shouldIndex,
};
}
const commit = await signer.createDagJWS(rawCommit);
const updated = await this.api.applyCommit(this.id, commit, opts);
this.state$.next(updated.state);
}
shouldIndex(shouldIndex, opts = {}) {
return this.patch([], { shouldIndex: shouldIndex }, opts);
}
makeReadOnly() {
this.replace = throwReadOnlyError;
this.patch = throwReadOnlyError;
this.sync = throwReadOnlyError;
this._isReadOnly = true;
}
get isReadOnly() {
return this._isReadOnly;
}
static makeUpdateCommit(signer, prev, oldContent, newContent, header) {
const commit = ModelInstanceDocument_1._makeRawCommit(prev, oldContent, newContent, header);
return signer.createDagJWS(commit);
}
static _makeRawCommit(prev, oldContent, newContent, header) {
const patch = jsonpatch.compare(oldContent ?? {}, newContent ?? {});
const rawCommit = {
data: patch,
prev: prev.commit,
id: prev.baseID.cid,
};
if (header != null) {
rawCommit.header = header;
}
return rawCommit;
}
static async makeGenesis(context, content, metadata, unique) {
const commit = await this._makeRawGenesis(context.signer, content, metadata, unique);
if (metadata.deterministic) {
dagCbor.encode(commit);
return commit;
}
else {
return context.signer.createDagJWS(commit);
}
}
static async _makeRawGenesis(signer, content, metadata, unique) {
if (!metadata.model) {
throw new Error(`Must specify a 'model' when creating a ModelInstanceDocument`);
}
validateContentLength(content);
let controller = metadata.controller;
if (!controller) {
controller = await signer.asController();
}
const header = {
controllers: [controller],
model: metadata.model.bytes,
sep: 'model',
};
if (metadata.deterministic) {
if (Array.isArray(unique)) {
header.unique = fromString(unique.join('|'), 'utf8');
}
}
else {
header.unique = randomBytes(12);
}
if (metadata.context) {
if (!metadata.context?.bytes) {
throw new Error('Context must be a StreamID');
}
header.context = metadata.context.bytes;
}
return { data: content, header };
}
};
ModelInstanceDocument.STREAM_TYPE_NAME = 'MID';
ModelInstanceDocument.STREAM_TYPE_ID = 3;
ModelInstanceDocument.MAX_DOCUMENT_SIZE = 16000000;
ModelInstanceDocument = ModelInstanceDocument_1 = __decorate([
StreamStatic()
], ModelInstanceDocument);
export { ModelInstanceDocument };
export function validateContentLength(content) {
if (content) {
const contentLength = sizeof(content);
if (contentLength > ModelInstanceDocument.MAX_DOCUMENT_SIZE) {
throw new Error(`Content has length of ${contentLength}B which exceeds maximum size of ${ModelInstanceDocument.MAX_DOCUMENT_SIZE}B`);
}
}
}
//# sourceMappingURL=model-instance-document.js.map