@abraca/nuxt 2.0.11 → 2.3.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/dist/module.d.mts +68 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +99 -4
- package/dist/runtime/components/ACodeEditor.d.vue.ts +26 -0
- package/dist/runtime/components/ACodeEditor.vue +268 -0
- package/dist/runtime/components/ACodeEditor.vue.d.ts +26 -0
- package/dist/runtime/components/ADocumentTree.vue +52 -20
- package/dist/runtime/components/AEditor.d.vue.ts +20 -13
- package/dist/runtime/components/AEditor.vue +55 -2
- package/dist/runtime/components/AEditor.vue.d.ts +20 -13
- package/dist/runtime/components/ANodePanel.vue +64 -60
- package/dist/runtime/components/ANotificationBell.d.vue.ts +1 -1
- package/dist/runtime/components/ANotificationBell.vue.d.ts +1 -1
- package/dist/runtime/components/ASpaceFormModal.d.vue.ts +2 -2
- package/dist/runtime/components/ASpaceFormModal.vue.d.ts +2 -2
- package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
- package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
- package/dist/runtime/components/aware/APresenceBlobs.d.vue.ts +29 -1
- package/dist/runtime/components/aware/APresenceBlobs.vue +54 -8
- package/dist/runtime/components/aware/APresenceBlobs.vue.d.ts +29 -1
- package/dist/runtime/components/aware/APresenceCursors.d.vue.ts +11 -0
- package/dist/runtime/components/aware/APresenceCursors.vue +74 -9
- package/dist/runtime/components/aware/APresenceCursors.vue.d.ts +11 -0
- package/dist/runtime/components/aware/AToggleGroup.d.vue.ts +28 -13
- package/dist/runtime/components/aware/AToggleGroup.vue +56 -20
- package/dist/runtime/components/aware/AToggleGroup.vue.d.ts +28 -13
- package/dist/runtime/components/docs/ADocsNavigation.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsNavigation.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsToc.d.vue.ts +2 -2
- package/dist/runtime/components/docs/ADocsToc.vue.d.ts +2 -2
- package/dist/runtime/components/editor/AEditorRedoButton.d.vue.ts +1 -1
- package/dist/runtime/components/editor/AEditorRedoButton.vue.d.ts +1 -1
- package/dist/runtime/components/editor/AEditorUndoButton.d.vue.ts +1 -1
- package/dist/runtime/components/editor/AEditorUndoButton.vue.d.ts +1 -1
- package/dist/runtime/components/editor/ANodeInlineLabel.d.vue.ts +1 -1
- package/dist/runtime/components/editor/ANodeInlineLabel.vue.d.ts +1 -1
- package/dist/runtime/components/registry/APluginBrowser.d.vue.ts +23 -0
- package/dist/runtime/components/registry/APluginBrowser.vue +155 -0
- package/dist/runtime/components/registry/APluginBrowser.vue.d.ts +23 -0
- package/dist/runtime/components/registry/APluginCapabilityDialog.d.vue.ts +17 -0
- package/dist/runtime/components/registry/APluginCapabilityDialog.vue +159 -0
- package/dist/runtime/components/registry/APluginCapabilityDialog.vue.d.ts +17 -0
- package/dist/runtime/components/registry/APluginCard.d.vue.ts +20 -0
- package/dist/runtime/components/registry/APluginCard.vue +91 -0
- package/dist/runtime/components/registry/APluginCard.vue.d.ts +20 -0
- package/dist/runtime/components/registry/APluginDetail.d.vue.ts +18 -0
- package/dist/runtime/components/registry/APluginDetail.vue +252 -0
- package/dist/runtime/components/registry/APluginDetail.vue.d.ts +18 -0
- package/dist/runtime/components/renderers/ACodeRenderer.d.vue.ts +15 -0
- package/dist/runtime/components/renderers/ACodeRenderer.vue +68 -0
- package/dist/runtime/components/renderers/ACodeRenderer.vue.d.ts +15 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue +416 -120
- package/dist/runtime/components/renderers/AProseRenderer.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/AProseRenderer.vue.d.ts +2 -2
- package/dist/runtime/components/shell/ABreadcrumbForDoc.d.vue.ts +11 -0
- package/dist/runtime/components/shell/ABreadcrumbForDoc.vue +16 -0
- package/dist/runtime/components/shell/ABreadcrumbForDoc.vue.d.ts +11 -0
- package/dist/runtime/components/shell/ASettingsSection.d.vue.ts +35 -0
- package/dist/runtime/components/shell/ASettingsSection.vue +26 -0
- package/dist/runtime/components/shell/ASettingsSection.vue.d.ts +35 -0
- package/dist/runtime/components/shell/ASidebar.d.vue.ts +1 -1
- package/dist/runtime/components/shell/ASidebar.vue.d.ts +1 -1
- package/dist/runtime/components/shell/AUserMenu.d.vue.ts +5 -2
- package/dist/runtime/components/shell/AUserMenu.vue +4 -0
- package/dist/runtime/components/shell/AUserMenu.vue.d.ts +5 -2
- package/dist/runtime/composables/useAbracadabraSchema.d.ts +83 -0
- package/dist/runtime/composables/useAbracadabraSchema.js +52 -0
- package/dist/runtime/composables/useAggregatedPresence.d.ts +1 -6
- package/dist/runtime/composables/useCalendarView.d.ts +1 -1
- package/dist/runtime/composables/useChat.js +1 -0
- package/dist/runtime/composables/useDocBreadcrumb.d.ts +21 -0
- package/dist/runtime/composables/useDocBreadcrumb.js +33 -0
- package/dist/runtime/composables/useDocEntryTyped.d.ts +60 -0
- package/dist/runtime/composables/useDocEntryTyped.js +70 -0
- package/dist/runtime/composables/useEditorDragHandle.js +18 -0
- package/dist/runtime/composables/useEditorSuggestions.js +2 -1
- package/dist/runtime/composables/useInstalledPlugins.d.ts +3 -21
- package/dist/runtime/composables/useInstalledPlugins.js +2 -12
- package/dist/runtime/composables/useMetaMenuItems.d.ts +21 -0
- package/dist/runtime/composables/useMetaMenuItems.js +115 -0
- package/dist/runtime/composables/useMetaValidator.d.ts +27 -0
- package/dist/runtime/composables/useMetaValidator.js +10 -0
- package/dist/runtime/composables/usePluginCatalog.d.ts +161 -0
- package/dist/runtime/composables/usePluginCatalog.js +234 -0
- package/dist/runtime/composables/useQuery.d.ts +79 -0
- package/dist/runtime/composables/useQuery.js +97 -0
- package/dist/runtime/composables/useSpaces.js +4 -5
- package/dist/runtime/composables/useTableView.d.ts +3 -3
- package/dist/runtime/composables/useTypedDoc.d.ts +97 -0
- package/dist/runtime/composables/useTypedDoc.js +114 -0
- package/dist/runtime/composables/useWebRTC.js +44 -5
- package/dist/runtime/extensions/document-meta.js +5 -0
- package/dist/runtime/extensions/timeline.d.ts +11 -0
- package/dist/runtime/extensions/timeline.js +52 -0
- package/dist/runtime/extensions/views/DocumentMetaView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/DocumentMetaView.vue +63 -0
- package/dist/runtime/extensions/views/DocumentMetaView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/TimelineItemView.d.vue.ts +4 -0
- package/dist/runtime/extensions/views/TimelineItemView.vue +131 -0
- package/dist/runtime/extensions/views/TimelineItemView.vue.d.ts +4 -0
- package/dist/runtime/extensions/views/TimelineView.d.vue.ts +9 -0
- package/dist/runtime/extensions/views/TimelineView.vue +29 -0
- package/dist/runtime/extensions/views/TimelineView.vue.d.ts +9 -0
- package/dist/runtime/locale.d.ts +2 -0
- package/dist/runtime/locale.js +2 -0
- package/dist/runtime/plugin-abracadabra.client.js +107 -6
- package/dist/runtime/plugin-registry.d.ts +11 -30
- package/dist/runtime/plugin-registry.js +2 -82
- package/dist/runtime/plugins/core.plugin.js +10 -4
- package/dist/runtime/server/api/_abracadabra/spaces.get.d.ts +1 -1
- package/dist/runtime/server/plugins/abracadabra-service.js +28 -0
- package/dist/runtime/server/utils/docCache.js +24 -3
- package/dist/runtime/server/utils/schemaServerSupport.d.ts +52 -0
- package/dist/runtime/server/utils/schemaServerSupport.js +51 -0
- package/dist/runtime/types.d.ts +63 -46
- package/dist/runtime/utils/docTypes.d.ts +15 -0
- package/dist/runtime/utils/docTypes.js +20 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +32 -0
- package/dist/runtime/utils/loadCodeMirror.js +65 -0
- package/dist/runtime/utils/markdownToYjs.d.ts +1 -23
- package/dist/runtime/utils/markdownToYjs.js +5 -440
- package/dist/runtime/utils/schemaSupport.d.ts +60 -0
- package/dist/runtime/utils/schemaSupport.js +40 -0
- package/dist/runtime/utils/yjsConvert.d.ts +1 -14
- package/dist/runtime/utils/yjsConvert.js +5 -331
- package/package.json +84 -23
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { NodeViewWrapper, NodeViewContent } from "@tiptap/vue-3";
|
|
4
|
+
import ANodeInlineLabel from "../../components/editor/ANodeInlineLabel.vue";
|
|
5
|
+
import AIconPickerPopover from "../../components/editor/AIconPickerPopover.vue";
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
decorations: { type: Array, required: true },
|
|
8
|
+
selected: { type: Boolean, required: true },
|
|
9
|
+
updateAttributes: { type: Function, required: true },
|
|
10
|
+
deleteNode: { type: Function, required: true },
|
|
11
|
+
node: { type: null, required: true },
|
|
12
|
+
view: { type: null, required: true },
|
|
13
|
+
getPos: { type: null, required: true },
|
|
14
|
+
innerDecorations: { type: null, required: true },
|
|
15
|
+
editor: { type: Object, required: true },
|
|
16
|
+
extension: { type: Object, required: true },
|
|
17
|
+
HTMLAttributes: { type: Object, required: true }
|
|
18
|
+
});
|
|
19
|
+
const date = computed(() => props.node.attrs.date || "");
|
|
20
|
+
const label = computed(() => props.node.attrs.label || "");
|
|
21
|
+
const icon = computed(() => props.node.attrs.icon || "");
|
|
22
|
+
const iconShort = computed(() => icon.value.replace(/^i-lucide-/, ""));
|
|
23
|
+
const iconPopoverOpen = ref(false);
|
|
24
|
+
const datePopoverOpen = ref(false);
|
|
25
|
+
const dateDraft = ref("");
|
|
26
|
+
watch(datePopoverOpen, (open) => {
|
|
27
|
+
if (open) dateDraft.value = date.value;
|
|
28
|
+
});
|
|
29
|
+
function setLabel(v) {
|
|
30
|
+
props.updateAttributes({ label: v });
|
|
31
|
+
}
|
|
32
|
+
function setIcon(name) {
|
|
33
|
+
props.updateAttributes({ icon: name ? `i-lucide-${name}` : "" });
|
|
34
|
+
iconPopoverOpen.value = false;
|
|
35
|
+
}
|
|
36
|
+
function commitDate() {
|
|
37
|
+
props.updateAttributes({ date: dateDraft.value.trim() });
|
|
38
|
+
datePopoverOpen.value = false;
|
|
39
|
+
}
|
|
40
|
+
function onDateKeydown(e) {
|
|
41
|
+
if (e.key === "Enter") {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
commitDate();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<NodeViewWrapper
|
|
50
|
+
as="div"
|
|
51
|
+
class="group/item relative ps-8 pb-5 last:pb-0"
|
|
52
|
+
>
|
|
53
|
+
<div
|
|
54
|
+
class="absolute left-0 top-1.5 size-4 rounded-full bg-(--ui-bg) border-2 border-(--ui-border-accented) flex items-center justify-center"
|
|
55
|
+
contenteditable="false"
|
|
56
|
+
>
|
|
57
|
+
<UIcon
|
|
58
|
+
v-if="icon"
|
|
59
|
+
:name="icon"
|
|
60
|
+
class="size-2.5 text-(--ui-primary)"
|
|
61
|
+
/>
|
|
62
|
+
<div
|
|
63
|
+
v-else
|
|
64
|
+
class="size-1.5 rounded-full bg-(--ui-primary)"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<div
|
|
68
|
+
class="flex items-center gap-2 text-sm"
|
|
69
|
+
contenteditable="false"
|
|
70
|
+
>
|
|
71
|
+
<AIconPickerPopover
|
|
72
|
+
:model-value="iconShort"
|
|
73
|
+
:open="iconPopoverOpen"
|
|
74
|
+
@update:model-value="setIcon"
|
|
75
|
+
@update:open="iconPopoverOpen = $event"
|
|
76
|
+
>
|
|
77
|
+
<button
|
|
78
|
+
class="shrink-0 size-5 flex items-center justify-center rounded-sm hover:bg-(--ui-bg-elevated)/60 text-(--ui-text-muted)"
|
|
79
|
+
:title="icon ? 'Change icon' : 'Add icon'"
|
|
80
|
+
@click.stop="iconPopoverOpen = true"
|
|
81
|
+
>
|
|
82
|
+
<UIcon
|
|
83
|
+
:name="icon || 'i-lucide-plus'"
|
|
84
|
+
class="size-4"
|
|
85
|
+
:class="!icon ? 'opacity-0 group-hover/item:opacity-60 transition-opacity' : ''"
|
|
86
|
+
/>
|
|
87
|
+
</button>
|
|
88
|
+
</AIconPickerPopover>
|
|
89
|
+
<ANodeInlineLabel
|
|
90
|
+
:model-value="label"
|
|
91
|
+
placeholder="Event"
|
|
92
|
+
variant="title"
|
|
93
|
+
@update:model-value="setLabel"
|
|
94
|
+
/>
|
|
95
|
+
<UPopover
|
|
96
|
+
v-model:open="datePopoverOpen"
|
|
97
|
+
:content="{ align: 'end' }"
|
|
98
|
+
>
|
|
99
|
+
<button
|
|
100
|
+
class="text-xs text-(--ui-text-muted) hover:text-(--ui-text) rounded-sm hover:bg-(--ui-bg-elevated)/60 px-1.5 py-0.5 inline-flex items-center gap-1"
|
|
101
|
+
type="button"
|
|
102
|
+
>
|
|
103
|
+
<UIcon
|
|
104
|
+
name="i-lucide-calendar"
|
|
105
|
+
class="size-3"
|
|
106
|
+
/>
|
|
107
|
+
<span v-if="date">{{ date }}</span>
|
|
108
|
+
<span
|
|
109
|
+
v-else
|
|
110
|
+
class="italic"
|
|
111
|
+
>Add date</span>
|
|
112
|
+
</button>
|
|
113
|
+
<template #content>
|
|
114
|
+
<div class="p-2 w-56 flex flex-col gap-1">
|
|
115
|
+
<UInput
|
|
116
|
+
v-model="dateDraft"
|
|
117
|
+
autofocus
|
|
118
|
+
size="sm"
|
|
119
|
+
placeholder="e.g. 2026-04-20 or April 2026"
|
|
120
|
+
@keydown="onDateKeydown"
|
|
121
|
+
@blur="commitDate"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
</template>
|
|
125
|
+
</UPopover>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="mt-1 text-sm text-(--ui-text-muted)">
|
|
128
|
+
<NodeViewContent class="[&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&>*]:my-1" />
|
|
129
|
+
</div>
|
|
130
|
+
</NodeViewWrapper>
|
|
131
|
+
</template>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NodeViewProps } from '@tiptap/vue-3';
|
|
2
|
+
declare const __VLS_export: import("vue").DefineComponent<NodeViewProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
3
|
+
declare const _default: typeof __VLS_export;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeView for the `timeline` block — renders the vertical guide line.
|
|
3
|
+
*
|
|
4
|
+
* Ported 1:1 from cou-sh/app/components/editor/TimelineView.vue.
|
|
5
|
+
*/
|
|
6
|
+
import type { NodeViewProps } from '@tiptap/vue-3';
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<NodeViewProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { NodeViewWrapper, NodeViewContent } from "@tiptap/vue-3";
|
|
3
|
+
defineProps({
|
|
4
|
+
decorations: { type: Array, required: true },
|
|
5
|
+
selected: { type: Boolean, required: true },
|
|
6
|
+
updateAttributes: { type: Function, required: true },
|
|
7
|
+
deleteNode: { type: Function, required: true },
|
|
8
|
+
node: { type: null, required: true },
|
|
9
|
+
view: { type: null, required: true },
|
|
10
|
+
getPos: { type: null, required: true },
|
|
11
|
+
innerDecorations: { type: null, required: true },
|
|
12
|
+
editor: { type: Object, required: true },
|
|
13
|
+
extension: { type: Object, required: true },
|
|
14
|
+
HTMLAttributes: { type: Object, required: true }
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<NodeViewWrapper
|
|
20
|
+
as="div"
|
|
21
|
+
class="my-5 ms-2 relative"
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
class="absolute left-2 top-2 bottom-2 w-px bg-(--ui-border)"
|
|
25
|
+
contenteditable="false"
|
|
26
|
+
/>
|
|
27
|
+
<NodeViewContent />
|
|
28
|
+
</NodeViewWrapper>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeView for the `timeline` block — renders the vertical guide line.
|
|
3
|
+
*
|
|
4
|
+
* Ported 1:1 from cou-sh/app/components/editor/TimelineView.vue.
|
|
5
|
+
*/
|
|
6
|
+
import type { NodeViewProps } from '@tiptap/vue-3';
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<NodeViewProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NodeViewProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
package/dist/runtime/locale.d.ts
CHANGED
package/dist/runtime/locale.js
CHANGED
|
@@ -130,6 +130,10 @@ function toNuxtUIPrimary(name) {
|
|
|
130
130
|
function toNuxtUINeutral(name) {
|
|
131
131
|
return NUXT_UI_NEUTRAL_COLORS.has(name) ? name : CUSTOM_TO_NUXT_UI_NEUTRAL[name] ?? "zinc";
|
|
132
132
|
}
|
|
133
|
+
function hostHasCustomPalette(name) {
|
|
134
|
+
if (typeof document === "undefined") return false;
|
|
135
|
+
return getComputedStyle(document.documentElement).getPropertyValue(`--color-${name}-500`).trim() !== "";
|
|
136
|
+
}
|
|
133
137
|
const STORAGE_KEY_EXTERNAL_PLUGINS = "abracadabra_external_plugins";
|
|
134
138
|
const STORAGE_KEY_DISABLED_BUILTINS = "abracadabra_disabled_builtins";
|
|
135
139
|
const CLAIMED_FLAG_KEY = "abracadabra_was_claimed";
|
|
@@ -238,10 +242,29 @@ export default defineNuxtPlugin({
|
|
|
238
242
|
localStorage.setItem(STORAGE_KEY_EXTERNAL_PLUGINS, JSON.stringify(updated));
|
|
239
243
|
}
|
|
240
244
|
}
|
|
245
|
+
const { _collectSchemaRegistries } = await import("./composables/useAbracadabraSchema.js");
|
|
246
|
+
const { schemas: schemaRegistries, plugins: inlinePlugins } = await _collectSchemaRegistries(nuxtApp);
|
|
247
|
+
for (const plugin of inlinePlugins) {
|
|
248
|
+
registry.register(plugin);
|
|
249
|
+
}
|
|
241
250
|
registry.freeze();
|
|
242
251
|
if (debug) {
|
|
243
252
|
console.log("[abracadabra] plugins:", registry.getPlugins().map((p) => p.name));
|
|
244
253
|
}
|
|
254
|
+
const schemaCfg = abraConfig.schema ?? {};
|
|
255
|
+
if (debug && schemaRegistries.length > 0) {
|
|
256
|
+
console.log(
|
|
257
|
+
"[abracadabra] schema registries attached:",
|
|
258
|
+
schemaRegistries.map((r) => Array.from(r.types.keys())),
|
|
259
|
+
"validate=",
|
|
260
|
+
schemaCfg.validate,
|
|
261
|
+
"migrateOnRead=",
|
|
262
|
+
schemaCfg.migrateOnRead
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (schemaCfg.bundleDir && debug) {
|
|
266
|
+
console.log("[abracadabra] server bundle_dir advertised:", schemaCfg.bundleDir);
|
|
267
|
+
}
|
|
245
268
|
const doc = shallowRef(new Y.Doc());
|
|
246
269
|
const provider = shallowRef(null);
|
|
247
270
|
const client = shallowRef(null);
|
|
@@ -300,6 +323,7 @@ export default defineNuxtPlugin({
|
|
|
300
323
|
let _serversLoaded = false;
|
|
301
324
|
let _initPromise = null;
|
|
302
325
|
let _wsp = null;
|
|
326
|
+
let _tokenManager = null;
|
|
303
327
|
let authFailureCount = 0;
|
|
304
328
|
const AUTH_FAILURE_LIMIT = 3;
|
|
305
329
|
let lastClientIds = /* @__PURE__ */ new Set();
|
|
@@ -421,14 +445,18 @@ export default defineNuxtPlugin({
|
|
|
421
445
|
userColorName.value = colorName;
|
|
422
446
|
localStorage.setItem("abracadabra_usercolor", colorName);
|
|
423
447
|
const appConfig = useAppConfig();
|
|
424
|
-
if (appConfig.ui?.colors)
|
|
448
|
+
if (appConfig.ui?.colors) {
|
|
449
|
+
appConfig.ui.colors.primary = hostHasCustomPalette(colorName) ? colorName : toNuxtUIPrimary(colorName);
|
|
450
|
+
}
|
|
425
451
|
publishAwarenessUser();
|
|
426
452
|
}
|
|
427
453
|
function setNeutralColor(colorName) {
|
|
428
454
|
userNeutralColorName.value = colorName;
|
|
429
455
|
localStorage.setItem("abracadabra_neutralcolor", colorName);
|
|
430
456
|
const appConfig = useAppConfig();
|
|
431
|
-
if (appConfig.ui?.colors)
|
|
457
|
+
if (appConfig.ui?.colors) {
|
|
458
|
+
appConfig.ui.colors.neutral = hostHasCustomPalette(colorName) ? colorName : toNuxtUINeutral(colorName);
|
|
459
|
+
}
|
|
432
460
|
}
|
|
433
461
|
function setUserStatusIcon(icon) {
|
|
434
462
|
const normalized = icon.replace(/^i-lucide-/, "");
|
|
@@ -474,6 +502,13 @@ export default defineNuxtPlugin({
|
|
|
474
502
|
}
|
|
475
503
|
provider.value = null;
|
|
476
504
|
}
|
|
505
|
+
if (_tokenManager) {
|
|
506
|
+
try {
|
|
507
|
+
_tokenManager.dispose();
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
_tokenManager = null;
|
|
511
|
+
}
|
|
477
512
|
client.value = null;
|
|
478
513
|
keystore.value = null;
|
|
479
514
|
isReady.value = false;
|
|
@@ -537,6 +572,7 @@ export default defineNuxtPlugin({
|
|
|
537
572
|
addLog("No active session \u2014 registering passkey from scratch", "auth");
|
|
538
573
|
await client.value.registerWithKey({
|
|
539
574
|
publicKey: newPubKey,
|
|
575
|
+
x25519Key: x25519PublicKey,
|
|
540
576
|
deviceName,
|
|
541
577
|
displayName: userName.value,
|
|
542
578
|
inviteCode: pendingInviteCode.value ?? void 0
|
|
@@ -623,6 +659,15 @@ export default defineNuxtPlugin({
|
|
|
623
659
|
}
|
|
624
660
|
async function logout() {
|
|
625
661
|
addLog("Clearing identity...", "auth");
|
|
662
|
+
const sessionId = _tokenManager?.currentSession?.sessionId;
|
|
663
|
+
if (client.value && sessionId) {
|
|
664
|
+
try {
|
|
665
|
+
await client.value.revokeDeviceSession(sessionId);
|
|
666
|
+
} catch (e) {
|
|
667
|
+
if (import.meta.dev) console.warn("[abracadabra] revoke device session failed", e);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
_tokenManager?.clearSession();
|
|
626
671
|
if (client.value) client.value.logout();
|
|
627
672
|
if (keystore.value) await keystore.value.clear();
|
|
628
673
|
localStorage.removeItem("abracadabra_privkey");
|
|
@@ -783,7 +828,7 @@ export default defineNuxtPlugin({
|
|
|
783
828
|
import("@noble/hashes/sha2.js")
|
|
784
829
|
]);
|
|
785
830
|
setSdkModule(sdkModule);
|
|
786
|
-
const { AbracadabraClient, AbracadabraProvider, AbracadabraWS, CryptoIdentityKeystore } = sdkModule;
|
|
831
|
+
const { AbracadabraClient, AbracadabraProvider, AbracadabraWS, CryptoIdentityKeystore, TokenManager, LocalStorageDeviceSessionStorage } = sdkModule;
|
|
787
832
|
const ed = edModule.default ?? edModule;
|
|
788
833
|
if (!ed.hashes) {
|
|
789
834
|
throw new Error("@noble/ed25519 v3: `hashes` export missing from imported namespace \u2014 check Vite optimizeDeps");
|
|
@@ -827,24 +872,38 @@ export default defineNuxtPlugin({
|
|
|
827
872
|
const storedColor = localStorage.getItem("abracadabra_usercolor");
|
|
828
873
|
const storedNeutral = localStorage.getItem("abracadabra_neutralcolor");
|
|
829
874
|
const derived = deriveColorFromPubKey(pubKey);
|
|
875
|
+
const appConfig = useAppConfig();
|
|
876
|
+
const fromAppConfigPrimary = appConfig.ui?.colors?.primary;
|
|
877
|
+
const fromAppConfigNeutral = appConfig.ui?.colors?.neutral;
|
|
878
|
+
let primaryFromHost = false;
|
|
879
|
+
let neutralFromHost = false;
|
|
830
880
|
if (storedColor && UI_COLORS.includes(storedColor)) {
|
|
831
881
|
const h = COLOR_HUES[storedColor] ?? 0;
|
|
832
882
|
userColor.value = `hsl(${h}, 70%, 75%)`;
|
|
833
883
|
userColorName.value = storedColor;
|
|
884
|
+
primaryFromHost = true;
|
|
885
|
+
} else if (fromAppConfigPrimary && UI_COLORS.includes(fromAppConfigPrimary)) {
|
|
886
|
+
const h = COLOR_HUES[fromAppConfigPrimary] ?? 0;
|
|
887
|
+
userColor.value = `hsl(${h}, 70%, 75%)`;
|
|
888
|
+
userColorName.value = fromAppConfigPrimary;
|
|
889
|
+
primaryFromHost = true;
|
|
834
890
|
} else {
|
|
835
891
|
userColor.value = derived.hsl;
|
|
836
892
|
userColorName.value = derived.name;
|
|
837
893
|
}
|
|
838
894
|
if (storedNeutral && UI_NEUTRALS.includes(storedNeutral)) {
|
|
839
895
|
userNeutralColorName.value = storedNeutral;
|
|
896
|
+
neutralFromHost = true;
|
|
897
|
+
} else if (fromAppConfigNeutral && UI_NEUTRALS.includes(fromAppConfigNeutral)) {
|
|
898
|
+
userNeutralColorName.value = fromAppConfigNeutral;
|
|
899
|
+
neutralFromHost = true;
|
|
840
900
|
} else {
|
|
841
901
|
userNeutralColorName.value = derived.neutralName;
|
|
842
902
|
}
|
|
843
903
|
try {
|
|
844
|
-
const appConfig = useAppConfig();
|
|
845
904
|
if (appConfig.ui?.colors) {
|
|
846
|
-
appConfig.ui.colors.primary = toNuxtUIPrimary(userColorName.value);
|
|
847
|
-
appConfig.ui.colors.neutral = toNuxtUINeutral(userNeutralColorName.value);
|
|
905
|
+
appConfig.ui.colors.primary = primaryFromHost || hostHasCustomPalette(userColorName.value) ? userColorName.value : toNuxtUIPrimary(userColorName.value);
|
|
906
|
+
appConfig.ui.colors.neutral = neutralFromHost || hostHasCustomPalette(userNeutralColorName.value) ? userNeutralColorName.value : toNuxtUINeutral(userNeutralColorName.value);
|
|
848
907
|
}
|
|
849
908
|
} catch {
|
|
850
909
|
}
|
|
@@ -860,6 +919,24 @@ export default defineNuxtPlugin({
|
|
|
860
919
|
const _client = new AbracadabraClient({ url: serverUrl, persistAuth, storageKey: authStorageKey });
|
|
861
920
|
client.value = _client;
|
|
862
921
|
addLog(`Server: ${serverUrl}`, "connection");
|
|
922
|
+
const tm = new TokenManager({
|
|
923
|
+
client: _client,
|
|
924
|
+
storage: new LocalStorageDeviceSessionStorage(`${authStorageKey}:device-session`)
|
|
925
|
+
});
|
|
926
|
+
_tokenManager = tm;
|
|
927
|
+
tm.on("refresh", () => addLog("JWT auto-refreshed via device session", "auth"));
|
|
928
|
+
tm.on("session-expired", () => {
|
|
929
|
+
addLog("Device session rejected \u2014 please re-authenticate", "auth");
|
|
930
|
+
if (identityState.value === "claimed") identityState.value = "needsReauth";
|
|
931
|
+
});
|
|
932
|
+
if (tm.hasSession && !_client.isTokenValid()) {
|
|
933
|
+
try {
|
|
934
|
+
await tm.bootstrap();
|
|
935
|
+
addLog("Refreshed JWT via stored device session", "auth");
|
|
936
|
+
} catch (e) {
|
|
937
|
+
addLog(`Device session refresh failed: ${e instanceof Error ? e.message : String(e)}`, "auth");
|
|
938
|
+
}
|
|
939
|
+
}
|
|
863
940
|
let useExistingToken = _client.isTokenValid();
|
|
864
941
|
if (useExistingToken) {
|
|
865
942
|
try {
|
|
@@ -876,6 +953,7 @@ export default defineNuxtPlugin({
|
|
|
876
953
|
}
|
|
877
954
|
;
|
|
878
955
|
_client.token = null;
|
|
956
|
+
tm.clearSession();
|
|
879
957
|
useExistingToken = false;
|
|
880
958
|
}
|
|
881
959
|
}
|
|
@@ -913,6 +991,23 @@ export default defineNuxtPlugin({
|
|
|
913
991
|
}
|
|
914
992
|
_client.updateMe({ displayName: userName.value }).catch(() => {
|
|
915
993
|
});
|
|
994
|
+
if (_client.isTokenValid() && !tm.hasSession) {
|
|
995
|
+
try {
|
|
996
|
+
const sess = await _client.requestDeviceSession({
|
|
997
|
+
publicKey: pubKey,
|
|
998
|
+
deviceName: "Abracadabra Web (" + (navigator.platform || "Browser") + ")"
|
|
999
|
+
});
|
|
1000
|
+
await tm.attachSession({
|
|
1001
|
+
sessionId: sess.sessionId,
|
|
1002
|
+
sessionToken: sess.sessionToken,
|
|
1003
|
+
expiresAt: sess.expiresAt
|
|
1004
|
+
});
|
|
1005
|
+
addLog("Device session registered \u2014 JWT auto-refresh enabled", "auth");
|
|
1006
|
+
} catch (e) {
|
|
1007
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1008
|
+
addLog(`Device session unavailable: ${msg.slice(0, 80)}`, "auth");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
916
1011
|
const [info, spaceDocs] = await Promise.all([
|
|
917
1012
|
fetchServerInfo(serverUrl),
|
|
918
1013
|
_client.listSpaces().catch((e) => {
|
|
@@ -967,6 +1062,12 @@ export default defineNuxtPlugin({
|
|
|
967
1062
|
document: _doc,
|
|
968
1063
|
websocketProvider: wsp,
|
|
969
1064
|
client: _client,
|
|
1065
|
+
// Route every WS auth handshake (initial + reconnects) through
|
|
1066
|
+
// the TokenManager so an expired JWT is silently refreshed via
|
|
1067
|
+
// the device session before being sent. Without this, a tab
|
|
1068
|
+
// left open past the 4 h JWT TTL ends up reconnecting forever
|
|
1069
|
+
// with a dead token.
|
|
1070
|
+
token: () => tm.getValidToken(),
|
|
970
1071
|
onStatus({ status: s }) {
|
|
971
1072
|
if (s === status.value) return;
|
|
972
1073
|
const oldStatus = status.value;
|
|
@@ -1,34 +1,15 @@
|
|
|
1
|
-
import type { Extension } from '@tiptap/core';
|
|
2
|
-
import type { AbracadabraPlugin, AbracadabraPageType, AbracadabraToolbarItem, AbracadabraSuggestionItem, AbracadabraDragHandleItem, AbracadabraMentionProvider, AbracadabraAwarenessContribution, AbracadabraCommandItem, AbracadabraNodePanelSlot, AbracadabraSettingsPanel, AbracadabraKeyboardShortcut, EditorPluginCtx, DragHandlePluginCtx, CommandPaletteCtx } from './types.js';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* The module's plugin registry — a thin wrapper around `@abraca/plugin`'s
|
|
3
|
+
* generic `PluginRegistry`, typed with `AbracadabraPlugin` so consumers get
|
|
4
|
+
* fully-typed aggregator returns (toolbar item type, page type, command
|
|
5
|
+
* palette item type, etc.) without casts.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The class extension is intentionally empty — all behaviour lives in the
|
|
8
|
+
* shared base. We re-export the class under the historical name so existing
|
|
9
|
+
* imports (`import { PluginRegistry } from './plugin-registry'`) keep
|
|
10
|
+
* resolving.
|
|
9
11
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
register(plugin: AbracadabraPlugin): void;
|
|
14
|
-
/** Lock the registry. Called after all plugins (built-in + external) are loaded. */
|
|
15
|
-
freeze(): void;
|
|
16
|
-
isFrozen(): boolean;
|
|
17
|
-
getPlugins(): readonly AbracadabraPlugin[];
|
|
18
|
-
getAllExtensions(): Extension[];
|
|
19
|
-
/** Waits until all plugins with async extension bundles have finished loading. */
|
|
20
|
-
waitForExtensions(): Promise<void>;
|
|
21
|
-
getServerExtensions(): Extension[];
|
|
22
|
-
getAllPageTypes(): Record<string, AbracadabraPageType>;
|
|
23
|
-
getAllCustomHandlers(): Record<string, (...args: any[]) => any>;
|
|
24
|
-
getAllToolbarItems(ctx: EditorPluginCtx): AbracadabraToolbarItem[][];
|
|
25
|
-
getAllBubbleMenuItems(ctx: EditorPluginCtx): AbracadabraToolbarItem[][];
|
|
26
|
-
getAllSuggestionItems(ctx: EditorPluginCtx): AbracadabraSuggestionItem[][];
|
|
27
|
-
getAllDragHandleItems(ctx: DragHandlePluginCtx): AbracadabraDragHandleItem[][];
|
|
28
|
-
getAllMentionProviders(): AbracadabraMentionProvider[];
|
|
29
|
-
getAllAwarenessContributions(): AbracadabraAwarenessContribution[];
|
|
30
|
-
getAllCommandPaletteItems(ctx: CommandPaletteCtx): Promise<AbracadabraCommandItem[]>;
|
|
31
|
-
getAllNodePanelSlots(): AbracadabraNodePanelSlot[];
|
|
32
|
-
getSettingsPanels(): AbracadabraSettingsPanel[];
|
|
33
|
-
getAllKeyboardShortcuts(): AbracadabraKeyboardShortcut[];
|
|
12
|
+
import { PluginRegistry as BasePluginRegistry } from '@abraca/plugin';
|
|
13
|
+
import type { AbracadabraPlugin } from './types.js';
|
|
14
|
+
export declare class PluginRegistry extends BasePluginRegistry<AbracadabraPlugin> {
|
|
34
15
|
}
|
|
@@ -1,83 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
_frozen = false;
|
|
4
|
-
register(plugin) {
|
|
5
|
-
if (this._frozen) {
|
|
6
|
-
console.warn(`[abracadabra] Plugin registry frozen \u2014 cannot register "${plugin.name}"`);
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
if (this._plugins.some((p) => p.name === plugin.name)) {
|
|
10
|
-
console.warn(`[abracadabra] Plugin "${plugin.name}" already registered`);
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
this._plugins.push(plugin);
|
|
14
|
-
}
|
|
15
|
-
/** Lock the registry. Called after all plugins (built-in + external) are loaded. */
|
|
16
|
-
freeze() {
|
|
17
|
-
this._frozen = true;
|
|
18
|
-
}
|
|
19
|
-
isFrozen() {
|
|
20
|
-
return this._frozen;
|
|
21
|
-
}
|
|
22
|
-
getPlugins() {
|
|
23
|
-
return this._plugins;
|
|
24
|
-
}
|
|
25
|
-
// ── Aggregated getters ───────────────────────────────────────────────────────
|
|
26
|
-
getAllExtensions() {
|
|
27
|
-
return this._plugins.flatMap((p) => p.extensions?.() ?? []);
|
|
28
|
-
}
|
|
29
|
-
/** Waits until all plugins with async extension bundles have finished loading. */
|
|
30
|
-
async waitForExtensions() {
|
|
31
|
-
await Promise.all(this._plugins.map((p) => p.extensionsReady ?? Promise.resolve()));
|
|
32
|
-
}
|
|
33
|
-
getServerExtensions() {
|
|
34
|
-
return this._plugins.flatMap((p) => p.serverExtensions?.() ?? []);
|
|
35
|
-
}
|
|
36
|
-
getAllPageTypes() {
|
|
37
|
-
const merged = {};
|
|
38
|
-
for (const p of this._plugins) {
|
|
39
|
-
if (p.pageTypes) Object.assign(merged, p.pageTypes);
|
|
40
|
-
}
|
|
41
|
-
return merged;
|
|
42
|
-
}
|
|
43
|
-
getAllCustomHandlers() {
|
|
44
|
-
const merged = {};
|
|
45
|
-
for (const p of this._plugins) {
|
|
46
|
-
if (p.customHandlers) Object.assign(merged, p.customHandlers());
|
|
47
|
-
}
|
|
48
|
-
return merged;
|
|
49
|
-
}
|
|
50
|
-
getAllToolbarItems(ctx) {
|
|
51
|
-
return this._plugins.flatMap((p) => p.toolbarItems?.(ctx) ?? []);
|
|
52
|
-
}
|
|
53
|
-
getAllBubbleMenuItems(ctx) {
|
|
54
|
-
return this._plugins.flatMap((p) => p.bubbleMenuItems?.(ctx) ?? []);
|
|
55
|
-
}
|
|
56
|
-
getAllSuggestionItems(ctx) {
|
|
57
|
-
return this._plugins.flatMap((p) => p.suggestionItems?.(ctx) ?? []);
|
|
58
|
-
}
|
|
59
|
-
getAllDragHandleItems(ctx) {
|
|
60
|
-
return this._plugins.flatMap((p) => p.dragHandleItems?.(ctx) ?? []);
|
|
61
|
-
}
|
|
62
|
-
getAllMentionProviders() {
|
|
63
|
-
return this._plugins.flatMap((p) => p.mentionProviders ?? []);
|
|
64
|
-
}
|
|
65
|
-
getAllAwarenessContributions() {
|
|
66
|
-
return this._plugins.flatMap((p) => p.awarenessContributions ?? []);
|
|
67
|
-
}
|
|
68
|
-
async getAllCommandPaletteItems(ctx) {
|
|
69
|
-
const results = await Promise.all(
|
|
70
|
-
this._plugins.filter((p) => p.commandPaletteItems).map((p) => Promise.resolve(p.commandPaletteItems(ctx)))
|
|
71
|
-
);
|
|
72
|
-
return results.flat().filter((item) => !item.when || item.when(ctx));
|
|
73
|
-
}
|
|
74
|
-
getAllNodePanelSlots() {
|
|
75
|
-
return this._plugins.flatMap((p) => p.nodePanelSlots ?? []);
|
|
76
|
-
}
|
|
77
|
-
getSettingsPanels() {
|
|
78
|
-
return this._plugins.flatMap((p) => p.settingsPanel ? [p.settingsPanel] : []);
|
|
79
|
-
}
|
|
80
|
-
getAllKeyboardShortcuts() {
|
|
81
|
-
return this._plugins.flatMap((p) => p.keyboardShortcuts ?? []);
|
|
82
|
-
}
|
|
1
|
+
import { PluginRegistry as BasePluginRegistry } from "@abraca/plugin";
|
|
2
|
+
export class PluginRegistry extends BasePluginRegistry {
|
|
83
3
|
}
|
|
@@ -16,7 +16,9 @@ const OPTIONAL_BUILTIN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
16
16
|
"mathBlock",
|
|
17
17
|
"mathInline",
|
|
18
18
|
"diff",
|
|
19
|
-
"svgEmbed"
|
|
19
|
+
"svgEmbed",
|
|
20
|
+
"timeline",
|
|
21
|
+
"timelineItem"
|
|
20
22
|
]);
|
|
21
23
|
async function loadClientExtensions() {
|
|
22
24
|
const [
|
|
@@ -73,7 +75,8 @@ async function loadClientExtensions() {
|
|
|
73
75
|
{ ColorSwatch },
|
|
74
76
|
{ MathBlock, MathInline },
|
|
75
77
|
{ Diff },
|
|
76
|
-
{ SvgEmbed }
|
|
78
|
+
{ SvgEmbed },
|
|
79
|
+
{ Timeline, TimelineItem }
|
|
77
80
|
] = await Promise.all([
|
|
78
81
|
import("@tiptap/extension-task-list"),
|
|
79
82
|
import("@tiptap/extension-task-item"),
|
|
@@ -128,7 +131,8 @@ async function loadClientExtensions() {
|
|
|
128
131
|
import("../extensions/color-swatch.js"),
|
|
129
132
|
import("../extensions/math.js"),
|
|
130
133
|
import("../extensions/diff.js"),
|
|
131
|
-
import("../extensions/svg-embed.js")
|
|
134
|
+
import("../extensions/svg-embed.js"),
|
|
135
|
+
import("../extensions/timeline.js")
|
|
132
136
|
]);
|
|
133
137
|
const lowlight = createLowlight(common);
|
|
134
138
|
const extensions = [
|
|
@@ -202,7 +206,9 @@ async function loadClientExtensions() {
|
|
|
202
206
|
MathBlock,
|
|
203
207
|
MathInline,
|
|
204
208
|
Diff,
|
|
205
|
-
SvgEmbed
|
|
209
|
+
SvgEmbed,
|
|
210
|
+
Timeline,
|
|
211
|
+
TimelineItem
|
|
206
212
|
];
|
|
207
213
|
try {
|
|
208
214
|
const emojiPkg = "@tiptap/extension-emoji";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<Omit<import("h3").EventHandlerRequest, "body">, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<Omit<import("h3").EventHandlerRequest, "body">, Promise<unknown[] | never[]>>;
|
|
2
2
|
export default _default;
|
|
@@ -4,6 +4,7 @@ import { useStorage } from "nitropack/runtime/storage";
|
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { registerServerPlugin, bootRunners, shutdownAllRunners } from "../utils/serverRunner.js";
|
|
7
|
+
import { buildServerSchemaValidator } from "../utils/schemaServerSupport.js";
|
|
7
8
|
import { createDocCacheAPI } from "../utils/docCache.js";
|
|
8
9
|
import { docTreeCacheRunner } from "../runners/doc-tree-cache.js";
|
|
9
10
|
import { initSlugMap, loadPersistedSlugMap } from "../utils/slugMap.js";
|
|
@@ -212,7 +213,34 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
212
213
|
};
|
|
213
214
|
registerServerPlugin(coreServerPlugin);
|
|
214
215
|
}
|
|
216
|
+
const attachedSchemas = [];
|
|
217
|
+
const schemaCfg = publicConfig?.schema ?? {};
|
|
218
|
+
if (schemaCfg.bundleDir) {
|
|
219
|
+
console.log(
|
|
220
|
+
`[abracadabra-service] schema bundle_dir advertised: ${schemaCfg.bundleDir} (server-side bundle_dir = same path; informational only \u2014 server is authoritative)`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
Object.assign(ctx, {
|
|
224
|
+
attachSchema: (registry) => {
|
|
225
|
+
if (!registry || typeof registry !== "object") return;
|
|
226
|
+
if (typeof registry.validateMeta !== "function") {
|
|
227
|
+
console.warn("[abracadabra-service] attachSchema received a value missing validateMeta() \u2014 skipping");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
attachedSchemas.push(registry);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
215
233
|
await nitroApp.hooks.callHook("abracadabra:before-runners", ctx);
|
|
234
|
+
delete ctx.attachSchema;
|
|
235
|
+
if (attachedSchemas.length > 0) {
|
|
236
|
+
const validator = buildServerSchemaValidator(attachedSchemas);
|
|
237
|
+
if (validator) ctx.validateMeta = validator;
|
|
238
|
+
const advertisedTypes = /* @__PURE__ */ new Set();
|
|
239
|
+
for (const r of attachedSchemas) for (const t of r.types.keys()) advertisedTypes.add(t);
|
|
240
|
+
console.log(
|
|
241
|
+
`[abracadabra-service] ${attachedSchemas.length} schema registr${attachedSchemas.length === 1 ? "y" : "ies"} attached covering types: [${[...advertisedTypes].sort().join(", ")}]`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
216
244
|
await bootRunners(ctx);
|
|
217
245
|
console.log("[abracadabra-service] All runners started");
|
|
218
246
|
nitroApp.hooks.hook("close", async () => {
|