@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 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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@holo-js/adapter-nuxt",
3
3
  "configKey": "@holo-js/adapter-nuxt",
4
- "version": "0.1.9",
4
+ "version": "0.2.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
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 MODEL_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"]);
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
- return await currentForm().submit();
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
@@ -0,0 +1,3 @@
1
+ export function isPlainObject(value) {
2
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Blob);
3
+ }
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
@@ -0,0 +1,4 @@
1
+ import { defineNuxtPlugin } from "#app";
2
+ import "../composables/realtime";
3
+ export default defineNuxtPlugin(() => {
4
+ });
@@ -1,10 +1,6 @@
1
- import {
2
- holo as baseHolo,
3
- useHoloDb as baseUseHoloDb,
4
- useHoloDebug as baseUseHoloDebug,
5
- useHoloEnv as baseUseHoloEnv
6
- } from "../../composables/index.js";
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";
@@ -1,4 +1,4 @@
1
1
  export {
2
2
  Storage,
3
3
  useStorage
4
- } from "../utils/storage.js";
4
+ } from "@holo-js/storage/runtime";
@@ -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
+ });
@@ -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
+ });
@@ -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.9",
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.9",
39
- "@holo-js/core": "^0.1.9",
40
- "@holo-js/db": "^0.1.9",
41
- "@holo-js/validation": "^0.1.9",
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/forms": "^0.1.9",
46
- "@holo-js/storage": "^0.1.9",
47
- "@holo-js/storage-s3": "^0.1.9"
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/storage-s3": "^0.1.9",
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",