liminal 0.5.11 → 0.5.13

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.
Files changed (175) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/Config.ts +13 -0
  3. package/Context.ts +24 -32
  4. package/Definition.ts +32 -0
  5. package/EventBase.ts +1 -1
  6. package/Handler.ts +3 -5
  7. package/L/L.ts +5 -2
  8. package/L/all.ts +35 -0
  9. package/L/assistant.ts +5 -6
  10. package/L/catch.ts +16 -18
  11. package/L/context.ts +12 -0
  12. package/L/continuation.ts +13 -0
  13. package/L/emit.ts +10 -8
  14. package/L/infer.ts +22 -27
  15. package/L/message.ts +5 -9
  16. package/L/model.ts +6 -8
  17. package/L/reflect.ts +12 -0
  18. package/L/run.ts +29 -0
  19. package/L/strand.ts +14 -95
  20. package/L/stream.ts +11 -24
  21. package/L/system.ts +13 -8
  22. package/L/user.ts +13 -8
  23. package/LEvent.ts +9 -17
  24. package/LiminalAssertionError.ts +19 -0
  25. package/Model.ts +5 -3
  26. package/ModelRegistry.ts +1 -7
  27. package/Rune.test.ts +5 -0
  28. package/Rune.ts +24 -12
  29. package/Schema.ts +185 -0
  30. package/Strand.ts +253 -0
  31. package/Tool.ts +8 -16
  32. package/TypeAdapter.ts +3 -0
  33. package/dist/Config.d.ts +12 -0
  34. package/dist/Config.js +2 -0
  35. package/dist/Config.js.map +1 -0
  36. package/dist/Context.d.ts +11 -10
  37. package/dist/Context.js +15 -26
  38. package/dist/Context.js.map +1 -1
  39. package/dist/Definition.d.ts +10 -0
  40. package/dist/Definition.js +18 -0
  41. package/dist/Definition.js.map +1 -0
  42. package/dist/EventBase.js +1 -1
  43. package/dist/EventBase.js.map +1 -1
  44. package/dist/Handler.d.ts +3 -4
  45. package/dist/Handler.js +1 -2
  46. package/dist/Handler.js.map +1 -1
  47. package/dist/L/L.d.ts +5 -2
  48. package/dist/L/L.js +5 -2
  49. package/dist/L/L.js.map +1 -1
  50. package/dist/L/all.d.ts +10 -0
  51. package/dist/L/all.js +20 -0
  52. package/dist/L/all.js.map +1 -0
  53. package/dist/L/assistant.d.ts +2 -2
  54. package/dist/L/assistant.js +4 -5
  55. package/dist/L/assistant.js.map +1 -1
  56. package/dist/L/catch.d.ts +3 -2
  57. package/dist/L/catch.js +12 -15
  58. package/dist/L/catch.js.map +1 -1
  59. package/dist/L/context.d.ts +2 -0
  60. package/dist/L/context.js +12 -0
  61. package/dist/L/context.js.map +1 -0
  62. package/dist/L/continuation.d.ts +3 -0
  63. package/dist/L/continuation.js +12 -0
  64. package/dist/L/continuation.js.map +1 -0
  65. package/dist/L/emit.d.ts +3 -5
  66. package/dist/L/emit.js +8 -3
  67. package/dist/L/emit.js.map +1 -1
  68. package/dist/L/infer.d.ts +2 -5
  69. package/dist/L/infer.js +21 -22
  70. package/dist/L/infer.js.map +1 -1
  71. package/dist/L/message.d.ts +2 -4
  72. package/dist/L/message.js +4 -6
  73. package/dist/L/message.js.map +1 -1
  74. package/dist/L/model.d.ts +2 -4
  75. package/dist/L/model.js +4 -5
  76. package/dist/L/model.js.map +1 -1
  77. package/dist/L/reflect.d.ts +4 -0
  78. package/dist/L/reflect.js +10 -0
  79. package/dist/L/reflect.js.map +1 -0
  80. package/dist/L/run.d.ts +15 -0
  81. package/dist/L/run.js +16 -0
  82. package/dist/L/run.js.map +1 -0
  83. package/dist/L/strand.d.ts +4 -25
  84. package/dist/L/strand.js +8 -65
  85. package/dist/L/strand.js.map +1 -1
  86. package/dist/L/stream.d.ts +2 -4
  87. package/dist/L/stream.js +8 -19
  88. package/dist/L/stream.js.map +1 -1
  89. package/dist/L/system.d.ts +2 -4
  90. package/dist/L/system.js +4 -3
  91. package/dist/L/system.js.map +1 -1
  92. package/dist/L/user.d.ts +2 -4
  93. package/dist/L/user.js +4 -3
  94. package/dist/L/user.js.map +1 -1
  95. package/dist/LEvent.d.ts +13 -41
  96. package/dist/LEvent.js +4 -15
  97. package/dist/LEvent.js.map +1 -1
  98. package/dist/LiminalAssertionError.d.ts +8 -0
  99. package/dist/LiminalAssertionError.js +20 -0
  100. package/dist/LiminalAssertionError.js.map +1 -0
  101. package/dist/Model.d.ts +4 -2
  102. package/dist/Model.js +1 -1
  103. package/dist/Model.js.map +1 -1
  104. package/dist/ModelRegistry.d.ts +0 -2
  105. package/dist/ModelRegistry.js +0 -2
  106. package/dist/ModelRegistry.js.map +1 -1
  107. package/dist/Rune.d.ts +19 -7
  108. package/dist/Rune.js +8 -4
  109. package/dist/Rune.js.map +1 -1
  110. package/dist/Rune.test.d.ts +1 -0
  111. package/dist/Rune.test.js +5 -0
  112. package/dist/Rune.test.js.map +1 -0
  113. package/dist/Schema.d.ts +46 -0
  114. package/dist/Schema.js +130 -0
  115. package/dist/Schema.js.map +1 -0
  116. package/dist/Strand.d.ts +57 -0
  117. package/dist/Strand.js +177 -0
  118. package/dist/Strand.js.map +1 -0
  119. package/dist/Tool.d.ts +6 -5
  120. package/dist/Tool.js +3 -4
  121. package/dist/Tool.js.map +1 -1
  122. package/dist/TypeAdapter.d.ts +1 -0
  123. package/dist/TypeAdapter.js +3 -0
  124. package/dist/TypeAdapter.js.map +1 -0
  125. package/dist/errors.d.ts +9 -0
  126. package/dist/errors.js +11 -0
  127. package/dist/errors.js.map +1 -0
  128. package/dist/index.d.ts +6 -4
  129. package/dist/index.js +6 -4
  130. package/dist/index.js.map +1 -1
  131. package/dist/tsconfig.tsbuildinfo +1 -1
  132. package/dist/util/EnsureNarrow.d.ts +1 -0
  133. package/dist/util/EnsureNarrow.js +2 -0
  134. package/dist/util/EnsureNarrow.js.map +1 -0
  135. package/dist/util/JSONValue.d.ts +8 -0
  136. package/dist/util/JSONValue.js +20 -0
  137. package/dist/util/JSONValue.js.map +1 -0
  138. package/dist/util/attachCustomInspect.d.ts +1 -0
  139. package/dist/util/attachCustomInspect.js +11 -0
  140. package/dist/util/attachCustomInspect.js.map +1 -0
  141. package/dist/util/isTemplateStringsArray.d.ts +1 -0
  142. package/dist/util/isTemplateStringsArray.js +4 -0
  143. package/dist/util/isTemplateStringsArray.js.map +1 -0
  144. package/errors.ts +12 -0
  145. package/index.ts +6 -4
  146. package/package.json +3 -13
  147. package/tsconfig.json +1 -5
  148. package/util/EnsureNarrow.ts +1 -0
  149. package/util/JSONValue.ts +20 -0
  150. package/util/attachCustomInspect.ts +14 -0
  151. package/util/isTemplateStringsArray.ts +3 -0
  152. package/Fiber.ts +0 -126
  153. package/L/_common.ts +0 -6
  154. package/L/rune.ts +0 -12
  155. package/MessageRegistry.ts +0 -22
  156. package/Runic.ts +0 -30
  157. package/ToolRegistry.ts +0 -10
  158. package/dist/Fiber.d.ts +0 -36
  159. package/dist/Fiber.js +0 -97
  160. package/dist/Fiber.js.map +0 -1
  161. package/dist/L/_common.d.ts +0 -4
  162. package/dist/L/_common.js +0 -7
  163. package/dist/L/_common.js.map +0 -1
  164. package/dist/L/rune.d.ts +0 -3
  165. package/dist/L/rune.js +0 -9
  166. package/dist/L/rune.js.map +0 -1
  167. package/dist/MessageRegistry.d.ts +0 -9
  168. package/dist/MessageRegistry.js +0 -15
  169. package/dist/MessageRegistry.js.map +0 -1
  170. package/dist/Runic.d.ts +0 -9
  171. package/dist/Runic.js +0 -18
  172. package/dist/Runic.js.map +0 -1
  173. package/dist/ToolRegistry.d.ts +0 -6
  174. package/dist/ToolRegistry.js +0 -8
  175. package/dist/ToolRegistry.js.map +0 -1
package/L/system.ts CHANGED
@@ -1,13 +1,18 @@
1
- import { isTemplateStringsArray } from "liminal-util"
2
1
  import type { LEvent } from "../LEvent.ts"
3
2
  import type { Rune } from "../Rune.ts"
3
+ import { isTemplateStringsArray } from "../util/isTemplateStringsArray.ts"
4
4
  import { message } from "./message.ts"
5
5
 
6
- export interface system extends Generator<Rune<LEvent>, void> {}
7
-
8
- export function system(template: TemplateStringsArray, ...substitutions: Array<string>): system
9
- export function system(value: string): system
10
- export function system(e0: TemplateStringsArray | string, ...rest: Array<string>): system {
11
- const part = isTemplateStringsArray(e0) ? String.raw(e0, ...rest) : e0
12
- return message("system", [{ part }])
6
+ export function system(
7
+ template: TemplateStringsArray,
8
+ ...substitutions: Array<number | string>
9
+ ): Generator<Rune<LEvent>, void>
10
+ export function system(value: string): Generator<Rune<LEvent>, void>
11
+ export function system(
12
+ e0: TemplateStringsArray | string,
13
+ ...rest: Array<number | string>
14
+ ): Generator<Rune<LEvent>, void> {
15
+ return message("system", [{
16
+ part: isTemplateStringsArray(e0) ? String.raw(e0, ...rest) : e0,
17
+ }])
13
18
  }
package/L/user.ts CHANGED
@@ -1,13 +1,18 @@
1
- import { isTemplateStringsArray } from "liminal-util"
2
1
  import type { LEvent } from "../LEvent.ts"
3
2
  import type { Rune } from "../Rune.ts"
3
+ import { isTemplateStringsArray } from "../util/isTemplateStringsArray.ts"
4
4
  import { message } from "./message.ts"
5
5
 
6
- export interface user extends Generator<Rune<LEvent>, void> {}
7
-
8
- export function user(template: TemplateStringsArray, ...substitutions: Array<string>): user
9
- export function user(value: string): user
10
- export function user(e0: TemplateStringsArray | string, ...rest: Array<string>): user {
11
- const part = isTemplateStringsArray(e0) ? String.raw(e0, ...rest) : e0
12
- return message("user", [{ part }])
6
+ export function user(
7
+ template: TemplateStringsArray,
8
+ ...substitutions: Array<number | string>
9
+ ): Generator<Rune<LEvent>, void>
10
+ export function user(value: string): Generator<Rune<LEvent>, void>
11
+ export function user(
12
+ e0: TemplateStringsArray | string,
13
+ ...rest: Array<number | string>
14
+ ): Generator<Rune<LEvent>, void> {
15
+ return message("user", [{
16
+ part: isTemplateStringsArray(e0) ? String.raw(e0, ...rest) : e0,
17
+ }])
13
18
  }
package/LEvent.ts CHANGED
@@ -1,12 +1,11 @@
1
- import type { SchemaObject } from "liminal-schema"
2
1
  import { EventBase } from "./EventBase.ts"
3
2
  import type { Message } from "./Message.ts"
4
3
  import type { Model } from "./Model.ts"
4
+ import type { Schema } from "./Schema.ts"
5
+ import type { StrandStatus } from "./Strand.ts"
5
6
 
6
7
  export type LEvent =
7
- | FiberCreated
8
- | FiberResolved
9
- | FiberStarted
8
+ | StrandStatusChanged
10
9
  | InferenceRequested
11
10
  | Inferred
12
11
  | MessageAppended
@@ -28,10 +27,10 @@ export class ModelRegistered extends EventBase(LEventTag, "model_registered") {
28
27
  }
29
28
 
30
29
  export class InferenceRequested extends EventBase(LEventTag, "inference_requested") {
31
- declare schema?: SchemaObject
30
+ declare schema?: Schema
32
31
  constructor(
33
- readonly requestId: number,
34
- schema?: SchemaObject | undefined,
32
+ readonly requestId: string,
33
+ schema?: Schema | undefined,
35
34
  ) {
36
35
  super()
37
36
  if (schema) {
@@ -42,7 +41,7 @@ export class InferenceRequested extends EventBase(LEventTag, "inference_requeste
42
41
 
43
42
  export class Inferred extends EventBase(LEventTag, "inferred") {
44
43
  constructor(
45
- readonly requestId: number,
44
+ readonly requestId: string,
46
45
  readonly inference: string,
47
46
  ) {
48
47
  super()
@@ -55,15 +54,8 @@ export class MessageAppended extends EventBase(LEventTag, "message_appended") {
55
54
  }
56
55
  }
57
56
 
58
- export class FiberCreated extends EventBase(LEventTag, "fiber_created") {}
59
- export class FiberStarted extends EventBase(LEventTag, "fiber_started") {}
60
- export class FiberResolved extends EventBase(LEventTag, "fiber_resolved") {
61
- constructor(readonly value: any) {
62
- super()
63
- }
64
- }
65
- export class FiberRejected extends EventBase(LEventTag, "fiber_rejected") {
66
- constructor(readonly reason: any) {
57
+ export class StrandStatusChanged extends EventBase(LEventTag, "strand_status_changed") {
58
+ constructor(readonly status: StrandStatus) {
67
59
  super()
68
60
  }
69
61
  }
@@ -0,0 +1,19 @@
1
+ export class LiminalAssertionError extends Error {
2
+ override readonly name = "LiminalAssertionError"
3
+ }
4
+
5
+ export namespace LiminalAssertionError {
6
+ export function assert(expr: unknown, msg = ""): asserts expr {
7
+ if (!expr) {
8
+ throw new LiminalAssertionError(msg)
9
+ }
10
+ }
11
+
12
+ export function unimplemented(): never {
13
+ throw new LiminalAssertionError()
14
+ }
15
+
16
+ export function unreachable(): never {
17
+ throw new LiminalAssertionError()
18
+ }
19
+ }
package/Model.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { SchemaObject } from "liminal-schema"
2
- import { attachCustomInspect } from "liminal-util"
3
1
  import type { Message } from "./Message.ts"
2
+ import type { SchemaObject } from "./Schema.ts"
3
+ import type { Tool } from "./Tool.ts"
4
+ import { attachCustomInspect } from "./util/attachCustomInspect.ts"
4
5
 
5
6
  export class Model {
6
7
  constructor(
@@ -16,7 +17,8 @@ export class Model {
16
17
  export interface Envelope {
17
18
  messages: Array<Message>
18
19
  schema?: SchemaObject | undefined
19
- signal?: AbortSignal
20
+ signal: AbortSignal
21
+ tools?: Set<Tool> | undefined
20
22
  }
21
23
 
22
24
  export interface SealedEnvelope {
package/ModelRegistry.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ContextPart } from "./Context.ts"
2
1
  import type { Model } from "./Model.ts"
3
2
 
4
3
  /** An intrusive list for storing `Model`s. */
@@ -40,7 +39,7 @@ export class ModelRegistry {
40
39
  node.prev = node.next = undefined
41
40
  }
42
41
 
43
- clone(): ModelRegistry {
42
+ clone() {
44
43
  const instance = new ModelRegistry()
45
44
  for (let node = this.head; node; node = node.next) {
46
45
  instance.register(node.model)
@@ -54,8 +53,3 @@ export interface ModelRegistryNode {
54
53
  model: Model
55
54
  next?: ModelRegistryNode | undefined
56
55
  }
57
-
58
- export const ModelRegistryContext: ContextPart<ModelRegistry> = ContextPart(
59
- (parent) => parent?.clone() ?? new ModelRegistry(),
60
- "model_registry",
61
- )
package/Rune.test.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ test("adds 1 + 2 to equal 3", () => {
4
+ expect(1 + 2).toBe(3)
5
+ })
package/Rune.ts CHANGED
@@ -1,19 +1,31 @@
1
- import type { Fiber } from "./Fiber.ts"
1
+ import type { Context } from "./Context.ts"
2
+ import type { Definition } from "./Definition.ts"
2
3
 
3
- export interface Rune<out E = any> {
4
- E: E
5
- (fiber: Fiber): any
4
+ export interface Rune<E> {
6
5
  [RuneKey]: true
7
- debug?: string
6
+ value: {
7
+ kind: "continuation"
8
+ debug: string
9
+ f: () => any
10
+ } | {
11
+ kind: "event"
12
+ event: E
13
+ } | {
14
+ kind: "reflect"
15
+ } | {
16
+ kind: "child"
17
+ definition: Definition
18
+ context?: Context | undefined
19
+ }
8
20
  }
9
21
 
10
- export declare namespace Rune {
11
- export type E<X extends Rune> = X["E"]
22
+ export namespace Rune {
23
+ export type E<X extends Rune<any>> = X extends Rune<infer E> ? E : never
24
+
25
+ export function is(value: unknown): value is Rune<any> {
26
+ return typeof value === "object" && value !== null && RuneKey in value
27
+ }
12
28
  }
13
29
 
14
- export const RuneKey: unique symbol = Symbol.for("liminal/Rune")
30
+ export const RuneKey: unique symbol = Symbol.for("liminal/RuneKey")
15
31
  export type RuneKey = typeof RuneKey
16
-
17
- export function isRune(value: unknown): value is Rune {
18
- return typeof value === "object" && value !== null && RuneKey in value
19
- }
package/Schema.ts ADDED
@@ -0,0 +1,185 @@
1
+ import { subtle } from "node:crypto"
2
+ import { LiminalAssertionError as LE } from "./LiminalAssertionError.ts"
3
+
4
+ export type Schema<T = any> = SchemaType<T> | SchemaAnyOf<T>
5
+
6
+ export type SchemaType<T = any> =
7
+ | SchemaNull<T>
8
+ | SchemaBoolean<T>
9
+ | SchemaInteger<T>
10
+ | SchemaNumber<T>
11
+ | SchemaString<T>
12
+ | SchemaArray<T>
13
+ | SchemaObject<T>
14
+
15
+ export interface SchemaNull<T = any> extends SchemaTypeBase<"null", T> {}
16
+
17
+ export interface SchemaBoolean<T = any> extends SchemaTypeBase<"boolean", T> {}
18
+
19
+ export interface SchemaInteger<T = any> extends SchemaTypeBase<"integer", T> {}
20
+
21
+ export interface SchemaNumber<T = any> extends SchemaTypeBase<"number", T> {}
22
+
23
+ export interface SchemaString<T = any> extends SchemaTypeBase<"string", T> {
24
+ enum?: Array<string>
25
+ const?: string
26
+ }
27
+
28
+ export interface SchemaArray<T = any> extends SchemaTypeBase<"array", T> {
29
+ items: Schema
30
+ }
31
+
32
+ export interface SchemaObject<T = any> extends SchemaTypeBase<"object", T> {
33
+ properties: Record<string, Schema>
34
+ required: Array<string>
35
+ additionalProperties: false
36
+ }
37
+
38
+ interface SchemaTypeBase<K extends SchemaTypeName, T> extends SchemaBase<T> {
39
+ type: K
40
+ anyOf?: never
41
+ }
42
+
43
+ export type SchemaTypeName = "null" | "boolean" | "integer" | "number" | "string" | "array" | "object"
44
+
45
+ export interface SchemaAnyOf<T = any> extends SchemaBase<T> {
46
+ type?: never
47
+ anyOf: Array<Schema>
48
+ }
49
+
50
+ export interface SchemaBase<T> {
51
+ T: T
52
+ description?: string
53
+ }
54
+
55
+ export namespace Schema {
56
+ const ids = new WeakMap<Schema, Record<string, string>>()
57
+ const schemas = new WeakMap<WeakKey, Schema>()
58
+ const validators = new WeakMap<Schema, (value: unknown) => Promise<unknown>>()
59
+
60
+ export function compile<T>(key: WeakKey, {
61
+ schema: schema_,
62
+ validate,
63
+ }: {
64
+ schema(): unknown
65
+ validate(value: unknown): Promise<T>
66
+ }) {
67
+ let schema = schemas.get(key)
68
+ if (!schema) {
69
+ schema = Schema.validate(schema_())
70
+ schemas.set(key, schema)
71
+ }
72
+ validators.set(schema, validate)
73
+ return schema
74
+ }
75
+
76
+ export async function validateValue<T>(schema: Schema<T>, value: unknown): Promise<T> {
77
+ const validator = validators.get(schema)
78
+ LE.assert(validator)
79
+ return await validator(value) as never
80
+ }
81
+
82
+ export async function id(schema: Schema, description?: string): Promise<string> {
83
+ description = description ?? ""
84
+ if (!ids.has(schema)) {
85
+ ids.set(schema, {})
86
+ }
87
+ const schemaIds = ids.get(schema)!
88
+ let id = schemaIds[description]
89
+ if (!id) {
90
+ const content = `${description}_${JSON.stringify(schema)}`
91
+ const bytes = new Uint8Array(await subtle.digest("SHA-256", new TextEncoder().encode(content)))
92
+ let binary = ""
93
+ for (const b of bytes) binary += String.fromCharCode(b)
94
+ id = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
95
+ schemaIds[description] = id
96
+ }
97
+ return id
98
+ }
99
+
100
+ export function wrap(schema: Schema): SchemaObject {
101
+ return {
102
+ type: "object",
103
+ properties: { value: schema },
104
+ additionalProperties: false,
105
+ required: ["value"],
106
+ } satisfies Omit<SchemaObject, "T"> as never
107
+ }
108
+
109
+ export function validate(value: unknown): Schema {
110
+ LE.assert(typeof value === "object")
111
+ LE.assert(value !== null)
112
+ if ("anyOf" in value) {
113
+ LE.assert(Array.isArray(value.anyOf))
114
+ return {
115
+ anyOf: value.anyOf.map(validate),
116
+ } satisfies Omit<SchemaAnyOf, "T"> as never
117
+ } else {
118
+ if ("enum" in value) {
119
+ LE.assert(Array.isArray(value.enum))
120
+ LE.assert(!("const" in value))
121
+ value.enum.forEach((v) => LE.assert(typeof v === "string"))
122
+ return {
123
+ type: "string",
124
+ enum: value.enum,
125
+ } satisfies Omit<SchemaString, "T"> as never
126
+ } else if ("const" in value) {
127
+ LE.assert(typeof value.const === "string")
128
+ return {
129
+ type: "string",
130
+ const: value.const,
131
+ } satisfies Omit<SchemaString, "T"> as never
132
+ } else if ("type" in value) {
133
+ LE.assert("type" in value)
134
+ LE.assert(typeof value.type === "string")
135
+ LE.assert(SCHEMA_TYPE_NAMES[value.type])
136
+ switch (value.type) {
137
+ case "null":
138
+ case "boolean":
139
+ case "integer":
140
+ case "number": {
141
+ break
142
+ }
143
+ case "string": {
144
+ if ("const" in value) {
145
+ LE.assert(typeof value.const === "string")
146
+ }
147
+ break
148
+ }
149
+ case "array": {
150
+ LE.assert("items" in value)
151
+ return {
152
+ type: "array",
153
+ items: validate(value.items),
154
+ } satisfies Omit<SchemaArray, "T"> as never
155
+ }
156
+ case "object": {
157
+ LE.assert("properties" in value)
158
+ LE.assert(typeof value.properties === "object")
159
+ const { properties } = value
160
+ LE.assert(properties !== null)
161
+ return {
162
+ type: "object",
163
+ properties: Object.fromEntries(
164
+ Object.entries(properties).map(([key, value]) => [key, validate(value)]),
165
+ ),
166
+ required: Object.keys(properties),
167
+ additionalProperties: false,
168
+ } satisfies Omit<SchemaObject, "T"> as never
169
+ }
170
+ }
171
+ }
172
+ }
173
+ return value as Schema
174
+ }
175
+ }
176
+
177
+ const SCHEMA_TYPE_NAMES: Record<string, boolean> = {
178
+ null: true,
179
+ boolean: true,
180
+ integer: true,
181
+ number: true,
182
+ string: true,
183
+ array: true,
184
+ object: true,
185
+ } satisfies Record<SchemaTypeName, true>
package/Strand.ts ADDED
@@ -0,0 +1,253 @@
1
+ import { Context } from "./Context.ts"
2
+ import { Definition } from "./Definition.ts"
3
+ import { StrandRejectedError } from "./errors.ts"
4
+ import { continuation } from "./L/continuation.ts"
5
+ import { StrandStatusChanged } from "./LEvent.ts"
6
+ import type { Rune } from "./Rune.ts"
7
+ import { attachCustomInspect } from "./util/attachCustomInspect.ts"
8
+
9
+ export interface StrandConfig {
10
+ parent?: Strand | undefined
11
+ context?: Context | undefined
12
+ signal?: AbortSignal | undefined
13
+ }
14
+
15
+ let nextIndex: number = 0
16
+
17
+ export class Strand<Y extends Rune<any> = Rune<any>, T = any> implements Iterable<Y, T>, PromiseLike<T> {
18
+ declare T: T
19
+ declare Y: Y
20
+
21
+ readonly #controller: AbortController = new AbortController()
22
+ readonly signal: AbortSignal = this.#controller.signal
23
+ #handle?: ((this: Strand, event: any) => void) | undefined
24
+ #definition: Definition<Y, T>
25
+ status: StrandStatus<T> = { type: "untouched" }
26
+ readonly index: number = nextIndex++
27
+ readonly depth: number
28
+ declare readonly parent?: Strand
29
+ readonly context: Context
30
+
31
+ constructor(definition: Definition<Y, T>, config: StrandConfig) {
32
+ this.#definition = definition
33
+ this.depth = (config?.parent?.depth ?? -1) + 1
34
+ const { parent, context, signal: configSignal } = config ?? {}
35
+ if (parent) {
36
+ this.parent = parent
37
+ this.#attachSignal(parent.signal, () => ({
38
+ type: "parent_aborted",
39
+ reason: parent.signal.reason,
40
+ }))
41
+ }
42
+ if (configSignal) {
43
+ this.#attachSignal(configSignal, () => ({
44
+ type: "config_signal_aborted",
45
+ reason: configSignal.reason,
46
+ }))
47
+ }
48
+ if (context) {
49
+ this.context = context
50
+ const { handler } = context
51
+ if (handler) {
52
+ this.#handle = (function(this: Strand, event: any) {
53
+ try {
54
+ handler.call(this, event)
55
+ } catch (exception) {
56
+ this.#setTerminalStatus({
57
+ type: "handler_exception_thrown",
58
+ exception,
59
+ })
60
+ }
61
+ }).bind(this)
62
+ this.#handle(new StrandStatusChanged(this.status))
63
+ }
64
+ } else {
65
+ this.context = Context()
66
+ }
67
+ }
68
+
69
+ #setStatus(status: StrandStatus<T>): void {
70
+ this.status = status
71
+ this.#handle?.(new StrandStatusChanged(this.status))
72
+ }
73
+
74
+ #setTerminalStatus = (status: StrandStatus.Rejected | StrandStatus.Resolved<T>): void => {
75
+ this.#setStatus(status)
76
+ this.#controller.abort(this.status)
77
+ }
78
+
79
+ #attachSignal(
80
+ signal: AbortSignal,
81
+ getStatus: () => StrandStatus.Rejected.ConfigSignalAborted | StrandStatus.Rejected.ParentAborted,
82
+ ): void {
83
+ const f = () => {
84
+ this.#setTerminalStatus(getStatus())
85
+ }
86
+ if (signal.aborted) {
87
+ return f()
88
+ }
89
+ signal.addEventListener("abort", f, { once: true })
90
+ this.#controller.signal.addEventListener(
91
+ "abort",
92
+ () => {
93
+ signal.removeEventListener("abort", f)
94
+ },
95
+ { once: true },
96
+ )
97
+ }
98
+
99
+ then<TResult1 = T, TResult2 = never>(
100
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
101
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
102
+ ): PromiseLike<TResult1 | TResult2> {
103
+ if (this.status.type !== "untouched") {
104
+ return this.#replay(this.status).then(onfulfilled, onrejected)
105
+ }
106
+ let resolve: (value: T) => void = undefined!
107
+ let reject: (reason?: unknown) => void = undefined!
108
+ const promise = new Promise<T>((resolve_, reject_) => {
109
+ resolve = resolve_
110
+ reject = reject_
111
+ })
112
+ this.status = {
113
+ type: "pending",
114
+ promise,
115
+ resolve,
116
+ reject,
117
+ }
118
+ this.#handle?.(new StrandStatusChanged(this.status))
119
+ const iterator = Definition.unwrap(this.#definition)
120
+ let nextArg: unknown
121
+ queueMicrotask(async () => {
122
+ try {
123
+ let current = await iterator.next()
124
+ while (!current.done) {
125
+ const rune = current.value
126
+ const { value } = rune
127
+ switch (value.kind) {
128
+ case "reflect": {
129
+ nextArg = this
130
+ break
131
+ }
132
+ case "continuation": {
133
+ nextArg = await value.f()
134
+ break
135
+ }
136
+ case "event": {
137
+ this.#handle?.(value.event)
138
+ nextArg = undefined
139
+ break
140
+ }
141
+ case "child": {
142
+ nextArg = await new Strand(value.definition, {
143
+ parent: this,
144
+ context: value.context ?? this.context.clone(),
145
+ }).then()
146
+ break
147
+ }
148
+ }
149
+ switch (this.status.type) {
150
+ case "config_signal_aborted":
151
+ case "parent_aborted":
152
+ case "continuation_exception_thrown":
153
+ case "handler_exception_thrown": {
154
+ try {
155
+ await iterator.return?.(undefined)
156
+ } catch (exception) {}
157
+ return Promise.reject(new StrandRejectedError(this.status))
158
+ }
159
+ }
160
+ current = await iterator.next(nextArg)
161
+ }
162
+ const { value } = current
163
+ this.#setTerminalStatus({
164
+ type: "resolved",
165
+ value,
166
+ })
167
+ resolve(value)
168
+ } catch (exception) {
169
+ this.#setTerminalStatus({
170
+ type: "continuation_exception_thrown",
171
+ exception,
172
+ })
173
+ reject(exception)
174
+ }
175
+ })
176
+ return promise.then(onfulfilled, onrejected)
177
+ }
178
+
179
+ *[Symbol.iterator](): Generator<Y, T> {
180
+ return yield* continuation("run_strand", () => this.then()) as any
181
+ }
182
+
183
+ #replay(status: Exclude<StrandStatus, StrandStatus.Untouched>): Promise<T> {
184
+ switch (status.type) {
185
+ case "config_signal_aborted":
186
+ case "parent_aborted":
187
+ case "continuation_exception_thrown":
188
+ case "handler_exception_thrown": {
189
+ return Promise.reject(new StrandRejectedError(status))
190
+ }
191
+ case "resolved": {
192
+ return Promise.resolve(status.value)
193
+ }
194
+ case "pending": {
195
+ return status.promise
196
+ }
197
+ }
198
+ }
199
+
200
+ static {
201
+ attachCustomInspect(this, ({ index, parent }) => ({ index, ...parent && { parent } }))
202
+ }
203
+ }
204
+
205
+ export type StrandStatus<T = any> =
206
+ | StrandStatus.Untouched
207
+ | StrandStatus.Pending<T>
208
+ | StrandStatus.Resolved<T>
209
+ | StrandStatus.Rejected
210
+ export namespace StrandStatus {
211
+ export interface Untouched {
212
+ type: "untouched"
213
+ }
214
+
215
+ export interface Pending<T> {
216
+ type: "pending"
217
+ promise: Promise<T>
218
+ resolve: (value: T) => void
219
+ reject: (reason?: unknown) => void
220
+ }
221
+
222
+ export interface Resolved<T> {
223
+ type: "resolved"
224
+ value: T
225
+ }
226
+
227
+ export type Rejected =
228
+ | Rejected.ConfigSignalAborted
229
+ | Rejected.ParentAborted
230
+ | Rejected.ContinuationExceptionThrown
231
+ | Rejected.HandlerExceptionThrown
232
+ // | Rejected.ModelError
233
+ // | Rejected.ValidationError
234
+ // | Rejected.Timeout
235
+ export namespace Rejected {
236
+ export interface ConfigSignalAborted {
237
+ type: "config_signal_aborted"
238
+ reason: unknown
239
+ }
240
+ export interface ParentAborted {
241
+ type: "parent_aborted"
242
+ reason: unknown
243
+ }
244
+ export interface ContinuationExceptionThrown {
245
+ type: "continuation_exception_thrown"
246
+ exception: unknown
247
+ }
248
+ export interface HandlerExceptionThrown {
249
+ type: "handler_exception_thrown"
250
+ exception: unknown
251
+ }
252
+ }
253
+ }