@game-infra/story-schemas 0.2.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/README.md +133 -0
- package/dist/contracts.d.ts +165 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +104 -0
- package/dist/contracts.js.map +1 -0
- package/dist/designContent.d.ts +236 -0
- package/dist/designContent.d.ts.map +1 -0
- package/dist/designContent.js +195 -0
- package/dist/designContent.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/nodePaths.d.ts +107 -0
- package/dist/nodePaths.d.ts.map +1 -0
- package/dist/nodePaths.js +123 -0
- package/dist/nodePaths.js.map +1 -0
- package/dist/storyPayloads.d.ts +226 -0
- package/dist/storyPayloads.d.ts.map +1 -0
- package/dist/storyPayloads.js +142 -0
- package/dist/storyPayloads.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { type InferOutput } from "valibot";
|
|
2
|
+
/**
|
|
3
|
+
* The transport for everything an author writes: worlds, the content written
|
|
4
|
+
* against them (locations, characters, skill definitions, lore entries), the
|
|
5
|
+
* story arcs chosen for a world, and the beats those arcs are told in.
|
|
6
|
+
*
|
|
7
|
+
* All of it is one shape — a keyed JSON payload, scoped to a parent, tagged
|
|
8
|
+
* with a type — so it is one resource rather than a family of near-identical
|
|
9
|
+
* ones. The service stores and indexes `data`; it never parses it. That is the
|
|
10
|
+
* same rule `GameEntitySaveRequestSchema` follows in `@game-infra/api-schemas-core`,
|
|
11
|
+
* and it is what lets a game's design vocabulary move without a contract bump.
|
|
12
|
+
*
|
|
13
|
+
* The shapes that go *inside* `data` are typed too — see `storyPayloads.ts` —
|
|
14
|
+
* but as schemas a client parses with, not as part of any endpoint.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The scope of a row that has no parent. A world is scoped to nothing, but the
|
|
18
|
+
* key `(contentType, scopeId, contentId)` still needs three parts and a URL
|
|
19
|
+
* cannot carry an empty path segment, so unparented rows spell their scope `-`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { ROOT_SCOPE, designContentLoadContract } from '@game-infra/story-schemas'
|
|
24
|
+
*
|
|
25
|
+
* designContentLoadContract.pathResolver({
|
|
26
|
+
* gameId: 'g1', contentType: 'world', scopeId: ROOT_SCOPE, contentId: 'the-ash-reckoning',
|
|
27
|
+
* }) // '/games/g1/design-content/world/-/the-ash-reckoning'
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const ROOT_SCOPE = "-";
|
|
31
|
+
/**
|
|
32
|
+
* The vocabulary this contract was written against, and how each type is scoped:
|
|
33
|
+
*
|
|
34
|
+
* | Type | Scoped to |
|
|
35
|
+
* | ------------------------------------------------------------- | ------------ |
|
|
36
|
+
* | `world` | {@link ROOT_SCOPE} |
|
|
37
|
+
* | `locations`, `characters`, `events`, `stats`, `skills`, `perks`, `resources`, `lore` | a world id |
|
|
38
|
+
* | `story-arc` | a world id |
|
|
39
|
+
* | `story-node` | a story id |
|
|
40
|
+
*
|
|
41
|
+
* `contentType` is a plain `string()` in every schema below — a game re-tightens
|
|
42
|
+
* it in its own validation layer — so treat this as the suggested set, not a
|
|
43
|
+
* closed one. The plural spellings match the collections of a design document;
|
|
44
|
+
* the singular ones name things there is one of.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { DESIGN_CONTENT_TYPES } from '@game-infra/story-schemas'
|
|
49
|
+
*
|
|
50
|
+
* DESIGN_CONTENT_TYPES.includes('story-node') // true
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare const DESIGN_CONTENT_TYPES: readonly ["world", "locations", "characters", "events", "stats", "skills", "perks", "resources", "lore", "story-arc", "story-node"];
|
|
54
|
+
/** One of {@link DESIGN_CONTENT_TYPES}, widened to `string` at the wire boundary. */
|
|
55
|
+
export type DesignContentType = (typeof DESIGN_CONTENT_TYPES)[number];
|
|
56
|
+
/**
|
|
57
|
+
* One row.
|
|
58
|
+
*
|
|
59
|
+
* `data` is present only when the request asked for it — a listing returns
|
|
60
|
+
* metadata by default, because a world picker needs names and counts, not
|
|
61
|
+
* every character's biography.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import type { DesignContentRecord } from '@game-infra/story-schemas'
|
|
66
|
+
*
|
|
67
|
+
* const row: DesignContentRecord = {
|
|
68
|
+
* contentType: 'characters',
|
|
69
|
+
* scopeId: 'the-ash-reckoning',
|
|
70
|
+
* contentId: 'tallow',
|
|
71
|
+
* name: 'Tallow',
|
|
72
|
+
* data: JSON.stringify({ id: 'tallow', name: 'Tallow', role: 'Drift-runner' }),
|
|
73
|
+
* createdAt: '2026-08-29T09:00:00.000Z',
|
|
74
|
+
* updatedAt: '2026-08-29T09:30:00.000Z',
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare const DesignContentRecordSchema: import("valibot").ObjectSchema<{
|
|
79
|
+
readonly contentType: import("valibot").StringSchema<undefined>;
|
|
80
|
+
readonly scopeId: import("valibot").StringSchema<undefined>;
|
|
81
|
+
readonly contentId: import("valibot").StringSchema<undefined>;
|
|
82
|
+
/** For display in a picker, so a list need not carry payloads to be readable. */
|
|
83
|
+
readonly name: import("valibot").StringSchema<undefined>;
|
|
84
|
+
readonly data: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
85
|
+
readonly createdAt: import("valibot").StringSchema<undefined>;
|
|
86
|
+
readonly updatedAt: import("valibot").StringSchema<undefined>;
|
|
87
|
+
}, undefined>;
|
|
88
|
+
export type DesignContentRecord = InferOutput<typeof DesignContentRecordSchema>;
|
|
89
|
+
/**
|
|
90
|
+
* Create or replace one row. Re-saving the same key keeps its `createdAt`.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import type { DesignContentSaveRequest } from '@game-infra/story-schemas'
|
|
95
|
+
*
|
|
96
|
+
* const request: DesignContentSaveRequest = {
|
|
97
|
+
* contentType: 'story-node',
|
|
98
|
+
* scopeId: 'the-quiet-forge-answers',
|
|
99
|
+
* contentId: 'root.take-the-tunnel',
|
|
100
|
+
* name: 'take-the-tunnel',
|
|
101
|
+
* data: JSON.stringify({ narration: 'The tunnel breathes out ash…', choices: [] }),
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare const DesignContentSaveRequestSchema: import("valibot").ObjectSchema<{
|
|
106
|
+
readonly contentType: import("valibot").StringSchema<undefined>;
|
|
107
|
+
readonly scopeId: import("valibot").StringSchema<undefined>;
|
|
108
|
+
readonly contentId: import("valibot").StringSchema<undefined>;
|
|
109
|
+
readonly name: import("valibot").StringSchema<undefined>;
|
|
110
|
+
readonly data: import("valibot").StringSchema<undefined>;
|
|
111
|
+
}, undefined>;
|
|
112
|
+
export type DesignContentSaveRequest = InferOutput<typeof DesignContentSaveRequestSchema>;
|
|
113
|
+
/**
|
|
114
|
+
* What a bulk save clears before it writes.
|
|
115
|
+
*
|
|
116
|
+
* Omitting `contentType` sweeps the whole scope. Naming one sweeps just that
|
|
117
|
+
* type within it, which is the usual case: "these are all the locations for
|
|
118
|
+
* this world" has to remove the location the author deleted locally, or it
|
|
119
|
+
* would survive on the server forever.
|
|
120
|
+
*/
|
|
121
|
+
export declare const DesignContentReplaceScopeSchema: import("valibot").ObjectSchema<{
|
|
122
|
+
readonly scopeId: import("valibot").StringSchema<undefined>;
|
|
123
|
+
readonly contentType: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
124
|
+
}, undefined>;
|
|
125
|
+
export type DesignContentReplaceScope = InferOutput<typeof DesignContentReplaceScopeSchema>;
|
|
126
|
+
/**
|
|
127
|
+
* Write many rows at once, optionally as a snapshot rather than a merge.
|
|
128
|
+
*
|
|
129
|
+
* A whole design document — the world plus every collection — lands in one
|
|
130
|
+
* request this way.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* import type { DesignContentBulkSaveRequest } from '@game-infra/story-schemas'
|
|
135
|
+
*
|
|
136
|
+
* const request: DesignContentBulkSaveRequest = {
|
|
137
|
+
* items: [
|
|
138
|
+
* {
|
|
139
|
+
* contentType: 'locations',
|
|
140
|
+
* scopeId: 'the-ash-reckoning',
|
|
141
|
+
* contentId: 'cinderhold',
|
|
142
|
+
* name: 'Cinderhold',
|
|
143
|
+
* data: '{}',
|
|
144
|
+
* },
|
|
145
|
+
* ],
|
|
146
|
+
* replace: [{ scopeId: 'the-ash-reckoning', contentType: 'locations' }],
|
|
147
|
+
* }
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export declare const DesignContentBulkSaveRequestSchema: import("valibot").ObjectSchema<{
|
|
151
|
+
readonly items: import("valibot").ArraySchema<import("valibot").ObjectSchema<{
|
|
152
|
+
readonly contentType: import("valibot").StringSchema<undefined>;
|
|
153
|
+
readonly scopeId: import("valibot").StringSchema<undefined>;
|
|
154
|
+
readonly contentId: import("valibot").StringSchema<undefined>;
|
|
155
|
+
readonly name: import("valibot").StringSchema<undefined>;
|
|
156
|
+
readonly data: import("valibot").StringSchema<undefined>;
|
|
157
|
+
}, undefined>, undefined>;
|
|
158
|
+
readonly replace: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").ObjectSchema<{
|
|
159
|
+
readonly scopeId: import("valibot").StringSchema<undefined>;
|
|
160
|
+
readonly contentType: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
161
|
+
}, undefined>, undefined>, undefined>;
|
|
162
|
+
}, undefined>;
|
|
163
|
+
export type DesignContentBulkSaveRequest = InferOutput<typeof DesignContentBulkSaveRequestSchema>;
|
|
164
|
+
/**
|
|
165
|
+
* How far a delete reaches.
|
|
166
|
+
*
|
|
167
|
+
* - `none` — the row alone.
|
|
168
|
+
* - `prefix` — the row and every row whose `contentId` sits under it. Story
|
|
169
|
+
* beats are addressed by the trail of choices taken to reach them, so this is
|
|
170
|
+
* how discarding a branch takes its descendants with it.
|
|
171
|
+
* - `scope` — the row and everything scoped to it, transitively. Deleting a
|
|
172
|
+
* world this way also removes its content, its story arcs, and their beats.
|
|
173
|
+
*/
|
|
174
|
+
export declare const DESIGN_CONTENT_CASCADES: readonly ["none", "prefix", "scope"];
|
|
175
|
+
export declare const DesignContentCascadeSchema: import("valibot").PicklistSchema<readonly ["none", "prefix", "scope"], undefined>;
|
|
176
|
+
export type DesignContentCascade = InferOutput<typeof DesignContentCascadeSchema>;
|
|
177
|
+
/** Rows matching the listing, most recently edited first. */
|
|
178
|
+
export declare const DesignContentListResponseSchema: import("valibot").ObjectSchema<{
|
|
179
|
+
readonly success: import("valibot").BooleanSchema<undefined>;
|
|
180
|
+
readonly error: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
181
|
+
} & {
|
|
182
|
+
items: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").ObjectSchema<{
|
|
183
|
+
readonly contentType: import("valibot").StringSchema<undefined>;
|
|
184
|
+
readonly scopeId: import("valibot").StringSchema<undefined>;
|
|
185
|
+
readonly contentId: import("valibot").StringSchema<undefined>;
|
|
186
|
+
/** For display in a picker, so a list need not carry payloads to be readable. */
|
|
187
|
+
readonly name: import("valibot").StringSchema<undefined>;
|
|
188
|
+
readonly data: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
189
|
+
readonly createdAt: import("valibot").StringSchema<undefined>;
|
|
190
|
+
readonly updatedAt: import("valibot").StringSchema<undefined>;
|
|
191
|
+
}, undefined>, undefined>, undefined>;
|
|
192
|
+
}, undefined>;
|
|
193
|
+
export type DesignContentListResponse = InferOutput<typeof DesignContentListResponseSchema>;
|
|
194
|
+
/** One row, absent when nothing is stored under that key. */
|
|
195
|
+
export declare const DesignContentLoadResponseSchema: import("valibot").ObjectSchema<{
|
|
196
|
+
readonly success: import("valibot").BooleanSchema<undefined>;
|
|
197
|
+
readonly error: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
198
|
+
} & {
|
|
199
|
+
item: import("valibot").OptionalSchema<import("valibot").ObjectSchema<{
|
|
200
|
+
readonly contentType: import("valibot").StringSchema<undefined>;
|
|
201
|
+
readonly scopeId: import("valibot").StringSchema<undefined>;
|
|
202
|
+
readonly contentId: import("valibot").StringSchema<undefined>;
|
|
203
|
+
/** For display in a picker, so a list need not carry payloads to be readable. */
|
|
204
|
+
readonly name: import("valibot").StringSchema<undefined>;
|
|
205
|
+
readonly data: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
206
|
+
readonly createdAt: import("valibot").StringSchema<undefined>;
|
|
207
|
+
readonly updatedAt: import("valibot").StringSchema<undefined>;
|
|
208
|
+
}, undefined>, undefined>;
|
|
209
|
+
}, undefined>;
|
|
210
|
+
export type DesignContentLoadResponse = InferOutput<typeof DesignContentLoadResponseSchema>;
|
|
211
|
+
/** The saved row's id, echoed back. */
|
|
212
|
+
export declare const DesignContentSaveResponseSchema: import("valibot").ObjectSchema<{
|
|
213
|
+
readonly success: import("valibot").BooleanSchema<undefined>;
|
|
214
|
+
readonly error: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
215
|
+
} & {
|
|
216
|
+
contentId: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
217
|
+
}, undefined>;
|
|
218
|
+
export type DesignContentSaveResponse = InferOutput<typeof DesignContentSaveResponseSchema>;
|
|
219
|
+
/** How many rows a bulk save wrote, and how many its `replace` sweep removed. */
|
|
220
|
+
export declare const DesignContentBulkSaveResponseSchema: import("valibot").ObjectSchema<{
|
|
221
|
+
readonly success: import("valibot").BooleanSchema<undefined>;
|
|
222
|
+
readonly error: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
223
|
+
} & {
|
|
224
|
+
savedCount: import("valibot").OptionalSchema<import("valibot").NumberSchema<undefined>, undefined>;
|
|
225
|
+
removedCount: import("valibot").OptionalSchema<import("valibot").NumberSchema<undefined>, undefined>;
|
|
226
|
+
}, undefined>;
|
|
227
|
+
export type DesignContentBulkSaveResponse = InferOutput<typeof DesignContentBulkSaveResponseSchema>;
|
|
228
|
+
/** How many rows the delete removed — 0 when nothing matched. */
|
|
229
|
+
export declare const DesignContentDeleteResponseSchema: import("valibot").ObjectSchema<{
|
|
230
|
+
readonly success: import("valibot").BooleanSchema<undefined>;
|
|
231
|
+
readonly error: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
232
|
+
} & {
|
|
233
|
+
deletedCount: import("valibot").OptionalSchema<import("valibot").NumberSchema<undefined>, undefined>;
|
|
234
|
+
}, undefined>;
|
|
235
|
+
export type DesignContentDeleteResponse = InferOutput<typeof DesignContentDeleteResponseSchema>;
|
|
236
|
+
//# sourceMappingURL=designContent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"designContent.d.ts","sourceRoot":"","sources":["../src/designContent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAAqD,MAAM,SAAS,CAAC;AAE9F;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU,MAAM,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oBAAoB,YAC/B,OAAO,EACP,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,WAAW,EACX,MAAM,EACN,WAAW,EACX,YAAY,CACJ,CAAC;AAEX,qFAAqF;AACrF,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,yBAAyB;;;;IAIpC,iFAAiF;;;;;aAKjF,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,8BAA8B;;;;;;aAMzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,WAAW,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAE1F;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B;;;aAG1C,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,WAAW,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAE5F;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kCAAkC;;;;;;;;;;;;aAG7C,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,WAAW,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAElG;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,YAAI,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AAE5E,eAAO,MAAM,0BAA0B,mFAAoC,CAAC;AAE5E,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAElF,6DAA6D;AAC7D,eAAO,MAAM,+BAA+B;;;;;;;;QAlG1C,iFAAiF;;;;;;aAoGjF,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,WAAW,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAE5F,6DAA6D;AAC7D,eAAO,MAAM,+BAA+B;;;;;;;;QAzG1C,iFAAiF;;;;;;aA2GjF,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,WAAW,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAE5F,uCAAuC;AACvC,eAAO,MAAM,+BAA+B;;;;;aAE1C,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,WAAW,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAE5F,iFAAiF;AACjF,eAAO,MAAM,mCAAmC;;;;;;aAG9C,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,WAAW,CAAC,OAAO,mCAAmC,CAAC,CAAC;AAEpG,iEAAiE;AACjE,eAAO,MAAM,iCAAiC;;;;;aAE5C,CAAC;AAEH,MAAM,MAAM,2BAA2B,GAAG,WAAW,CAAC,OAAO,iCAAiC,CAAC,CAAC"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { createSuccessResponseSchema } from "@game-infra/api-schemas-core";
|
|
2
|
+
import { array, number, object, optional, picklist, string } from "valibot";
|
|
3
|
+
/**
|
|
4
|
+
* The transport for everything an author writes: worlds, the content written
|
|
5
|
+
* against them (locations, characters, skill definitions, lore entries), the
|
|
6
|
+
* story arcs chosen for a world, and the beats those arcs are told in.
|
|
7
|
+
*
|
|
8
|
+
* All of it is one shape — a keyed JSON payload, scoped to a parent, tagged
|
|
9
|
+
* with a type — so it is one resource rather than a family of near-identical
|
|
10
|
+
* ones. The service stores and indexes `data`; it never parses it. That is the
|
|
11
|
+
* same rule `GameEntitySaveRequestSchema` follows in `@game-infra/api-schemas-core`,
|
|
12
|
+
* and it is what lets a game's design vocabulary move without a contract bump.
|
|
13
|
+
*
|
|
14
|
+
* The shapes that go *inside* `data` are typed too — see `storyPayloads.ts` —
|
|
15
|
+
* but as schemas a client parses with, not as part of any endpoint.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The scope of a row that has no parent. A world is scoped to nothing, but the
|
|
19
|
+
* key `(contentType, scopeId, contentId)` still needs three parts and a URL
|
|
20
|
+
* cannot carry an empty path segment, so unparented rows spell their scope `-`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { ROOT_SCOPE, designContentLoadContract } from '@game-infra/story-schemas'
|
|
25
|
+
*
|
|
26
|
+
* designContentLoadContract.pathResolver({
|
|
27
|
+
* gameId: 'g1', contentType: 'world', scopeId: ROOT_SCOPE, contentId: 'the-ash-reckoning',
|
|
28
|
+
* }) // '/games/g1/design-content/world/-/the-ash-reckoning'
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const ROOT_SCOPE = "-";
|
|
32
|
+
/**
|
|
33
|
+
* The vocabulary this contract was written against, and how each type is scoped:
|
|
34
|
+
*
|
|
35
|
+
* | Type | Scoped to |
|
|
36
|
+
* | ------------------------------------------------------------- | ------------ |
|
|
37
|
+
* | `world` | {@link ROOT_SCOPE} |
|
|
38
|
+
* | `locations`, `characters`, `events`, `stats`, `skills`, `perks`, `resources`, `lore` | a world id |
|
|
39
|
+
* | `story-arc` | a world id |
|
|
40
|
+
* | `story-node` | a story id |
|
|
41
|
+
*
|
|
42
|
+
* `contentType` is a plain `string()` in every schema below — a game re-tightens
|
|
43
|
+
* it in its own validation layer — so treat this as the suggested set, not a
|
|
44
|
+
* closed one. The plural spellings match the collections of a design document;
|
|
45
|
+
* the singular ones name things there is one of.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { DESIGN_CONTENT_TYPES } from '@game-infra/story-schemas'
|
|
50
|
+
*
|
|
51
|
+
* DESIGN_CONTENT_TYPES.includes('story-node') // true
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export const DESIGN_CONTENT_TYPES = [
|
|
55
|
+
"world",
|
|
56
|
+
"locations",
|
|
57
|
+
"characters",
|
|
58
|
+
"events",
|
|
59
|
+
"stats",
|
|
60
|
+
"skills",
|
|
61
|
+
"perks",
|
|
62
|
+
"resources",
|
|
63
|
+
"lore",
|
|
64
|
+
"story-arc",
|
|
65
|
+
"story-node",
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* One row.
|
|
69
|
+
*
|
|
70
|
+
* `data` is present only when the request asked for it — a listing returns
|
|
71
|
+
* metadata by default, because a world picker needs names and counts, not
|
|
72
|
+
* every character's biography.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* import type { DesignContentRecord } from '@game-infra/story-schemas'
|
|
77
|
+
*
|
|
78
|
+
* const row: DesignContentRecord = {
|
|
79
|
+
* contentType: 'characters',
|
|
80
|
+
* scopeId: 'the-ash-reckoning',
|
|
81
|
+
* contentId: 'tallow',
|
|
82
|
+
* name: 'Tallow',
|
|
83
|
+
* data: JSON.stringify({ id: 'tallow', name: 'Tallow', role: 'Drift-runner' }),
|
|
84
|
+
* createdAt: '2026-08-29T09:00:00.000Z',
|
|
85
|
+
* updatedAt: '2026-08-29T09:30:00.000Z',
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export const DesignContentRecordSchema = object({
|
|
90
|
+
contentType: string(),
|
|
91
|
+
scopeId: string(),
|
|
92
|
+
contentId: string(),
|
|
93
|
+
/** For display in a picker, so a list need not carry payloads to be readable. */
|
|
94
|
+
name: string(),
|
|
95
|
+
data: optional(string()),
|
|
96
|
+
createdAt: string(),
|
|
97
|
+
updatedAt: string(),
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* Create or replace one row. Re-saving the same key keeps its `createdAt`.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import type { DesignContentSaveRequest } from '@game-infra/story-schemas'
|
|
105
|
+
*
|
|
106
|
+
* const request: DesignContentSaveRequest = {
|
|
107
|
+
* contentType: 'story-node',
|
|
108
|
+
* scopeId: 'the-quiet-forge-answers',
|
|
109
|
+
* contentId: 'root.take-the-tunnel',
|
|
110
|
+
* name: 'take-the-tunnel',
|
|
111
|
+
* data: JSON.stringify({ narration: 'The tunnel breathes out ash…', choices: [] }),
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export const DesignContentSaveRequestSchema = object({
|
|
116
|
+
contentType: string(),
|
|
117
|
+
scopeId: string(),
|
|
118
|
+
contentId: string(),
|
|
119
|
+
name: string(),
|
|
120
|
+
data: string(),
|
|
121
|
+
});
|
|
122
|
+
/**
|
|
123
|
+
* What a bulk save clears before it writes.
|
|
124
|
+
*
|
|
125
|
+
* Omitting `contentType` sweeps the whole scope. Naming one sweeps just that
|
|
126
|
+
* type within it, which is the usual case: "these are all the locations for
|
|
127
|
+
* this world" has to remove the location the author deleted locally, or it
|
|
128
|
+
* would survive on the server forever.
|
|
129
|
+
*/
|
|
130
|
+
export const DesignContentReplaceScopeSchema = object({
|
|
131
|
+
scopeId: string(),
|
|
132
|
+
contentType: optional(string()),
|
|
133
|
+
});
|
|
134
|
+
/**
|
|
135
|
+
* Write many rows at once, optionally as a snapshot rather than a merge.
|
|
136
|
+
*
|
|
137
|
+
* A whole design document — the world plus every collection — lands in one
|
|
138
|
+
* request this way.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* import type { DesignContentBulkSaveRequest } from '@game-infra/story-schemas'
|
|
143
|
+
*
|
|
144
|
+
* const request: DesignContentBulkSaveRequest = {
|
|
145
|
+
* items: [
|
|
146
|
+
* {
|
|
147
|
+
* contentType: 'locations',
|
|
148
|
+
* scopeId: 'the-ash-reckoning',
|
|
149
|
+
* contentId: 'cinderhold',
|
|
150
|
+
* name: 'Cinderhold',
|
|
151
|
+
* data: '{}',
|
|
152
|
+
* },
|
|
153
|
+
* ],
|
|
154
|
+
* replace: [{ scopeId: 'the-ash-reckoning', contentType: 'locations' }],
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export const DesignContentBulkSaveRequestSchema = object({
|
|
159
|
+
items: array(DesignContentSaveRequestSchema),
|
|
160
|
+
replace: optional(array(DesignContentReplaceScopeSchema)),
|
|
161
|
+
});
|
|
162
|
+
/**
|
|
163
|
+
* How far a delete reaches.
|
|
164
|
+
*
|
|
165
|
+
* - `none` — the row alone.
|
|
166
|
+
* - `prefix` — the row and every row whose `contentId` sits under it. Story
|
|
167
|
+
* beats are addressed by the trail of choices taken to reach them, so this is
|
|
168
|
+
* how discarding a branch takes its descendants with it.
|
|
169
|
+
* - `scope` — the row and everything scoped to it, transitively. Deleting a
|
|
170
|
+
* world this way also removes its content, its story arcs, and their beats.
|
|
171
|
+
*/
|
|
172
|
+
export const DESIGN_CONTENT_CASCADES = ["none", "prefix", "scope"];
|
|
173
|
+
export const DesignContentCascadeSchema = picklist(DESIGN_CONTENT_CASCADES);
|
|
174
|
+
/** Rows matching the listing, most recently edited first. */
|
|
175
|
+
export const DesignContentListResponseSchema = createSuccessResponseSchema({
|
|
176
|
+
items: optional(array(DesignContentRecordSchema)),
|
|
177
|
+
});
|
|
178
|
+
/** One row, absent when nothing is stored under that key. */
|
|
179
|
+
export const DesignContentLoadResponseSchema = createSuccessResponseSchema({
|
|
180
|
+
item: optional(DesignContentRecordSchema),
|
|
181
|
+
});
|
|
182
|
+
/** The saved row's id, echoed back. */
|
|
183
|
+
export const DesignContentSaveResponseSchema = createSuccessResponseSchema({
|
|
184
|
+
contentId: optional(string()),
|
|
185
|
+
});
|
|
186
|
+
/** How many rows a bulk save wrote, and how many its `replace` sweep removed. */
|
|
187
|
+
export const DesignContentBulkSaveResponseSchema = createSuccessResponseSchema({
|
|
188
|
+
savedCount: optional(number()),
|
|
189
|
+
removedCount: optional(number()),
|
|
190
|
+
});
|
|
191
|
+
/** How many rows the delete removed — 0 when nothing matched. */
|
|
192
|
+
export const DesignContentDeleteResponseSchema = createSuccessResponseSchema({
|
|
193
|
+
deletedCount: optional(number()),
|
|
194
|
+
});
|
|
195
|
+
//# sourceMappingURL=designContent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"designContent.js","sourceRoot":"","sources":["../src/designContent.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { DESIGN_CONTENT_CASCADES, DESIGN_CONTENT_TYPES, DesignContentBulkSaveRequestSchema, DesignContentBulkSaveResponseSchema, DesignContentCascadeSchema, DesignContentDeleteResponseSchema, DesignContentListResponseSchema, DesignContentLoadResponseSchema, DesignContentRecordSchema, DesignContentReplaceScopeSchema, DesignContentSaveRequestSchema, DesignContentSaveResponseSchema, ROOT_SCOPE, } from "./designContent.js";
|
|
2
|
+
export type { DesignContentBulkSaveRequest, DesignContentBulkSaveResponse, DesignContentCascade, DesignContentDeleteResponse, DesignContentListResponse, DesignContentLoadResponse, DesignContentRecord, DesignContentReplaceScope, DesignContentSaveRequest, DesignContentSaveResponse, DesignContentType, } from "./designContent.js";
|
|
3
|
+
export { ActivationDTOSchema, ChoiceDefinitionDTOSchema, ConsumableConditionDTOSchema, EncounterStageDTOSchema, PreconditionDTOSchema, STORY_BEAT_ORIGINS, StoryArcPayloadSchema, StoryBeatOriginSchema, StoryBeatPayloadSchema, WorldPayloadSchema, } from "./storyPayloads.js";
|
|
4
|
+
export type { ActivationDTO, ChoiceDefinitionDTO, ConsumableConditionDTO, EncounterStageDTO, PreconditionDTO, StoryArcPayload, StoryBeatOrigin, StoryBeatPayload, WorldPayload, } from "./storyPayloads.js";
|
|
5
|
+
export { NODE_ID_SEPARATOR, ROOT_NODE_ID, childStoryNodeId, isStoryNodeAtOrBelow, parentStoryNodeId, storyNodeChoiceIds, storyNodeDepth, storyNodeId, storyNodePath, } from "./nodePaths.js";
|
|
6
|
+
export { designContentBulkSaveContract, designContentDeleteContract, designContentListContract, designContentLoadContract, designContentSaveContract, } from "./contracts.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,kCAAkC,EAClC,mCAAmC,EACnC,0BAA0B,EAC1B,iCAAiC,EACjC,+BAA+B,EAC/B,+BAA+B,EAC/B,yBAAyB,EACzB,+BAA+B,EAC/B,8BAA8B,EAC9B,+BAA+B,EAC/B,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,yBAAyB,EACzB,wBAAwB,EACxB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,aAAa,GACd,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,6BAA6B,EAC7B,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Explicit barrel (no export *). Every public symbol is re-exported by name.
|
|
2
|
+
// The transport: one keyed, scoped, typed JSON row for every kind of authored
|
|
3
|
+
// design content.
|
|
4
|
+
export { DESIGN_CONTENT_CASCADES, DESIGN_CONTENT_TYPES, DesignContentBulkSaveRequestSchema, DesignContentBulkSaveResponseSchema, DesignContentCascadeSchema, DesignContentDeleteResponseSchema, DesignContentListResponseSchema, DesignContentLoadResponseSchema, DesignContentRecordSchema, DesignContentReplaceScopeSchema, DesignContentSaveRequestSchema, DesignContentSaveResponseSchema, ROOT_SCOPE, } from "./designContent.js";
|
|
5
|
+
// What goes inside a row's `data`. Not part of any endpoint — the service never
|
|
6
|
+
// parses it — but shared so two clients agree on the shapes.
|
|
7
|
+
//
|
|
8
|
+
// A beat is an encounter stage and a branch is a choice: the event editor's own
|
|
9
|
+
// DTOs, re-exported here so a story client needs one import rather than two.
|
|
10
|
+
export { ActivationDTOSchema, ChoiceDefinitionDTOSchema, ConsumableConditionDTOSchema, EncounterStageDTOSchema, PreconditionDTOSchema, STORY_BEAT_ORIGINS, StoryArcPayloadSchema, StoryBeatOriginSchema, StoryBeatPayloadSchema, WorldPayloadSchema, } from "./storyPayloads.js";
|
|
11
|
+
// Beat addressing, shared by the client that mints ids and the service that
|
|
12
|
+
// sweeps subtrees.
|
|
13
|
+
export { NODE_ID_SEPARATOR, ROOT_NODE_ID, childStoryNodeId, isStoryNodeAtOrBelow, parentStoryNodeId, storyNodeChoiceIds, storyNodeDepth, storyNodeId, storyNodePath, } from "./nodePaths.js";
|
|
14
|
+
// Typed endpoint contracts (toad-contracts). Each contract carries its own
|
|
15
|
+
// `pathResolver`; wire them into a client via @toad-contracts/frontend-http-client
|
|
16
|
+
// and into a Hono service via @toad-contracts/hono.
|
|
17
|
+
export { designContentBulkSaveContract, designContentDeleteContract, designContentListContract, designContentLoadContract, designContentSaveContract, } from "./contracts.js";
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,8EAA8E;AAC9E,kBAAkB;AAClB,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,kCAAkC,EAClC,mCAAmC,EACnC,0BAA0B,EAC1B,iCAAiC,EACjC,+BAA+B,EAC/B,+BAA+B,EAC/B,yBAAyB,EACzB,+BAA+B,EAC/B,8BAA8B,EAC9B,+BAA+B,EAC/B,UAAU,GACX,MAAM,oBAAoB,CAAC;AAe5B,gFAAgF;AAChF,6DAA6D;AAC7D,EAAE;AACF,gFAAgF;AAChF,6EAA6E;AAC7E,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAa5B,4EAA4E;AAC5E,mBAAmB;AACnB,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB,2EAA2E;AAC3E,mFAAmF;AACnF,oDAAoD;AACpD,OAAO,EACL,6BAA6B,EAC7B,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a beat is addressed.
|
|
3
|
+
*
|
|
4
|
+
* A story beat's identity is the trail of choices taken to reach it — nothing
|
|
5
|
+
* else. That is what makes "has this branch been travelled before?" a lookup
|
|
6
|
+
* rather than a search, and it is why both the client minting ids and the
|
|
7
|
+
* service sweeping subtrees have to agree on the spelling. They agree here.
|
|
8
|
+
*
|
|
9
|
+
* Two spellings, one meaning:
|
|
10
|
+
*
|
|
11
|
+
* - `nodeId` — `root`, then choice ids joined with `.`. It is the row's
|
|
12
|
+
* `contentId`, and it travels as a single URL path segment
|
|
13
|
+
* (`/design-content/story-node/:scopeId/:contentId`), which a slash would
|
|
14
|
+
* split. The dot is also what `cascade=prefix` sweeps on.
|
|
15
|
+
* - `path` — the same trail joined with `/`, empty at the opening. This one is
|
|
16
|
+
* for reading: it is what a breadcrumb shows.
|
|
17
|
+
*/
|
|
18
|
+
/** The opening beat of every story. */
|
|
19
|
+
export declare const ROOT_NODE_ID = "root";
|
|
20
|
+
/** Separator between choice ids inside a node id. */
|
|
21
|
+
export declare const NODE_ID_SEPARATOR = ".";
|
|
22
|
+
/**
|
|
23
|
+
* The id of the beat reached by taking `choiceIds` in order.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { storyNodeId } from '@game-infra/story-schemas'
|
|
28
|
+
*
|
|
29
|
+
* storyNodeId([]) // 'root'
|
|
30
|
+
* storyNodeId(['take-the-tunnel', 'follow-the-voice']) // 'root.take-the-tunnel.follow-the-voice'
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function storyNodeId(choiceIds: readonly string[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* The readable trail for the same beat, empty at the opening.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { storyNodePath } from '@game-infra/story-schemas'
|
|
40
|
+
*
|
|
41
|
+
* storyNodePath(['take-the-tunnel', 'follow-the-voice']) // 'take-the-tunnel/follow-the-voice'
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function storyNodePath(choiceIds: readonly string[]): string;
|
|
45
|
+
/**
|
|
46
|
+
* The choice trail a node id encodes. The inverse of {@link storyNodeId}.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { storyNodeChoiceIds } from '@game-infra/story-schemas'
|
|
51
|
+
*
|
|
52
|
+
* storyNodeChoiceIds('root.take-the-tunnel') // ['take-the-tunnel']
|
|
53
|
+
* storyNodeChoiceIds('root') // []
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function storyNodeChoiceIds(nodeId: string): readonly string[];
|
|
57
|
+
/**
|
|
58
|
+
* The id of the beat one choice further on.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { childStoryNodeId } from '@game-infra/story-schemas'
|
|
63
|
+
*
|
|
64
|
+
* childStoryNodeId('root', 'take-the-tunnel') // 'root.take-the-tunnel'
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function childStoryNodeId(parentNodeId: string, choiceId: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* The id of the beat this one branched from, or `undefined` at the opening.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { parentStoryNodeId } from '@game-infra/story-schemas'
|
|
74
|
+
*
|
|
75
|
+
* parentStoryNodeId('root.take-the-tunnel') // 'root'
|
|
76
|
+
* parentStoryNodeId('root') // undefined
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function parentStoryNodeId(nodeId: string): string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* How many choices deep a beat sits. The opening is 0.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* import { storyNodeDepth } from '@game-infra/story-schemas'
|
|
86
|
+
*
|
|
87
|
+
* storyNodeDepth('root.take-the-tunnel.follow-the-voice') // 2
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare function storyNodeDepth(nodeId: string): number;
|
|
91
|
+
/**
|
|
92
|
+
* Whether `nodeId` sits at or below `ancestorId`. This is `cascade=prefix`,
|
|
93
|
+
* expressed for a client that holds the tree in memory.
|
|
94
|
+
*
|
|
95
|
+
* The separator is part of the test on purpose: `root.take` is not an ancestor
|
|
96
|
+
* of `root.taken`, however alike the two ids look.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { isStoryNodeAtOrBelow } from '@game-infra/story-schemas'
|
|
101
|
+
*
|
|
102
|
+
* isStoryNodeAtOrBelow('root.take.left', 'root.take') // true
|
|
103
|
+
* isStoryNodeAtOrBelow('root.taken', 'root.take') // false
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare function isStoryNodeAtOrBelow(nodeId: string, ancestorId: string): boolean;
|
|
107
|
+
//# sourceMappingURL=nodePaths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodePaths.d.ts","sourceRoot":"","sources":["../src/nodePaths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,uCAAuC;AACvC,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,qDAAqD;AACrD,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAEhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAElE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAGpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEhF"}
|