# core/persistence/export.persistence.ts

> 78 lines of code and 16 definitions.

Tree: Bane's Lab Social Share
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/social-share#file-social-share-core-persistence-export-persistence-ts
Source text: https://banes-lab.com/source/social-share/core/persistence/export.persistence.ts.txt

Listed in [core/persistence](https://banes-lab.com/api/source/social-share/core/persistence.md), before [core/persistence/image.persistence.ts](https://banes-lab.com/source/social-share/core/persistence/image.persistence.ts.md).

## Definitions

- `strayShares` (lexical_declaration, line 60, exported)
- `publishShares` (lexical_declaration, line 68, exported)
- `isAlive` (lexical_declaration, line 14)
- `unpublishedShares` (lexical_declaration, line 53, exported)
- `holderOf` (lexical_declaration, line 26)
- `TEXT` (lexical_declaration, line 10)
- `MISSING_PROCESS` (lexical_declaration, line 11)
- `SLASH` (lexical_declaration, line 12)
- `pid` (lexical_declaration, line 30)
- `holdCaptureLock` (lexical_declaration, line 34, exported)
- `holder` (lexical_declaration, line 35, exported)
- `servedNames` (lexical_declaration, line 49)
- `named` (lexical_declaration, line 64, exported)
- `published` (lexical_declaration, line 77, exported)
- `target` (lexical_declaration, line 78, exported)
- `source` (lexical_declaration, line 79, exported)

## Contained in

- [core/persistence](https://banes-lab.com/anatomy/social-share/folder-social-share-core-persistence.md)

## Used by

- [core/coordinators/export.coordinator.ts](https://banes-lab.com/source/social-share/core/coordinators/export.coordinator.ts.md)
- [runtime/entrypoints/validation.entrypoint.ts](https://banes-lab.com/source/social-share/runtime/entrypoints/validation.entrypoint.ts.md)

## Linked from

- [core/coordinators](https://banes-lab.com/anatomy/social-share/folder-social-share-core-coordinators.md)
- [core/persistence](https://banes-lab.com/anatomy/social-share/folder-social-share-core-persistence.md)
- [runtime/entrypoints](https://banes-lab.com/anatomy/social-share/folder-social-share-runtime-entrypoints.md)

## Source

```typescript
import { EXPORT_RUNNING, SUBJECT_SEPARATOR } from "#configuration/strings/card.strings";
import { dirname, join } from "node:path";
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
import type { Disposer } from "@banes-lab/web/types/base.types.ts";
import { LOCK_FILE } from "#configuration/constants/card.constants";
import type { ShareEntry } from "#types/image.types";
import { absolutePath } from "@ssot/paths";
import { writeVerbatim } from "@govlab/canonical-write";

const TEXT = "utf8";
const MISSING_PROCESS = "ESRCH";
const SLASH = "/";

const isAlive = function isAlive(pid: number): boolean {
    try {
        process.kill(pid, 0);
        return true;
    } catch (error) {
        if (error instanceof Error && Reflect.get(error, "code") === MISSING_PROCESS) {
            return false;
        }
        throw error;
    }
};

const holderOf = function holderOf(location: string): number | null {
    if (!existsSync(location)) {
        return null;
    }
    const pid = Number(readFileSync(location, TEXT).trim());
    return Number.isInteger(pid) && isAlive(pid) ? pid : null;
};

export const holdCaptureLock = function holdCaptureLock(location = absolutePath("builds.root", LOCK_FILE)): Disposer {
    const holder = holderOf(location);
    if (holder === process.pid) {
        return () => {};
    }
    if (holder !== null) {
        throw new Error(EXPORT_RUNNING + SUBJECT_SEPARATOR + String(holder));
    }
    mkdirSync(dirname(location), { recursive: true });
    writeVerbatim(location, String(process.pid));
    return () => {
        rmSync(location, { force: true });
    };
};

const servedNames = function servedNames(entry: ShareEntry): readonly string[] {
    return [entry.source, ...entry.stills].map((address) => address.slice(address.lastIndexOf(SLASH) + 1));
};

export const unpublishedShares = function unpublishedShares(
    entries: readonly ShareEntry[],
    served: string,
): readonly string[] {
    return entries.flatMap(servedNames).filter((file) => !existsSync(join(served, file)));
};

export const strayShares = function strayShares(entries: readonly ShareEntry[], served: string): readonly string[] {
    if (!existsSync(served)) {
        return [];
    }
    const named = new Set(entries.flatMap(servedNames));
    return readdirSync(served).filter((file) => !named.has(file));
};

export const publishShares = function publishShares(
    entries: readonly ShareEntry[],
    renders: string,
    served: string,
): readonly string[] {
    mkdirSync(served, { recursive: true });
    for (const file of strayShares(entries, served)) {
        rmSync(join(served, file));
    }
    const published = entries.flatMap(servedNames).filter((file) => {
        const target = join(served, file);
        const source = join(renders, file);
        return !existsSync(target) || !readFileSync(target).equals(readFileSync(source));
    });
    for (const file of published) {
        writeVerbatim(join(served, file), readFileSync(join(renders, file)));
    }
    return published;
};
```
