mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add distributed settings and workspace import/export (M5)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Export envelope — the single JSON object Astrolabe downloads to back up or
|
||||
* transfer a whole workspace (spec §08 → Export, Export envelope shape).
|
||||
*
|
||||
* Portable core: no browser APIs, no React. Builds the in-memory envelope, the
|
||||
* download filename, and the success-toast message as plain data. The actual file
|
||||
* download (Blob/anchor/DOM) is browser code that lives in the app layer; this
|
||||
* module only shapes what that code serializes and reports.
|
||||
*
|
||||
* The envelope `version` is the **file-format** version, distinct from the
|
||||
* per-record `version` fields each snippet/dataset carries (their read-time
|
||||
* migration target — see snippet.ts / dataset.ts).
|
||||
*/
|
||||
|
||||
import type { Dataset } from './dataset';
|
||||
import type { Snippet } from './snippet';
|
||||
|
||||
/**
|
||||
* Current export file-format version (spec §08 → "currently `\"1.0\"`"). Bump when
|
||||
* the envelope shape changes in a way importers must branch on; this is NOT the
|
||||
* per-record schema version.
|
||||
*/
|
||||
export const EXPORT_ENVELOPE_VERSION = '1.0';
|
||||
|
||||
/**
|
||||
* The downloaded file's top-level shape (spec §08 → Export envelope shape): format
|
||||
* metadata plus the two complete-record arrays. Each record keeps its own `version`
|
||||
* field unchanged.
|
||||
*/
|
||||
export interface ExportEnvelope {
|
||||
/** Export format version (currently `"1.0"`). */
|
||||
version: string;
|
||||
/** ISO 8601 timestamp of the export. */
|
||||
exportedAt: string;
|
||||
/** Fixed exporter tag. */
|
||||
exportedBy: 'Astrolabe';
|
||||
/** All snippets, as complete records (each including its record `version`). */
|
||||
snippets: Snippet[];
|
||||
/** All datasets, as complete records (each including its record `version`). */
|
||||
datasets: Dataset[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the export envelope (spec §08 → Export). Stamps the format version, the
|
||||
* export timestamp (`now.toISOString()`), and the fixed exporter tag, and copies
|
||||
* the records into fresh arrays so the envelope does not alias the caller's arrays
|
||||
* (the records themselves are referenced as-is — they are the complete records to
|
||||
* serialize, each keeping its own `version`).
|
||||
*/
|
||||
export function buildExportEnvelope(
|
||||
snippets: ReadonlyArray<Snippet>,
|
||||
datasets: ReadonlyArray<Dataset>,
|
||||
opts: { now: Date },
|
||||
): ExportEnvelope {
|
||||
return {
|
||||
version: EXPORT_ENVELOPE_VERSION,
|
||||
exportedAt: opts.now.toISOString(),
|
||||
exportedBy: 'Astrolabe',
|
||||
snippets: [...snippets],
|
||||
datasets: [...datasets],
|
||||
};
|
||||
}
|
||||
|
||||
/** Two-digit zero-pad for the filename date (mirrors snippet.ts → generateSnippetName). */
|
||||
function pad(n: number): string {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Download filename for an export (spec §08 → Filename):
|
||||
* `astrolabe-project-YYYY-MM-DD.json`, where the date is today's date (export day).
|
||||
* Uses local date parts, like `generateSnippetName`.
|
||||
*/
|
||||
export function exportFilename(now: Date): string {
|
||||
const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
||||
return `astrolabe-project-${date}.json`;
|
||||
}
|
||||
|
||||
/** Pluralize a count's noun: "1 snippet" / "4 snippets". */
|
||||
function countClause(count: number, noun: string): string {
|
||||
return `${count} ${noun}${count === 1 ? '' : 's'}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Success-toast message reporting the export counts (spec §08 → Feedback), e.g.
|
||||
* "Exported 4 snippets and 2 datasets". The dataset clause is omitted entirely when
|
||||
* there are no datasets; singular/plural wording adapts to the counts.
|
||||
*/
|
||||
export function exportSummaryMessage(snippetCount: number, datasetCount: number): string {
|
||||
const snippets = countClause(snippetCount, 'snippet');
|
||||
if (datasetCount === 0) return `Exported ${snippets}`;
|
||||
return `Exported ${snippets} and ${countClause(datasetCount, 'dataset')}`;
|
||||
}
|
||||
Reference in New Issue
Block a user