diff --git a/shared/codeEditor/codeEditor.module.scss b/shared/codeEditor/codeEditor.module.scss index 4165dd3c..697ed062 100644 --- a/shared/codeEditor/codeEditor.module.scss +++ b/shared/codeEditor/codeEditor.module.scss @@ -4,6 +4,10 @@ .codeEditor { display: contents; + :global(.cm-content) { + user-select: text; + } + &.terminalCursor { :global { .cm-cursorLayer { diff --git a/shared/inspector/buildScalar.tsx b/shared/inspector/buildScalar.tsx index 457897a7..f69ce213 100644 --- a/shared/inspector/buildScalar.tsx +++ b/shared/inspector/buildScalar.tsx @@ -70,7 +70,7 @@ export function renderValue( showTypeTag: boolean = true, overrideStyles: {[key: string]: string} = {}, implicitLength?: number, - noMultiline: boolean = false + singleLineLimit?: boolean | number ): {body: JSX.Element; height?: number} { if (value == null) { return {body: {"{}"}}; @@ -155,9 +155,14 @@ export function renderValue( // @ts-ignore - Intentional fallthrough case "std::json": - value = noMultiline ? value : prettyPrintJSON(value); + value = singleLineLimit ? value : prettyPrintJSON(value); case "std::str": { - const str = noMultiline ? toSingleLineStr(value) : strToString(value); + const str = singleLineLimit + ? toSingleLineStr( + value, + singleLineLimit === true ? undefined : singleLineLimit + ) + : strToString(value); return { body: ( @@ -165,7 +170,7 @@ export function renderValue( {implicitLength && value.length === implicitLength ? "…" : ""} ), - height: noMultiline ? 1 : (str as string).split("\n").length, + height: singleLineLimit ? 1 : (str as string).split("\n").length, }; } } diff --git a/shared/studio/components/dataEditor/dataEditor.module.scss b/shared/studio/components/dataEditor/dataEditor.module.scss index 23b62495..28c7ca17 100644 --- a/shared/studio/components/dataEditor/dataEditor.module.scss +++ b/shared/studio/components/dataEditor/dataEditor.module.scss @@ -19,6 +19,12 @@ background: #262626; } } + + & > .panel { + max-height: calc(100vh - 230px); + overflow: auto; + @include hideScrollbar; + } } .actions { @@ -155,8 +161,13 @@ font-size: 14px; line-height: 22px; min-height: 32px; + max-height: 300px; white-space: pre; @include hideScrollbar; + + .dataEditor & { + resize: vertical; + } } @include darkTheme { @@ -202,6 +213,10 @@ border-radius: 4px 0 0 4px; } } + + .dataEditor > & textarea { + max-height: calc(100vh - 200px); + } } .errMessage { diff --git a/shared/studio/components/dataEditor/editor.tsx b/shared/studio/components/dataEditor/editor.tsx index 4739d8c8..24b331d7 100644 --- a/shared/studio/components/dataEditor/editor.tsx +++ b/shared/studio/components/dataEditor/editor.tsx @@ -1,5 +1,5 @@ import {useEffect, useRef} from "react"; -import {action, makeObservable, observable} from "mobx"; +import {action, makeObservable, observable, runInAction} from "mobx"; import cn from "@edgedb/common/utils/classNames"; @@ -33,40 +33,54 @@ export class DataEditorState { ) { makeObservable(this); - if (isEditorValue) { - this.value = value; + const _init = (_value: any) => + runInAction(() => { + if (isEditorValue) { + this.value = _value; + } else { + this.value = + _value != null + ? isMulti + ? _value.map((val: any) => valueToEditorValue(val, type)) + : valueToEditorValue(_value, type) + : isRequired + ? isMulti + ? [] + : newPrimitiveValue(type)[0] + : null; + } + + this.hasError = + this.value === null + ? false + : isMulti + ? (this.value as EditorValue[]).some( + (v: any) => !isEditorValueValid(v, type) + ) + : !isEditorValueValid(this.value, type); + + this.loaded = true; + }); + + if (value instanceof Promise) { + value.then((_value) => _init(_value)); } else { - this.value = - value != null - ? isMulti - ? value.map((val: any) => valueToEditorValue(val, type)) - : valueToEditorValue(value, type) - : isRequired - ? isMulti - ? [] - : newPrimitiveValue(type)[0] - : null; + _init(value); } - - this.hasError = - this.value === null - ? false - : isMulti - ? (this.value as EditorValue[]).some( - (v: any) => !isEditorValueValid(v, type) - ) - : !isEditorValueValid(this.value, type); } + @observable + loaded = false; + isEdited = false; - @observable.ref value: EditorValue | null; + @observable.ref value: EditorValue | null = null; @action setValue(val: EditorValue | null) { this.value = val; this.isEdited = true; } - @observable hasError: boolean; + @observable hasError: boolean = false; @action setError(err: boolean) { this.hasError = err; } diff --git a/shared/studio/components/sessionState/index.tsx b/shared/studio/components/sessionState/index.tsx index 8866ab24..87da3668 100644 --- a/shared/studio/components/sessionState/index.tsx +++ b/shared/studio/components/sessionState/index.tsx @@ -487,7 +487,14 @@ function ListItem({ ) : null} -
+
{active ? ( <> (type ? `[is ${escapeName(type, true)}].` : "") + - escapeName(name, false) + (name.startsWith("@") + ? `@${escapeName(name.slice(1), false)}` + : escapeName(name, false)) ), ...[...query.links].map( ([name, subQuery]) => diff --git a/shared/studio/state/connection.ts b/shared/studio/state/connection.ts index 12058c3e..c0db3377 100644 --- a/shared/studio/state/connection.ts +++ b/shared/studio/state/connection.ts @@ -63,6 +63,7 @@ type QueryOpts = { newCodec?: boolean; ignoreSessionConfig?: boolean; implicitLimit?: bigint; + ignoreForceDatabaseError?: boolean; }; type PendingQuery = { @@ -214,6 +215,9 @@ export class Connection extends Model({ if (opts.ignoreSessionConfig) { state = Session.defaults().withGlobals(state.globals); } + if (opts.ignoreForceDatabaseError) { + state = state.withConfig({force_database_error: "false"}); + } if (kind === "execute") { await this.conn.rawExecute( diff --git a/shared/studio/state/database.ts b/shared/studio/state/database.ts index f1622d50..983579b8 100644 --- a/shared/studio/state/database.ts +++ b/shared/studio/state/database.ts @@ -164,7 +164,11 @@ export class DatabaseState extends Model({ } async updateObjectCount() { - const {result} = await this.connection.query(`select count(std::Object)`); + const {result} = await this.connection.query( + `select count(std::Object)`, + undefined, + {ignoreSessionConfig: true, ignoreForceDatabaseError: true} + ); if (result) { runInAction(() => { this.objectCount = Number(result[0]); @@ -195,7 +199,9 @@ export class DatabaseState extends Model({ } FILTER NOT EXISTS .children).id ), version := sys::get_version(), - }` + }`, + undefined, + {ignoreSessionConfig: true, ignoreForceDatabaseError: true} ) .then(({result}) => ({ migrationId: (result![0].migrationId[0] ?? null) as @@ -235,7 +241,10 @@ export class DatabaseState extends Model({ try { rawData = yield* _await( conn - .query(getIntrospectionQuery(edgedbVersion)) + .query(getIntrospectionQuery(edgedbVersion), undefined, { + ignoreSessionConfig: true, + ignoreForceDatabaseError: true, + }) .then(({result}) => { return result![0] as RawIntrospectionResult; }) diff --git a/shared/studio/state/sessionState.ts b/shared/studio/state/sessionState.ts index 7929cd82..ff39c984 100644 --- a/shared/studio/state/sessionState.ts +++ b/shared/studio/state/sessionState.ts @@ -18,23 +18,28 @@ import { newPrimitiveValue, parseEditorValue, PrimitiveType, + valueToEditorValue, } from "../components/dataEditor/utils"; import {fetchSessionState, storeSessionState} from "../idbStore"; import {connCtx} from "./connection"; import {dbCtx} from "./database"; import {instanceCtx} from "./instance"; -type DraftStateItem = { +interface DraftStateItem { active: boolean; type: Frozen; description?: string; - value: Frozen; + value: Frozen; error: boolean; -}; +} + +interface DraftStateGlobalItem extends Omit { + value: Frozen; +} type DraftState = { globals: { - [key: string]: DraftStateItem; + [key: string]: DraftStateGlobalItem; }; config: { [key: string]: DraftStateItem; @@ -186,6 +191,7 @@ export class SessionState extends Model({ .filter( (prop) => prop.name !== "id" && + prop.cardinality !== "Many" && !prop.annotations.some( (anno) => (anno.name === "cfg::system" || anno.name === "cfg::internal") && @@ -221,7 +227,8 @@ export class SessionState extends Model({ } for (const configName of this.configNames) { - const type = configType.properties[configName].target!; + const configSchema = configType.properties[configName]; + const type = configSchema.target!; const storedItem = sessionStateData?.config[configName]; const newVal = storedItem?.value == null @@ -229,7 +236,7 @@ export class SessionState extends Model({ : null; draftState.config[configName] = { type: frozen(type, FrozenCheckMode.Off), - description: configType.properties[configName].annotations.find( + description: configSchema.annotations.find( (anno) => anno.name === "std::description" )?.["@value"], active: storedItem?.active ?? false, @@ -337,18 +344,25 @@ export class SessionState extends Model({ const result = await conn.query( `select cfg::Config {${this.configNames.join(", ")}}`, undefined, - {ignoreSessionConfig: true} + {ignoreSessionConfig: true, ignoreForceDatabaseError: true} ); if (result.result) { const values = result.result![0]; runInAction(() => (this.configValues = values)); for (const configName of this.configNames) { - if (this.draftState?.config[configName].value === null) { + const config = this.draftState?.config[configName]; + if (config?.active === false) { + let value = values[configName]; + if (typeof value === "boolean") { + value = !value; + } objectActions.set( - this.draftState.config[configName], + this.draftState!.config[configName], "value", - values[configName].toString() + frozen( + valueToEditorValue(value, config.type.data as PrimitiveType) + ) ); } } diff --git a/shared/studio/tabs/dataview/dataInspector.tsx b/shared/studio/tabs/dataview/dataInspector.tsx index 3b9e3f12..11a9ce36 100644 --- a/shared/studio/tabs/dataview/dataInspector.tsx +++ b/shared/studio/tabs/dataview/dataInspector.tsx @@ -391,12 +391,8 @@ const GridCell = observer(function GridCell({ field.type === ObjectFieldType.property && edits.activePropertyEdit?.cellId === cellId ) { - return cellEditState?.value === undefined && - field.schemaType.name === "std::str" && - typeof value === "string" && - value.length === 100 && - !state.fullyFetchedData.has(cellId) ? ( - + return !edits.activePropertyEdit.loaded ? ( +
loading...
) : ( ); @@ -532,14 +528,15 @@ const GridCell = observer(function GridCell({ [styles.hasEdits]: !isDeletedRow && rowData && (!!cellEditState || !!linkEditState), [styles.hasErrors]: - (cellEditState && !cellEditState.value.valid) || - (insertedRow && _value && !_value.valid) || - (!rowData && - isEditable && - field.required && - !field.hasDefault && - value === null && - !linkEditState), + isEditable && + ((cellEditState && !cellEditState.value.valid) || + (insertedRow && _value && !_value.valid) || + (!rowData && + isEditable && + field.required && + !field.hasDefault && + value === null && + !linkEditState)), })} onClick={() => { if (field.type === ObjectFieldType.link && content !== null) { @@ -548,18 +545,16 @@ const GridCell = observer(function GridCell({ navigate, data.id, data.__tname__, - field + field, + typeof data.id === "number" ); } }} onDoubleClick={() => { if (isEditable) { if (field.type === ObjectFieldType.property) { - edits.startEditingCell( - data.id, - data.__tname__, - field, - state.fullyFetchedData.get(cellId) ?? value + edits.startEditingCell(data.id, data.__tname__, field, () => + state.fetchFullCellData(data.id, value, field) ); } else { state.openNestedView( @@ -579,22 +574,6 @@ const GridCell = observer(function GridCell({ ); }); -function FetchingDataPlaceholder({ - state, - data, - field, -}: { - state: DataInspectorState; - data: any; - field: ObjectField; -}) { - useEffect(() => { - state.fetchFullCellData(data.id, field); - }, []); - - return
loading...
; -} - const FieldHeaders = observer(function FieldHeaders() { const {state} = useDataInspectorState(); const isMobile = useIsMobile(); diff --git a/shared/studio/tabs/dataview/index.tsx b/shared/studio/tabs/dataview/index.tsx index 20e53d7f..afe376a2 100644 --- a/shared/studio/tabs/dataview/index.tsx +++ b/shared/studio/tabs/dataview/index.tsx @@ -181,7 +181,10 @@ const DataInspectorView = observer(function DataInspectorView({ {!isMobile && (
{inspectorState.rowCount !== null ? ( - <>{inspectorState.rowCount} Items + <> + {inspectorState.rowCount} Item + {inspectorState.rowCount === 1 ? "" : "s"} + ) : ( loading... )} @@ -225,7 +228,8 @@ const DataInspectorView = observer(function DataInspectorView({ items={null} actions={inspectorState.insertTypeNames.map((name) => ({ label: name, - action: () => dataviewState.edits.createNewRow(name), + action: () => + dataviewState.edits.createNewRow(name, inspectorState), }))} rightAlign /> @@ -234,7 +238,8 @@ const DataInspectorView = observer(function DataInspectorView({ className={styles.headerButton} onClick={() => dataviewState.edits.createNewRow( - inspectorState.insertTypeNames[0] + inspectorState.insertTypeNames[0], + inspectorState ) } > diff --git a/shared/studio/tabs/dataview/reviewEditsModal.tsx b/shared/studio/tabs/dataview/reviewEditsModal.tsx index 6a3e014e..ecdf4be2 100644 --- a/shared/studio/tabs/dataview/reviewEditsModal.tsx +++ b/shared/studio/tabs/dataview/reviewEditsModal.tsx @@ -191,7 +191,10 @@ function renderParam({ type.schemaType === "Range" || type.schemaType === "Multirange" ? type.elementType.name : undefined, - false + false, + undefined, + undefined, + 50 ).body; } } diff --git a/shared/studio/tabs/dataview/state/edits.ts b/shared/studio/tabs/dataview/state/edits.ts index a97b466e..c47a06a3 100644 --- a/shared/studio/tabs/dataview/state/edits.ts +++ b/shared/studio/tabs/dataview/state/edits.ts @@ -14,7 +14,7 @@ import { import {PrimitiveType} from "../../../components/dataEditor"; import {connCtx, dbCtx} from "../../../state"; -import {ObjectPropertyField} from "."; +import {ObjectPropertyField, DataInspector} from "."; enum EditKind { UpdateProperty, @@ -106,7 +106,7 @@ export class DataEditingManager extends Model({}) { objectId: string | number, objectTypeName: string, field: ObjectPropertyField, - value: any + valueGetter: () => any ) { const cellId = `${objectId}__${field.name}`; @@ -117,7 +117,7 @@ export class DataEditingManager extends Model({}) { field.schemaType as PrimitiveType, field.required, field.multi, - editValue?.value ?? value, + editValue ? editValue.value : valueGetter(), editValue != null && !editValue.valid, (discard) => { if (!discard && state.isEdited) { @@ -179,7 +179,7 @@ export class DataEditingManager extends Model({}) { } @action - createNewRow(objectTypeName: string) { + createNewRow(objectTypeName: string, dataInspState: DataInspector) { const insertId = insertEditId++; this.insertEdits.set(insertId, { kind: EditKind.InsertObject, @@ -191,6 +191,18 @@ export class DataEditingManager extends Model({}) { __tname__: objectTypeName, }, }); + + const parentObject = dataInspState.parentObject; + if (parentObject?.editMode) { + this.toggleLinkInsert( + parentObject.id, + parentObject.subtypeName ?? parentObject.objectTypeName, + parentObject.fieldName, + dataInspState.objectType!, + this.insertEdits.get(insertId)!, + !parentObject.isMultiLink + ); + } } @action diff --git a/shared/studio/tabs/dataview/state/index.ts b/shared/studio/tabs/dataview/state/index.ts index adcdcef6..f8377e61 100644 --- a/shared/studio/tabs/dataview/state/index.ts +++ b/shared/studio/tabs/dataview/state/index.ts @@ -496,7 +496,9 @@ export class DataInspector extends Model({ }); this.allFields = { - fields: [...baseFields, ...subtypeFields], + fields: [...baseFields, ...subtypeFields].sort((a, b) => + a.name === "id" ? -1 : b.name === "id" ? 1 : 0 + ), subtypeIndex: baseFields.length, }; this.fieldWidths = Array(this.allFields.fields.length).fill(180); @@ -788,16 +790,44 @@ export class DataInspector extends Model({ @observable.shallow fullyFetchedData = new Map(); - async fetchFullCellData(dataId: string, field: ObjectField) { + fetchFullCellData( + dataId: string | number, + value: any, + field: ObjectPropertyField + ) { + if (typeof dataId !== "string") { + return value; + } + + const cellId = `${dataId}__${field.name}`; + + if (this.fullyFetchedData.has(cellId)) { + return this.fullyFetchedData.get(cellId); + } + + const isTruncated = + field.schemaType.name === "std::str" && + (field.multi + ? Array.isArray(value) && + typeof value[0] === "string" && + value.some((val) => val.length === 100) + : typeof value === "string" && value.length === 100); + + if (!isTruncated) { + return value; + } + const query = `select (select ${ field.escapedSubtypeName ?? this._getObjectTypeQuery } filter .id = $id).${field.escapedName}`; - const conn = connCtx.get(this)!; - const val = (await conn.query(query, {id: dataId})).result![0]; - - runInAction(() => { - this.fullyFetchedData.set(`${dataId}__${field.name}`, val); + const conn = connCtx.get(this)!; + return conn.query(query, {id: dataId}).then(({result}) => { + const val = field.multi ? result! : result![0]; + runInAction(() => { + this.fullyFetchedData.set(cellId, val); + }); + return val; }); } @@ -1143,7 +1173,8 @@ export class DataInspector extends Model({ .replace(/#.*/g, "") .trimStart() .replace(/^filter\s/i, "") - .trimEnd(), + .trimEnd() + .replace(/;+$/, ""), filter, ] as [string, string]; } diff --git a/shared/studio/tabs/repl/index.tsx b/shared/studio/tabs/repl/index.tsx index fd2bd816..5c9abc10 100644 --- a/shared/studio/tabs/repl/index.tsx +++ b/shared/studio/tabs/repl/index.tsx @@ -325,14 +325,14 @@ const ReplInput = observer(function ReplInput() { { key: "Mod-ArrowUp", run: () => { - replState.navigateHistory(-1); + replState.navigateHistory(1); return true; }, }, { key: "Mod-ArrowDown", run: () => { - replState.navigateHistory(1); + replState.navigateHistory(-1); return true; }, }, diff --git a/shared/studio/tabs/repl/state/index.ts b/shared/studio/tabs/repl/state/index.ts index 9b122ef8..bfe9f4e6 100644 --- a/shared/studio/tabs/repl/state/index.ts +++ b/shared/studio/tabs/repl/state/index.ts @@ -89,7 +89,7 @@ export class ReplHistoryItem extends Model({ } @modelAction - setResult(status: string, hasResult: boolean, implicitLimit: number) { + setResult(status: string, hasResult: boolean, implicitLimit: number | null) { this.hasResult = hasResult; this.status = status; this.implicitLimit = implicitLimit; @@ -202,28 +202,52 @@ export class Repl extends Model({ initialScrollPos = 0; + // -1 is draft, then counts how many history items back from most recent historyCursor = -1; draftQuery = Text.empty; + dedupedQueryHistory: ReplHistoryItem[] = []; + + _addDedupedHistoryQueries(history: ReplHistoryItem[]) { + let lastQuery = + this.dedupedQueryHistory[this.dedupedQueryHistory.length - 1]?.query; + for (let i = history.length - 1; i >= 0; i--) { + const item = history[i]; + if (item.query !== lastQuery) { + this.dedupedQueryHistory.push(item); + lastQuery = item.query; + } + } + } + @action navigateHistory(direction: 1 | -1) { - let cursor = - (this.historyCursor === -1 - ? this.queryHistory.length - : this.historyCursor) + direction; + // 1 => backwards, -1 => forwards + if (this._fetchingHistory) { + return; + } + let cursor = this.historyCursor + direction; - if (cursor === this.queryHistory.length) { + if ( + cursor < -1 || + (cursor >= this.dedupedQueryHistory.length && !this._hasUnfetchedHistory) + ) { + return; + } + if (cursor === -1) { this.currentQuery = this.draftQuery; this.historyCursor = -1; } else { - const historyItem = this.queryHistory[cursor]; - if (historyItem) { - if (this.historyCursor === -1) { - this.draftQuery = this.currentQuery; - } - this.currentQuery = Text.of(historyItem.query.split("\n")); - this.historyCursor = cursor; + if (cursor >= this.dedupedQueryHistory.length) { + this.fetchReplHistory().then(() => this.navigateHistory(1)); + return; + } + const historyItem = this.dedupedQueryHistory[cursor]; + if (this.historyCursor === -1) { + this.draftQuery = this.currentQuery; } + this.currentQuery = Text.of(historyItem.query.split("\n")); + this.historyCursor = cursor; } } @@ -293,6 +317,7 @@ export class Repl extends Model({ this.itemHeights.addHistoryItems( Array(history.length).fill(defaultItemHeight) ); + this._addDedupedHistoryQueries(historyItems); this._fetchingHistory = false; }); @@ -363,7 +388,7 @@ export class Repl extends Model({ if (isCommandQuery) { yield* _await(handleSlashCommand(query, this, historyItem)); } else { - const implicitLimit = sessionStateCtx + const implicitLimitConfig = sessionStateCtx .get(this)! .activeState.options.find( (opt) => opt.name === "Implicit Limit" @@ -376,8 +401,8 @@ export class Repl extends Model({ undefined, { implicitLimit: - implicitLimit != null - ? implicitLimit + BigInt(1) + implicitLimitConfig != null + ? implicitLimitConfig + BigInt(1) : undefined, }, (this._runningQuery as AbortController).signal @@ -386,7 +411,9 @@ export class Repl extends Model({ dbState.refreshCaches(capabilities, status ? [status] : []); - historyItem.setResult(status, !!result, Number(implicitLimit)); + const implicitLimit = + implicitLimitConfig != null ? Number(implicitLimitConfig) : null; + historyItem.setResult(status, !!result, implicitLimit); if (result) { if ( status === ExplainStateType.explain || @@ -399,7 +426,7 @@ export class Repl extends Model({ } else { this.resultInspectorCache.set( historyItem.$modelId, - createInspector(result, Number(implicitLimit), (item) => + createInspector(result, implicitLimit, (item) => this.setExtendedViewerItem(item) ) ); diff --git a/web/tests/_globalSetup.ts b/web/tests/_globalSetup.ts index 5630c722..fb5057a6 100644 --- a/web/tests/_globalSetup.ts +++ b/web/tests/_globalSetup.ts @@ -34,15 +34,23 @@ function checkEdgeDBServerAlive() { }); } -async function waitUntilAlive(check: () => Promise, event: Event) { +async function waitUntilAlive( + check: () => Promise, + errMessage: string, + event?: Event +) { for (let i = 0; i < STARTUP_TIMEOUT / 1000; i++) { if (await check()) { - event.set(); + event?.set(); return; } await sleep(1000); } - event.setError("EdgeDB server startup timed out"); + if (event) { + event.setError(errMessage); + } else { + throw new Error(errMessage); + } } async function checkUIServerAlive() { @@ -61,14 +69,31 @@ async function checkUIServerAlive() { }); } +async function checkConfigApplied() { + try { + const res = await fetch("http://localhost:5656/server-info"); + if (res.ok) { + const info = await res.json(); + if (info.instance_config.cors_allow_origins.length > 0) { + return true; + } + } + return false; + } catch { + return false; + } +} + export default async function globalSetup() { console.log("\n"); let edbServerProc: ChildProcess | null = null; const edbServerAlive = new Event(); + let usingExistingDevServer = false; if (await checkEdgeDBServerAlive()) { console.log("Re-using EdgeDB server already running on 5656"); + usingExistingDevServer = true; edbServerAlive.set(); } else { console.log("Starting EdgeDB server..."); @@ -84,9 +109,7 @@ export default async function globalSetup() { ] : ["--devmode"]; - edbServerProc = spawn(srvcmd, args, { - env: {...process.env, EDGEDB_DEBUG_HTTP_INJECT_CORS: "1"}, - }) as ChildProcess; + edbServerProc = spawn(srvcmd, args) as ChildProcess; edbServerProc.once("close", (code) => { if (!edbServerAlive.done) { edbServerAlive.setError( @@ -94,7 +117,11 @@ export default async function globalSetup() { ); } }); - waitUntilAlive(checkEdgeDBServerAlive, edbServerAlive); + waitUntilAlive( + checkEdgeDBServerAlive, + "EdgeDB server startup timed out", + edbServerAlive + ); } let uiServerProc: ChildProcess | null = null; @@ -116,7 +143,11 @@ export default async function globalSetup() { ); } }); - waitUntilAlive(checkUIServerAlive, uiServerAlive); + waitUntilAlive( + checkUIServerAlive, + "UI server startup timed out", + uiServerAlive + ); } await Promise.all([ @@ -146,6 +177,12 @@ export default async function globalSetup() { try { await testClient.execute(schemaScript); + if (!usingExistingDevServer) { + await testClient.execute( + `configure instance set cors_allow_origins := {'*'}` + ); + await waitUntilAlive(checkConfigApplied, "Config apply timed out"); + } break; } catch (err) { if (!(err instanceof AccessError)) {