effect-start 0.25.0 → 0.27.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.
Files changed (174) hide show
  1. package/package.json +20 -86
  2. package/src/Entity.ts +6 -6
  3. package/src/FileRouterCodegen.ts +4 -4
  4. package/src/FileSystem.ts +4 -8
  5. package/src/RouteHook.ts +1 -1
  6. package/src/RouteSse.ts +3 -3
  7. package/src/SqlIntrospect.ts +2 -2
  8. package/src/Start.ts +102 -2
  9. package/src/Values.ts +11 -0
  10. package/src/bun/BunRoute.ts +1 -1
  11. package/src/bun/BunRuntime.ts +5 -5
  12. package/src/hyper/HyperHtml.ts +11 -7
  13. package/src/hyper/jsx.d.ts +1 -1
  14. package/src/lint/plugin.js +174 -4
  15. package/src/sql/SqlClient.ts +355 -0
  16. package/src/sql/bun/index.ts +117 -50
  17. package/src/sql/index.ts +1 -1
  18. package/src/sql/libsql/index.ts +91 -77
  19. package/src/sql/libsql/libsql.d.ts +4 -1
  20. package/src/sql/mssql/index.ts +141 -108
  21. package/src/sql/mssql/mssql.d.ts +1 -0
  22. package/src/testing/TestLogger.ts +4 -4
  23. package/src/x/tailwind/compile.ts +6 -14
  24. package/dist/ChildProcess.js +0 -42
  25. package/dist/Commander.js +0 -410
  26. package/dist/ContentNegotiation.js +0 -465
  27. package/dist/Cookies.js +0 -371
  28. package/dist/Development.js +0 -94
  29. package/dist/Effectify.js +0 -27
  30. package/dist/Entity.js +0 -289
  31. package/dist/Fetch.js +0 -192
  32. package/dist/FilePathPattern.js +0 -97
  33. package/dist/FileRouter.js +0 -204
  34. package/dist/FileRouterCodegen.js +0 -298
  35. package/dist/FileSystem.js +0 -132
  36. package/dist/Http.js +0 -107
  37. package/dist/PathPattern.js +0 -451
  38. package/dist/PlatformError.js +0 -40
  39. package/dist/PlatformRuntime.js +0 -71
  40. package/dist/Route.js +0 -143
  41. package/dist/RouteBody.js +0 -92
  42. package/dist/RouteError.js +0 -76
  43. package/dist/RouteHook.js +0 -64
  44. package/dist/RouteHttp.js +0 -367
  45. package/dist/RouteHttpTracer.js +0 -90
  46. package/dist/RouteMount.js +0 -86
  47. package/dist/RouteSchema.js +0 -271
  48. package/dist/RouteSse.js +0 -94
  49. package/dist/RouteTree.js +0 -119
  50. package/dist/RouteTrie.js +0 -179
  51. package/dist/SchemaExtra.js +0 -99
  52. package/dist/Socket.js +0 -40
  53. package/dist/SqlIntrospect.js +0 -515
  54. package/dist/Start.js +0 -79
  55. package/dist/StartApp.js +0 -3
  56. package/dist/StreamExtra.js +0 -135
  57. package/dist/System.js +0 -38
  58. package/dist/TuplePathPattern.js +0 -74
  59. package/dist/Unique.js +0 -226
  60. package/dist/Values.js +0 -52
  61. package/dist/bun/BunBundle.js +0 -186
  62. package/dist/bun/BunChildProcessSpawner.js +0 -142
  63. package/dist/bun/BunImportTrackerPlugin.js +0 -91
  64. package/dist/bun/BunRoute.js +0 -157
  65. package/dist/bun/BunRuntime.js +0 -41
  66. package/dist/bun/BunServer.js +0 -285
  67. package/dist/bun/BunVirtualFilesPlugin.js +0 -54
  68. package/dist/bun/_BunEnhancedResolve.js +0 -127
  69. package/dist/bun/index.js +0 -5
  70. package/dist/bundler/Bundle.js +0 -92
  71. package/dist/bundler/BundleFiles.js +0 -154
  72. package/dist/bundler/BundleRoute.js +0 -62
  73. package/dist/client/Overlay.js +0 -33
  74. package/dist/client/ScrollState.js +0 -106
  75. package/dist/client/index.js +0 -97
  76. package/dist/console/Console.js +0 -42
  77. package/dist/console/ConsoleErrors.js +0 -211
  78. package/dist/console/ConsoleLogger.js +0 -56
  79. package/dist/console/ConsoleMetrics.js +0 -72
  80. package/dist/console/ConsoleProcess.js +0 -59
  81. package/dist/console/ConsoleStore.js +0 -72
  82. package/dist/console/ConsoleTracer.js +0 -107
  83. package/dist/console/Simulation.js +0 -784
  84. package/dist/console/index.js +0 -3
  85. package/dist/console/routes/tree.js +0 -30
  86. package/dist/datastar/actions/fetch.js +0 -536
  87. package/dist/datastar/actions/peek.js +0 -13
  88. package/dist/datastar/actions/setAll.js +0 -19
  89. package/dist/datastar/actions/toggleAll.js +0 -19
  90. package/dist/datastar/attributes/attr.js +0 -49
  91. package/dist/datastar/attributes/bind.js +0 -194
  92. package/dist/datastar/attributes/class.js +0 -54
  93. package/dist/datastar/attributes/computed.js +0 -25
  94. package/dist/datastar/attributes/effect.js +0 -10
  95. package/dist/datastar/attributes/indicator.js +0 -33
  96. package/dist/datastar/attributes/init.js +0 -27
  97. package/dist/datastar/attributes/jsonSignals.js +0 -33
  98. package/dist/datastar/attributes/on.js +0 -81
  99. package/dist/datastar/attributes/onIntersect.js +0 -53
  100. package/dist/datastar/attributes/onInterval.js +0 -31
  101. package/dist/datastar/attributes/onSignalPatch.js +0 -51
  102. package/dist/datastar/attributes/ref.js +0 -11
  103. package/dist/datastar/attributes/show.js +0 -32
  104. package/dist/datastar/attributes/signals.js +0 -18
  105. package/dist/datastar/attributes/style.js +0 -57
  106. package/dist/datastar/attributes/text.js +0 -29
  107. package/dist/datastar/engine.js +0 -1145
  108. package/dist/datastar/index.js +0 -25
  109. package/dist/datastar/utils.js +0 -250
  110. package/dist/datastar/watchers/patchElements.js +0 -486
  111. package/dist/datastar/watchers/patchSignals.js +0 -14
  112. package/dist/experimental/EncryptedCookies.js +0 -328
  113. package/dist/experimental/index.js +0 -1
  114. package/dist/hyper/Hyper.js +0 -28
  115. package/dist/hyper/HyperHtml.js +0 -165
  116. package/dist/hyper/HyperNode.js +0 -13
  117. package/dist/hyper/HyperRoute.js +0 -45
  118. package/dist/hyper/html.js +0 -30
  119. package/dist/hyper/index.js +0 -5
  120. package/dist/hyper/jsx-runtime.js +0 -14
  121. package/dist/index.js +0 -8
  122. package/dist/node/NodeFileSystem.js +0 -675
  123. package/dist/node/NodeUtils.js +0 -23
  124. package/dist/sql/Sql.js +0 -8
  125. package/dist/sql/bun/index.js +0 -142
  126. package/dist/sql/index.js +0 -1
  127. package/dist/sql/libsql/index.js +0 -156
  128. package/dist/sql/mssql/docker.js +0 -110
  129. package/dist/sql/mssql/index.js +0 -194
  130. package/dist/testing/TestLogger.js +0 -42
  131. package/dist/testing/index.js +0 -2
  132. package/dist/testing/utils.js +0 -61
  133. package/dist/x/cloudflare/CloudflareTunnel.js +0 -63
  134. package/dist/x/cloudflare/index.js +0 -1
  135. package/dist/x/tailscale/TailscaleTunnel.js +0 -94
  136. package/dist/x/tailscale/index.js +0 -1
  137. package/dist/x/tailwind/TailwindPlugin.js +0 -294
  138. package/dist/x/tailwind/compile.js +0 -210
  139. package/dist/x/tailwind/plugin.js +0 -17
  140. package/src/console/Console.ts +0 -42
  141. package/src/console/ConsoleErrors.ts +0 -213
  142. package/src/console/ConsoleLogger.ts +0 -56
  143. package/src/console/ConsoleMetrics.ts +0 -72
  144. package/src/console/ConsoleProcess.ts +0 -59
  145. package/src/console/ConsoleStore.ts +0 -187
  146. package/src/console/ConsoleTracer.ts +0 -107
  147. package/src/console/Simulation.ts +0 -814
  148. package/src/console/console.html +0 -340
  149. package/src/console/index.ts +0 -3
  150. package/src/console/routes/errors/route.tsx +0 -97
  151. package/src/console/routes/fiberDetail.tsx +0 -54
  152. package/src/console/routes/fibers/route.tsx +0 -45
  153. package/src/console/routes/git/route.tsx +0 -64
  154. package/src/console/routes/layout.tsx +0 -4
  155. package/src/console/routes/logs/route.tsx +0 -77
  156. package/src/console/routes/metrics/route.tsx +0 -36
  157. package/src/console/routes/route.tsx +0 -8
  158. package/src/console/routes/routes/route.tsx +0 -30
  159. package/src/console/routes/services/route.tsx +0 -21
  160. package/src/console/routes/system/route.tsx +0 -43
  161. package/src/console/routes/traceDetail.tsx +0 -22
  162. package/src/console/routes/traces/route.tsx +0 -81
  163. package/src/console/routes/tree.ts +0 -30
  164. package/src/console/ui/Errors.tsx +0 -76
  165. package/src/console/ui/Fibers.tsx +0 -321
  166. package/src/console/ui/Git.tsx +0 -182
  167. package/src/console/ui/Logs.tsx +0 -46
  168. package/src/console/ui/Metrics.tsx +0 -78
  169. package/src/console/ui/Routes.tsx +0 -125
  170. package/src/console/ui/Services.tsx +0 -273
  171. package/src/console/ui/Shell.tsx +0 -62
  172. package/src/console/ui/System.tsx +0 -131
  173. package/src/console/ui/Traces.tsx +0 -426
  174. package/src/sql/Sql.ts +0 -51
package/dist/Entity.js DELETED
@@ -1,289 +0,0 @@
1
- import * as Effect from "effect/Effect"
2
- import * as ParseResult from "effect/ParseResult"
3
- import * as Pipeable from "effect/Pipeable"
4
- import * as Predicate from "effect/Predicate"
5
- import * as Schema from "effect/Schema"
6
- import * as Stream from "effect/Stream"
7
- import * as StreamExtra from "./StreamExtra.js"
8
- import * as Values from "./Values.js"
9
-
10
- export const TypeId = Symbol.for("effect-start/Entity")
11
-
12
- const textDecoder = new TextDecoder()
13
- const textEncoder = new TextEncoder()
14
-
15
- function isBinary(v) {
16
- return v instanceof Uint8Array || v instanceof ArrayBuffer
17
- }
18
-
19
- /**
20
- * Header keys are guaranteed to be lowercase.
21
- */
22
-
23
- function parseJson(s) {
24
- try {
25
- return Effect.succeed(JSON.parse(s))
26
- } catch (e) {
27
- return Effect.fail(
28
- new ParseResult.ParseError({
29
- issue: new ParseResult.Type(
30
- Schema.Unknown.ast,
31
- s,
32
- e instanceof Error ? e.message : "Failed to parse JSON",
33
- ),
34
- }),
35
- )
36
- }
37
- }
38
-
39
- function getText(
40
- self,
41
- ) {
42
- const v = self.body
43
- if (StreamExtra.isStream(v)) {
44
- return Stream.mkString(Stream.decodeText(v))
45
- }
46
- if (Effect.isEffect(v)) {
47
- return Effect.flatMap(
48
- v,
49
- (inner) => {
50
- if (isEntity(inner)) {
51
- return inner.text
52
- }
53
- if (typeof inner === "string") {
54
- return Effect.succeed(inner)
55
- }
56
- if (isBinary(inner)) {
57
- return Effect.succeed(textDecoder.decode(inner))
58
- }
59
- return Effect.fail(mismatch(Schema.String, inner))
60
- },
61
- )
62
- }
63
- if (typeof v === "string") {
64
- return Effect.succeed(v)
65
- }
66
- if (isBinary(v)) {
67
- return Effect.succeed(textDecoder.decode(v))
68
- }
69
- return Effect.fail(mismatch(Schema.String, v))
70
- }
71
-
72
- function getJson(
73
- self,
74
- ) {
75
- const v = self.body
76
- if (StreamExtra.isStream(v)) {
77
- return Effect.flatMap(getText(self), parseJson)
78
- }
79
- if (Effect.isEffect(v)) {
80
- return Effect.flatMap(
81
- v,
82
- (inner) => {
83
- if (isEntity(inner)) {
84
- return inner.json
85
- }
86
- if (typeof inner === "object" && inner !== null && !isBinary(inner)) {
87
- return Effect.succeed(inner)
88
- }
89
- if (typeof inner === "string") {
90
- return parseJson(inner)
91
- }
92
- if (isBinary(inner)) {
93
- return parseJson(textDecoder.decode(inner))
94
- }
95
- return Effect.fail(mismatch(Schema.Unknown, inner))
96
- },
97
- )
98
- }
99
- if (typeof v === "object" && v !== null && !isBinary(v)) {
100
- return Effect.succeed(v)
101
- }
102
- if (typeof v === "string") {
103
- return parseJson(v)
104
- }
105
- if (isBinary(v)) {
106
- return parseJson(textDecoder.decode(v))
107
- }
108
- return Effect.fail(mismatch(Schema.Unknown, v))
109
- }
110
-
111
- function getBytes(
112
- self,
113
- ) {
114
- const v = self.body
115
- if (StreamExtra.isStream(v)) {
116
- return Stream.runFold(
117
- v,
118
- new Uint8Array(0),
119
- Values.concatBytes,
120
- )
121
- }
122
- if (Effect.isEffect(v)) {
123
- return Effect.flatMap(
124
- v,
125
- (inner) => {
126
- if (isEntity(inner)) {
127
- return inner.bytes
128
- }
129
- if (inner instanceof Uint8Array) {
130
- return Effect.succeed(inner)
131
- }
132
- if (inner instanceof ArrayBuffer) {
133
- return Effect.succeed(new Uint8Array(inner))
134
- }
135
- if (typeof inner === "string") {
136
- return Effect.succeed(textEncoder.encode(inner))
137
- }
138
- return Effect.fail(mismatch(Schema.Uint8ArrayFromSelf, inner))
139
- },
140
- )
141
- }
142
- if (v instanceof Uint8Array) {
143
- return Effect.succeed(v)
144
- }
145
- if (v instanceof ArrayBuffer) {
146
- return Effect.succeed(new Uint8Array(v))
147
- }
148
- if (typeof v === "string") {
149
- return Effect.succeed(textEncoder.encode(v))
150
- }
151
- // Allows entity.stream to work when body is a JSON object
152
- if (typeof v === "object" && v !== null && !isBinary(v)) {
153
- return Effect.succeed(textEncoder.encode(JSON.stringify(v)))
154
- }
155
- return Effect.fail(mismatch(Schema.Uint8ArrayFromSelf, v))
156
- }
157
-
158
- function getStream(self) {
159
- const v = self.body
160
- if (StreamExtra.isStream(v)) {
161
- return v
162
- }
163
- if (Effect.isEffect(v)) {
164
- return Stream.unwrap(
165
- Effect.map(v, (inner) => {
166
- if (isEntity(inner)) {
167
- return inner.stream
168
- }
169
- return Stream.fromEffect(getBytes(make(inner)))
170
- }),
171
- )
172
- }
173
- return Stream.fromEffect(getBytes(self))
174
- }
175
-
176
- const Proto = Object.defineProperties(Object.create(null), {
177
- [TypeId]: { value: TypeId },
178
- pipe: {
179
- value: function () {
180
- return Pipeable.pipeArguments(this, arguments)
181
- },
182
- },
183
- text: {
184
- get() {
185
- return getText(this)
186
- },
187
- },
188
- json: {
189
- get() {
190
- return getJson(this)
191
- },
192
- },
193
- bytes: {
194
- get() {
195
- return getBytes(this)
196
- },
197
- },
198
- stream: {
199
- get() {
200
- return getStream(this)
201
- },
202
- },
203
- })
204
-
205
- export function isEntity(input) {
206
- return Predicate.hasProperty(input, TypeId)
207
- }
208
-
209
- export function make(body, options) {
210
- return Object.assign(Object.create(Proto), {
211
- body,
212
- headers: options?.headers ?? {},
213
- url: options?.url,
214
- status: options?.status,
215
- })
216
- }
217
-
218
- export function effect(body) {
219
- return make(body)
220
- }
221
-
222
- export function resolve(entity) {
223
- const body = entity.body
224
- if (Effect.isEffect(body)) {
225
- return Effect.map(body, (inner) =>
226
- isEntity(inner)
227
- ? (inner)
228
- : (make(inner, {
229
- status: entity.status,
230
- headers: entity.headers,
231
- url: entity.url,
232
- })),
233
- )
234
- }
235
- return Effect.succeed(entity)
236
- }
237
-
238
- export function type(self) {
239
- const h = self.headers
240
- if (h["content-type"]) {
241
- return h["content-type"]
242
- }
243
- const v = self.body
244
- if (typeof v === "string") {
245
- return "text/plain"
246
- }
247
- if (typeof v === "object" && v !== null && !isBinary(v)) {
248
- return "application/json"
249
- }
250
- return "application/octet-stream"
251
- }
252
-
253
- export function length(self) {
254
- const h = self.headers
255
- if (h["content-length"]) {
256
- return parseInt(h["content-length"], 10)
257
- }
258
- const v = self.body
259
- if (typeof v === "string") {
260
- return textEncoder.encode(v).byteLength
261
- }
262
- if (isBinary(v)) {
263
- return v.byteLength
264
- }
265
- return undefined
266
- }
267
-
268
- export function fromResponse(
269
- response,
270
- request,
271
- ) {
272
- return make(
273
- Effect.tryPromise({
274
- try: () => response.arrayBuffer().then((buf) => new Uint8Array(buf)),
275
- catch: (e) => (e instanceof Error ? e : new Error(String(e))),
276
- }),
277
- {
278
- headers: Object.fromEntries(response.headers.entries()),
279
- status: response.status,
280
- url: response.url || request?.url,
281
- },
282
- )
283
- }
284
-
285
- function mismatch(expected, actual) {
286
- return new ParseResult.ParseError({
287
- issue: new ParseResult.Type(expected.ast, actual),
288
- })
289
- }
package/dist/Fetch.js DELETED
@@ -1,192 +0,0 @@
1
- import * as Data from "effect/Data"
2
- import * as Effect from "effect/Effect"
3
- import * as Schedule from "effect/Schedule"
4
- import * as Entity from "./Entity.js"
5
-
6
- const TypeId = Symbol.for("effect-start/FetchClient")
7
-
8
- export class FetchError extends Data.TaggedError("FetchError") {}
9
-
10
- const tryFetch = (request) =>
11
- Effect.map(
12
- Effect.tryPromise({
13
- try: () => globalThis.fetch(request),
14
- catch: (e) => new FetchError({ reason: "Network", cause: e, request }),
15
- }),
16
- (response) => Entity.fromResponse(response, request),
17
- )
18
-
19
- const defaultMiddleware = [tryFetch]
20
- const noopNext = () => Effect.die("no middleware")
21
-
22
- function withTrace(
23
- request,
24
- effect,
25
- ) {
26
- const url = new URL(request.url)
27
-
28
- return Effect.useSpan(
29
- `http.client ${request.method}`,
30
- { kind: "client", captureStackTrace: false },
31
- (span) => {
32
- span.attribute("http.request.method", request.method)
33
- span.attribute("url.full", url.toString())
34
- span.attribute("url.path", url.pathname)
35
- const query = url.search.slice(1)
36
- if (query !== "") {
37
- span.attribute("url.query", query)
38
- }
39
- span.attribute("url.scheme", url.protocol.slice(0, -1))
40
-
41
- return Effect.flatMap(Effect.exit(Effect.withParentSpan(effect, span)), (exit) => {
42
- if (exit._tag === "Success") {
43
- span.attribute("http.response.status_code", exit.value.status ?? 0)
44
- }
45
- return exit
46
- })
47
- },
48
- )
49
- }
50
-
51
- export function fetch(
52
- input,
53
- init,
54
- ) {
55
- const middleware =
56
- (this)?.middleware ?? defaultMiddleware
57
- return Effect.gen(function* () {
58
- const request = new Request(input, init)
59
-
60
- let handler = noopNext
61
- for (let i = middleware.length - 1; i >= 0; i--) {
62
- const nextHandler = handler
63
- handler = ((req) => middleware[i](req, nextHandler))
64
- }
65
-
66
- return yield* withTrace(request, handler(request))
67
- })
68
- }
69
-
70
- export function get(
71
- input,
72
- init,
73
- ) {
74
- return fetch.call(this, input, { ...init, method: "GET" })
75
- }
76
-
77
- export function post(
78
- input,
79
- init,
80
- ) {
81
- return fetch.call(this, input, { ...init, method: "POST" })
82
- }
83
-
84
- export function use(
85
- ...middleware
86
- ) {
87
- const base = (this)?.middleware ?? defaultMiddleware
88
- const transport = base[base.length - 1]
89
- const existing = base.slice(0, -1)
90
- return Object.create(ClientProto, {
91
- middleware: { value: [...existing, ...middleware, transport] },
92
- })
93
- }
94
-
95
- const ClientProto = {
96
- [TypeId]: TypeId,
97
- middleware: defaultMiddleware,
98
- fetch,
99
- get,
100
- post,
101
- use,
102
- }
103
-
104
- export function filterStatus(
105
- predicate,
106
- options,
107
- ) {
108
- return ((_request, next) =>
109
- Effect.gen(function* () {
110
- const entity = yield* next(_request)
111
- const status = entity.status ?? 0
112
- if (predicate(status)) {
113
- return entity
114
- }
115
- if (options?.orElse) {
116
- return yield* options.orElse(entity)
117
- }
118
- return yield* Effect.fail(new FetchError({ reason: "Status", response: entity }))
119
- }))
120
- }
121
-
122
- export function filterStatusOk(options) {
123
- return filterStatus((status) => status >= 200 && status < 300, options)
124
- }
125
-
126
- export function followRedirects(options) {
127
- const maxRedirects = options?.maxRedirects ?? 10
128
-
129
- return (request, next) =>
130
- Effect.gen(function* () {
131
- let currentRequest = new Request(request.url, {
132
- ...request,
133
- redirect: "manual",
134
- })
135
- let redirectCount = 0
136
-
137
- while (true) {
138
- const entity = yield* next(currentRequest)
139
- const status = entity.status ?? 0
140
-
141
- if (status >= 300 && status < 400) {
142
- if (redirectCount >= maxRedirects) {
143
- return entity
144
- }
145
-
146
- const location = entity.headers["location"]
147
- if (!location) {
148
- return entity
149
- }
150
-
151
- redirectCount++
152
- const nextUrl = new URL(location, currentRequest.url)
153
-
154
- if (status === 303) {
155
- currentRequest = new Request(nextUrl.toString(), {
156
- method: "GET",
157
- headers: currentRequest.headers,
158
- redirect: "manual",
159
- })
160
- } else {
161
- currentRequest = new Request(nextUrl.toString(), {
162
- method: currentRequest.method,
163
- headers: currentRequest.headers,
164
- body: currentRequest.body,
165
- redirect: "manual",
166
- })
167
- }
168
- } else {
169
- return entity
170
- }
171
- }
172
- })
173
- }
174
-
175
- export function retry(options) {
176
- return ((request, next) => {
177
- if (options.schedule) {
178
- return Effect.retry(
179
- Effect.suspend(() => next(request)),
180
- options.schedule,
181
- )
182
- }
183
-
184
- const times = options.times ?? 3
185
- const delay = options.delay ?? 1000
186
-
187
- return Effect.retry(
188
- Effect.suspend(() => next(request)),
189
- Schedule.intersect(Schedule.recurs(times), Schedule.exponential(delay)),
190
- )
191
- })
192
- }
@@ -1,97 +0,0 @@
1
- import * as Either from "effect/Either"
2
-
3
- export function segments(pattern) {
4
- const parts = pattern.split("/").filter(Boolean)
5
- const result = []
6
-
7
- for (const part of parts) {
8
- if (/^\(\w+\)$/.test(part)) {
9
- result.push({ _tag: "GroupSegment", name: part.slice(1, -1) })
10
- } else if (part.startsWith("[[") && part.endsWith("]]")) {
11
- result.push({ _tag: "RestSegment", name: part.slice(2, -2) })
12
- } else if (part.startsWith("[") && part.endsWith("]")) {
13
- result.push({ _tag: "ParamSegment", name: part.slice(1, -1) })
14
- } else if (/^[\p{L}\p{N}._~-]+$/u.test(part)) {
15
- result.push({ _tag: "LiteralSegment", value: part })
16
- } else {
17
- result.push({ _tag: "InvalidSegment", value: part })
18
- }
19
- }
20
-
21
- return result
22
- }
23
-
24
- export function validate(pattern) {
25
- const segs = segments(pattern)
26
-
27
- const invalid = segs.find((s) => s._tag === "InvalidSegment")
28
- if (invalid) {
29
- return Either.left({
30
- _tag: "FilePathPatternError",
31
- pattern,
32
- message: `Invalid segment: "${invalid.value}"`,
33
- })
34
- }
35
-
36
- const restIndex = segs.findIndex((s) => s._tag === "RestSegment")
37
- if (restIndex !== -1 && restIndex !== segs.length - 1) {
38
- return Either.left({
39
- _tag: "FilePathPatternError",
40
- pattern,
41
- message: "Rest segment must be the last segment",
42
- })
43
- }
44
-
45
- return Either.right(segs)
46
- }
47
-
48
- export function format(segs) {
49
- const parts = segs.map((seg) => {
50
- switch (seg._tag) {
51
- case "GroupSegment":
52
- return `(${seg.name})`
53
- case "RestSegment":
54
- return `[[${seg.name}]]`
55
- case "ParamSegment":
56
- return `[${seg.name}]`
57
- case "LiteralSegment":
58
- return seg.value
59
- case "InvalidSegment":
60
- return seg.value
61
- }
62
- })
63
- const joined = parts.join("/")
64
- return (joined ? `/${joined}` : "/")
65
- }
66
-
67
- export function toPathPattern(
68
- pattern,
69
- ) {
70
- const result = validate(pattern)
71
-
72
- if (Either.isLeft(result)) {
73
- return Either.left(result.left)
74
- }
75
-
76
- const segs = result.right
77
- const pathParts = []
78
-
79
- for (const seg of segs) {
80
- switch (seg._tag) {
81
- case "GroupSegment":
82
- continue
83
- case "RestSegment":
84
- pathParts.push(`:${seg.name}*`)
85
- break
86
- case "ParamSegment":
87
- pathParts.push(`:${seg.name}`)
88
- break
89
- case "LiteralSegment":
90
- pathParts.push(seg.value)
91
- break
92
- }
93
- }
94
-
95
- const joined = pathParts.join("/")
96
- return Either.right((joined ? `/${joined}` : "/"))
97
- }