@datalayer/core
Version:
[](https://datalayer.io)
187 lines (186 loc) • 6.72 kB
JavaScript
/*
* Copyright (c) 2023-2025 Datalayer, Inc.
* Distributed under the terms of the Modified BSD License.
*/
import * as items from '../api/spacer/items';
/**
* Abstract base class for all Datalayer content items.
* Provides common functionality for content management including lifecycle tracking.
*
* @template TData - Raw data type from API
* @template TUpdateRequest - Update request type for API
*/
export class ItemDTO {
_data;
_sdk;
_deleted = false;
/**
* Create an Item instance.
* @param data - Item data from API
* @param sdk - SDK instance
*/
constructor(data, sdk) {
this._data = data;
this._sdk = sdk;
}
// ========================================================================
// Deletion State Management
// ========================================================================
/** Check if this item has been deleted. */
get isDeleted() {
return this._deleted;
}
/**
* Check if this item has been deleted and throw error if so.
* @throws Error if deleted
*/
_checkDeleted() {
if (this._deleted) {
throw new Error(`${this.constructor.name} ${this._data.id} has been deleted and no longer exists`);
}
}
// ========================================================================
// Static Properties (set at creation, never change)
// ========================================================================
/** Item ID. */
get id() {
this._checkDeleted();
return this._data.id;
}
/** Unique identifier for the item. */
get uid() {
this._checkDeleted();
return this._data.uid;
}
/** Parent space ID. */
get spaceId() {
this._checkDeleted();
// Try the direct field first (if API provides it)
if (this._data.space_id) {
return this._data.space_id;
}
// Extract from s3_path_s if available: "datalayer.app/SPACE_ID/..."
const s3Path = this._data.s3_path_s;
if (s3Path && typeof s3Path === 'string') {
const match = s3Path.match(/^datalayer\.app\/([^/]+)\//);
if (match) {
return match[1];
}
}
// Fallback to empty string if no space ID can be determined
return '';
}
/** Owner user ID. */
get ownerId() {
this._checkDeleted();
return (this._data.owner_id || this._data.creator_uid || '');
}
/** When the item was created. */
get createdAt() {
this._checkDeleted();
const dateStr = this._data.creation_ts_dt || this._data.created_at;
if (!dateStr) {
throw new Error(`No creation timestamp available for ${this.constructor.name.toLowerCase()}`);
}
return new Date(dateStr);
}
/** The cached update time. */
get updatedAt() {
this._checkDeleted();
const dateStr = this._data.last_update_ts_dt ||
this._data.updated_at ||
this._data.creation_ts_dt ||
this._data.created_at;
if (!dateStr) {
throw new Error(`No update timestamp available for ${this.constructor.name.toLowerCase()}`);
}
return new Date(dateStr);
}
// ========================================================================
// Action Methods
// ========================================================================
/**
* Delete this item permanently.
* After deletion, all subsequent method calls will throw errors.
*/
async delete() {
this._checkDeleted();
const token = this._sdk.getToken();
const spacerRunUrl = this._sdk.getSpacerRunUrl();
await items.deleteItem(token, this.uid, spacerRunUrl);
this._deleted = true;
}
/** Get the document content from API. */
async getContent() {
this._checkDeleted();
// First try: CDN URL (fastest)
const cdnUrl = this._data.cdn_url_s;
if (cdnUrl) {
try {
const response = await fetch(cdnUrl);
if (!response.ok) {
throw new Error('Failed to fetch content from CDN');
}
const data = await response.json();
if (data.content !== undefined && data.content !== null) {
return data.content;
}
}
catch (error) {
console.error('Error fetching content from CDN:', error);
}
}
// Second try: Check if content is already loaded
const cachedContent = this.content;
if (cachedContent !== undefined && cachedContent !== null) {
return cachedContent;
}
// Third try: Fetch full item details from API
try {
const token = this._sdk.getToken();
const spacerRunUrl = this._sdk.getSpacerRunUrl();
const response = await items.getItem(token, this.uid, spacerRunUrl);
// Update internal data with full item details
if (response.success && response.item) {
this._data = response.item;
const freshContent = this.content;
if (freshContent !== undefined && freshContent !== null) {
return freshContent;
}
}
}
catch (error) {
console.error('Error fetching full item details:', error);
}
throw new Error(`Content is not available for item ${this.uid}. The item may not have been fully loaded or the content is not yet available.`);
}
// ========================================================================
// Utility Methods
// ========================================================================
/** Get raw item data object. */
rawData() {
this._checkDeleted();
return this._data;
}
/** String representation of the item. */
toString() {
this._checkDeleted();
const name = this.name || 'Unnamed';
return `${this.constructor.name}(${this.id}, ${name})`;
}
// ========================================================================
// Protected Helper Methods
// ========================================================================
/** Get SDK token for API calls. */
_getToken() {
return this._sdk.getToken();
}
/** Get spacer API URL for API calls. */
_getSpacerRunUrl() {
return this._sdk.getSpacerRunUrl();
}
/** Update internal data after API call. */
_updateData(newData) {
this._data = newData;
}
}