@holo-js/adapter-nuxt 0.1.9 → 0.2.1
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/dist/module.d.mts +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +346 -2
- package/dist/runtime/composables/client-errors.d.ts +0 -0
- package/dist/runtime/composables/client-errors.js +67 -0
- package/dist/runtime/composables/forms.js +42 -5
- package/dist/runtime/composables/object.d.ts +0 -0
- package/dist/runtime/composables/object.js +3 -0
- package/dist/runtime/composables/realtime.d.ts +0 -0
- package/dist/runtime/composables/realtime.js +81 -0
- package/dist/runtime/plugins/realtime.client.d.ts +0 -0
- package/dist/runtime/plugins/realtime.client.js +1 -0
- package/dist/runtime/plugins/realtime.d.ts +0 -0
- package/dist/runtime/plugins/realtime.js +4 -0
- package/dist/runtime/server/auto-imports/holo.js +6 -10
- package/dist/runtime/server/imports/storage.js +1 -1
- package/dist/runtime/server/routes/broadcast-auth.post.d.ts +0 -0
- package/dist/runtime/server/routes/broadcast-auth.post.js +47 -0
- package/dist/runtime/server/routes/broadcast-config.get.d.ts +0 -0
- package/dist/runtime/server/routes/broadcast-config.get.js +7 -0
- package/dist/runtime/shims.d.ts +8 -0
- package/package.json +23 -9
package/dist/module.d.mts
CHANGED
|
@@ -53,12 +53,14 @@ type ViteOptimizeDepsOptions = {
|
|
|
53
53
|
};
|
|
54
54
|
type NuxtViteOptions = {
|
|
55
55
|
optimizeDeps?: ViteOptimizeDepsOptions;
|
|
56
|
+
plugins?: unknown[];
|
|
56
57
|
[key: string]: unknown;
|
|
57
58
|
};
|
|
58
59
|
interface NuxtOptionsWithNitro {
|
|
59
60
|
nitro: {
|
|
60
61
|
storage: Record<string, unknown>;
|
|
61
62
|
errorHandler?: string | string[];
|
|
63
|
+
virtual?: Record<string, () => string>;
|
|
62
64
|
experimental?: {
|
|
63
65
|
asyncContext?: boolean;
|
|
64
66
|
[key: string]: unknown;
|
|
@@ -85,6 +87,8 @@ interface NuxtOptionsWithNitro {
|
|
|
85
87
|
_holoStorageModuleOptions?: StorageModuleOptions;
|
|
86
88
|
_holoStorageFinalizeRegistered?: boolean;
|
|
87
89
|
_holoStorageRuntimeRegistered?: boolean;
|
|
90
|
+
_holoBroadcastAuthRouteRegistered?: boolean;
|
|
91
|
+
_holoRealtimeRouteRegistered?: boolean;
|
|
88
92
|
_holoCoreRuntimeRegistered?: boolean;
|
|
89
93
|
_holoTypesRegistered?: boolean;
|
|
90
94
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,11 +1,254 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { readdir, mkdir, writeFile, access } from 'node:fs/promises';
|
|
2
3
|
import { createRequire } from 'node:module';
|
|
3
4
|
import { resolve, extname, relative, basename, dirname } from 'node:path';
|
|
4
|
-
import { defineNuxtModule, createResolver, addServerPlugin, addServerImportsDir, addServerHandler } from '@nuxt/kit';
|
|
5
|
+
import { defineNuxtModule, createResolver, addServerPlugin, addServerImportsDir, addServerHandler, addPlugin } from '@nuxt/kit';
|
|
5
6
|
import { loadConfigDirectory } from '@holo-js/config';
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const serverOnlyRealtimeProperties = /* @__PURE__ */ new Set(["handler", "authorize"]);
|
|
9
|
+
function isIdentifierBoundary(value) {
|
|
10
|
+
return !value || !/[$\w]/.test(value);
|
|
11
|
+
}
|
|
12
|
+
function skipString(source, index, quote) {
|
|
13
|
+
let cursor = index + 1;
|
|
14
|
+
while (cursor < source.length) {
|
|
15
|
+
const char = source[cursor];
|
|
16
|
+
if (char === "\\") {
|
|
17
|
+
cursor += 2;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (char === quote) {
|
|
21
|
+
return cursor + 1;
|
|
22
|
+
}
|
|
23
|
+
cursor += 1;
|
|
24
|
+
}
|
|
25
|
+
return cursor;
|
|
26
|
+
}
|
|
27
|
+
function skipLineComment(source, index) {
|
|
28
|
+
const end = source.indexOf("\n", index + 2);
|
|
29
|
+
return end === -1 ? source.length : end + 1;
|
|
30
|
+
}
|
|
31
|
+
function skipBlockComment(source, index) {
|
|
32
|
+
const end = source.indexOf("*/", index + 2);
|
|
33
|
+
return end === -1 ? source.length : end + 2;
|
|
34
|
+
}
|
|
35
|
+
function skipSyntax(source, index) {
|
|
36
|
+
const char = source[index];
|
|
37
|
+
const next = source[index + 1];
|
|
38
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
39
|
+
return skipString(source, index, char);
|
|
40
|
+
}
|
|
41
|
+
if (char === "/" && next === "/") {
|
|
42
|
+
return skipLineComment(source, index);
|
|
43
|
+
}
|
|
44
|
+
if (char === "/" && next === "*") {
|
|
45
|
+
return skipBlockComment(source, index);
|
|
46
|
+
}
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
function findRealtimePropertyValueEnd(source, index) {
|
|
50
|
+
let cursor = index;
|
|
51
|
+
let braceDepth = 0;
|
|
52
|
+
let bracketDepth = 0;
|
|
53
|
+
let parenDepth = 0;
|
|
54
|
+
while (cursor < source.length) {
|
|
55
|
+
const syntaxEnd = skipSyntax(source, cursor);
|
|
56
|
+
if (typeof syntaxEnd === "number") {
|
|
57
|
+
cursor = syntaxEnd;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const char = source[cursor];
|
|
61
|
+
if (char === "{") {
|
|
62
|
+
braceDepth += 1;
|
|
63
|
+
} else if (char === "}") {
|
|
64
|
+
if (braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
|
|
65
|
+
return cursor;
|
|
66
|
+
}
|
|
67
|
+
braceDepth -= 1;
|
|
68
|
+
} else if (char === "[") {
|
|
69
|
+
bracketDepth += 1;
|
|
70
|
+
} else if (char === "]") {
|
|
71
|
+
bracketDepth -= 1;
|
|
72
|
+
} else if (char === "(") {
|
|
73
|
+
parenDepth += 1;
|
|
74
|
+
} else if (char === ")") {
|
|
75
|
+
parenDepth -= 1;
|
|
76
|
+
} else if (char === "," && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
|
|
77
|
+
return cursor;
|
|
78
|
+
}
|
|
79
|
+
cursor += 1;
|
|
80
|
+
}
|
|
81
|
+
return source.length;
|
|
82
|
+
}
|
|
83
|
+
function findClosingBrace(source, index) {
|
|
84
|
+
let cursor = index;
|
|
85
|
+
let depth = 0;
|
|
86
|
+
while (cursor < source.length) {
|
|
87
|
+
const syntaxEnd = skipSyntax(source, cursor);
|
|
88
|
+
if (typeof syntaxEnd === "number") {
|
|
89
|
+
cursor = syntaxEnd;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const char = source[cursor];
|
|
93
|
+
if (char === "{") {
|
|
94
|
+
depth += 1;
|
|
95
|
+
} else if (char === "}") {
|
|
96
|
+
depth -= 1;
|
|
97
|
+
if (depth === 0) {
|
|
98
|
+
return cursor;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
cursor += 1;
|
|
102
|
+
}
|
|
103
|
+
return -1;
|
|
104
|
+
}
|
|
105
|
+
function applyReplacements(source, replacements) {
|
|
106
|
+
if (replacements.length === 0) {
|
|
107
|
+
return source;
|
|
108
|
+
}
|
|
109
|
+
let output = "";
|
|
110
|
+
let cursor = 0;
|
|
111
|
+
const ordered = [...replacements].sort((left, right) => left.start - right.start);
|
|
112
|
+
for (const replacement of ordered) {
|
|
113
|
+
output += source.slice(cursor, replacement.start);
|
|
114
|
+
output += replacement.text;
|
|
115
|
+
cursor = replacement.end;
|
|
116
|
+
}
|
|
117
|
+
return output + source.slice(cursor);
|
|
118
|
+
}
|
|
119
|
+
function extractObjectPropertyValue(objectSource, propertyName) {
|
|
120
|
+
let cursor = 1;
|
|
121
|
+
while (cursor < objectSource.length - 1) {
|
|
122
|
+
const syntaxEnd = skipSyntax(objectSource, cursor);
|
|
123
|
+
if (typeof syntaxEnd === "number") {
|
|
124
|
+
cursor = syntaxEnd;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const match = objectSource.slice(cursor).match(/^[$A-Z_a-z][$\w]*/);
|
|
128
|
+
const name = match?.[0];
|
|
129
|
+
if (!name || name !== propertyName) {
|
|
130
|
+
cursor += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const afterName = cursor + name.length;
|
|
134
|
+
if (!isIdentifierBoundary(objectSource[cursor - 1]) || !isIdentifierBoundary(objectSource[afterName])) {
|
|
135
|
+
cursor += name.length;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
let colon = afterName;
|
|
139
|
+
while (/\s/.test(objectSource[colon] ?? "")) {
|
|
140
|
+
colon += 1;
|
|
141
|
+
}
|
|
142
|
+
if (objectSource[colon] !== ":") {
|
|
143
|
+
cursor += name.length;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const valueStart = colon + 1;
|
|
147
|
+
const valueEnd = findRealtimePropertyValueEnd(objectSource, valueStart);
|
|
148
|
+
return objectSource.slice(valueStart, valueEnd).trim();
|
|
149
|
+
}
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
function collectRealtimeDefinitionExports(source) {
|
|
153
|
+
const definitions = [];
|
|
154
|
+
let cursor = 0;
|
|
155
|
+
while (cursor < source.length) {
|
|
156
|
+
const syntaxEnd = skipSyntax(source, cursor);
|
|
157
|
+
if (typeof syntaxEnd === "number") {
|
|
158
|
+
cursor = syntaxEnd;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const match = source.slice(cursor).match(/^export\s+const\s+([$A-Z_a-z][$\w]*)\s*=\s*(query|mutation)\s*\(/);
|
|
162
|
+
const exportedName = match?.[1];
|
|
163
|
+
const factoryName = match?.[2];
|
|
164
|
+
if (!exportedName || !factoryName) {
|
|
165
|
+
cursor += 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
let objectStart = cursor + match[0].length;
|
|
169
|
+
while (/\s/.test(source[objectStart] ?? "")) {
|
|
170
|
+
objectStart += 1;
|
|
171
|
+
}
|
|
172
|
+
if (source[objectStart] !== "{") {
|
|
173
|
+
cursor += match[0].length;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const objectEnd = findClosingBrace(source, objectStart);
|
|
177
|
+
if (objectEnd === -1) {
|
|
178
|
+
cursor += match[0].length;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const objectSource = source.slice(objectStart, objectEnd + 1);
|
|
182
|
+
definitions.push({
|
|
183
|
+
exportedName,
|
|
184
|
+
factoryName,
|
|
185
|
+
nameValue: extractObjectPropertyValue(objectSource, "name")
|
|
186
|
+
});
|
|
187
|
+
cursor = objectEnd + 1;
|
|
188
|
+
}
|
|
189
|
+
return definitions;
|
|
190
|
+
}
|
|
191
|
+
function createRealtimeClientDefinitionModule(source) {
|
|
192
|
+
const definitions = collectRealtimeDefinitionExports(source);
|
|
193
|
+
if (definitions.length === 0) {
|
|
194
|
+
return stripRealtimeServerHandlers(source);
|
|
195
|
+
}
|
|
196
|
+
const factories = [...new Set(definitions.map((definition) => definition.factoryName))];
|
|
197
|
+
return [
|
|
198
|
+
`import { ${factories.join(", ")} } from '@holo-js/adapter-nuxt/realtime'`,
|
|
199
|
+
"",
|
|
200
|
+
...definitions.flatMap((definition) => [
|
|
201
|
+
`export const ${definition.exportedName} = ${definition.factoryName}({`,
|
|
202
|
+
...definition.nameValue ? [` name: ${definition.nameValue},`] : [],
|
|
203
|
+
" handler: undefined,",
|
|
204
|
+
"})",
|
|
205
|
+
""
|
|
206
|
+
])
|
|
207
|
+
].join("\n");
|
|
208
|
+
}
|
|
209
|
+
function stripRealtimeServerHandlers(source) {
|
|
210
|
+
const replacements = [];
|
|
211
|
+
let cursor = 0;
|
|
212
|
+
while (cursor < source.length) {
|
|
213
|
+
const syntaxEnd = skipSyntax(source, cursor);
|
|
214
|
+
if (typeof syntaxEnd === "number") {
|
|
215
|
+
cursor = syntaxEnd;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const match = source.slice(cursor).match(/^[$A-Z_a-z][$\w]*/);
|
|
219
|
+
const name = match?.[0];
|
|
220
|
+
if (!name || !serverOnlyRealtimeProperties.has(name)) {
|
|
221
|
+
cursor += 1;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const afterName = cursor + name.length;
|
|
225
|
+
if (!isIdentifierBoundary(source[cursor - 1]) || !isIdentifierBoundary(source[afterName])) {
|
|
226
|
+
cursor += name.length;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
let colon = afterName;
|
|
230
|
+
while (/\s/.test(source[colon] ?? "")) {
|
|
231
|
+
colon += 1;
|
|
232
|
+
}
|
|
233
|
+
if (source[colon] !== ":") {
|
|
234
|
+
cursor += name.length;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const valueStart = colon + 1;
|
|
238
|
+
const valueEnd = findRealtimePropertyValueEnd(source, valueStart);
|
|
239
|
+
replacements.push({
|
|
240
|
+
start: valueStart,
|
|
241
|
+
end: valueEnd,
|
|
242
|
+
text: " undefined"
|
|
243
|
+
});
|
|
244
|
+
cursor = valueEnd;
|
|
245
|
+
}
|
|
246
|
+
return applyReplacements(source, replacements);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const MODEL_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
8
250
|
const HOLO_PACKAGE_SCOPE = "@holo-js/";
|
|
251
|
+
const realtimeClientDefinitionPrefix = "\0holo-realtime-client:";
|
|
9
252
|
const CLIENT_OPTIMIZE_DEPS = [
|
|
10
253
|
{
|
|
11
254
|
packageName: `${HOLO_PACKAGE_SCOPE}forms`,
|
|
@@ -46,6 +289,18 @@ function isModuleResolutionFailure(error) {
|
|
|
46
289
|
}
|
|
47
290
|
return "code" in error && (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_MODULE_NOT_FOUND");
|
|
48
291
|
}
|
|
292
|
+
function hasProjectPackage(rootDir, packageName) {
|
|
293
|
+
const projectRequire = createRequire(resolve(rootDir, "package.json"));
|
|
294
|
+
try {
|
|
295
|
+
projectRequire.resolve(packageName);
|
|
296
|
+
return true;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (isModuleResolutionFailure(error)) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
49
304
|
function resolveClientOptimizeDeps(rootDir) {
|
|
50
305
|
const projectRequire = createRequire(resolve(rootDir, "package.json"));
|
|
51
306
|
const deps = [];
|
|
@@ -74,6 +329,68 @@ function addViteOptimizeDeps(opts, deps) {
|
|
|
74
329
|
])
|
|
75
330
|
];
|
|
76
331
|
}
|
|
332
|
+
function isRealtimeDefinitionModule(_rootDir, id) {
|
|
333
|
+
const normalizedId = id.split("?")[0]?.replaceAll("\\", "/") ?? "";
|
|
334
|
+
return normalizedId.includes("/server/realtime/") && MODEL_FILE_EXTENSIONS.has(extname(normalizedId));
|
|
335
|
+
}
|
|
336
|
+
function resolveExistingRealtimeDefinitionFile(path) {
|
|
337
|
+
if (MODEL_FILE_EXTENSIONS.has(extname(path)) && existsFile(path)) {
|
|
338
|
+
return path;
|
|
339
|
+
}
|
|
340
|
+
for (const extension of MODEL_FILE_EXTENSIONS) {
|
|
341
|
+
const candidate = `${path}${extension}`;
|
|
342
|
+
if (existsFile(candidate)) {
|
|
343
|
+
return candidate;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return void 0;
|
|
347
|
+
}
|
|
348
|
+
function existsFile(path) {
|
|
349
|
+
try {
|
|
350
|
+
return readFileSync(path).length >= 0;
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function resolveRealtimeDefinitionImport(source, importer) {
|
|
356
|
+
if (!source.includes("server/realtime") && !source.includes("server\\realtime")) {
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
const basePath = source.startsWith(".") ? importer ? resolve(dirname(importer.split("?")[0] ?? importer), source) : void 0 : resolve(source);
|
|
360
|
+
return basePath ? resolveExistingRealtimeDefinitionFile(basePath) : void 0;
|
|
361
|
+
}
|
|
362
|
+
function createRealtimeDefinitionVitePlugin(rootDir) {
|
|
363
|
+
return {
|
|
364
|
+
name: "holo-realtime-client-definitions",
|
|
365
|
+
enforce: "pre",
|
|
366
|
+
resolveId(source, importer, options) {
|
|
367
|
+
if (options?.ssr) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const resolved = resolveRealtimeDefinitionImport(source, importer);
|
|
371
|
+
return resolved ? `${realtimeClientDefinitionPrefix}${resolved}` : null;
|
|
372
|
+
},
|
|
373
|
+
load(id) {
|
|
374
|
+
if (!id.startsWith(realtimeClientDefinitionPrefix)) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
const sourcePath = id.slice(realtimeClientDefinitionPrefix.length);
|
|
378
|
+
return createRealtimeClientDefinitionModule(readFileSync(sourcePath, "utf8"));
|
|
379
|
+
},
|
|
380
|
+
transform(code, id, options) {
|
|
381
|
+
if (options?.ssr || !isRealtimeDefinitionModule(rootDir, id)) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
code: createRealtimeClientDefinitionModule(code),
|
|
386
|
+
map: null
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function addVitePlugin(vite, plugin) {
|
|
392
|
+
vite.plugins = [...vite.plugins ?? [], plugin];
|
|
393
|
+
}
|
|
77
394
|
function addNitroErrorHandler(opts, handler) {
|
|
78
395
|
const current = opts.nitro.errorHandler;
|
|
79
396
|
const handlers = Array.isArray(current) ? current : typeof current === "string" ? [current] : [];
|
|
@@ -243,6 +560,33 @@ const module$1 = defineNuxtModule({
|
|
|
243
560
|
addServerPlugin(resolver.resolve("./runtime/plugins/storage"));
|
|
244
561
|
opts._holoStorageRuntimeRegistered = true;
|
|
245
562
|
}
|
|
563
|
+
if (hasProjectPackage(rootDir, "@holo-js/broadcast") && !opts._holoBroadcastAuthRouteRegistered) {
|
|
564
|
+
addServerHandler({
|
|
565
|
+
route: "/broadcasting/config",
|
|
566
|
+
handler: resolver.resolve("./runtime/server/routes/broadcast-config.get")
|
|
567
|
+
});
|
|
568
|
+
addServerHandler({
|
|
569
|
+
route: "/broadcasting/auth",
|
|
570
|
+
handler: resolver.resolve("./runtime/server/routes/broadcast-auth.post")
|
|
571
|
+
});
|
|
572
|
+
opts._holoBroadcastAuthRouteRegistered = true;
|
|
573
|
+
}
|
|
574
|
+
if (hasProjectPackage(rootDir, "@holo-js/realtime") && !opts._holoRealtimeRouteRegistered) {
|
|
575
|
+
const realtimeDefinitionPlugin = createRealtimeDefinitionVitePlugin(rootDir);
|
|
576
|
+
opts.vite = opts.vite || {};
|
|
577
|
+
addVitePlugin(opts.vite, realtimeDefinitionPlugin);
|
|
578
|
+
nuxt.hook("vite:extendConfig", (config, context) => {
|
|
579
|
+
if (context.isServer) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
addVitePlugin(config, realtimeDefinitionPlugin);
|
|
583
|
+
});
|
|
584
|
+
addPlugin({
|
|
585
|
+
src: resolver.resolve("./runtime/plugins/realtime"),
|
|
586
|
+
mode: "all"
|
|
587
|
+
});
|
|
588
|
+
opts._holoRealtimeRouteRegistered = true;
|
|
589
|
+
}
|
|
246
590
|
if (storageModule && normalizedStorage && (!opts.nitro.storage || Object.keys(opts.nitro.storage).every((key) => !key.startsWith("holo:")))) {
|
|
247
591
|
storageModule.applyNitroStorageConfig(opts, normalizedStorage, s3Driver);
|
|
248
592
|
}
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function renderNuxtClientHttpErrorPage(error) {
|
|
2
|
+
const nuxtGlobal = globalThis;
|
|
3
|
+
const nuxtError = {
|
|
4
|
+
statusCode: error.status,
|
|
5
|
+
statusMessage: error.message,
|
|
6
|
+
message: error.message
|
|
7
|
+
};
|
|
8
|
+
if (typeof nuxtGlobal.showError === "function") {
|
|
9
|
+
nuxtGlobal.showError(nuxtError);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
renderFallbackClientHttpErrorPage(error);
|
|
13
|
+
}
|
|
14
|
+
function renderFallbackClientHttpErrorPage(error) {
|
|
15
|
+
const browserDocument = globalThis.document;
|
|
16
|
+
if (!browserDocument) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
browserDocument.title = `${error.status}: ${error.message}`;
|
|
20
|
+
const existingRoot = browserDocument.getElementById("__holo_nuxt_client_http_error__");
|
|
21
|
+
const root = existingRoot ?? browserDocument.createElement("main");
|
|
22
|
+
root.id = "__holo_nuxt_client_http_error__";
|
|
23
|
+
root.setAttribute("role", "alert");
|
|
24
|
+
root.style.cssText = [
|
|
25
|
+
"position:fixed",
|
|
26
|
+
"inset:0",
|
|
27
|
+
"z-index:2147483647",
|
|
28
|
+
"display:grid",
|
|
29
|
+
"place-items:center",
|
|
30
|
+
"margin:0",
|
|
31
|
+
"background:#fff",
|
|
32
|
+
"color:#000",
|
|
33
|
+
"font-family:Arial,Helvetica,sans-serif"
|
|
34
|
+
].join(";");
|
|
35
|
+
const wrapper = browserDocument.createElement("div");
|
|
36
|
+
wrapper.style.cssText = "display:flex;align-items:center;gap:1.5rem;padding:1.5rem";
|
|
37
|
+
const status = browserDocument.createElement("h1");
|
|
38
|
+
status.className = "nuxt-error-h1";
|
|
39
|
+
status.textContent = String(error.status);
|
|
40
|
+
status.style.cssText = [
|
|
41
|
+
"display:inline-block",
|
|
42
|
+
"margin:0 1.25rem 0 0",
|
|
43
|
+
"padding:0 1.5rem 0 0",
|
|
44
|
+
"font-size:24px",
|
|
45
|
+
"font-weight:500",
|
|
46
|
+
"line-height:49px",
|
|
47
|
+
"vertical-align:top",
|
|
48
|
+
"border-right:1px solid rgba(0,0,0,.3)"
|
|
49
|
+
].join(";");
|
|
50
|
+
const description = browserDocument.createElement("div");
|
|
51
|
+
description.style.cssText = "display:inline-block;text-align:left;line-height:49px;height:49px;vertical-align:middle";
|
|
52
|
+
const message = browserDocument.createElement("h2");
|
|
53
|
+
message.textContent = error.message;
|
|
54
|
+
message.style.cssText = "font-size:14px;font-weight:400;line-height:49px;margin:0";
|
|
55
|
+
description.append(message);
|
|
56
|
+
wrapper.append(status, description);
|
|
57
|
+
root.replaceChildren(wrapper);
|
|
58
|
+
for (const child of Array.from(browserDocument.body.children)) {
|
|
59
|
+
if (child.id === root.id) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
child.style.display = "none";
|
|
63
|
+
}
|
|
64
|
+
if (!existingRoot) {
|
|
65
|
+
browserDocument.body.append(root);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { onScopeDispose, reactive, shallowRef, watchEffect } from "vue";
|
|
2
2
|
import { useCookie } from "#app";
|
|
3
|
+
import { normalizeHoloHttpError } from "@holo-js/core/errors";
|
|
3
4
|
import { DEFAULT_VALIDATION_BAG, createErrorBag } from "@holo-js/validation";
|
|
4
5
|
import {
|
|
5
6
|
createFormClient
|
|
6
7
|
} from "@holo-js/forms/internal/client";
|
|
8
|
+
import { renderNuxtClientHttpErrorPage } from "./client-errors.js";
|
|
9
|
+
import { isPlainObject } from "./object.js";
|
|
7
10
|
const FORM_FAILURE_COOKIE = "holo_form_failure";
|
|
8
11
|
const ARRAY_MUTATION_METHODS = /* @__PURE__ */ new Set([
|
|
9
12
|
"copyWithin",
|
|
@@ -16,9 +19,6 @@ const ARRAY_MUTATION_METHODS = /* @__PURE__ */ new Set([
|
|
|
16
19
|
"splice",
|
|
17
20
|
"unshift"
|
|
18
21
|
]);
|
|
19
|
-
function isPlainObject(value) {
|
|
20
|
-
return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Blob);
|
|
21
|
-
}
|
|
22
22
|
function isLeafValue(value) {
|
|
23
23
|
return value instanceof Date || value instanceof Blob || !Array.isArray(value) && !isPlainObject(value);
|
|
24
24
|
}
|
|
@@ -68,6 +68,41 @@ function parseFlashedValidationPayload() {
|
|
|
68
68
|
return void 0;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function getHttpFailureMessage(result) {
|
|
72
|
+
return result.errors?._root?.[0];
|
|
73
|
+
}
|
|
74
|
+
function renderFormHttpFailure(result) {
|
|
75
|
+
if (!isPlainObject(result)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const httpError = result.ok === false && typeof result.status === "number" ? normalizeHoloHttpError({
|
|
79
|
+
status: result.status,
|
|
80
|
+
message: getHttpFailureMessage(result)
|
|
81
|
+
}) : normalizeHoloHttpError(result);
|
|
82
|
+
if (!httpError || httpError.status === 422) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
renderNuxtClientHttpErrorPage(httpError);
|
|
86
|
+
}
|
|
87
|
+
function createHttpHandledFormOptions(options) {
|
|
88
|
+
const submitter = options.submitter;
|
|
89
|
+
if (!submitter) {
|
|
90
|
+
return options;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...options,
|
|
94
|
+
async submitter(context) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await submitter(context);
|
|
97
|
+
renderFormHttpFailure(result);
|
|
98
|
+
return result;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
renderFormHttpFailure(error);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
71
106
|
export function useValidationErrors(bag = DEFAULT_VALIDATION_BAG) {
|
|
72
107
|
const payload = parseFlashedValidationPayload();
|
|
73
108
|
if (!payload || (payload.bag ?? DEFAULT_VALIDATION_BAG) !== bag) {
|
|
@@ -229,7 +264,7 @@ export function useForm(schemaDefinition, options = {}) {
|
|
|
229
264
|
);
|
|
230
265
|
};
|
|
231
266
|
const stopWatching = watchEffect((onCleanup) => {
|
|
232
|
-
const localForm = createFormClient(schemaDefinition, options);
|
|
267
|
+
const localForm = createFormClient(schemaDefinition, createHttpHandledFormOptions(options));
|
|
233
268
|
syncValuesFromForm(localForm);
|
|
234
269
|
form.value = localForm;
|
|
235
270
|
version.value = ++versionCounter;
|
|
@@ -272,7 +307,9 @@ export function useForm(schemaDefinition, options = {}) {
|
|
|
272
307
|
return await currentForm().validateField(path);
|
|
273
308
|
},
|
|
274
309
|
async submit() {
|
|
275
|
-
|
|
310
|
+
const result = await currentForm().submit();
|
|
311
|
+
renderFormHttpFailure(result);
|
|
312
|
+
return result;
|
|
276
313
|
},
|
|
277
314
|
reset(nextValues) {
|
|
278
315
|
currentForm().reset(nextValues);
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { onScopeDispose, reactive } from "vue";
|
|
2
|
+
import { normalizeHoloHttpError } from "@holo-js/core/errors";
|
|
3
|
+
import {
|
|
4
|
+
configureRealtimeClientRuntime,
|
|
5
|
+
configureRealtimeClientTransport,
|
|
6
|
+
createBroadcastRealtimeTransport,
|
|
7
|
+
getRealtimeQueryStore
|
|
8
|
+
} from "@holo-js/realtime/client";
|
|
9
|
+
import {
|
|
10
|
+
mutation as createRealtimeMutation,
|
|
11
|
+
query as createRealtimeQuery
|
|
12
|
+
} from "@holo-js/realtime";
|
|
13
|
+
import { renderNuxtClientHttpErrorPage } from "./client-errors.js";
|
|
14
|
+
import { isPlainObject } from "./object.js";
|
|
15
|
+
function emitRealtimeError(error) {
|
|
16
|
+
const httpError = normalizeHoloHttpError(error);
|
|
17
|
+
if (httpError) {
|
|
18
|
+
renderNuxtClientHttpErrorPage(httpError);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const query = createRealtimeQuery;
|
|
22
|
+
export const mutation = createRealtimeMutation;
|
|
23
|
+
function replaceReactiveObject(target, value) {
|
|
24
|
+
for (const key of Object.keys(target)) {
|
|
25
|
+
if (!(key in value)) {
|
|
26
|
+
delete target[key];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
Object.assign(target, value);
|
|
30
|
+
}
|
|
31
|
+
function replaceReactiveArray(target, value) {
|
|
32
|
+
target.splice(0, target.length, ...value);
|
|
33
|
+
}
|
|
34
|
+
function createRealtimeReactiveValue(value) {
|
|
35
|
+
if (value === void 0) {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return reactive([...value]);
|
|
40
|
+
}
|
|
41
|
+
if (isPlainObject(value)) {
|
|
42
|
+
return reactive({ ...value });
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function replaceRealtimeReactiveValue(target, value) {
|
|
47
|
+
if (typeof target === "undefined") {
|
|
48
|
+
return createRealtimeReactiveValue(value);
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(target) && Array.isArray(value)) {
|
|
51
|
+
replaceReactiveArray(target, value);
|
|
52
|
+
return target;
|
|
53
|
+
}
|
|
54
|
+
if (isPlainObject(target) && isPlainObject(value)) {
|
|
55
|
+
replaceReactiveObject(target, value);
|
|
56
|
+
return target;
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
function useReactiveRealtimeQuery(definition, args) {
|
|
61
|
+
const store = getRealtimeQueryStore(definition, args);
|
|
62
|
+
const initialData = store.snapshot?.data;
|
|
63
|
+
let current = typeof initialData === "undefined" ? reactive({}) : createRealtimeReactiveValue(initialData);
|
|
64
|
+
const unsubscribe = store.subscribe(() => {
|
|
65
|
+
const nextData = store.snapshot?.data;
|
|
66
|
+
if (typeof nextData !== "undefined") {
|
|
67
|
+
current = replaceRealtimeReactiveValue(current, nextData);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if ("window" in globalThis) {
|
|
71
|
+
store.connect();
|
|
72
|
+
}
|
|
73
|
+
onScopeDispose(unsubscribe);
|
|
74
|
+
return current;
|
|
75
|
+
}
|
|
76
|
+
configureRealtimeClientRuntime({
|
|
77
|
+
handleError: emitRealtimeError,
|
|
78
|
+
useQuery: useReactiveRealtimeQuery
|
|
79
|
+
});
|
|
80
|
+
configureRealtimeClientTransport(createBroadcastRealtimeTransport());
|
|
81
|
+
export {};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./realtime.js";
|
|
File without changes
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
holo
|
|
3
|
-
useHoloDb
|
|
4
|
-
useHoloDebug
|
|
5
|
-
useHoloEnv
|
|
6
|
-
} from "
|
|
7
|
-
export const holo = baseHolo;
|
|
8
|
-
export const useHoloDb = baseUseHoloDb;
|
|
9
|
-
export const useHoloDebug = baseUseHoloDebug;
|
|
10
|
-
export const useHoloEnv = baseUseHoloEnv;
|
|
1
|
+
export {
|
|
2
|
+
holo,
|
|
3
|
+
useHoloDb,
|
|
4
|
+
useHoloDebug,
|
|
5
|
+
useHoloEnv
|
|
6
|
+
} from "../imports/holo.js";
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineEventHandler, getHeaders, getRequestURL, readRawBody } from "h3";
|
|
2
|
+
import { renderBroadcastAuthResponse } from "@holo-js/broadcast/auth";
|
|
3
|
+
import { holo } from "../../composables/index.js";
|
|
4
|
+
function resolveBroadcastSigning(connection) {
|
|
5
|
+
if (!connection || typeof connection !== "object") {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
const candidate = connection;
|
|
9
|
+
if (candidate.driver !== "holo" || typeof candidate.key !== "string" || typeof candidate.secret !== "string") {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
appKey: candidate.key,
|
|
14
|
+
appSecret: candidate.secret
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export default defineEventHandler(async (event) => {
|
|
18
|
+
const app = await holo.getApp();
|
|
19
|
+
const auth = await holo.getAuth();
|
|
20
|
+
const broadcast = app.config?.broadcast;
|
|
21
|
+
const connection = broadcast ? broadcast.connections[broadcast.default] : void 0;
|
|
22
|
+
const signing = resolveBroadcastSigning(connection);
|
|
23
|
+
const headers = new Headers();
|
|
24
|
+
for (const [key, value] of Object.entries(getHeaders(event))) {
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
headers.set(key, value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const request = new Request(getRequestURL(event), {
|
|
30
|
+
method: event.method,
|
|
31
|
+
headers,
|
|
32
|
+
body: await readRawBody(event)
|
|
33
|
+
});
|
|
34
|
+
return await renderBroadcastAuthResponse(request, {
|
|
35
|
+
...signing,
|
|
36
|
+
resolveUser: async (_request, context) => {
|
|
37
|
+
const guardAuth = context.guard ? auth?.guard(context.guard) : void 0;
|
|
38
|
+
return guardAuth ? await guardAuth.user() : await auth?.user();
|
|
39
|
+
},
|
|
40
|
+
channelAuth: {
|
|
41
|
+
registry: {
|
|
42
|
+
projectRoot: app.projectRoot,
|
|
43
|
+
channels: app.registry?.channels ?? []
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineEventHandler } from "h3";
|
|
2
|
+
import { renderBroadcastClientConfigResponse } from "@holo-js/broadcast/client-config";
|
|
3
|
+
import { holo } from "../../composables/index.js";
|
|
4
|
+
export default defineEventHandler(async () => {
|
|
5
|
+
const app = await holo.getApp();
|
|
6
|
+
return renderBroadcastClientConfigResponse(app.config.broadcast);
|
|
7
|
+
});
|
package/dist/runtime/shims.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ interface HoloRuntimeConfig extends RuntimeConfigInput {
|
|
|
41
41
|
* runtime config, storage access, and Nitro route/plugin globals.
|
|
42
42
|
*/
|
|
43
43
|
declare module '#app' {
|
|
44
|
+
export function defineNuxtPlugin<T>(plugin: T): T
|
|
44
45
|
export function useRuntimeConfig(): HoloRuntimeConfig
|
|
45
46
|
export function useCookie<T = string | null>(name: string): { value: T | null | undefined }
|
|
46
47
|
}
|
|
@@ -67,6 +68,13 @@ declare module 'nitropack/runtime/storage' {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
declare global {
|
|
71
|
+
interface ImportMeta {
|
|
72
|
+
glob<TModule>(
|
|
73
|
+
pattern: string,
|
|
74
|
+
options: { readonly eager: true },
|
|
75
|
+
): Record<string, TModule>
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
function createError(input: { statusCode: number, statusMessage: string }): Error
|
|
71
79
|
function defineNitroErrorHandler<T>(handler: T): T
|
|
72
80
|
function defineNitroPlugin<T>(plugin: T): T
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/adapter-nuxt",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Holo-JS Framework - Nuxt adapter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"types": "./dist/runtime/composables/index.d.ts",
|
|
18
18
|
"import": "./dist/runtime/composables/index.js"
|
|
19
19
|
},
|
|
20
|
+
"./realtime": {
|
|
21
|
+
"types": "./dist/runtime/composables/realtime.d.ts",
|
|
22
|
+
"import": "./dist/runtime/composables/realtime.js"
|
|
23
|
+
},
|
|
20
24
|
"./storage": {
|
|
21
25
|
"types": "./dist/runtime/composables/storage.d.ts",
|
|
22
26
|
"import": "./dist/runtime/composables/storage.js"
|
|
@@ -35,21 +39,29 @@
|
|
|
35
39
|
},
|
|
36
40
|
"dependencies": {
|
|
37
41
|
"@nuxt/kit": "^4.4.4",
|
|
38
|
-
"@holo-js/config": "^0.1
|
|
39
|
-
"@holo-js/core": "^0.1
|
|
40
|
-
"@holo-js/db": "^0.1
|
|
41
|
-
"@holo-js/validation": "^0.1
|
|
42
|
+
"@holo-js/config": "^0.2.1",
|
|
43
|
+
"@holo-js/core": "^0.2.1",
|
|
44
|
+
"@holo-js/db": "^0.2.1",
|
|
45
|
+
"@holo-js/validation": "^0.2.1",
|
|
42
46
|
"h3": "^1.15.11"
|
|
43
47
|
},
|
|
44
48
|
"peerDependencies": {
|
|
45
|
-
"@holo-js/
|
|
46
|
-
"@holo-js/
|
|
47
|
-
"@holo-js/
|
|
49
|
+
"@holo-js/broadcast": "^0.2.1",
|
|
50
|
+
"@holo-js/forms": "^0.2.1",
|
|
51
|
+
"@holo-js/realtime": "^0.2.1",
|
|
52
|
+
"@holo-js/storage": "^0.2.1",
|
|
53
|
+
"@holo-js/storage-s3": "^0.2.1"
|
|
48
54
|
},
|
|
49
55
|
"peerDependenciesMeta": {
|
|
56
|
+
"@holo-js/broadcast": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
50
59
|
"@holo-js/forms": {
|
|
51
60
|
"optional": true
|
|
52
61
|
},
|
|
62
|
+
"@holo-js/realtime": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
53
65
|
"@holo-js/storage": {
|
|
54
66
|
"optional": true
|
|
55
67
|
},
|
|
@@ -58,7 +70,9 @@
|
|
|
58
70
|
}
|
|
59
71
|
},
|
|
60
72
|
"devDependencies": {
|
|
61
|
-
"@holo-js/
|
|
73
|
+
"@holo-js/broadcast": "^0.2.1",
|
|
74
|
+
"@holo-js/realtime": "^0.2.1",
|
|
75
|
+
"@holo-js/storage-s3": "^0.2.1",
|
|
62
76
|
"@nuxt/module-builder": "^1.0.2",
|
|
63
77
|
"@types/node": "^22.10.2",
|
|
64
78
|
"nuxt": "^4.4.4",
|