File size: 2,183 Bytes
7596726
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// Lightweight JSON fetch helper used during browser boot.
async function requestJson(root, path, options = undefined) {
  const fetchFn = root.fetch || globalThis.fetch;
  if (typeof fetchFn !== 'function') {
    throw new Error(`No fetch implementation is available for ${path}`);
  }
  const response = await fetchFn(path, options);
  if (!response || typeof response.json !== 'function') {
    throw new Error(`Expected JSON response for ${path}`);
  }
  return response.json();
}

// Guards the human-authored config file before the app uses it.
function validateConfig(config) {
  if (!config || typeof config.title !== 'string' || typeof config.subtitle !== 'string') {
    throw new Error('sf-config.json must define title and subtitle');
  }
  if (!String(config.defaultDemoId || '').trim()) {
    throw new Error('sf-config.json must define defaultDemoId');
  }
}

// Guards the generated UI model before the app tries to render from it.
function validateUiModel(uiModel) {
  const valid = uiModel
    && Array.isArray(uiModel.constraints)
    && Array.isArray(uiModel.views)
    && Array.isArray(uiModel.entities)
    && Array.isArray(uiModel.facts);
  if (!valid) {
    throw new Error('generated/ui-model.json must define constraints, views, entities, and facts arrays');
  }
  if (!uiModel.views.length) {
    throw new Error('generated/ui-model.json must define at least one view');
  }
}

// Loads every browser-boot dependency and returns the assembled boot context.
export async function loadAppConfig(root = globalThis) {
  const [config, uiModel] = await Promise.all([
    requestJson(root, '/sf-config.json'),
    requestJson(root, '/generated/ui-model.json'),
  ]);

  validateConfig(config);
  validateUiModel(uiModel);

  const backend = root.SF.createBackend({ type: 'axum', baseUrl: '' });
  const availableDemoIds = await backend.listDemoData();
  const demoId = String(config.defaultDemoId).trim();
  if (!Array.isArray(availableDemoIds) || !availableDemoIds.includes(demoId)) {
    throw new Error(`Configured demo "${demoId}" is not exposed by /demo-data`);
  }

  return {
    config,
    uiModel,
    backend,
    demoId,
    availableDemoIds,
  };
}