@ceramicnetwork/stream-model
Version:
Ceramic Model stream type
147 lines • 6.35 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 Model_1;
import { Stream, StreamStatic, SyncOptions, } from '@ceramicnetwork/common';
import { StreamID, StreamRef } from '@ceramicnetwork/streamid';
import { CID } from 'multiformats/cid';
import { create } from 'multiformats/hashes/digest';
import { code, encode } from '@ipld/dag-cbor';
import { identity } from 'multiformats/hashes/identity';
import { asDIDString } from '@ceramicnetwork/codecs';
import { decode } from 'codeco';
import { ModelDefinition, ModelRelationsDefinitionV2 } from './codecs.js';
export async function loadInterfaceImplements(reader, modelID) {
const model = await Model.load(reader, modelID);
if (model.content.version === '1.0' || !model.content.interface) {
throw new Error(`Model ${modelID} is not an interface`);
}
return model.content.implements ?? [];
}
export async function loadAllModelInterfaces(reader, interfaces, loading = {}) {
const toLoad = interfaces.map((modelID) => {
if (loading[modelID] == null) {
loading[modelID] = loadInterfaceImplements(reader, modelID).then((ownImplements) => {
return loadAllModelInterfaces(reader, ownImplements, loading).then((subImplements) => {
return [...ownImplements, ...subImplements];
});
});
}
return loading[modelID];
});
const loaded = await Promise.all(toLoad);
return Array.from(new Set(interfaces.concat(loaded.flat())));
}
export const MODEL_VERSION_REGEXP = /^[0-9]+\.[0-9]+$/;
export function parseModelVersion(version) {
if (!MODEL_VERSION_REGEXP.test(version)) {
throw new Error(`Unsupported version format: ${version}`);
}
const [major, minor] = version.split('.').map((part) => parseInt(part, 10));
return [major, minor];
}
const DEFAULT_LOAD_OPTS = { sync: SyncOptions.PREFER_CACHE };
async function throwReadOnlyError() {
throw new Error('Historical stream commits cannot be modified. Load the stream without specifying a commit to make updates.');
}
let Model = Model_1 = class Model extends Stream {
constructor() {
super(...arguments);
this._isReadOnly = false;
}
get content() {
return super.content;
}
get metadata() {
return {
controller: asDIDString(this.state$.value.metadata.controllers[0]),
model: Model_1.MODEL,
};
}
static async create(ceramic, content, metadata) {
Model_1.assertVersionValid(content, 'minor');
Model_1.assertComplete(content);
const opts = {
publish: true,
anchor: true,
sync: SyncOptions.NEVER_SYNC,
};
const commit = await Model_1._makeGenesis(ceramic.signer, content, metadata);
const model = await ceramic.createStreamFromGenesis(Model_1.STREAM_TYPE_ID, commit, opts);
return model;
}
static assertComplete(content, _streamId) {
decode(ModelDefinition, content);
}
static assertVersionValid(content, satisfies = 'minor') {
if (content.version == null) {
throw new Error(`Missing version for model ${content.name}`);
}
const [expectedMajor, expectedMinor] = parseModelVersion(Model_1.VERSION);
const [major, minor] = parseModelVersion(content.version);
if (major > expectedMajor ||
(satisfies === 'minor' && major === expectedMajor && minor > expectedMinor)) {
throw new Error(`Unsupported version ${content.version} for model ${content.name}, the maximum version supported by the Ceramic node is ${Model_1.VERSION}. Please update your Ceramic node to a newer version supporting at least version ${content.version} of the Model definition.`);
}
}
static assertRelationsValid(content) {
if (content.relations != null) {
decode(ModelRelationsDefinitionV2, content.relations);
}
}
static async load(ceramic, streamId, opts = {}) {
opts = { ...DEFAULT_LOAD_OPTS, ...opts };
const streamRef = StreamRef.from(streamId);
if (streamRef.type != Model_1.STREAM_TYPE_ID) {
throw new Error(`StreamID ${streamRef.toString()} does not refer to a '${Model_1.STREAM_TYPE_NAME}' stream, but to a ${streamRef.typeName}`);
}
const model = await ceramic.loadStream(streamRef, opts);
return model;
}
static async _makeGenesis(context, content, metadata) {
const commit = await this._makeRawGenesis(context.signer, content, metadata);
return context.signer.createDagJWS(commit);
}
static async _makeRawGenesis(signer, content, metadata) {
if (content == null) {
throw new Error(`Genesis content cannot be null`);
}
if (!metadata) {
metadata = { controller: await signer.asController() };
}
else if (!metadata.controller) {
metadata.controller = await signer.asController();
}
const header = {
controllers: [metadata.controller],
model: Model_1.MODEL.bytes,
sep: 'model',
};
return { data: content, header };
}
makeReadOnly() {
this.sync = throwReadOnlyError;
this._isReadOnly = true;
}
get isReadOnly() {
return this._isReadOnly;
}
};
Model.STREAM_TYPE_NAME = 'model';
Model.STREAM_TYPE_ID = 2;
Model.MODEL = (function () {
const data = encode('model-v1');
const multihash = identity.digest(data);
const digest = create(code, multihash.bytes);
const cid = CID.createV1(code, digest);
return new StreamID('UNLOADABLE', cid);
})();
Model.VERSION = '2.0';
Model = Model_1 = __decorate([
StreamStatic()
], Model);
export { Model };
//# sourceMappingURL=model.js.map