tsprose 0.0.1 → 1.0.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/README.md CHANGED
@@ -14,6 +14,9 @@ Write and structure prose content using TypeScript TSX with strong typing.
14
14
 
15
15
  TSProse is a **parser + structurer**: you write content in TSX, TSProse turns it into a typed element tree. Rendering, styling, and analysis are up to you.
16
16
 
17
+ Think of TSProse as a TeX but written in TypeScript.
18
+ It is a foundation for writing prose. What tags you define and what you do with the prose (render to site or pdf, analyze, serialize, etc) is up to you.
19
+
17
20
  ## Installation
18
21
 
19
22
  ```bash
@@ -25,4 +25,5 @@ export declare function defineDocument<UniquesTemplate extends DocumentUniquesTe
25
25
  export declare function defineDocument<UniquesTemplate extends DocumentUniquesTemplate>(documentId: string, documentParameters: {
26
26
  uniques: UniquesTemplate;
27
27
  }): DocumentFinalizer<UniquesTemplate>;
28
+ export declare function isDocument(document: any): document is Document;
28
29
  export declare function injectDocumentId(documentId: string, injectTo: string, defineDocumentAlias?: string): string;
package/dist/document.js CHANGED
@@ -42,6 +42,12 @@ export function defineDocument(arg1, arg2) {
42
42
  return finalizeDocument;
43
43
  }
44
44
  //
45
+ // isDocument
46
+ //
47
+ export function isDocument(document) {
48
+ return Boolean(document?.[DOCUMENT_PREFIX] === true);
49
+ }
50
+ //
45
51
  // injectDocumentId
46
52
  //
47
53
  export function injectDocumentId(documentId, injectTo, defineDocumentAlias) {
package/dist/element.d.ts CHANGED
@@ -11,21 +11,25 @@ export type ToRawElement<TSchema extends Schema, TagName extends string = string
11
11
  storageKey: TSchema['Storage'] extends undefined ? undefined : string;
12
12
  children: ToRawElementChildren<TSchema['Children']>;
13
13
  } : never;
14
- type ToRawElementChildren<TChildren> = TChildren extends (infer E extends Schema)[] ? ToRawElement<E>[] : TChildren extends undefined ? undefined : never;
14
+ type ToRawElementChildren<TChildren> = TChildren extends undefined ? undefined : TChildren extends readonly Schema[] ? {
15
+ [K in keyof TChildren]: TChildren[K] extends Schema ? ToRawElement<TChildren[K]> : TChildren[K];
16
+ } : never;
15
17
  export type RawElement = ToRawElement<Schema>;
16
18
  export type BlockRawElement = ToRawElement<BlockSchema>;
17
19
  export type InlinerRawElement = ToRawElement<InlinerSchema>;
18
20
  export type LinkableRawElement = ToRawElement<LinkableSchema>;
19
21
  export declare const PROSE_ELEMENT_PREFIX = "__TSPROSE_proseElement";
20
- export type ToProseElement<TSchema extends Schema> = {
22
+ export type ToProseElement<TSchema extends Schema> = TSchema extends Schema ? {
21
23
  [PROSE_ELEMENT_PREFIX]: true;
22
24
  schema: TSchema;
23
- id: TSchema['linkable'] extends false ? undefined : string;
25
+ id: false extends TSchema['linkable'] ? TSchema['linkable'] extends false ? undefined : string | undefined : string;
24
26
  data: TSchema['Data'];
25
- storageKey: TSchema['Storage'] extends undefined ? undefined : string;
27
+ storageKey: false extends TSchema['Storage'] ? TSchema['linkable'] extends false ? undefined : undefined | string : string;
26
28
  children: ToProseElementChildren<TSchema['Children']>;
27
- };
28
- type ToProseElementChildren<TChildren> = TChildren extends (infer E extends Schema)[] ? ToProseElement<E>[] : TChildren extends undefined ? undefined : never;
29
+ } : never;
30
+ type ToProseElementChildren<TChildren> = TChildren extends undefined ? undefined : TChildren extends readonly Schema[] ? {
31
+ [K in keyof TChildren]: TChildren[K] extends Schema ? ToProseElement<TChildren[K]> : TChildren[K];
32
+ } : never;
29
33
  export type ProseElement = ToProseElement<Schema>;
30
34
  export type BlockProseElement = ToProseElement<BlockSchema>;
31
35
  export type InlinerProseElement = ToProseElement<InlinerSchema>;
@@ -8,6 +8,9 @@ export function makeRawElement(parameters) {
8
8
  tagName: parameters.tagName,
9
9
  };
10
10
  parameters.elementHandler?.(element);
11
+ if (Array.isArray(element.children) && element.children.length === 0) {
12
+ delete element.children;
13
+ }
11
14
  element.hash = hash(element.schema.name +
12
15
  JSON.stringify(element.data) +
13
16
  JSON.stringify(element.children?.map((child) => child.hash).join()), 12);
@@ -19,6 +22,9 @@ export function makeProseElement(parameters) {
19
22
  schema: parameters.schema,
20
23
  };
21
24
  parameters.elementHandler?.(element);
25
+ if (Array.isArray(element.children) && element.children.length === 0) {
26
+ delete element.children;
27
+ }
22
28
  return element;
23
29
  }
24
30
  export function isRawElement(element, schema) {
@@ -1,57 +1,57 @@
1
- //
2
- // Images
3
- //
4
-
5
- declare module '*.png' {
6
- const src: string;
7
- export default src;
8
- }
9
-
10
- declare module '*.jpg' {
11
- const src: string;
12
- export default src;
13
- }
14
-
15
- declare module '*.jpeg' {
16
- const src: string;
17
- export default src;
18
- }
19
-
20
- declare module '*.gif' {
21
- const src: string;
22
- export default src;
23
- }
24
-
25
- declare module '*.bmp' {
26
- const src: string;
27
- export default src;
28
- }
29
-
30
- declare module '*.webp' {
31
- const src: string;
32
- export default src;
33
- }
34
-
35
- declare module '*.svg' {
36
- const src: string;
37
- export default src;
38
- }
39
-
40
- //
41
- // Videos
42
- //
43
-
44
- declare module '*.mp4' {
45
- const src: string;
46
- export default src;
47
- }
48
-
49
- declare module '*.webm' {
50
- const src: string;
51
- export default src;
52
- }
53
-
54
- declare module '*.ogg' {
55
- const src: string;
56
- export default src;
57
- }
1
+ //
2
+ // Images
3
+ //
4
+
5
+ declare module '*.png' {
6
+ const src: string;
7
+ export default src;
8
+ }
9
+
10
+ declare module '*.jpg' {
11
+ const src: string;
12
+ export default src;
13
+ }
14
+
15
+ declare module '*.jpeg' {
16
+ const src: string;
17
+ export default src;
18
+ }
19
+
20
+ declare module '*.gif' {
21
+ const src: string;
22
+ export default src;
23
+ }
24
+
25
+ declare module '*.bmp' {
26
+ const src: string;
27
+ export default src;
28
+ }
29
+
30
+ declare module '*.webp' {
31
+ const src: string;
32
+ export default src;
33
+ }
34
+
35
+ declare module '*.svg' {
36
+ const src: string;
37
+ export default src;
38
+ }
39
+
40
+ //
41
+ // Videos
42
+ //
43
+
44
+ declare module '*.mp4' {
45
+ const src: string;
46
+ export default src;
47
+ }
48
+
49
+ declare module '*.webm' {
50
+ const src: string;
51
+ export default src;
52
+ }
53
+
54
+ declare module '*.ogg' {
55
+ const src: string;
56
+ export default src;
57
+ }
package/dist/id.d.ts CHANGED
@@ -1,3 +1,8 @@
1
- import type { RawElement } from './element.js';
2
- export type IdMaker = (rawElement: RawElement, takenIds?: Set<string>) => string;
1
+ import type { ProseElement, RawElement } from './element.js';
2
+ export type TakenIds = Map<string, ProseElement>;
3
+ export type IdMaker = (args: {
4
+ rawElement: RawElement;
5
+ takenIds?: TakenIds;
6
+ slugify?: (str: string) => string;
7
+ }) => string;
3
8
  export declare const defaultIdMaker: IdMaker;
package/dist/id.js CHANGED
@@ -1,17 +1,37 @@
1
- export const defaultIdMaker = (rawElement, takenIds) => {
2
- takenIds ||= new Set();
1
+ export const defaultIdMaker = ({ rawElement, takenIds, slugify }) => {
3
2
  let id = '';
4
3
  const humanReadable = rawElement.uniqueName || rawElement.slug;
5
4
  if (humanReadable) {
6
- id = humanReadable;
5
+ id = slugify ? slugify(humanReadable) : humanReadable;
7
6
  }
8
7
  else {
9
8
  id = rawElement.schema.name + '-' + rawElement.hash.substring(0, 9);
10
9
  }
11
- let candidate = id;
12
- let counter = 1;
13
- while (takenIds.has(candidate)) {
14
- candidate = `${id}-${counter++}`;
10
+ if (takenIds) {
11
+ if (rawElement.uniqueName) {
12
+ // Unique elements keep their id; colliding elements get deduplicated ids
13
+ if (takenIds.has(id)) {
14
+ const collidingProse = takenIds.get(id);
15
+ takenIds.delete(id);
16
+ const newIdForColliding = collidingProse.schema.name + '-' + collidingProse.id;
17
+ let candidate = newIdForColliding;
18
+ let counter = 1;
19
+ while (takenIds.has(candidate)) {
20
+ candidate = `${newIdForColliding}-${counter++}`;
21
+ }
22
+ collidingProse.id = candidate;
23
+ takenIds.set(candidate, collidingProse);
24
+ }
25
+ }
26
+ else {
27
+ // Non-unique elements find the next available id
28
+ let candidate = id;
29
+ let counter = 1;
30
+ while (takenIds.has(candidate)) {
31
+ candidate = `${id}-${counter++}`;
32
+ }
33
+ id = candidate;
34
+ }
15
35
  }
16
- return candidate;
36
+ return id;
17
37
  };
package/dist/index.d.ts CHANGED
@@ -7,7 +7,6 @@ export * from './element.js';
7
7
  export * from './elementUtils.js';
8
8
  export * from './error.js';
9
9
  export * from './id.js';
10
- export * from './json.js';
11
10
  export * from './rawToProse.js';
12
11
  export * from './schema.js';
13
12
  export * from './storage.js';
package/dist/index.js CHANGED
@@ -7,7 +7,6 @@ export * from './element.js';
7
7
  export * from './elementUtils.js';
8
8
  export * from './error.js';
9
9
  export * from './id.js';
10
- export * from './json.js';
11
10
  export * from './rawToProse.js';
12
11
  export * from './schema.js';
13
12
  export * from './storage.js';
@@ -1,19 +1,20 @@
1
1
  import type { LinkableProseElement, ProseElement, RawElement } from './element.js';
2
- import { type IdMaker } from './id.js';
2
+ import { type IdMaker, type TakenIds } from './id.js';
3
3
  export type RawToProseStep = (elements: {
4
4
  rawElement: RawElement;
5
5
  proseElement: ProseElement;
6
6
  }) => void | Promise<void>;
7
7
  export interface RawToProseResult {
8
8
  prose: ProseElement;
9
- takenIds: Set<string>;
9
+ takenIds: TakenIds;
10
10
  uniques: Record<string, LinkableProseElement>;
11
11
  }
12
12
  export declare function rawToProse(args: {
13
13
  rawProse: RawElement;
14
- takenIds?: Set<string>;
14
+ takenIds?: TakenIds;
15
15
  pre?: (rawElement: RawElement) => void | Promise<void>;
16
16
  post?: (proseElement: ProseElement) => void | Promise<void>;
17
17
  step?: RawToProseStep;
18
18
  idMaker?: IdMaker;
19
+ slugify?: (str: string) => string;
19
20
  }): Promise<RawToProseResult>;
@@ -3,9 +3,9 @@ import { TSProseError } from './error.js';
3
3
  import { defaultIdMaker } from './id.js';
4
4
  import { isWalkStop, walkPost } from './walk.js';
5
5
  export async function rawToProse(args) {
6
- const { rawProse, pre, post, step } = args;
6
+ const { rawProse, pre, post, step, slugify } = args;
7
7
  const idMaker = args.idMaker || defaultIdMaker;
8
- const takenIds = new Set(args.takenIds);
8
+ const takenIds = new Map(args.takenIds);
9
9
  const uniques = {};
10
10
  const prose = await walkPost(rawProse, async (rawElement, children) => {
11
11
  if (pre) {
@@ -20,19 +20,22 @@ export async function rawToProse(args) {
20
20
  if (rawElement.storageKey) {
21
21
  proseElement.storageKey = rawElement.storageKey;
22
22
  }
23
- if (children) {
23
+ if (children && children.length > 0) {
24
24
  proseElement.children = children;
25
25
  }
26
26
  if (rawElement.schema.linkable) {
27
- const elementId = idMaker(rawElement, takenIds);
27
+ const elementId = idMaker({ rawElement, takenIds, slugify });
28
+ if (!elementId.trim()) {
29
+ throw new TSProseError(`Empty ID generated from "${rawElement.schema.name}" element!\nThis might happen because of wrongly configured "idMaker" or obscure element data that was passed to create an ID!`);
30
+ }
28
31
  if (takenIds.has(elementId)) {
29
32
  throw new TSProseError(`Element ID collision: "${elementId}" is already taken!\nMake sure "idMaker" you are using generates non-repeating IDs!`);
30
33
  }
31
34
  proseElement.id = elementId;
32
- takenIds.add(elementId);
35
+ takenIds.set(elementId, proseElement);
33
36
  if (rawElement.uniqueName) {
34
37
  if (uniques[rawElement.uniqueName]) {
35
- throw new TSProseError(`Duplicate uniqueName: "${rawElement.uniqueName}" is already used by another element!\nIf you are using document prose, make sure not to directly insert imported or manually created external uniques as they might intersect with document uniques!`);
38
+ throw new TSProseError(`Duplicate uniqueName: "${rawElement.uniqueName}" is already used by another element!\nIf you are using document prose, make sure not to directly insert imported or manually created external uniques as their names might intersect with document-level uniques names!`);
36
39
  }
37
40
  uniques[rawElement.uniqueName] = proseElement;
38
41
  }
package/dist/storage.d.ts CHANGED
@@ -5,10 +5,13 @@ export interface ProseWithStorage {
5
5
  prose: ProseElement;
6
6
  storage: ProseStorage;
7
7
  }
8
- export type ElementStorageCreator<TSchema extends Schema> = TSchema['Storage'] extends undefined ? never : (element: ToProseElement<TSchema>) => null | TSchema['Storage'] | Promise<null | TSchema['Storage']>;
8
+ interface _StorageCreatorMethod<TSchema extends Schema> {
9
+ creator(element: ToProseElement<TSchema>): null | TSchema['Storage'] | Promise<null | TSchema['Storage']>;
10
+ }
11
+ export type ElementStorageCreator<TSchema extends Schema> = TSchema['Storage'] extends undefined ? never : _StorageCreatorMethod<TSchema>['creator'];
9
12
  export declare function fillProseStorage(args: {
10
13
  prose: ProseElement;
11
- storageCreators: Record<string, ElementStorageCreator<any>>;
14
+ storageCreators: Record<string, ElementStorageCreator<Schema>>;
12
15
  alterValue?: (args: {
13
16
  element: ToProseElement<Schema>;
14
17
  storageKey: string;
@@ -16,3 +19,4 @@ export declare function fillProseStorage(args: {
16
19
  }) => any | Promise<any>;
17
20
  storage?: ProseStorage;
18
21
  }): Promise<ProseStorage>;
22
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsprose",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "📜 Write and parse any prose in TypeScript TSX!",
6
6
  "license": "MIT",
package/dist/json.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export declare function toJSON(value: unknown, indent?: number): string;
2
- export declare function fromJSON(json: string): any;
package/dist/json.js DELETED
@@ -1,46 +0,0 @@
1
- import { PROSE_ELEMENT_PREFIX, RAW_ELEMENT_PREFIX } from './element.js';
2
- import { SCHEMA_PREFIX } from './schema.js';
3
- function encodeSchema(schema) {
4
- return (schema.name +
5
- (schema.type === 'block' ? '0' : '1') +
6
- (schema.linkable === false ? '0' : schema.linkable === 'always' ? '2' : '1'));
7
- }
8
- function decodeSchema(encoded) {
9
- const name = encoded.slice(0, -2);
10
- const typeFlag = encoded.at(-2);
11
- const linkFlag = encoded.at(-1);
12
- return {
13
- [SCHEMA_PREFIX]: true,
14
- name,
15
- type: typeFlag === '0' ? 'block' : 'inliner',
16
- linkable: linkFlag === '0' ? false : linkFlag === '2' ? 'always' : true,
17
- };
18
- }
19
- function isElement(obj) {
20
- return obj[RAW_ELEMENT_PREFIX] === true || obj[PROSE_ELEMENT_PREFIX] === true;
21
- }
22
- export function toJSON(value, indent) {
23
- return JSON.stringify(value, function (key, val) {
24
- if (!isElement(this))
25
- return val;
26
- if (key === RAW_ELEMENT_PREFIX || key === PROSE_ELEMENT_PREFIX) {
27
- const schema = this.schema;
28
- return encodeSchema(schema);
29
- }
30
- if (key === 'schema') {
31
- return undefined;
32
- }
33
- return val;
34
- }, indent);
35
- }
36
- export function fromJSON(json) {
37
- return JSON.parse(json, function (key, val) {
38
- if ((key === RAW_ELEMENT_PREFIX || key === PROSE_ELEMENT_PREFIX) &&
39
- typeof val === 'string') {
40
- const schema = decodeSchema(val);
41
- this.schema = schema;
42
- return true;
43
- }
44
- return val;
45
- });
46
- }