UNPKG

@baqhub/sdk

Version:

The official JavaScript SDK for the BAQ federated app platform.

257 lines (256 loc) 8.5 kB
import * as IO from "../../helpers/io.js"; import { Uuid } from "../../helpers/uuid.js"; import { EntityLink } from "../links/entityLink.js"; import { RRecordPermissions, RecordPermissions } from "./recordPermissions.js"; import { RAnyRecordType } from "./recordType.js"; export var RecordSource; (function (RecordSource) { RecordSource["SELF"] = "self"; RecordSource["RESOLUTION"] = "resolution"; RecordSource["NOTIFICATION"] = "notification"; RecordSource["NOTIFICATION_UNKNOWN"] = "notification_unknown"; RecordSource["SUBSCRIPTION"] = "subscription"; RecordSource["PROXY"] = "proxy"; })(RecordSource || (RecordSource = {})); export var RecordMode; (function (RecordMode) { RecordMode["SYNCED"] = "synced"; RecordMode["LOCAL"] = "local"; })(RecordMode || (RecordMode = {})); export var NoContentRecordAction; (function (NoContentRecordAction) { NoContentRecordAction["DELETE"] = "delete"; NoContentRecordAction["LOCAL"] = "local"; NoContentRecordAction["REPORT"] = "report"; NoContentRecordAction["LEAVE"] = "leave"; })(NoContentRecordAction || (NoContentRecordAction = {})); export const subscriptionRecordContainer = { SubscriptionRecord: null, }; // // Runtime model. // const RRecordSource = IO.weakEnumeration(RecordSource); const RRecordMode = IO.weakEnumeration(RecordMode); const RRecordVersion = IO.intersection([ IO.object({ author: EntityLink.io(), hash: IO.union([IO.undefined, IO.string]), createdAt: IO.isoDate, receivedAt: IO.union([IO.undefined, IO.isoDate]), }), IO.partialObject({ parentHash: IO.string, }), ]); class RecordClassBase extends IO.Type { RType; RContent; constructor(RType, RContent) { const model = IO.object({ author: EntityLink.io(), id: IO.string, source: IO.defaultValue(RRecordSource, RecordSource.PROXY), createdAt: IO.isoDate, receivedAt: IO.union([IO.undefined, IO.isoDate]), version: IO.union([IO.undefined, RRecordVersion]), permissions: RRecordPermissions, type: RType, content: RContent, mode: IO.defaultValue(RRecordMode, RecordMode.SYNCED), }); super("Record", model.is, model.validate, model.encode); this.RType = RType; this.RContent = RContent; } } class RecordClass extends RecordClassBase { type; constructor(type, RType, RContent) { super(RType, RContent); this.type = type; this.link = { entity: type.entity, recordId: type.recordId, }; } link; new(entity, content, options) { return buildRecord(this, entity, this.type, content, options); } update(entity, record, content, options) { return buildRecordUpdate(this, entity, record, content, options); } subscribe(entity, publisherEntity, options) { return buildSubscriptionRecord(entity, publisherEntity, this.type, options); } } function record(type, RType, RContent) { return new RecordClass(type, RType, RContent); } function cleanRecord(RRecord) { return RRecord; } export const AnyRecord = new RecordClassBase(IO.any, IO.any); const RNoContentRecordAction = IO.weakEnumeration(NoContentRecordAction); export const RNoContentRecord = IO.object({ author: EntityLink.io(), id: IO.string, source: RRecordSource, createdAt: IO.isoDate, receivedAt: IO.union([IO.undefined, IO.isoDate]), version: IO.union([IO.undefined, RRecordVersion]), permissions: RRecordPermissions, type: RAnyRecordType, noContent: IO.dualObject({ action: RNoContentRecordAction }, { links: IO.unknown }), mode: IO.defaultValue(RRecordMode, RecordMode.SYNCED), }); function buildRecord(model, entity, type, content, { id, permissions, mode } = {}) { const record = { author: { entity }, id: id || Uuid.new(), source: "self", createdAt: new Date(), receivedAt: undefined, version: undefined, permissions: permissions || RecordPermissions.private, type, content, mode: mode || RecordMode.SYNCED, }; return IO.validate(model, record); } function buildRecordUpdate(model, entity, record, content, { permissions } = {}) { if (record.source === "proxy") { throw new Error("Cannot update proxied record."); } // Make sure the new date is higher. // TODO: bound this and keep local server offset. const versionCreatedAt = record.version?.createdAt; const now = new Date(); const newVersionCreatedAt = versionCreatedAt && versionCreatedAt > now ? new Date(versionCreatedAt.getTime() + 1) : now; const recordUpdate = { author: { entity: record.author.entity }, id: record.id, source: "self", createdAt: record.createdAt, receivedAt: record.receivedAt, version: { author: { entity }, hash: undefined, createdAt: newVersionCreatedAt, receivedAt: undefined, parentHash: record.version?.hash, }, permissions: permissions || record.permissions, type: record.type, content, mode: record.mode, }; return IO.validate(model, recordUpdate); } function buildNoContentRecord(entity, record, action = NoContentRecordAction.DELETE) { if (record.source === "proxy") { throw new Error("Cannot delete proxied record."); } // Make sure the new date is higher. // TODO: bound this and keep local server offset. const versionCreatedAt = record.version?.createdAt; const now = new Date(); const newVersionCreatedAt = versionCreatedAt && versionCreatedAt > now ? new Date(versionCreatedAt.getTime() + 1) : now; return { author: { entity: record.author.entity }, id: record.id, source: "self", createdAt: record.createdAt, receivedAt: undefined, version: { author: { entity }, hash: undefined, createdAt: newVersionCreatedAt, receivedAt: undefined, parentHash: record.version?.hash, }, permissions: record.permissions, type: record.type, noContent: { action }, mode: record.mode, }; } function buildSubscriptionRecord(entity, publisherEntity, type, { id, readPermissions } = {}) { return buildRecord(subscriptionRecordContainer.SubscriptionRecord, entity, subscriptionRecordContainer.SubscriptionRecord.type, { publisher: { entity: publisherEntity }, recordType: { entity: type.entity, recordId: type.recordId, }, }, { id, permissions: { read: readPermissions, notify: [{ entity: publisherEntity }], }, }); } // // Helpers. // function isSameRecord(record1, record2) { return (record1.author.entity === record2.author.entity && record1.id === record2.id); } function isPublicRecord(record) { return record.permissions.read === "public"; } function recordHasType(record, type) { return (record.type.entity === type.type.entity && record.type.recordId === type.type.recordId); } function updateToSynced(record) { return { ...record, mode: RecordMode.SYNCED }; } function updateToSelf(record) { return { ...record, source: RecordSource.SELF }; } function updateToResolution(record) { return { ...record, source: "resolution" }; } function recordToKey(record) { return `${record.author.entity}+${record.id}`; } function recordToLink(record) { return { entity: record.author.entity, recordId: record.id, }; } function recordToVersionHash(record) { if (!record.version?.hash) { throw new Error("This record does not have a version."); } return record.version.hash; } function noContentRecordToLink(record) { return { entity: record.author.entity, recordId: record.id, }; } export const Record = { io: record, ioClean: cleanRecord, isSame: isSameRecord, isPublic: isPublicRecord, hasType: recordHasType, toSynced: updateToSynced, toSelf: updateToSelf, toResolution: updateToResolution, toKey: recordToKey, toLink: recordToLink, toVersionHash: recordToVersionHash, noContentToLink: noContentRecordToLink, delete: buildNoContentRecord, };