Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/lib/EnvelopeEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { envelopeDocumentJSON } from '$lib/helpers/envelope'
import EditorCode from './editor/code/EditorCode.svelte'
import EditorForm from './editor/form/EditorForm.svelte'
import { isEnvelope, setApiBaseUrl, DEFAULT_API_BASE_URL } from '$lib/gobl/client'
import { isEnvelope, setApiBaseUrl } from '$lib/gobl/client'
import { problemSeverityMap } from './editor/EditorProblem.js'
import * as actions from './editor/actions'
import type { BuildOptions, DocumentHeader, State } from './types/editor'
Expand All @@ -19,7 +19,7 @@

let {
jsonSchemaURL = '',
apiBaseUrl = DEFAULT_API_BASE_URL,
apiBaseUrl = '',
data = $bindable(''),
state: initialState = $bindable('init'),
problems = $bindable([]),
Expand All @@ -41,7 +41,9 @@
}: EnvelopeEditorProps = $props()

// Configure the GOBL API endpoint before any operation runs. The initial
// value is applied eagerly during init; the effect keeps it in sync.
// value is applied eagerly during init; the effect keeps it in sync. An
// empty prop is a no-op so an editor without an explicit endpoint inherits
// the currently-configured one instead of resetting it to the default.
// svelte-ignore state_referenced_locally
setApiBaseUrl(apiBaseUrl)
$effect(() => {
Expand Down
8 changes: 5 additions & 3 deletions src/lib/ObjectEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
import DynamicForm from '$lib/editor/form/DynamicForm.svelte'
import { getUIModel } from '$lib/editor/form/utils/model'
import type { SchemaValue } from '$lib/editor/form/utils/schema'
import { setApiBaseUrl, DEFAULT_API_BASE_URL } from '$lib/gobl/client'
import { setApiBaseUrl } from '$lib/gobl/client'
import { createBuilderContext } from './store/builder'
import type { ObjectEditorProps } from './types/editor'

let {
jsonSchemaURL = '',
apiBaseUrl = DEFAULT_API_BASE_URL,
apiBaseUrl = '',
data = undefined,
id = `editor-${Math.random().toString(36).slice(2, 7)}`,
readOnly = false,
model = $bindable(undefined)
}: ObjectEditorProps = $props()

// Configure the GOBL API endpoint before any schema is fetched. The initial
// value is applied eagerly during init; the effect keeps it in sync.
// value is applied eagerly during init; the effect keeps it in sync. An
// empty prop is a no-op so nested editors (e.g. the correct/headers modals)
// inherit the embedder's endpoint instead of resetting it to the default.
// svelte-ignore state_referenced_locally
setApiBaseUrl(apiBaseUrl)
$effect(() => {
Expand Down
38 changes: 27 additions & 11 deletions src/lib/editor/form/utils/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,34 @@ function getRelativeSchema(parentSchema: Schema, id: string, del = '/'): Schema
return path(parentSchema, id, del)
}

async function fetchExternalSchema(id: string): Promise<Schema> {
let schema = SchemaRegistry[id]
if (schema) return schema
// In-flight schema requests, keyed by URL, so that concurrent consumers
// (the form parser branches and the Monaco preload) share a single fetch
// instead of each missing the registry cache and requesting the same
// schema again.
const pendingSchemas: Record<string, Promise<Schema>> = {}
Comment thread
samlown marked this conversation as resolved.

function fetchExternalSchemaStrict(id: string): Promise<Schema> {
const schema = SchemaRegistry[id]
if (schema) return Promise.resolve(schema)

let req = pendingSchemas[id]
if (!req) {
req = fetchJsonSchema(id)
.then((fetched) => {
SchemaRegistry[id] = fetched
return fetched
})
.finally(() => {
delete pendingSchemas[id]
})
pendingSchemas[id] = req
}
return req
}

async function fetchExternalSchema(id: string): Promise<Schema> {
try {
schema = await fetchJsonSchema(id)

SchemaRegistry[id] = schema

return schema
return await fetchExternalSchemaStrict(id)
} catch (error) {
return EMPTY_SCHEMA
}
Expand Down Expand Up @@ -72,9 +90,7 @@ async function fetchSchema(id: string): Promise<Schema> {
// degrade to an empty placeholder instead.
export async function loadSchemaSet(url: string): Promise<Array<{ uri: string; schema: Schema }>> {
const rootId = url.split('#')[0]
if (!SchemaRegistry[rootId]) {
SchemaRegistry[rootId] = await fetchJsonSchema(rootId)
}
await fetchExternalSchemaStrict(rootId)

const found = new Map<string, Schema>()
const queue = [rootId]
Expand Down
8 changes: 5 additions & 3 deletions src/lib/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,11 @@ export interface EnvelopeEditorProps {
// Used for JSON Schema validation within Monaco Editor. When set, this should be the JSON Schema URL of a GOBL document, e.g. an invoice. Not an envelope.
jsonSchemaURL?: string
// Base URL of the GOBL API used for build, sign, validate, correct,
// replicate, keygen and schema operations. Defaults to the public service at
// `https://gobl.dev/v0`. Embedders may point this at a same-origin path
// (e.g. `/api/gobl`) that proxies the GOBL API and adds authentication.
// replicate, keygen and schema operations. Embedders may point this at a
// same-origin path (e.g. `/api/gobl`) that proxies the GOBL API and adds
// authentication. When left unset the editor keeps whatever endpoint is
// already configured (the public `https://gobl.dev/v0` service initially),
// so nested editors never reset an embedder's choice.
apiBaseUrl?: string
// Data is used for setting editor contents. Note: there is "one way" binding;
// e.g. you can set data but changes are not bound to the parent. Use the
Expand Down
Loading