@miadi/ncp-story-studio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/PHASES.md +160 -0
- package/README.md +140 -0
- package/assets/PROVENANCE.json +5 -0
- package/assets/examples/PROVENANCE.json +26 -0
- package/assets/examples/anora.json +228 -0
- package/assets/examples/example-story.json +112 -0
- package/assets/examples/the-shawshank-redemption.json +2068 -0
- package/assets/examples/weaver_of_words__the_catalyst_of_change_251101.json +1132 -0
- package/assets/ncp-schema.json +794 -0
- package/assets/terminology.json +2919 -0
- package/dist/agents/adapter.d.ts +60 -0
- package/dist/agents/adapter.d.ts.map +1 -0
- package/dist/agents/adapter.js +69 -0
- package/dist/agents/adapter.js.map +1 -0
- package/dist/agents/context.d.ts +19 -0
- package/dist/agents/context.d.ts.map +1 -0
- package/dist/agents/context.js +49 -0
- package/dist/agents/context.js.map +1 -0
- package/dist/agents/index.d.ts +5 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +7 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/links.d.ts +41 -0
- package/dist/agents/links.d.ts.map +1 -0
- package/dist/agents/links.js +76 -0
- package/dist/agents/links.js.map +1 -0
- package/dist/agents/personas.d.ts +16 -0
- package/dist/agents/personas.d.ts.map +1 -0
- package/dist/agents/personas.js +34 -0
- package/dist/agents/personas.js.map +1 -0
- package/dist/fixtures/index.d.ts +27 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +69 -0
- package/dist/fixtures/index.js.map +1 -0
- package/dist/graph/index.d.ts +60 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +97 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/react/index.d.ts +2 -0
- package/dist/graph/react/index.d.ts.map +1 -0
- package/dist/graph/react/index.js +5 -0
- package/dist/graph/react/index.js.map +1 -0
- package/dist/graph/react/story-graph.d.ts +17 -0
- package/dist/graph/react/story-graph.d.ts.map +1 -0
- package/dist/graph/react/story-graph.js +26 -0
- package/dist/graph/react/story-graph.js.map +1 -0
- package/dist/graph/tones.d.ts +36 -0
- package/dist/graph/tones.d.ts.map +1 -0
- package/dist/graph/tones.js +38 -0
- package/dist/graph/tones.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/constants.d.ts +3 -0
- package/dist/schema/constants.d.ts.map +1 -0
- package/dist/schema/constants.js +3 -0
- package/dist/schema/constants.js.map +1 -0
- package/dist/schema/extensions.d.ts +14 -0
- package/dist/schema/extensions.d.ts.map +1 -0
- package/dist/schema/extensions.js +61 -0
- package/dist/schema/extensions.js.map +1 -0
- package/dist/schema/generated-terms.d.ts +7 -0
- package/dist/schema/generated-terms.d.ts.map +1 -0
- package/dist/schema/generated-terms.js +518 -0
- package/dist/schema/generated-terms.js.map +1 -0
- package/dist/schema/generated.d.ts +64 -0
- package/dist/schema/generated.d.ts.map +1 -0
- package/dist/schema/generated.js +497 -0
- package/dist/schema/generated.js.map +1 -0
- package/dist/schema/index.d.ts +48 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +92 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/normalize.d.ts +20 -0
- package/dist/schema/normalize.d.ts.map +1 -0
- package/dist/schema/normalize.js +88 -0
- package/dist/schema/normalize.js.map +1 -0
- package/dist/schema/read.d.ts +31 -0
- package/dist/schema/read.d.ts.map +1 -0
- package/dist/schema/read.js +640 -0
- package/dist/schema/read.js.map +1 -0
- package/dist/schema/terms.d.ts +11 -0
- package/dist/schema/terms.d.ts.map +1 -0
- package/dist/schema/terms.js +80 -0
- package/dist/schema/terms.js.map +1 -0
- package/dist/schema/types.d.ts +140 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +2 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema/validate.d.ts +54 -0
- package/dist/schema/validate.d.ts.map +1 -0
- package/dist/schema/validate.js +490 -0
- package/dist/schema/validate.js.map +1 -0
- package/dist/store/index.d.ts +76 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +96 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.d.ts +35 -0
- package/dist/store/json.d.ts.map +1 -0
- package/dist/store/json.js +192 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/mutations.d.ts +45 -0
- package/dist/store/mutations.d.ts.map +1 -0
- package/dist/store/mutations.js +181 -0
- package/dist/store/mutations.js.map +1 -0
- package/dist/store/neon.d.ts +42 -0
- package/dist/store/neon.d.ts.map +1 -0
- package/dist/store/neon.js +167 -0
- package/dist/store/neon.js.map +1 -0
- package/dist/terminology/generated.d.ts +3 -0
- package/dist/terminology/generated.d.ts.map +1 -0
- package/dist/terminology/generated.js +2923 -0
- package/dist/terminology/generated.js.map +1 -0
- package/dist/terminology/index.d.ts +20 -0
- package/dist/terminology/index.d.ts.map +1 -0
- package/dist/terminology/index.js +71 -0
- package/dist/terminology/index.js.map +1 -0
- package/dist/terminology/types.d.ts +25 -0
- package/dist/terminology/types.d.ts.map +1 -0
- package/dist/terminology/types.js +2 -0
- package/dist/terminology/types.js.map +1 -0
- package/package.json +117 -0
- package/rispecs/README.rise.md +5 -0
- package/rispecs/RISPEC.md +9 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// `@miadi/ncp-story-studio/store` — story projects, and the provider contract
|
|
2
|
+
// every backing store implements. The JSON provider lives in `./json`, the Neon
|
|
3
|
+
// provider in `./neon`; both satisfy this interface, so a host swaps them by
|
|
4
|
+
// configuration and nothing above the store changes.
|
|
5
|
+
import { summarizeStory } from '../schema/index.js';
|
|
6
|
+
export * from './mutations.js';
|
|
7
|
+
export class StoryStoreConflictError extends Error {
|
|
8
|
+
projectId;
|
|
9
|
+
expected;
|
|
10
|
+
actual;
|
|
11
|
+
constructor(projectId, expected, actual) {
|
|
12
|
+
super(`project \`${projectId}\` changed since it was read (expected ${expected}, found ${actual})`);
|
|
13
|
+
this.projectId = projectId;
|
|
14
|
+
this.expected = expected;
|
|
15
|
+
this.actual = actual;
|
|
16
|
+
this.name = 'StoryStoreConflictError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class StoryStoreReadOnlyError extends Error {
|
|
20
|
+
projectId;
|
|
21
|
+
constructor(projectId) {
|
|
22
|
+
super(`project \`${projectId}\` is read-only in this store`);
|
|
23
|
+
this.projectId = projectId;
|
|
24
|
+
this.name = 'StoryStoreReadOnlyError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Turn a title into a project id a filesystem and a URL both accept. */
|
|
28
|
+
export function toProjectId(value) {
|
|
29
|
+
const slug = value
|
|
30
|
+
.normalize('NFKD')
|
|
31
|
+
.replace(/[̀-ͯ]/g, '')
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
34
|
+
.replace(/^-+|-+$/g, '')
|
|
35
|
+
.slice(0, 80);
|
|
36
|
+
return slug.length > 0 ? slug : 'story';
|
|
37
|
+
}
|
|
38
|
+
/** Build a project record around a document, stamping both timestamps. */
|
|
39
|
+
export function newProject(document, options) {
|
|
40
|
+
const now = options?.now ?? new Date().toISOString();
|
|
41
|
+
return {
|
|
42
|
+
project_id: options?.projectId ?? toProjectId(document.story.id || document.story.title),
|
|
43
|
+
document,
|
|
44
|
+
created_at: now,
|
|
45
|
+
updated_at: now,
|
|
46
|
+
meta: options?.meta,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function summarizeProject(project) {
|
|
50
|
+
return {
|
|
51
|
+
...summarizeStory(project.document.story, project.updated_at),
|
|
52
|
+
project_id: project.project_id,
|
|
53
|
+
updated_at: project.updated_at,
|
|
54
|
+
meta: project.meta,
|
|
55
|
+
read_only: project.read_only,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** An in-memory store. Useful for tests and for a read-only fixture catalog. */
|
|
59
|
+
export class MemoryStoryStore {
|
|
60
|
+
kind = 'memory';
|
|
61
|
+
location = 'memory';
|
|
62
|
+
projects = new Map();
|
|
63
|
+
constructor(seed = []) {
|
|
64
|
+
for (const project of seed)
|
|
65
|
+
this.projects.set(project.project_id, project);
|
|
66
|
+
}
|
|
67
|
+
async list() {
|
|
68
|
+
return [...this.projects.values()].map(summarizeProject).sort((a, b) => a.title.localeCompare(b.title));
|
|
69
|
+
}
|
|
70
|
+
async get(projectId) {
|
|
71
|
+
const project = this.projects.get(projectId);
|
|
72
|
+
return project ? structuredClone(project) : undefined;
|
|
73
|
+
}
|
|
74
|
+
async save(project, options) {
|
|
75
|
+
const existing = this.projects.get(project.project_id);
|
|
76
|
+
if (existing?.read_only)
|
|
77
|
+
throw new StoryStoreReadOnlyError(project.project_id);
|
|
78
|
+
if (options?.expected_updated_at && existing && existing.updated_at !== options.expected_updated_at) {
|
|
79
|
+
throw new StoryStoreConflictError(project.project_id, options.expected_updated_at, existing.updated_at);
|
|
80
|
+
}
|
|
81
|
+
const saved = {
|
|
82
|
+
...project,
|
|
83
|
+
created_at: existing?.created_at ?? project.created_at,
|
|
84
|
+
updated_at: new Date().toISOString(),
|
|
85
|
+
};
|
|
86
|
+
this.projects.set(saved.project_id, saved);
|
|
87
|
+
return structuredClone(saved);
|
|
88
|
+
}
|
|
89
|
+
async remove(projectId) {
|
|
90
|
+
const existing = this.projects.get(projectId);
|
|
91
|
+
if (existing?.read_only)
|
|
92
|
+
throw new StoryStoreReadOnlyError(projectId);
|
|
93
|
+
return this.projects.delete(projectId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type SaveOptions, type StoryProject, type StoryProjectSummary, type StoryStore } from './index.js';
|
|
2
|
+
export declare function assertProjectId(projectId: string): void;
|
|
3
|
+
export interface JsonStoryStoreOptions {
|
|
4
|
+
/** Directory that holds one folder per project. Created on demand. */
|
|
5
|
+
root: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class JsonStoryStore implements StoryStore {
|
|
8
|
+
readonly kind = "json";
|
|
9
|
+
readonly root: string;
|
|
10
|
+
/** Project directories `list()` could not read, and why. */
|
|
11
|
+
private readonly unreadable;
|
|
12
|
+
/** One save at a time per project, so a concurrent write cannot interleave. */
|
|
13
|
+
private readonly saveQueue;
|
|
14
|
+
constructor(options: JsonStoryStoreOptions);
|
|
15
|
+
/** What the last `list()` could not read. Empty when everything read cleanly. */
|
|
16
|
+
problems(): {
|
|
17
|
+
project_id: string;
|
|
18
|
+
error: string;
|
|
19
|
+
}[];
|
|
20
|
+
get location(): string;
|
|
21
|
+
init(): Promise<void>;
|
|
22
|
+
private projectDir;
|
|
23
|
+
list(): Promise<StoryProjectSummary[]>;
|
|
24
|
+
get(projectId: string): Promise<StoryProject | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* Saves for one project run one at a time. Two processes writing the same
|
|
27
|
+
* project still race — a filesystem gives no atomic compare-and-set — so
|
|
28
|
+
* `expected_updated_at` is a guard within this process and best-effort across
|
|
29
|
+
* processes. Use the Neon provider when several writers are real.
|
|
30
|
+
*/
|
|
31
|
+
save(project: StoryProject, options?: SaveOptions): Promise<StoryProject>;
|
|
32
|
+
private writeProject;
|
|
33
|
+
remove(projectId: string): Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/store/json.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,YAAY,EAEjB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAA;AAqBnB,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAMvD;AAED,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;CACb;AAED,qBAAa,cAAe,YAAW,UAAU;IAC/C,QAAQ,CAAC,IAAI,UAAS;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4B;IACvD,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;gBAEpD,OAAO,EAAE,qBAAqB;IAI1C,iFAAiF;IACjF,QAAQ,IAAI;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE;IAInD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,OAAO,CAAC,UAAU;IAUZ,IAAI,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAwBtC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAgD/D;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;YAiBjE,YAAY;IA0BpB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CASlD"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// JSON-file provider. One directory per project:
|
|
2
|
+
// <root>/<project_id>/story.ncp.json the NCP document, exactly as exported
|
|
3
|
+
// <root>/<project_id>/project.json timestamps and host metadata
|
|
4
|
+
// The document stays a plain NCP file, so exporting a project is copying it.
|
|
5
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { StoryStoreConflictError, StoryStoreReadOnlyError, summarizeProject, } from './index.js';
|
|
8
|
+
const DOCUMENT_FILE = 'story.ncp.json';
|
|
9
|
+
const PROJECT_FILE = 'project.json';
|
|
10
|
+
/**
|
|
11
|
+
* A project id names one directory under the root and nothing else. `.` and `''`
|
|
12
|
+
* both resolve to the root itself, which would make the root a project and let
|
|
13
|
+
* `remove` delete every project in it, so they are rejected here rather than
|
|
14
|
+
* caught downstream.
|
|
15
|
+
*/
|
|
16
|
+
const PROJECT_ID = /^[A-Za-z0-9][A-Za-z0-9._~@+-]{0,127}$/;
|
|
17
|
+
export function assertProjectId(projectId) {
|
|
18
|
+
if (typeof projectId !== 'string' || !PROJECT_ID.test(projectId) || projectId === '.' || projectId === '..') {
|
|
19
|
+
throw new Error(`invalid project id \`${projectId}\`: 1-128 characters, letters/digits/._~@+- only, not \`.\` or \`..\``);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class JsonStoryStore {
|
|
23
|
+
kind = 'json';
|
|
24
|
+
root;
|
|
25
|
+
/** Project directories `list()` could not read, and why. */
|
|
26
|
+
unreadable = new Map();
|
|
27
|
+
/** One save at a time per project, so a concurrent write cannot interleave. */
|
|
28
|
+
saveQueue = new Map();
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.root = resolve(options.root);
|
|
31
|
+
}
|
|
32
|
+
/** What the last `list()` could not read. Empty when everything read cleanly. */
|
|
33
|
+
problems() {
|
|
34
|
+
return [...this.unreadable].map(([project_id, error]) => ({ project_id, error }));
|
|
35
|
+
}
|
|
36
|
+
get location() {
|
|
37
|
+
return this.root;
|
|
38
|
+
}
|
|
39
|
+
async init() {
|
|
40
|
+
await mkdir(this.root, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
projectDir(projectId) {
|
|
43
|
+
assertProjectId(projectId);
|
|
44
|
+
const dir = resolve(this.root, projectId);
|
|
45
|
+
// Belt and braces: the id pattern already excludes separators.
|
|
46
|
+
if (dir === this.root || dirname(dir) !== this.root) {
|
|
47
|
+
throw new Error(`invalid project id \`${projectId}\``);
|
|
48
|
+
}
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
async list() {
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = (await readdir(this.root, { withFileTypes: true }))
|
|
55
|
+
.filter((entry) => entry.isDirectory())
|
|
56
|
+
.map((entry) => entry.name);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error.code === 'ENOENT')
|
|
60
|
+
return [];
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
const projects = [];
|
|
64
|
+
for (const projectId of entries) {
|
|
65
|
+
// A project that cannot be read is skipped and recorded, never allowed to
|
|
66
|
+
// take the whole listing down with it.
|
|
67
|
+
try {
|
|
68
|
+
const project = await this.get(projectId);
|
|
69
|
+
if (project)
|
|
70
|
+
projects.push(summarizeProject(project));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.unreadable.set(projectId, error instanceof Error ? error.message : String(error));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return projects.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
77
|
+
}
|
|
78
|
+
async get(projectId) {
|
|
79
|
+
const dir = this.projectDir(projectId);
|
|
80
|
+
let documentRaw;
|
|
81
|
+
try {
|
|
82
|
+
documentRaw = await readFile(join(dir, DOCUMENT_FILE), 'utf8');
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error.code === 'ENOENT')
|
|
86
|
+
return undefined;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
const document = JSON.parse(documentRaw);
|
|
90
|
+
const now = new Date().toISOString();
|
|
91
|
+
const adopted = {
|
|
92
|
+
project_id: projectId,
|
|
93
|
+
created_at: now,
|
|
94
|
+
updated_at: now,
|
|
95
|
+
meta: { source: 'adopted-directory' },
|
|
96
|
+
};
|
|
97
|
+
let sidecar = adopted;
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(await readFile(join(dir, PROJECT_FILE), 'utf8'));
|
|
100
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
101
|
+
const holder = parsed;
|
|
102
|
+
sidecar = {
|
|
103
|
+
project_id: projectId,
|
|
104
|
+
created_at: typeof holder['created_at'] === 'string' ? holder['created_at'] : adopted.created_at,
|
|
105
|
+
updated_at: typeof holder['updated_at'] === 'string' ? holder['updated_at'] : adopted.updated_at,
|
|
106
|
+
meta: (holder['meta'] ?? undefined),
|
|
107
|
+
read_only: holder['read_only'] === true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// A hand-placed document with no sidecar is still a project; a corrupt
|
|
113
|
+
// sidecar is treated the same way rather than hiding the document.
|
|
114
|
+
if (error.code !== 'ENOENT') {
|
|
115
|
+
sidecar = { ...adopted, meta: { source: 'unreadable-sidecar' } };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.unreadable.delete(projectId);
|
|
119
|
+
return {
|
|
120
|
+
project_id: projectId,
|
|
121
|
+
document,
|
|
122
|
+
created_at: sidecar.created_at,
|
|
123
|
+
updated_at: sidecar.updated_at,
|
|
124
|
+
meta: sidecar.meta,
|
|
125
|
+
...(sidecar.read_only ? { read_only: true } : {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Saves for one project run one at a time. Two processes writing the same
|
|
130
|
+
* project still race — a filesystem gives no atomic compare-and-set — so
|
|
131
|
+
* `expected_updated_at` is a guard within this process and best-effort across
|
|
132
|
+
* processes. Use the Neon provider when several writers are real.
|
|
133
|
+
*/
|
|
134
|
+
async save(project, options) {
|
|
135
|
+
const dir = this.projectDir(project.project_id);
|
|
136
|
+
const previous = this.saveQueue.get(project.project_id) ?? Promise.resolve();
|
|
137
|
+
const run = previous
|
|
138
|
+
.catch(() => undefined)
|
|
139
|
+
.then(() => this.writeProject(dir, project, options));
|
|
140
|
+
this.saveQueue.set(project.project_id, run.catch(() => undefined));
|
|
141
|
+
try {
|
|
142
|
+
return await run;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
if (this.saveQueue.get(project.project_id) === run)
|
|
146
|
+
this.saveQueue.delete(project.project_id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async writeProject(dir, project, options) {
|
|
150
|
+
const existing = await this.get(project.project_id);
|
|
151
|
+
if (existing?.read_only)
|
|
152
|
+
throw new StoryStoreReadOnlyError(project.project_id);
|
|
153
|
+
if (options?.expected_updated_at && existing && existing.updated_at !== options.expected_updated_at) {
|
|
154
|
+
throw new StoryStoreConflictError(project.project_id, options.expected_updated_at, existing.updated_at);
|
|
155
|
+
}
|
|
156
|
+
const saved = {
|
|
157
|
+
...project,
|
|
158
|
+
created_at: existing?.created_at ?? project.created_at,
|
|
159
|
+
updated_at: new Date().toISOString(),
|
|
160
|
+
};
|
|
161
|
+
await mkdir(dir, { recursive: true });
|
|
162
|
+
const sidecar = {
|
|
163
|
+
project_id: saved.project_id,
|
|
164
|
+
created_at: saved.created_at,
|
|
165
|
+
updated_at: saved.updated_at,
|
|
166
|
+
meta: saved.meta,
|
|
167
|
+
...(saved.read_only ? { read_only: true } : {}),
|
|
168
|
+
};
|
|
169
|
+
// Write to a temporary name and rename, so a crash mid-save never leaves a
|
|
170
|
+
// half-written document behind.
|
|
171
|
+
await writeAtomic(join(dir, DOCUMENT_FILE), `${JSON.stringify(saved.document, null, 2)}\n`);
|
|
172
|
+
await writeAtomic(join(dir, PROJECT_FILE), `${JSON.stringify(sidecar, null, 2)}\n`);
|
|
173
|
+
return saved;
|
|
174
|
+
}
|
|
175
|
+
async remove(projectId) {
|
|
176
|
+
const dir = this.projectDir(projectId);
|
|
177
|
+
const existing = await this.get(projectId);
|
|
178
|
+
if (!existing)
|
|
179
|
+
return false;
|
|
180
|
+
if (existing.read_only)
|
|
181
|
+
throw new StoryStoreReadOnlyError(projectId);
|
|
182
|
+
await rm(dir, { recursive: true, force: true });
|
|
183
|
+
this.unreadable.delete(projectId);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function writeAtomic(path, contents) {
|
|
188
|
+
const temporary = `${path}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
189
|
+
await writeFile(temporary, contents, 'utf8');
|
|
190
|
+
await rename(temporary, path);
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/store/json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Dynamic, Moment, Perspective, Player, Story, Storybeat, Storypoint } from '../schema/types.js';
|
|
2
|
+
export declare function setStoryTitle(story: Story, title: string): Story;
|
|
3
|
+
export declare function setStoryMetadata(story: Story, patch: Partial<Pick<Story, 'title' | 'genre' | 'logline'>>): Story;
|
|
4
|
+
/**
|
|
5
|
+
* Patch one beat. `id` in the patch is ignored — an id is identity, and changing
|
|
6
|
+
* it here would produce two beats with the same id. Passing `tones: null` clears
|
|
7
|
+
* the tone object; passing a partial `tones` merges into what is there.
|
|
8
|
+
*/
|
|
9
|
+
export declare function updateStorybeat(story: Story, narrativeId: string, beatId: string, patch: Partial<Omit<Storybeat, 'id'>> & {
|
|
10
|
+
tones?: Storybeat['tones'] | null;
|
|
11
|
+
}): Story;
|
|
12
|
+
export declare function addStorybeat(story: Story, narrativeId: string, beat: Storybeat): Story;
|
|
13
|
+
/** Remove a beat and every moment reference to it. Throws when the id is absent. */
|
|
14
|
+
export declare function removeStorybeat(story: Story, narrativeId: string, beatId: string): Story;
|
|
15
|
+
/**
|
|
16
|
+
* Reorder beats to the given id order and renumber `sequence` from 1, so a
|
|
17
|
+
* drag never leaves holes or duplicate sequences behind.
|
|
18
|
+
*/
|
|
19
|
+
export declare function reorderStorybeats(story: Story, narrativeId: string, orderedBeatIds: readonly string[]): Story;
|
|
20
|
+
export declare function upsertPerspective(story: Story, narrativeId: string, perspective: Perspective): Story;
|
|
21
|
+
export declare function upsertPlayer(story: Story, narrativeId: string, player: Player): Story;
|
|
22
|
+
/**
|
|
23
|
+
* Insert or replace a dynamic. The canonical `dynamic` and `vector` fields are
|
|
24
|
+
* replaced outright rather than merged, so clearing one actually clears it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function upsertDynamic(story: Story, narrativeId: string, dynamic: Dynamic): Story;
|
|
27
|
+
export declare function upsertStorypoint(story: Story, narrativeId: string, storypoint: Storypoint): Story;
|
|
28
|
+
export declare function upsertMoment(story: Story, narrativeId: string, moment: Moment): Story;
|
|
29
|
+
/** Remove one subtext element. Throws when the id is absent, as the beat edits do. */
|
|
30
|
+
export declare function removeElement(story: Story, narrativeId: string, collection: 'perspectives' | 'players' | 'dynamics' | 'storypoints', elementId: string): Story;
|
|
31
|
+
/**
|
|
32
|
+
* An empty story, ready to be filled in by the editor. Fields the caller did
|
|
33
|
+
* not supply are left out rather than set to `undefined`, so a saved document
|
|
34
|
+
* and the object in memory stay comparable.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createEmptyStory(input: {
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
genre?: string;
|
|
40
|
+
logline?: string;
|
|
41
|
+
narrativeId?: string;
|
|
42
|
+
narrativeTitle?: string;
|
|
43
|
+
createdAt?: string;
|
|
44
|
+
}): Story;
|
|
45
|
+
//# sourceMappingURL=mutations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/store/mutations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,MAAM,EAEN,WAAW,EACX,MAAM,EACN,KAAK,EACL,SAAS,EACT,UAAU,EACX,MAAM,oBAAoB,CAAA;AAa3B,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAEhE;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC,GACzD,KAAK,CAEP;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,GAC5E,KAAK,CAoBP;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,KAAK,CAOtF;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAiBxF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAuB7G;AAUD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,KAAK,CAKpG;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAKrF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,KAAK,CAcxF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,KAAK,CAKjG;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAKrF;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,EACnE,SAAS,EAAE,MAAM,GAChB,KAAK,CAaP;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,KAAK,CAgBR"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
function replaceNarrative(story, narrativeId, update) {
|
|
2
|
+
let touched = false;
|
|
3
|
+
const narratives = story.narratives.map((narrative) => {
|
|
4
|
+
if (narrative.id !== narrativeId)
|
|
5
|
+
return narrative;
|
|
6
|
+
touched = true;
|
|
7
|
+
return update(narrative);
|
|
8
|
+
});
|
|
9
|
+
if (!touched)
|
|
10
|
+
throw new Error(`no narrative with id \`${narrativeId}\` in story \`${story.id}\``);
|
|
11
|
+
return { ...story, narratives };
|
|
12
|
+
}
|
|
13
|
+
export function setStoryTitle(story, title) {
|
|
14
|
+
return { ...story, title };
|
|
15
|
+
}
|
|
16
|
+
export function setStoryMetadata(story, patch) {
|
|
17
|
+
return { ...story, ...patch };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Patch one beat. `id` in the patch is ignored — an id is identity, and changing
|
|
21
|
+
* it here would produce two beats with the same id. Passing `tones: null` clears
|
|
22
|
+
* the tone object; passing a partial `tones` merges into what is there.
|
|
23
|
+
*/
|
|
24
|
+
export function updateStorybeat(story, narrativeId, beatId, patch) {
|
|
25
|
+
return replaceNarrative(story, narrativeId, (narrative) => {
|
|
26
|
+
let found = false;
|
|
27
|
+
const storybeats = narrative.subtext.storybeats.map((beat) => {
|
|
28
|
+
if (beat.id !== beatId)
|
|
29
|
+
return beat;
|
|
30
|
+
found = true;
|
|
31
|
+
const { id: _ignoredId, tones: patchTones, ...rest } = patch;
|
|
32
|
+
const next = { ...beat, ...rest, id: beat.id };
|
|
33
|
+
if (patchTones === null) {
|
|
34
|
+
delete next.tones;
|
|
35
|
+
}
|
|
36
|
+
else if (patchTones !== undefined) {
|
|
37
|
+
next.tones = { ...beat.tones, ...patchTones };
|
|
38
|
+
}
|
|
39
|
+
return next;
|
|
40
|
+
});
|
|
41
|
+
if (!found)
|
|
42
|
+
throw new Error(`no storybeat with id \`${beatId}\` in narrative \`${narrativeId}\``);
|
|
43
|
+
return { ...narrative, subtext: { ...narrative.subtext, storybeats } };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function addStorybeat(story, narrativeId, beat) {
|
|
47
|
+
return replaceNarrative(story, narrativeId, (narrative) => {
|
|
48
|
+
if (narrative.subtext.storybeats.some((existing) => existing.id === beat.id)) {
|
|
49
|
+
throw new Error(`storybeat id \`${beat.id}\` already exists in narrative \`${narrativeId}\``);
|
|
50
|
+
}
|
|
51
|
+
return { ...narrative, subtext: { ...narrative.subtext, storybeats: [...narrative.subtext.storybeats, beat] } };
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Remove a beat and every moment reference to it. Throws when the id is absent. */
|
|
55
|
+
export function removeStorybeat(story, narrativeId, beatId) {
|
|
56
|
+
return replaceNarrative(story, narrativeId, (narrative) => {
|
|
57
|
+
if (!narrative.subtext.storybeats.some((beat) => beat.id === beatId)) {
|
|
58
|
+
throw new Error(`no storybeat with id \`${beatId}\` in narrative \`${narrativeId}\``);
|
|
59
|
+
}
|
|
60
|
+
const storybeats = narrative.subtext.storybeats.filter((beat) => beat.id !== beatId);
|
|
61
|
+
const moments = narrative.storytelling.moments.map((moment) => moment.storybeats
|
|
62
|
+
? { ...moment, storybeats: moment.storybeats.filter((ref) => ref.storybeat_id !== beatId) }
|
|
63
|
+
: moment);
|
|
64
|
+
return {
|
|
65
|
+
...narrative,
|
|
66
|
+
subtext: { ...narrative.subtext, storybeats },
|
|
67
|
+
storytelling: { ...narrative.storytelling, moments },
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Reorder beats to the given id order and renumber `sequence` from 1, so a
|
|
73
|
+
* drag never leaves holes or duplicate sequences behind.
|
|
74
|
+
*/
|
|
75
|
+
export function reorderStorybeats(story, narrativeId, orderedBeatIds) {
|
|
76
|
+
return replaceNarrative(story, narrativeId, (narrative) => {
|
|
77
|
+
const beats = narrative.subtext.storybeats;
|
|
78
|
+
const byId = new Map(beats.map((beat) => [beat.id, beat]));
|
|
79
|
+
const distinct = new Set(orderedBeatIds);
|
|
80
|
+
if (orderedBeatIds.length !== beats.length ||
|
|
81
|
+
distinct.size !== orderedBeatIds.length ||
|
|
82
|
+
byId.size !== beats.length) {
|
|
83
|
+
throw new Error(`reorder needs every beat id exactly once: got ${orderedBeatIds.length} ids ` +
|
|
84
|
+
`(${distinct.size} distinct) for ${beats.length} beats ` +
|
|
85
|
+
`(${byId.size} distinct ids in the narrative)`);
|
|
86
|
+
}
|
|
87
|
+
const storybeats = orderedBeatIds.map((beatId, index) => {
|
|
88
|
+
const beat = byId.get(beatId);
|
|
89
|
+
if (!beat)
|
|
90
|
+
throw new Error(`no storybeat with id \`${beatId}\` in narrative \`${narrativeId}\``);
|
|
91
|
+
return { ...beat, sequence: index + 1 };
|
|
92
|
+
});
|
|
93
|
+
return { ...narrative, subtext: { ...narrative.subtext, storybeats } };
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function upsertById(items, item) {
|
|
97
|
+
const index = items.findIndex((candidate) => candidate.id === item.id);
|
|
98
|
+
if (index === -1)
|
|
99
|
+
return [...items, item];
|
|
100
|
+
const next = [...items];
|
|
101
|
+
next[index] = { ...items[index], ...item };
|
|
102
|
+
return next;
|
|
103
|
+
}
|
|
104
|
+
export function upsertPerspective(story, narrativeId, perspective) {
|
|
105
|
+
return replaceNarrative(story, narrativeId, (narrative) => ({
|
|
106
|
+
...narrative,
|
|
107
|
+
subtext: { ...narrative.subtext, perspectives: upsertById(narrative.subtext.perspectives, perspective) },
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
export function upsertPlayer(story, narrativeId, player) {
|
|
111
|
+
return replaceNarrative(story, narrativeId, (narrative) => ({
|
|
112
|
+
...narrative,
|
|
113
|
+
subtext: { ...narrative.subtext, players: upsertById(narrative.subtext.players, player) },
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Insert or replace a dynamic. The canonical `dynamic` and `vector` fields are
|
|
118
|
+
* replaced outright rather than merged, so clearing one actually clears it.
|
|
119
|
+
*/
|
|
120
|
+
export function upsertDynamic(story, narrativeId, dynamic) {
|
|
121
|
+
return replaceNarrative(story, narrativeId, (narrative) => {
|
|
122
|
+
const dynamics = narrative.subtext.dynamics;
|
|
123
|
+
const index = dynamics.findIndex((candidate) => candidate.id === dynamic.id);
|
|
124
|
+
const next = index === -1
|
|
125
|
+
? [...dynamics, dynamic]
|
|
126
|
+
: dynamics.map((candidate, candidateIndex) => candidateIndex === index
|
|
127
|
+
? { ...candidate, ...dynamic, dynamic: dynamic.dynamic, vector: dynamic.vector }
|
|
128
|
+
: candidate);
|
|
129
|
+
return { ...narrative, subtext: { ...narrative.subtext, dynamics: next } };
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
export function upsertStorypoint(story, narrativeId, storypoint) {
|
|
133
|
+
return replaceNarrative(story, narrativeId, (narrative) => ({
|
|
134
|
+
...narrative,
|
|
135
|
+
subtext: { ...narrative.subtext, storypoints: upsertById(narrative.subtext.storypoints, storypoint) },
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
export function upsertMoment(story, narrativeId, moment) {
|
|
139
|
+
return replaceNarrative(story, narrativeId, (narrative) => ({
|
|
140
|
+
...narrative,
|
|
141
|
+
storytelling: { ...narrative.storytelling, moments: upsertById(narrative.storytelling.moments, moment) },
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
/** Remove one subtext element. Throws when the id is absent, as the beat edits do. */
|
|
145
|
+
export function removeElement(story, narrativeId, collection, elementId) {
|
|
146
|
+
return replaceNarrative(story, narrativeId, (narrative) => {
|
|
147
|
+
if (!narrative.subtext[collection].some((element) => element.id === elementId)) {
|
|
148
|
+
throw new Error(`no ${collection} element with id \`${elementId}\` in narrative \`${narrativeId}\``);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
...narrative,
|
|
152
|
+
subtext: {
|
|
153
|
+
...narrative.subtext,
|
|
154
|
+
[collection]: narrative.subtext[collection].filter((element) => element.id !== elementId),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* An empty story, ready to be filled in by the editor. Fields the caller did
|
|
161
|
+
* not supply are left out rather than set to `undefined`, so a saved document
|
|
162
|
+
* and the object in memory stay comparable.
|
|
163
|
+
*/
|
|
164
|
+
export function createEmptyStory(input) {
|
|
165
|
+
return {
|
|
166
|
+
id: input.id,
|
|
167
|
+
title: input.title,
|
|
168
|
+
...(input.genre === undefined ? {} : { genre: input.genre }),
|
|
169
|
+
...(input.logline === undefined ? {} : { logline: input.logline }),
|
|
170
|
+
created_at: input.createdAt ?? new Date().toISOString(),
|
|
171
|
+
narratives: [
|
|
172
|
+
{
|
|
173
|
+
id: input.narrativeId ?? `${input.id}-n1`,
|
|
174
|
+
title: input.narrativeTitle ?? input.title,
|
|
175
|
+
subtext: { perspectives: [], players: [], dynamics: [], storypoints: [], storybeats: [] },
|
|
176
|
+
storytelling: { overviews: [], moments: [] },
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=mutations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutations.js","sourceRoot":"","sources":["../../src/store/mutations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type SaveOptions, type StoryProject, type StoryProjectSummary, type StoryStore } from './index.js';
|
|
2
|
+
/** The slice of the Neon driver this store uses: `sql.query(text, values)`. */
|
|
3
|
+
export type NeonQuery = <T = Record<string, unknown>>(text: string, values?: unknown[]) => Promise<T[] | {
|
|
4
|
+
rows: T[];
|
|
5
|
+
}>;
|
|
6
|
+
export interface NeonStoryStoreOptions {
|
|
7
|
+
/** Postgres connection string. Never logged, never returned by `location`. */
|
|
8
|
+
connectionString?: string;
|
|
9
|
+
/** Supply a query function directly (tests, or a client the host owns). */
|
|
10
|
+
query?: NeonQuery;
|
|
11
|
+
/** Table name, default `ncp_story_projects`. */
|
|
12
|
+
table?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class NeonStoryStore implements StoryStore {
|
|
15
|
+
readonly kind = "neon";
|
|
16
|
+
private readonly table;
|
|
17
|
+
private queryFn;
|
|
18
|
+
private readonly connectionString;
|
|
19
|
+
private initialized;
|
|
20
|
+
/** Rows `list()` could not read, and why. */
|
|
21
|
+
private readonly unreadable;
|
|
22
|
+
/** What the last `list()` could not read. Empty when every row read cleanly. */
|
|
23
|
+
problems(): {
|
|
24
|
+
project_id: string;
|
|
25
|
+
error: string;
|
|
26
|
+
}[];
|
|
27
|
+
constructor(options: NeonStoryStoreOptions);
|
|
28
|
+
/** Host and database only: never the credentials. */
|
|
29
|
+
get location(): string;
|
|
30
|
+
private query;
|
|
31
|
+
init(): Promise<void>;
|
|
32
|
+
list(): Promise<StoryProjectSummary[]>;
|
|
33
|
+
get(projectId: string): Promise<StoryProject | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* The version guard is part of the statement, not a read before it: the
|
|
36
|
+
* `ON CONFLICT` update only fires while the stored `updated_at` still matches,
|
|
37
|
+
* so two concurrent writers cannot both succeed.
|
|
38
|
+
*/
|
|
39
|
+
save(project: StoryProject, options?: SaveOptions): Promise<StoryProject>;
|
|
40
|
+
remove(projectId: string): Promise<boolean>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=neon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neon.d.ts","sourceRoot":"","sources":["../../src/store/neon.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,YAAY,EAEjB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAA;AAEnB,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC,EAAE,GAAG;IAAE,IAAI,EAAE,CAAC,EAAE,CAAA;CAAE,CAAC,CAAA;AAEvH,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiDD,qBAAa,cAAe,YAAW,UAAU;IAC/C,QAAQ,CAAC,IAAI,UAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;IACrD,OAAO,CAAC,WAAW,CAAQ;IAC3B,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4B;IAEvD,gFAAgF;IAChF,QAAQ,IAAI;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE;gBAIvC,OAAO,EAAE,qBAAqB;IAS1C,qDAAqD;IACrD,IAAI,QAAQ,IAAI,MAAM,CAQrB;YAEa,KAAK;IAoBb,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAcrB,IAAI,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAgBtC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAU/D;;;;OAIG;IACG,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA4CzE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUlD"}
|