@thomasfosterau/effect-sveltekit 0.5.0 → 0.7.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.md +9 -0
- package/README.md +178 -83
- package/dist/Hooks.d.ts +3 -3
- package/dist/Hooks.js +1 -1
- package/dist/Hooks.js.map +1 -1
- package/dist/Navigation.d.ts +70 -17
- package/dist/Navigation.js +93 -19
- package/dist/Navigation.js.map +1 -1
- package/dist/Remote.d.ts +54 -35
- package/dist/Remote.js +22 -11
- package/dist/Remote.js.map +1 -1
- package/dist/State.d.ts +79 -6
- package/dist/State.js +117 -27
- package/dist/State.js.map +1 -1
- package/dist/adapter/assets.d.ts +24 -7
- package/dist/adapter/assets.js +58 -37
- package/dist/adapter/assets.js.map +1 -1
- package/dist/adapter/handler.d.ts +27 -12
- package/dist/adapter/handler.js +53 -26
- package/dist/adapter/handler.js.map +1 -1
- package/dist/adapter/index.d.ts +3 -3
- package/dist/adapter/index.js +1 -1
- package/dist/adapter/index.js.map +1 -1
- package/dist/adapter/serve.d.ts +6 -5
- package/dist/adapter/serve.js +3 -1
- package/dist/adapter/serve.js.map +1 -1
- package/dist/api-client.d.ts +3 -4
- package/dist/api-client.js +2 -1
- package/dist/api-client.js.map +1 -1
- package/dist/app-runtime.d.ts +12 -9
- package/dist/app-runtime.js +23 -14
- package/dist/app-runtime.js.map +1 -1
- package/dist/client/Hooks.d.ts +4 -4
- package/dist/client/Hooks.js +2 -1
- package/dist/client/Hooks.js.map +1 -1
- package/dist/client.d.ts +2 -2
- package/dist/config.d.ts +7 -5
- package/dist/config.js +5 -34
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +14 -16
- package/dist/errors.js +20 -12
- package/dist/errors.js.map +1 -1
- package/dist/forms.d.ts +70 -8
- package/dist/forms.js +69 -5
- package/dist/forms.js.map +1 -1
- package/dist/internal/app-state.d.ts +42 -5
- package/dist/internal/app-state.js +64 -15
- package/dist/internal/app-state.js.map +1 -1
- package/dist/internal/auto.d.ts +23 -14
- package/dist/internal/auto.js +50 -40
- package/dist/internal/auto.js.map +1 -1
- package/dist/internal/enhance.d.ts +114 -14
- package/dist/internal/enhance.js +33 -7
- package/dist/internal/enhance.js.map +1 -1
- package/dist/internal/error.d.ts +13 -0
- package/dist/internal/error.js +4 -3
- package/dist/internal/error.js.map +1 -1
- package/dist/internal/navigation-events.js +3 -1
- package/dist/internal/navigation-events.js.map +1 -1
- package/dist/internal/remote-run.d.ts +6 -6
- package/dist/internal/remote-run.js +13 -16
- package/dist/internal/remote-run.js.map +1 -1
- package/dist/internal/remote-validators.js +4 -3
- package/dist/internal/remote-validators.js.map +1 -1
- package/dist/internal/route-params.d.ts +1 -2
- package/dist/internal/sse.js +2 -1
- package/dist/internal/sse.js.map +1 -1
- package/dist/internal/watch.svelte.js +33 -0
- package/dist/internal/watch.svelte.js.map +1 -0
- package/dist/kit.d.ts +12 -11
- package/dist/kit.js +6 -2
- package/dist/kit.js.map +1 -1
- package/dist/realtime.d.ts +49 -30
- package/dist/realtime.js +90 -46
- package/dist/realtime.js.map +1 -1
- package/dist/remote-functions.d.ts +34 -18
- package/dist/remote-functions.js +78 -46
- package/dist/remote-functions.js.map +1 -1
- package/dist/remote-reactive.d.ts +8 -4
- package/dist/remote-reactive.js.map +1 -1
- package/dist/request-parse.d.ts +13 -13
- package/dist/request-parse.js +29 -15
- package/dist/request-parse.js.map +1 -1
- package/dist/request-runtime.d.ts +16 -11
- package/dist/request-runtime.js +28 -29
- package/dist/request-runtime.js.map +1 -1
- package/dist/request-services.d.ts +10 -8
- package/dist/request-services.js +3 -1
- package/dist/request-services.js.map +1 -1
- package/dist/response-headers.d.ts +2 -3
- package/dist/response-headers.js +2 -1
- package/dist/response-headers.js.map +1 -1
- package/dist/runtime.d.ts +8 -5
- package/dist/runtime.js +4 -1
- package/dist/runtime.js.map +1 -1
- package/dist/server/Endpoint.d.ts +17 -11
- package/dist/server/Endpoint.js +18 -4
- package/dist/server/Endpoint.js.map +1 -1
- package/dist/server/Hooks.d.ts +6 -6
- package/dist/server/Hooks.js +1 -1
- package/dist/server/Hooks.js.map +1 -1
- package/dist/server.d.ts +8 -7
- package/dist/server.js +8 -8
- package/dist/server.js.map +1 -1
- package/dist/streamed-load.d.ts +2 -3
- package/dist/streamed-load.js +1 -1
- package/dist/streamed-load.js.map +1 -1
- package/dist/testing.d.ts +15 -14
- package/dist/testing.js +27 -7
- package/dist/testing.js.map +1 -1
- package/dist/transport.d.ts +6 -7
- package/dist/transport.js +3 -6
- package/dist/transport.js.map +1 -1
- package/dist/universal.d.ts +3 -3
- package/dist/universal.js +1 -1
- package/dist/universal.js.map +1 -1
- package/dist/vite.d.ts +4 -4
- package/dist/vite.js +102 -103
- package/dist/vite.js.map +1 -1
- package/package.json +14 -11
- package/LICENSE +0 -21
package/dist/vite.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import * as Rec from "effect/Record";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
import * as Arr from "effect/Array";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
1
5
|
import MagicString from "magic-string";
|
|
2
6
|
//#region src/vite.ts
|
|
3
7
|
/**
|
|
@@ -35,12 +39,12 @@ const INTERNAL_IMPORT = "@thomasfosterau/effect-sveltekit/internal/auto";
|
|
|
35
39
|
const ORIG_PREFIX = "__effect_kit_orig_";
|
|
36
40
|
const RUNTIME_IDENT = "__effect_kit_runtime";
|
|
37
41
|
/** File kinds whose exports only ever run on the server. */
|
|
38
|
-
const serverKinds =
|
|
42
|
+
const serverKinds = [
|
|
39
43
|
"pageServer",
|
|
40
44
|
"layoutServer",
|
|
41
45
|
"server",
|
|
42
46
|
"hooksServer"
|
|
43
|
-
]
|
|
47
|
+
];
|
|
44
48
|
const wrapperByKind = {
|
|
45
49
|
pageServer: {
|
|
46
50
|
load: "wrapServerLoad",
|
|
@@ -72,7 +76,7 @@ const wrapperByKind = {
|
|
|
72
76
|
hooks: { reroute: "wrapReroute" }
|
|
73
77
|
};
|
|
74
78
|
function getFileKind(id) {
|
|
75
|
-
const path = id.
|
|
79
|
+
const path = id.replace(/\?[\s\S]*$/, "").replace(/\\/g, "/");
|
|
76
80
|
if (/\/\+page\.server\.(?:ts|js|mts|mjs)$/.test(path)) return "pageServer";
|
|
77
81
|
if (/\/\+page\.(?:ts|js|mts|mjs)$/.test(path)) return "page";
|
|
78
82
|
if (/\/\+layout\.server\.(?:ts|js|mts|mjs)$/.test(path)) return "layoutServer";
|
|
@@ -83,95 +87,93 @@ function getFileKind(id) {
|
|
|
83
87
|
if (/\/hooks\.(?:ts|js|mts|mjs)$/.test(path)) return "hooks";
|
|
84
88
|
return null;
|
|
85
89
|
}
|
|
90
|
+
/** `text` with every character but a newline replaced by a space. */
|
|
91
|
+
const blank = (text) => text.replace(/[^\n]/g, " ");
|
|
92
|
+
/**
|
|
93
|
+
* A string or template literal's text, blanked: an escape (a backslash and the
|
|
94
|
+
* character after it, even a newline) becomes two spaces, and every other
|
|
95
|
+
* character but a newline one space.
|
|
96
|
+
*/
|
|
97
|
+
const blankLiteral = (text) => text.replace(/\\[\s\S]|[^\n]/g, (match) => " ".repeat(match.length));
|
|
98
|
+
/** A line comment, or a block comment (unterminated: to the end of the source). */
|
|
99
|
+
const COMMENT = /\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)/y;
|
|
100
|
+
/** A single- or double-quoted string (unterminated: to the end of the source). */
|
|
101
|
+
const QUOTED = /(["'])((?:\\[\s\S]?|(?!\1)[^\\])*)(\1?)/y;
|
|
102
|
+
/** A run of characters that start no comment or literal, or a lone `/`. */
|
|
103
|
+
const PLAIN = /[^/"'`]+|\//y;
|
|
104
|
+
/** Template literal text up to its closing backtick, a `${`, or the end of the source. */
|
|
105
|
+
const TEMPLATE_TEXT = /(?:\\[\s\S]?|\$(?!\{)|[^`\\$])*/y;
|
|
106
|
+
/** The text the sticky `pattern` matches at `index`, if any. */
|
|
107
|
+
const matchAt = (pattern, source, index) => {
|
|
108
|
+
pattern.lastIndex = index;
|
|
109
|
+
return pattern.exec(source);
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* The index just past the `}` closing a template expression whose body starts
|
|
113
|
+
* at `from` (braces counted naively, as raw text), or the end of the source.
|
|
114
|
+
*/
|
|
115
|
+
const expressionEnd = (source, from) => {
|
|
116
|
+
let depth = 1;
|
|
117
|
+
const closing = Arr.findFirst(source.slice(from).matchAll(/[{}]/g), (brace) => {
|
|
118
|
+
depth += brace[0] === "{" ? 1 : -1;
|
|
119
|
+
return depth === 0;
|
|
120
|
+
});
|
|
121
|
+
return Option.match(closing, {
|
|
122
|
+
onNone: () => source.length,
|
|
123
|
+
onSome: (brace) => from + brace.index + 1
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
/** The masked chunk at a position inside a template literal, and the position after it. */
|
|
127
|
+
const maskTemplateChunk = (source, index) => {
|
|
128
|
+
const text = matchAt(TEMPLATE_TEXT, source, index)?.[0] ?? "";
|
|
129
|
+
const end = index + text.length;
|
|
130
|
+
if (source.startsWith("${", end)) {
|
|
131
|
+
const close = expressionEnd(source, end + 2);
|
|
132
|
+
return [blankLiteral(text) + source.slice(end, close), {
|
|
133
|
+
index: close,
|
|
134
|
+
inTemplate: true
|
|
135
|
+
}];
|
|
136
|
+
}
|
|
137
|
+
return [blankLiteral(text) + source.slice(end, end + 1), {
|
|
138
|
+
index: end + 1,
|
|
139
|
+
inTemplate: false
|
|
140
|
+
}];
|
|
141
|
+
};
|
|
142
|
+
/** The masked chunk at a position outside any template literal, and the position after it. */
|
|
143
|
+
const maskChunk = (source, index) => {
|
|
144
|
+
const comment = matchAt(COMMENT, source, index);
|
|
145
|
+
if (comment !== null) return [blank(comment[0]), {
|
|
146
|
+
index: index + comment[0].length,
|
|
147
|
+
inTemplate: false
|
|
148
|
+
}];
|
|
149
|
+
const quoted = matchAt(QUOTED, source, index);
|
|
150
|
+
if (quoted !== null) {
|
|
151
|
+
const [whole, quote = "", body = "", closing = ""] = quoted;
|
|
152
|
+
return [quote + blankLiteral(body) + closing, {
|
|
153
|
+
index: index + whole.length,
|
|
154
|
+
inTemplate: false
|
|
155
|
+
}];
|
|
156
|
+
}
|
|
157
|
+
if (source.startsWith("`", index)) return ["`", {
|
|
158
|
+
index: index + 1,
|
|
159
|
+
inTemplate: true
|
|
160
|
+
}];
|
|
161
|
+
const plain = matchAt(PLAIN, source, index)?.[0] ?? source.charAt(index);
|
|
162
|
+
return [plain, {
|
|
163
|
+
index: index + plain.length,
|
|
164
|
+
inTemplate: false
|
|
165
|
+
}];
|
|
166
|
+
};
|
|
86
167
|
/**
|
|
87
168
|
* Returns a copy of `source` with line/block comments and string/template
|
|
88
169
|
* contents replaced by spaces (newlines preserved). Byte offsets in the
|
|
89
170
|
* returned string line up with the original, so regex scans don't trip on
|
|
90
171
|
* tokens inside comments or strings.
|
|
91
172
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
while (i < len) {
|
|
97
|
-
const c = source[i];
|
|
98
|
-
const next = source[i + 1];
|
|
99
|
-
if (c === "/" && next === "/") {
|
|
100
|
-
out.push(" ");
|
|
101
|
-
i += 2;
|
|
102
|
-
while (i < len && source[i] !== "\n") {
|
|
103
|
-
out.push(" ");
|
|
104
|
-
i++;
|
|
105
|
-
}
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (c === "/" && next === "*") {
|
|
109
|
-
out.push(" ");
|
|
110
|
-
i += 2;
|
|
111
|
-
while (i < len && !(source[i] === "*" && source[i + 1] === "/")) {
|
|
112
|
-
out.push(source[i] === "\n" ? "\n" : " ");
|
|
113
|
-
i++;
|
|
114
|
-
}
|
|
115
|
-
if (i < len) {
|
|
116
|
-
out.push(" ");
|
|
117
|
-
i += 2;
|
|
118
|
-
}
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (c === "\"" || c === "'") {
|
|
122
|
-
const quote = c;
|
|
123
|
-
out.push(quote);
|
|
124
|
-
i++;
|
|
125
|
-
while (i < len && source[i] !== quote) {
|
|
126
|
-
if (source[i] === "\\" && i + 1 < len) {
|
|
127
|
-
out.push(" ");
|
|
128
|
-
i += 2;
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
out.push(source[i] === "\n" ? "\n" : " ");
|
|
132
|
-
i++;
|
|
133
|
-
}
|
|
134
|
-
if (i < len) {
|
|
135
|
-
out.push(quote);
|
|
136
|
-
i++;
|
|
137
|
-
}
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (c === "`") {
|
|
141
|
-
out.push("`");
|
|
142
|
-
i++;
|
|
143
|
-
while (i < len && source[i] !== "`") {
|
|
144
|
-
if (source[i] === "\\" && i + 1 < len) {
|
|
145
|
-
out.push(" ");
|
|
146
|
-
i += 2;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (source[i] === "$" && source[i + 1] === "{") {
|
|
150
|
-
out.push("${");
|
|
151
|
-
i += 2;
|
|
152
|
-
let depth = 1;
|
|
153
|
-
while (i < len && depth > 0) {
|
|
154
|
-
out.push(source[i]);
|
|
155
|
-
if (source[i] === "{") depth++;
|
|
156
|
-
else if (source[i] === "}") depth--;
|
|
157
|
-
i++;
|
|
158
|
-
}
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
out.push(source[i] === "\n" ? "\n" : " ");
|
|
162
|
-
i++;
|
|
163
|
-
}
|
|
164
|
-
if (i < len) {
|
|
165
|
-
out.push("`");
|
|
166
|
-
i++;
|
|
167
|
-
}
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
out.push(c);
|
|
171
|
-
i++;
|
|
172
|
-
}
|
|
173
|
-
return out.join("");
|
|
174
|
-
}
|
|
173
|
+
const maskCommentsAndStrings = (source) => Arr.unfold({
|
|
174
|
+
index: 0,
|
|
175
|
+
inTemplate: false
|
|
176
|
+
}, (position) => position.index >= source.length ? Option.none() : Option.some(position.inTemplate ? maskTemplateChunk(source, position.index) : maskChunk(source, position.index))).join("");
|
|
175
177
|
/**
|
|
176
178
|
* Find top-level `export const|let|var|function NAME` declarations whose
|
|
177
179
|
* NAME is in `targetNames`. Returns the slice of the original source to
|
|
@@ -180,25 +182,21 @@ function maskCommentsAndStrings(source) {
|
|
|
180
182
|
* `export { NAME }`, `export default`, and destructuring forms are not
|
|
181
183
|
* supported — users who need them should use the explicit wrappers.
|
|
182
184
|
*/
|
|
183
|
-
function findExports(masked,
|
|
184
|
-
|
|
185
|
-
const pattern = /^[ \t]*(export)[ \t]+(?:async[ \t]+)?(?:const|let|var|function)[ \t]+([A-Za-z_$][\w$]*)/gm;
|
|
186
|
-
let m;
|
|
187
|
-
while (m = pattern.exec(masked)) {
|
|
185
|
+
function findExports(masked, isTarget) {
|
|
186
|
+
return Arr.filterMap(masked.matchAll(/^[ \t]*(export)[ \t]+(?:async[ \t]+)?(?:const|let|var|function)[ \t]+([A-Za-z_$][\w$]*)/gm), (m) => {
|
|
188
187
|
const [full, , name] = m;
|
|
189
|
-
if (!
|
|
188
|
+
if (name === void 0 || !isTarget(name)) return Result.failVoid;
|
|
190
189
|
const exportStart = m.index + full.indexOf("export");
|
|
191
190
|
const nameEnd = m.index + full.length;
|
|
192
191
|
const renamed = `${ORIG_PREFIX}${name}`;
|
|
193
192
|
const replacement = masked.slice(exportStart, nameEnd).replace(/^export[ \t]+/, "").replace(new RegExp(`\\b${name}$`), renamed);
|
|
194
|
-
|
|
193
|
+
return Result.succeed({
|
|
195
194
|
name,
|
|
196
195
|
start: exportStart,
|
|
197
196
|
end: nameEnd,
|
|
198
197
|
replacement
|
|
199
198
|
});
|
|
200
|
-
}
|
|
201
|
-
return matches;
|
|
199
|
+
});
|
|
202
200
|
}
|
|
203
201
|
/**
|
|
204
202
|
* Vite plugin that rewrites SvelteKit route exports so that Effects, or
|
|
@@ -206,22 +204,23 @@ function findExports(masked, targetNames) {
|
|
|
206
204
|
* function-shaped export.
|
|
207
205
|
*/
|
|
208
206
|
function effectKit(options = {}) {
|
|
209
|
-
const
|
|
207
|
+
const { include } = options;
|
|
210
208
|
return {
|
|
211
209
|
name: "effect-kit",
|
|
212
210
|
enforce: "pre",
|
|
213
211
|
transform(code, id) {
|
|
214
212
|
const kind = getFileKind(id);
|
|
215
213
|
if (!kind) return null;
|
|
216
|
-
if (include && !
|
|
214
|
+
if (include && !Arr.contains(include, kind)) return null;
|
|
217
215
|
const wrappers = wrapperByKind[kind];
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
if (found.length === 0) return null;
|
|
216
|
+
const found = findExports(maskCommentsAndStrings(code), (name) => Rec.has(wrappers, name));
|
|
217
|
+
if (Arr.isReadonlyArrayEmpty(found)) return null;
|
|
221
218
|
const s = new MagicString(code);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
Arr.forEach(found, (match) => {
|
|
220
|
+
s.overwrite(match.start, match.end, match.replacement);
|
|
221
|
+
});
|
|
222
|
+
const wrapperImports = Arr.dedupe(Arr.filterMap(found, (m) => Result.fromOption(Rec.get(wrappers, m.name), () => m.name)));
|
|
223
|
+
const runtimeSpecifier = Arr.contains(serverKinds, kind) ? options.serverRuntime : options.clientRuntime;
|
|
225
224
|
const imports = `\nimport { ${wrapperImports.join(", ")} } from ${JSON.stringify(INTERNAL_IMPORT)};\n` + (runtimeSpecifier ? `import * as ${RUNTIME_IDENT} from ${JSON.stringify(runtimeSpecifier)};\n` : "");
|
|
226
225
|
const runtimeArg = runtimeSpecifier ? `, ${RUNTIME_IDENT}` : "";
|
|
227
226
|
const exports = found.map((m) => `export const ${m.name} = ${wrappers[m.name]}(${ORIG_PREFIX}${m.name}${runtimeArg});`).join("\n");
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * Vite plugin that lets you write SvelteKit route exports as Effects (or\n * functions returning Effects) without manually wrapping them.\n *\n * ```ts\n * // vite.config.ts\n * import { sveltekit } from \"@sveltejs/kit/vite\";\n * import { effectKit } from \"@thomasfosterau/effect-sveltekit/vite\";\n *\n * export default {\n * plugins: [effectKit(), sveltekit()],\n * };\n * ```\n *\n * ```ts\n * // +page.server.ts\n * import { Effect } from \"effect\";\n * import * as HttpClient from \"effect/http/HttpClient\";\n *\n * export const load = (event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * return { hello: \"world\" };\n * });\n * ```\n *\n * Under the hood the plugin rewrites the export to call the same wrappers\n * that the explicit API (`serverLoad`, `actions`, `handle`, `endpoint`, ...)\n * uses. Plain functions that return promises or values pass through\n * unchanged, so the plugin is safe to enable on existing projects.\n */\n\nimport MagicString from \"magic-string\";\nimport type { Plugin } from \"vite\";\n\nconst INTERNAL_IMPORT = \"@thomasfosterau/effect-sveltekit/internal/auto\";\nconst ORIG_PREFIX = \"__effect_kit_orig_\";\nconst RUNTIME_IDENT = \"__effect_kit_runtime\";\n\n/** The SvelteKit route and hook file kinds `effectKit()` can rewrite. */\nexport type EffectKitFileKind =\n | \"pageServer\"\n | \"page\"\n | \"layoutServer\"\n | \"layout\"\n | \"server\"\n | \"hooks\"\n | \"hooksServer\"\n | \"hooksClient\";\n\n/** File kinds whose exports only ever run on the server. */\nconst serverKinds: ReadonlySet<EffectKitFileKind> = new Set([\n \"pageServer\",\n \"layoutServer\",\n \"server\",\n \"hooksServer\",\n]);\n\nconst wrapperByKind: Record<EffectKitFileKind, Record<string, string>> = {\n pageServer: { load: \"wrapServerLoad\", actions: \"wrapActions\" },\n page: { load: \"wrapLoad\" },\n layoutServer: { load: \"wrapServerLoad\" },\n layout: { load: \"wrapLoad\" },\n server: {\n GET: \"wrapEndpoint\",\n POST: \"wrapEndpoint\",\n PUT: \"wrapEndpoint\",\n PATCH: \"wrapEndpoint\",\n DELETE: \"wrapEndpoint\",\n OPTIONS: \"wrapEndpoint\",\n HEAD: \"wrapEndpoint\",\n fallback: \"wrapEndpoint\",\n },\n hooksServer: {\n handle: \"wrapHandle\",\n handleFetch: \"wrapHandleFetch\",\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n hooksClient: {\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n // Universal `src/hooks.ts`: only `reroute` is function-shaped (the `transport`\n // export is plain data and is intentionally left untouched).\n hooks: {\n reroute: \"wrapReroute\",\n },\n};\n\nfunction getFileKind(id: string): EffectKitFileKind | null {\n const path = id.split(\"?\")[0]!.replace(/\\\\/g, \"/\");\n if (/\\/\\+page\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"pageServer\";\n if (/\\/\\+page\\.(?:ts|js|mts|mjs)$/.test(path)) return \"page\";\n if (/\\/\\+layout\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layoutServer\";\n if (/\\/\\+layout\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layout\";\n if (/\\/\\+server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"server\";\n if (/\\/hooks\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksServer\";\n if (/\\/hooks\\.client\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksClient\";\n // Plain `hooks.ts` is the universal hooks file; checked last so the\n // `.server.`/`.client.` variants above take precedence.\n if (/\\/hooks\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooks\";\n return null;\n}\n\n/**\n * Returns a copy of `source` with line/block comments and string/template\n * contents replaced by spaces (newlines preserved). Byte offsets in the\n * returned string line up with the original, so regex scans don't trip on\n * tokens inside comments or strings.\n */\nfunction maskCommentsAndStrings(source: string): string {\n const out: string[] = [];\n let i = 0;\n const len = source.length;\n\n while (i < len) {\n const c = source[i]!;\n const next = source[i + 1];\n\n if (c === \"/\" && next === \"/\") {\n out.push(\" \");\n i += 2;\n while (i < len && source[i] !== \"\\n\") {\n out.push(\" \");\n i++;\n }\n continue;\n }\n\n if (c === \"/\" && next === \"*\") {\n out.push(\" \");\n i += 2;\n while (i < len && !(source[i] === \"*\" && source[i + 1] === \"/\")) {\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(\" \");\n i += 2;\n }\n continue;\n }\n\n if (c === '\"' || c === \"'\") {\n const quote = c;\n out.push(quote);\n i++;\n while (i < len && source[i] !== quote) {\n if (source[i] === \"\\\\\" && i + 1 < len) {\n out.push(\" \");\n i += 2;\n continue;\n }\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(quote);\n i++;\n }\n continue;\n }\n\n if (c === \"`\") {\n out.push(\"`\");\n i++;\n while (i < len && source[i] !== \"`\") {\n if (source[i] === \"\\\\\" && i + 1 < len) {\n out.push(\" \");\n i += 2;\n continue;\n }\n if (source[i] === \"$\" && source[i + 1] === \"{\") {\n // Preserve template expression so nested strings are masked by\n // the outer loop.\n out.push(\"${\");\n i += 2;\n let depth = 1;\n while (i < len && depth > 0) {\n out.push(source[i]!);\n if (source[i] === \"{\") depth++;\n else if (source[i] === \"}\") depth--;\n i++;\n }\n continue;\n }\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(\"`\");\n i++;\n }\n continue;\n }\n\n out.push(c);\n i++;\n }\n\n return out.join(\"\");\n}\n\ninterface ExportMatch {\n name: string;\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Find top-level `export const|let|var|function NAME` declarations whose\n * NAME is in `targetNames`. Returns the slice of the original source to\n * replace, along with a renamed declaration to put in its place.\n *\n * `export { NAME }`, `export default`, and destructuring forms are not\n * supported — users who need them should use the explicit wrappers.\n */\nfunction findExports(masked: string, targetNames: ReadonlySet<string>): ExportMatch[] {\n const matches: ExportMatch[] = [];\n const pattern =\n /^[ \\t]*(export)[ \\t]+(?:async[ \\t]+)?(?:const|let|var|function)[ \\t]+([A-Za-z_$][\\w$]*)/gm;\n\n let m: RegExpExecArray | null;\n while ((m = pattern.exec(masked))) {\n const [full, , name] = m;\n if (!targetNames.has(name!)) continue;\n\n const exportStart = m.index + full.indexOf(\"export\");\n const nameEnd = m.index + full.length;\n const renamed = `${ORIG_PREFIX}${name}`;\n\n const slice = masked.slice(exportStart, nameEnd);\n const replacement = slice\n .replace(/^export[ \\t]+/, \"\")\n .replace(new RegExp(`\\\\b${name}$`), renamed);\n\n matches.push({ name: name!, start: exportStart, end: nameEnd, replacement });\n }\n\n return matches;\n}\n\n/** Options for {@link effectKit}. */\nexport interface EffectKitPluginOptions {\n /**\n * Restrict transformation to a subset of SvelteKit file kinds.\n * Defaults to all standard route files.\n */\n include?: ReadonlyArray<EffectKitFileKind>;\n\n /**\n * Import specifier of a server-only module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeServerRuntime`.\n *\n * When set, Effects returned from server route files (`+page.server.ts`,\n * `+layout.server.ts`, `+server.ts`, `hooks.server.ts`) have access to the\n * services of that runtime's layer.\n *\n * @example \"#lib/server/runtime.ts\"\n */\n serverRuntime?: string;\n\n /**\n * Import specifier of a universal module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeUniversalRuntime`.\n *\n * When set, Effects returned from universal route files (`+page.ts`,\n * `+layout.ts`, `hooks.client.ts`) have access to the services of that\n * runtime's layer. The module must be safe to import in the browser.\n *\n * @example \"#lib/runtime.ts\"\n */\n clientRuntime?: string;\n}\n\n/**\n * Vite plugin that rewrites SvelteKit route exports so that Effects, or\n * functions returning Effects, are accepted wherever SvelteKit expects a\n * function-shaped export.\n */\nexport function effectKit(options: EffectKitPluginOptions = {}): Plugin {\n const include = options.include ? new Set(options.include) : null;\n\n return {\n name: \"effect-kit\",\n enforce: \"pre\",\n transform(code, id) {\n const kind = getFileKind(id);\n if (!kind) return null;\n if (include && !include.has(kind)) return null;\n\n const wrappers = wrapperByKind[kind];\n const targetNames = new Set(Object.keys(wrappers));\n\n const masked = maskCommentsAndStrings(code);\n const found = findExports(masked, targetNames);\n if (found.length === 0) return null;\n\n const s = new MagicString(code);\n\n for (const match of found) {\n s.overwrite(match.start, match.end, match.replacement);\n }\n\n const wrapperImports = Array.from(new Set(found.map((m) => wrappers[m.name]!)));\n\n // Server-only files get the server runtime module; universal/client\n // files get the client runtime module (it must be browser-safe).\n const runtimeSpecifier = serverKinds.has(kind)\n ? options.serverRuntime\n : options.clientRuntime;\n\n const imports =\n `\\nimport { ${wrapperImports.join(\", \")} } from ${JSON.stringify(INTERNAL_IMPORT)};\\n` +\n (runtimeSpecifier\n ? `import * as ${RUNTIME_IDENT} from ${JSON.stringify(runtimeSpecifier)};\\n`\n : \"\");\n const runtimeArg = runtimeSpecifier ? `, ${RUNTIME_IDENT}` : \"\";\n const exports = found\n .map(\n (m) =>\n `export const ${m.name} = ${wrappers[m.name]}(${ORIG_PREFIX}${m.name}${runtimeArg});`,\n )\n .join(\"\\n\");\n\n s.append(`\\n${imports}${exports}\\n`);\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: \"boundary\", source: id }),\n };\n },\n };\n}\n\nexport default effectKit;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * Vite plugin that lets you write SvelteKit route exports as Effects (or\n * functions returning Effects) without manually wrapping them.\n *\n * ```ts\n * // vite.config.ts\n * import { sveltekit } from \"@sveltejs/kit/vite\";\n * import { effectKit } from \"@thomasfosterau/effect-sveltekit/vite\";\n *\n * export default {\n * plugins: [effectKit(), sveltekit()],\n * };\n * ```\n *\n * ```ts\n * // +page.server.ts\n * import { Effect } from \"effect\";\n * import * as HttpClient from \"effect/http/HttpClient\";\n *\n * export const load = (event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * return { hello: \"world\" };\n * });\n * ```\n *\n * Under the hood the plugin rewrites the export to call the same wrappers\n * that the explicit API (`serverLoad`, `actions`, `handle`, `endpoint`, ...)\n * uses. Plain functions that return promises or values pass through\n * unchanged, so the plugin is safe to enable on existing projects.\n */\n\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport * as Rec from \"effect/Record\";\nimport * as Result from \"effect/Result\";\nimport MagicString from \"magic-string\";\nimport type { Plugin } from \"vite\";\n\nconst INTERNAL_IMPORT = \"@thomasfosterau/effect-sveltekit/internal/auto\";\nconst ORIG_PREFIX = \"__effect_kit_orig_\";\nconst RUNTIME_IDENT = \"__effect_kit_runtime\";\n\n/** The SvelteKit route and hook file kinds `effectKit()` can rewrite. */\nexport type EffectKitFileKind =\n | \"pageServer\"\n | \"page\"\n | \"layoutServer\"\n | \"layout\"\n | \"server\"\n | \"hooks\"\n | \"hooksServer\"\n | \"hooksClient\";\n\n/** File kinds whose exports only ever run on the server. */\nconst serverKinds: ReadonlyArray<EffectKitFileKind> = [\n \"pageServer\",\n \"layoutServer\",\n \"server\",\n \"hooksServer\",\n];\n\nconst wrapperByKind: Record<EffectKitFileKind, Record<string, string>> = {\n pageServer: { load: \"wrapServerLoad\", actions: \"wrapActions\" },\n page: { load: \"wrapLoad\" },\n layoutServer: { load: \"wrapServerLoad\" },\n layout: { load: \"wrapLoad\" },\n server: {\n GET: \"wrapEndpoint\",\n POST: \"wrapEndpoint\",\n PUT: \"wrapEndpoint\",\n PATCH: \"wrapEndpoint\",\n DELETE: \"wrapEndpoint\",\n OPTIONS: \"wrapEndpoint\",\n HEAD: \"wrapEndpoint\",\n fallback: \"wrapEndpoint\",\n },\n hooksServer: {\n handle: \"wrapHandle\",\n handleFetch: \"wrapHandleFetch\",\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n hooksClient: {\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n // Universal `src/hooks.ts`: only `reroute` is function-shaped (the `transport`\n // export is plain data and is intentionally left untouched).\n hooks: {\n reroute: \"wrapReroute\",\n },\n};\n\nfunction getFileKind(id: string): EffectKitFileKind | null {\n // Drop the query (everything from the first `?`), then normalise separators.\n const path = id.replace(/\\?[\\s\\S]*$/, \"\").replace(/\\\\/g, \"/\");\n if (/\\/\\+page\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"pageServer\";\n if (/\\/\\+page\\.(?:ts|js|mts|mjs)$/.test(path)) return \"page\";\n if (/\\/\\+layout\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layoutServer\";\n if (/\\/\\+layout\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layout\";\n if (/\\/\\+server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"server\";\n if (/\\/hooks\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksServer\";\n if (/\\/hooks\\.client\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksClient\";\n // Plain `hooks.ts` is the universal hooks file; checked last so the\n // `.server.`/`.client.` variants above take precedence.\n if (/\\/hooks\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooks\";\n return null;\n}\n\n/** `text` with every character but a newline replaced by a space. */\nconst blank = (text: string): string => text.replace(/[^\\n]/g, \" \");\n\n/**\n * A string or template literal's text, blanked: an escape (a backslash and the\n * character after it, even a newline) becomes two spaces, and every other\n * character but a newline one space.\n */\nconst blankLiteral = (text: string): string =>\n text.replace(/\\\\[\\s\\S]|[^\\n]/g, (match) => \" \".repeat(match.length));\n\n/** A line comment, or a block comment (unterminated: to the end of the source). */\nconst COMMENT = /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y;\n/** A single- or double-quoted string (unterminated: to the end of the source). */\nconst QUOTED = /([\"'])((?:\\\\[\\s\\S]?|(?!\\1)[^\\\\])*)(\\1?)/y;\n/** A run of characters that start no comment or literal, or a lone `/`. */\nconst PLAIN = /[^/\"'`]+|\\//y;\n/** Template literal text up to its closing backtick, a `${`, or the end of the source. */\nconst TEMPLATE_TEXT = /(?:\\\\[\\s\\S]?|\\$(?!\\{)|[^`\\\\$])*/y;\n\n/** The text the sticky `pattern` matches at `index`, if any. */\nconst matchAt = (pattern: RegExp, source: string, index: number): RegExpExecArray | null => {\n pattern.lastIndex = index;\n return pattern.exec(source);\n};\n\n/**\n * The index just past the `}` closing a template expression whose body starts\n * at `from` (braces counted naively, as raw text), or the end of the source.\n */\nconst expressionEnd = (source: string, from: number): number => {\n let depth = 1;\n const closing = Arr.findFirst(source.slice(from).matchAll(/[{}]/g), (brace) => {\n depth += brace[0] === \"{\" ? 1 : -1;\n return depth === 0;\n });\n return Option.match(closing, {\n onNone: () => source.length,\n onSome: (brace) => from + brace.index + 1,\n });\n};\n\n/** Where {@link maskCommentsAndStrings} is: an offset, inside a template literal or not. */\ninterface MaskPosition {\n readonly index: number;\n readonly inTemplate: boolean;\n}\n\n/** The masked chunk at a position inside a template literal, and the position after it. */\nconst maskTemplateChunk = (source: string, index: number): readonly [string, MaskPosition] => {\n const text = matchAt(TEMPLATE_TEXT, source, index)?.[0] ?? \"\";\n const end = index + text.length;\n if (source.startsWith(\"${\", end)) {\n // Keep a template expression verbatim, up to its closing brace.\n const close = expressionEnd(source, end + 2);\n return [blankLiteral(text) + source.slice(end, close), { index: close, inTemplate: true }];\n }\n // The closing backtick, or nothing at the end of the source.\n return [blankLiteral(text) + source.slice(end, end + 1), { index: end + 1, inTemplate: false }];\n};\n\n/** The masked chunk at a position outside any template literal, and the position after it. */\nconst maskChunk = (source: string, index: number): readonly [string, MaskPosition] => {\n const comment = matchAt(COMMENT, source, index);\n if (comment !== null) {\n return [blank(comment[0]), { index: index + comment[0].length, inTemplate: false }];\n }\n const quoted = matchAt(QUOTED, source, index);\n if (quoted !== null) {\n const [whole, quote = \"\", body = \"\", closing = \"\"] = quoted;\n return [\n quote + blankLiteral(body) + closing,\n { index: index + whole.length, inTemplate: false },\n ];\n }\n if (source.startsWith(\"`\", index)) return [\"`\", { index: index + 1, inTemplate: true }];\n const plain = matchAt(PLAIN, source, index)?.[0] ?? source.charAt(index);\n return [plain, { index: index + plain.length, inTemplate: false }];\n};\n\n/**\n * Returns a copy of `source` with line/block comments and string/template\n * contents replaced by spaces (newlines preserved). Byte offsets in the\n * returned string line up with the original, so regex scans don't trip on\n * tokens inside comments or strings.\n */\nconst maskCommentsAndStrings = (source: string): string =>\n Arr.unfold<MaskPosition, string>({ index: 0, inTemplate: false }, (position) =>\n position.index >= source.length\n ? Option.none()\n : Option.some(\n position.inTemplate\n ? maskTemplateChunk(source, position.index)\n : maskChunk(source, position.index),\n ),\n ).join(\"\");\n\ninterface ExportMatch {\n name: string;\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Find top-level `export const|let|var|function NAME` declarations whose\n * NAME is in `targetNames`. Returns the slice of the original source to\n * replace, along with a renamed declaration to put in its place.\n *\n * `export { NAME }`, `export default`, and destructuring forms are not\n * supported — users who need them should use the explicit wrappers.\n */\nfunction findExports(masked: string, isTarget: (name: string) => boolean): Array<ExportMatch> {\n const pattern =\n /^[ \\t]*(export)[ \\t]+(?:async[ \\t]+)?(?:const|let|var|function)[ \\t]+([A-Za-z_$][\\w$]*)/gm;\n\n return Arr.filterMap(masked.matchAll(pattern), (m) => {\n const [full, , name] = m;\n if (name === undefined || !isTarget(name)) return Result.failVoid;\n\n const exportStart = m.index + full.indexOf(\"export\");\n const nameEnd = m.index + full.length;\n const renamed = `${ORIG_PREFIX}${name}`;\n\n const slice = masked.slice(exportStart, nameEnd);\n const replacement = slice\n .replace(/^export[ \\t]+/, \"\")\n .replace(new RegExp(`\\\\b${name}$`), renamed);\n\n return Result.succeed({ name, start: exportStart, end: nameEnd, replacement });\n });\n}\n\n/** Options for {@link effectKit}. */\nexport interface EffectKitPluginOptions {\n /**\n * Restrict transformation to a subset of SvelteKit file kinds.\n * Defaults to all standard route files.\n */\n include?: ReadonlyArray<EffectKitFileKind>;\n\n /**\n * Import specifier of a server-only module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeServerRuntime`.\n *\n * When set, Effects returned from server route files (`+page.server.ts`,\n * `+layout.server.ts`, `+server.ts`, `hooks.server.ts`) have access to the\n * services of that runtime's layer.\n *\n * @example \"#lib/server/runtime.ts\"\n */\n serverRuntime?: string;\n\n /**\n * Import specifier of a universal module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeUniversalRuntime`.\n *\n * When set, Effects returned from universal route files (`+page.ts`,\n * `+layout.ts`, `hooks.client.ts`) have access to the services of that\n * runtime's layer. The module must be safe to import in the browser.\n *\n * @example \"#lib/runtime.ts\"\n */\n clientRuntime?: string;\n}\n\n/**\n * Vite plugin that rewrites SvelteKit route exports so that Effects, or\n * functions returning Effects, are accepted wherever SvelteKit expects a\n * function-shaped export.\n */\nexport function effectKit(options: EffectKitPluginOptions = {}): Plugin {\n const { include } = options;\n\n return {\n name: \"effect-kit\",\n enforce: \"pre\",\n transform(code, id) {\n const kind = getFileKind(id);\n if (!kind) return null;\n if (include && !Arr.contains(include, kind)) return null;\n\n const wrappers = wrapperByKind[kind];\n\n const masked = maskCommentsAndStrings(code);\n const found = findExports(masked, (name) => Rec.has(wrappers, name));\n if (Arr.isReadonlyArrayEmpty(found)) return null;\n\n const s = new MagicString(code);\n\n Arr.forEach(found, (match) => {\n s.overwrite(match.start, match.end, match.replacement);\n });\n\n const wrapperImports = Arr.dedupe(\n Arr.filterMap(found, (m) => Result.fromOption(Rec.get(wrappers, m.name), () => m.name)),\n );\n\n // Server-only files get the server runtime module; universal/client\n // files get the client runtime module (it must be browser-safe).\n const runtimeSpecifier = Arr.contains(serverKinds, kind)\n ? options.serverRuntime\n : options.clientRuntime;\n\n const imports =\n `\\nimport { ${wrapperImports.join(\", \")} } from ${JSON.stringify(INTERNAL_IMPORT)};\\n` +\n (runtimeSpecifier\n ? `import * as ${RUNTIME_IDENT} from ${JSON.stringify(runtimeSpecifier)};\\n`\n : \"\");\n const runtimeArg = runtimeSpecifier ? `, ${RUNTIME_IDENT}` : \"\";\n const exports = found\n .map(\n (m) =>\n `export const ${m.name} = ${wrappers[m.name]}(${ORIG_PREFIX}${m.name}${runtimeArg});`,\n )\n .join(\"\\n\");\n\n s.append(`\\n${imports}${exports}\\n`);\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: \"boundary\", source: id }),\n };\n },\n };\n}\n\nexport default effectKit;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thomasfosterau/effect-sveltekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Effect integration for SvelteKit: load/action/endpoint wrappers, remote functions, an effectKit() Vite plugin, and an Effect HTTP deployment adapter",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"typescript",
|
|
11
11
|
"vite-plugin"
|
|
12
12
|
],
|
|
13
|
-
"homepage": "https://github.com/thomasfosterau/effect-svelte/tree/
|
|
13
|
+
"homepage": "https://github.com/thomasfosterau/effect-svelte/tree/trunk/packages/sveltekit#readme",
|
|
14
14
|
"bugs": {
|
|
15
15
|
"url": "https://github.com/thomasfosterau/effect-svelte/issues"
|
|
16
16
|
},
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"type": "module",
|
|
30
|
+
"sideEffects": false,
|
|
30
31
|
"main": "./dist/index.js",
|
|
31
32
|
"module": "./dist/index.js",
|
|
32
33
|
"types": "./dist/index.d.ts",
|
|
@@ -73,19 +74,20 @@
|
|
|
73
74
|
},
|
|
74
75
|
"dependencies": {
|
|
75
76
|
"@standard-schema/spec": "^1.1.0",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
77
|
+
"@thomasfosterau/effect-svelte": "^0.6.0",
|
|
78
|
+
"magic-string": "^1.4.3"
|
|
78
79
|
},
|
|
79
80
|
"devDependencies": {
|
|
80
|
-
"@effect/platform-node": "4.0.
|
|
81
|
-
"@sveltejs/kit": "^3.
|
|
82
|
-
"svelte": "^5.57.
|
|
81
|
+
"@effect/platform-node": "4.0.5",
|
|
82
|
+
"@sveltejs/kit": "^3.1.0",
|
|
83
|
+
"svelte": "^5.57.2",
|
|
84
|
+
"svelte-check": "^4.7.6",
|
|
83
85
|
"typescript": "npm:@typescript/typescript6@6.0.2"
|
|
84
86
|
},
|
|
85
87
|
"peerDependencies": {
|
|
86
|
-
"@effect/platform-node": ">=4.0.
|
|
87
|
-
"@sveltejs/kit": "^3.
|
|
88
|
-
"effect": ">=4.0.
|
|
88
|
+
"@effect/platform-node": ">=4.0.2",
|
|
89
|
+
"@sveltejs/kit": "^3.1.0",
|
|
90
|
+
"effect": ">=4.0.2",
|
|
89
91
|
"svelte": "^5.40.0",
|
|
90
92
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
91
93
|
},
|
|
@@ -105,7 +107,8 @@
|
|
|
105
107
|
},
|
|
106
108
|
"scripts": {
|
|
107
109
|
"build": "vp pack",
|
|
108
|
-
"
|
|
110
|
+
"check": "svelte-check --tsconfig ./tsconfig.json --threshold error",
|
|
111
|
+
"test:unit": "vp test run",
|
|
109
112
|
"clean": "rm -rf dist"
|
|
110
113
|
}
|
|
111
114
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Thomas Foster
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|