@no-mess/client 0.4.0 → 0.5.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.
Files changed (39) hide show
  1. package/README.md +49 -2
  2. package/dist/react/no-mess-provider.d.ts.map +1 -1
  3. package/dist/react/no-mess-provider.js +12 -3
  4. package/dist/react/no-mess-provider.js.map +1 -1
  5. package/dist/schema/define-content-type.d.ts +14 -4
  6. package/dist/schema/define-content-type.d.ts.map +1 -1
  7. package/dist/schema/define-content-type.js +87 -20
  8. package/dist/schema/define-content-type.js.map +1 -1
  9. package/dist/schema/define-schema.d.ts +6 -3
  10. package/dist/schema/define-schema.d.ts.map +1 -1
  11. package/dist/schema/define-schema.js +20 -2
  12. package/dist/schema/define-schema.js.map +1 -1
  13. package/dist/schema/field-builders.d.ts +47 -17
  14. package/dist/schema/field-builders.d.ts.map +1 -1
  15. package/dist/schema/field-builders.js +26 -0
  16. package/dist/schema/field-builders.js.map +1 -1
  17. package/dist/schema/index.d.ts +6 -5
  18. package/dist/schema/index.d.ts.map +1 -1
  19. package/dist/schema/index.js +3 -2
  20. package/dist/schema/index.js.map +1 -1
  21. package/dist/schema/parse-schema.d.ts +1 -2
  22. package/dist/schema/parse-schema.d.ts.map +1 -1
  23. package/dist/schema/parse-schema.js +503 -275
  24. package/dist/schema/parse-schema.js.map +1 -1
  25. package/dist/schema/schema-types.d.ts +53 -14
  26. package/dist/schema/schema-types.d.ts.map +1 -1
  27. package/dist/schema/schema-types.js +10 -3
  28. package/dist/schema/schema-types.js.map +1 -1
  29. package/dist/schema/serialize-schema.d.ts.map +1 -1
  30. package/dist/schema/serialize-schema.js +125 -22
  31. package/dist/schema/serialize-schema.js.map +1 -1
  32. package/dist/schema/tree-utils.d.ts +18 -0
  33. package/dist/schema/tree-utils.d.ts.map +1 -0
  34. package/dist/schema/tree-utils.js +203 -0
  35. package/dist/schema/tree-utils.js.map +1 -0
  36. package/dist/types.d.ts +4 -12
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js.map +1 -1
  39. package/package.json +1 -1
@@ -1,24 +1,24 @@
1
1
  import { FIELD_TYPES, } from "./schema-types";
2
2
  const FIELD_TYPE_SET = new Set(FIELD_TYPES);
3
+ const DEFINITION_CALL_PATTERN = /define(ContentType|Template|Fragment)\s*\(/g;
4
+ const VARIABLE_DEFINITION_PATTERN = /(?:const|let|var|export\s+const)\s+(\w+)\s*=\s*define(ContentType|Template|Fragment)\s*\(/g;
3
5
  /**
4
- * Parses schema DSL source text and extracts ContentTypeDefinition[].
5
- * Custom recursive-descent parser — no TS compiler, no eval.
6
+ * Parses schema DSL source text and extracts template/fragment definitions.
6
7
  * Never throws; returns partial results + errors.
7
8
  */
8
9
  export function parseSchemaSource(source) {
9
10
  const errors = [];
10
11
  const warnings = [];
11
12
  const contentTypes = [];
12
- // Strip comments (line and block) while preserving line positions
13
13
  const stripped = stripComments(source);
14
- // Find all defineContentType( occurrences
15
- const pattern = /defineContentType\s*\(/g;
14
+ const references = collectDefinitionReferences(stripped);
16
15
  let match;
17
- while ((match = pattern.exec(stripped)) !== null) {
16
+ while ((match = DEFINITION_CALL_PATTERN.exec(stripped)) !== null) {
17
+ const definitionName = match[1];
18
18
  const startIdx = match.index + match[0].length;
19
19
  const line = lineAt(source, match.index);
20
20
  try {
21
- const result = parseDefineContentTypeArgs(stripped, startIdx, line);
21
+ const result = parseDefinitionArgs(stripped, startIdx, line, definitionName, references);
22
22
  if (result.contentType) {
23
23
  contentTypes.push(result.contentType);
24
24
  }
@@ -33,12 +33,10 @@ export function parseSchemaSource(source) {
33
33
  errors.push({
34
34
  line,
35
35
  column: 0,
36
- message: "Failed to parse defineContentType call",
36
+ message: `Failed to parse define${definitionName} call`,
37
37
  });
38
38
  }
39
39
  }
40
- // Also support standalone defineSchema({ contentTypes: [...] }) wrapping
41
- // The individual defineContentType calls inside are already captured above
42
40
  return {
43
41
  success: errors.length === 0 && contentTypes.length > 0,
44
42
  contentTypes,
@@ -46,282 +44,530 @@ export function parseSchemaSource(source) {
46
44
  warnings,
47
45
  };
48
46
  }
49
- /**
50
- * Strip line comments (//) and block comments while preserving newlines.
51
- */
52
- function stripComments(source) {
53
- let result = "";
54
- let i = 0;
55
- let inString = null;
56
- let escaped = false;
57
- while (i < source.length) {
58
- const ch = source[i];
59
- const next = source[i + 1];
60
- if (escaped) {
61
- result += ch;
62
- escaped = false;
63
- i++;
64
- continue;
65
- }
66
- if (inString) {
67
- if (ch === "\\") {
68
- escaped = true;
69
- result += ch;
70
- i++;
71
- continue;
72
- }
73
- if (ch === inString) {
74
- inString = null;
75
- }
76
- result += ch;
77
- i++;
78
- continue;
79
- }
80
- // Template literals
81
- if (ch === "`") {
82
- inString = "`";
83
- result += ch;
84
- i++;
85
- continue;
86
- }
87
- if (ch === '"' || ch === "'") {
88
- inString = ch;
89
- result += ch;
90
- i++;
91
- continue;
92
- }
93
- // Line comment
94
- if (ch === "/" && next === "/") {
95
- while (i < source.length && source[i] !== "\n") {
96
- result += " ";
97
- i++;
98
- }
99
- continue;
100
- }
101
- // Block comment
102
- if (ch === "/" && next === "*") {
103
- i += 2;
104
- while (i < source.length) {
105
- if (source[i] === "*" && source[i + 1] === "/") {
106
- i += 2;
107
- break;
108
- }
109
- result += source[i] === "\n" ? "\n" : " ";
110
- i++;
111
- }
47
+ function collectDefinitionReferences(source) {
48
+ const refs = new Map();
49
+ let match;
50
+ while ((match = VARIABLE_DEFINITION_PATTERN.exec(source)) !== null) {
51
+ const variableName = match[1];
52
+ const definitionName = match[2];
53
+ const slugStart = skipWhitespace(source, match.index + match[0].length);
54
+ const slug = parseStringLiteral(source, slugStart)?.value;
55
+ if (!slug) {
112
56
  continue;
113
57
  }
114
- result += ch;
115
- i++;
116
- }
117
- return result;
118
- }
119
- function lineAt(source, index) {
120
- let line = 1;
121
- for (let i = 0; i < index && i < source.length; i++) {
122
- if (source[i] === "\n")
123
- line++;
124
- }
125
- return line;
126
- }
127
- function columnAt(source, index) {
128
- let col = 1;
129
- for (let i = index - 1; i >= 0; i--) {
130
- if (source[i] === "\n")
131
- break;
132
- col++;
58
+ refs.set(variableName, {
59
+ kind: definitionName === "Fragment" ? "fragment" : "template",
60
+ slug,
61
+ });
133
62
  }
134
- return col;
63
+ return refs;
135
64
  }
136
- /**
137
- * Parse the arguments of defineContentType(slug, config).
138
- * `startIdx` points to the character after the opening `(`.
139
- */
140
- function parseDefineContentTypeArgs(source, startIdx, baseLine) {
65
+ function parseDefinitionArgs(source, startIdx, baseLine, definitionName, references) {
141
66
  const errors = [];
142
67
  const warnings = [];
143
- let i = skipWhitespace(source, startIdx);
144
- // Parse slug (first string argument)
145
- const slugResult = parseStringLiteral(source, i);
68
+ let index = skipWhitespace(source, startIdx);
69
+ const slugResult = parseStringLiteral(source, index);
146
70
  if (!slugResult) {
147
71
  errors.push({
148
72
  line: baseLine,
149
- column: columnAt(source, i),
150
- message: "Expected string literal for content type slug",
73
+ column: columnAt(source, index),
74
+ message: "Expected string literal for schema slug",
151
75
  });
152
76
  return { contentType: null, errors, warnings };
153
77
  }
154
78
  const slug = slugResult.value;
155
- i = skipWhitespace(source, slugResult.end);
156
- // Expect comma
157
- if (source[i] !== ",") {
79
+ index = skipWhitespace(source, slugResult.end);
80
+ if (source[index] !== ",") {
158
81
  errors.push({
159
- line: lineAt(source, i),
160
- column: columnAt(source, i),
82
+ line: lineAt(source, index),
83
+ column: columnAt(source, index),
161
84
  message: "Expected comma after slug",
162
85
  });
163
86
  return { contentType: null, errors, warnings };
164
87
  }
165
- i = skipWhitespace(source, i + 1);
166
- // Parse config object
167
- if (source[i] !== "{") {
88
+ index = skipWhitespace(source, index + 1);
89
+ if (source[index] !== "{") {
168
90
  errors.push({
169
- line: lineAt(source, i),
170
- column: columnAt(source, i),
171
- message: "Expected object literal for content type config",
91
+ line: lineAt(source, index),
92
+ column: columnAt(source, index),
93
+ message: "Expected object literal for schema config",
172
94
  });
173
95
  return { contentType: null, errors, warnings };
174
96
  }
175
- const configResult = extractBalanced(source, i, "{", "}");
176
- if (!configResult) {
97
+ const configBlock = extractBalanced(source, index, "{", "}");
98
+ if (!configBlock) {
177
99
  errors.push({
178
- line: lineAt(source, i),
179
- column: columnAt(source, i),
100
+ line: lineAt(source, index),
101
+ column: columnAt(source, index),
180
102
  message: "Unterminated config object",
181
103
  });
182
104
  return { contentType: null, errors, warnings };
183
105
  }
184
- const configBody = configResult.inner;
185
- // Extract name
186
- const name = extractPropertyString(configBody, "name");
106
+ const configEntries = parseObjectEntries(configBlock.inner);
107
+ const name = parseStringValue(findObjectEntry(configEntries, "name")?.value);
187
108
  if (!name) {
188
109
  warnings.push({
189
110
  line: baseLine,
190
- message: `Missing "name" for content type "${slug}" using slug as name`,
111
+ message: `Missing "name" for schema "${slug}"; using slug as name`,
191
112
  });
192
113
  }
193
- // Extract description
194
- const description = extractPropertyString(configBody, "description");
195
- // Extract fields block
196
- const fields = [];
197
- const fieldsMatch = /fields\s*:\s*\{/.exec(configBody);
198
- if (fieldsMatch) {
199
- const fieldsStart = configBody.indexOf("{", fieldsMatch.index + fieldsMatch[0].length - 1);
200
- const fieldsBlock = extractBalanced(configBody, fieldsStart, "{", "}");
201
- if (fieldsBlock) {
202
- const fieldEntries = parseFieldEntries(fieldsBlock.inner, lineAt(source, startIdx), errors, warnings);
203
- fields.push(...fieldEntries);
204
- }
205
- }
206
- const contentType = {
207
- slug,
208
- name: name || slug,
209
- fields,
210
- };
211
- if (description) {
212
- contentType.description = description;
114
+ const description = parseStringValue(findObjectEntry(configEntries, "description")?.value);
115
+ const route = parseStringValue(findObjectEntry(configEntries, "route")?.value);
116
+ const rawMode = parseStringValue(findObjectEntry(configEntries, "mode")?.value);
117
+ const mode = rawMode === "singleton" ? "singleton" : "collection";
118
+ const fieldsEntry = findObjectEntry(configEntries, "fields");
119
+ const fields = fieldsEntry
120
+ ? parseFieldEntries(unwrapObjectLiteral(fieldsEntry.value), baseLine, errors, warnings, references)
121
+ : [];
122
+ if (!fieldsEntry) {
123
+ warnings.push({
124
+ line: baseLine,
125
+ message: `Schema "${slug}" is missing a fields object`,
126
+ });
127
+ }
128
+ if (definitionName === "Fragment") {
129
+ return {
130
+ contentType: {
131
+ kind: "fragment",
132
+ slug,
133
+ name: name || slug,
134
+ ...(description ? { description } : {}),
135
+ fields,
136
+ },
137
+ errors,
138
+ warnings,
139
+ };
213
140
  }
214
- return { contentType, errors, warnings };
141
+ return {
142
+ contentType: {
143
+ kind: "template",
144
+ slug,
145
+ name: name || slug,
146
+ ...(description ? { description } : {}),
147
+ ...(route ? { route } : {}),
148
+ mode,
149
+ fields,
150
+ },
151
+ errors,
152
+ warnings,
153
+ };
215
154
  }
216
- /**
217
- * Parse field entries from the fields object body.
218
- * Each entry is: fieldName: field.type({ ... }) or field.type()
219
- */
220
- function parseFieldEntries(body, baseLine, errors, warnings) {
155
+ function parseFieldEntries(body, baseLine, errors, warnings, references) {
221
156
  const fields = [];
222
- // Match patterns like: fieldName: field.type(...) or fieldName: field.type()
223
- const fieldPattern = /(\w+)\s*:\s*field\s*\.\s*(\w+)\s*\(/g;
224
- let match;
225
- while ((match = fieldPattern.exec(body)) !== null) {
226
- const fieldName = match[1];
227
- const fieldType = match[2];
228
- const argsStart = match.index + match[0].length;
229
- if (!FIELD_TYPE_SET.has(fieldType)) {
157
+ const entries = parseObjectEntries(body);
158
+ for (const entry of entries) {
159
+ const definition = parseFieldExpression(entry.value, baseLine, entry.key, errors, warnings, references);
160
+ if (definition) {
161
+ fields.push({
162
+ name: entry.key,
163
+ ...definition,
164
+ });
165
+ }
166
+ }
167
+ return fields;
168
+ }
169
+ function parseFieldExpression(expression, baseLine, fieldName, errors, warnings, references) {
170
+ const trimmed = expression.trim();
171
+ const match = /^field\s*\.\s*(\w+)\s*\(/.exec(trimmed);
172
+ if (!match) {
173
+ errors.push({
174
+ line: baseLine,
175
+ column: 0,
176
+ message: `Expected field builder for "${fieldName}"`,
177
+ });
178
+ return null;
179
+ }
180
+ const fieldType = match[1];
181
+ if (!FIELD_TYPE_SET.has(fieldType)) {
182
+ errors.push({
183
+ line: baseLine,
184
+ column: 0,
185
+ message: `Unknown field type "${fieldType}" for field "${fieldName}"`,
186
+ });
187
+ return null;
188
+ }
189
+ const argsBlock = extractBalancedFrom(trimmed, match[0].length - 1, "(", ")");
190
+ const rawArgs = argsBlock?.inner.trim() ?? "";
191
+ if (fieldType === "object") {
192
+ const options = parseFieldOptionsObject(rawArgs);
193
+ const nestedFieldsEntry = findObjectEntry(options, "fields");
194
+ if (!nestedFieldsEntry) {
195
+ warnings.push({
196
+ line: baseLine,
197
+ message: `Object field "${fieldName}" is missing nested fields`,
198
+ });
199
+ }
200
+ return {
201
+ type: "object",
202
+ required: parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false,
203
+ label: parseStringValue(findObjectEntry(options, "label")?.value),
204
+ description: parseStringValue(findObjectEntry(options, "description")?.value),
205
+ fields: nestedFieldsEntry
206
+ ? parseFieldEntries(unwrapObjectLiteral(nestedFieldsEntry.value), baseLine, errors, warnings, references)
207
+ : [],
208
+ };
209
+ }
210
+ if (fieldType === "array") {
211
+ const options = parseFieldOptionsObject(rawArgs);
212
+ const ofEntry = findObjectEntry(options, "of");
213
+ const of = ofEntry
214
+ ? parseFieldExpression(ofEntry.value, baseLine, `${fieldName}[]`, errors, warnings, references)
215
+ : null;
216
+ if (!of) {
230
217
  errors.push({
231
218
  line: baseLine,
232
219
  column: 0,
233
- message: `Unknown field type "${fieldType}" for field "${fieldName}"`,
220
+ message: `Array field "${fieldName}" is missing a valid "of" definition`,
234
221
  });
235
- continue;
222
+ return null;
223
+ }
224
+ return {
225
+ type: "array",
226
+ required: parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false,
227
+ label: parseStringValue(findObjectEntry(options, "label")?.value),
228
+ description: parseStringValue(findObjectEntry(options, "description")?.value),
229
+ of,
230
+ minItems: parseNumberValue(findObjectEntry(options, "minItems")?.value),
231
+ maxItems: parseNumberValue(findObjectEntry(options, "maxItems")?.value),
232
+ };
233
+ }
234
+ if (fieldType === "fragment") {
235
+ const args = splitTopLevel(rawArgs);
236
+ const fragmentToken = args[0]?.trim() ?? "";
237
+ const fragment = parseStringValue(fragmentToken) ??
238
+ references.get(fragmentToken)?.slug ??
239
+ fragmentToken.replace(/[^\w-]/g, "");
240
+ if (!fragment) {
241
+ errors.push({
242
+ line: baseLine,
243
+ column: 0,
244
+ message: `Fragment field "${fieldName}" is missing a fragment reference`,
245
+ });
246
+ return null;
236
247
  }
237
- // Extract the arguments to field.type(...)
238
- // Find matching closing paren
239
- const argsResult = extractBalancedFrom(body, argsStart - 1, "(", ")");
240
- const argsBody = argsResult ? argsResult.inner : "";
241
- const fieldDef = {
242
- name: fieldName,
243
- type: fieldType,
244
- required: false,
248
+ const options = args.length > 1 ? parseFieldOptionsObject(args.slice(1).join(",")) : [];
249
+ return {
250
+ type: "fragment",
251
+ required: parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false,
252
+ label: parseStringValue(findObjectEntry(options, "label")?.value),
253
+ description: parseStringValue(findObjectEntry(options, "description")?.value),
254
+ fragment,
245
255
  };
246
- // Parse options from args body
247
- if (argsBody.trim()) {
248
- // Look for required
249
- const requiredMatch = /required\s*:\s*(true|false)/.exec(argsBody);
250
- if (requiredMatch) {
251
- fieldDef.required = requiredMatch[1] === "true";
256
+ }
257
+ const options = rawArgs ? parseFieldOptionsObject(rawArgs) : [];
258
+ const label = parseStringValue(findObjectEntry(options, "label")?.value);
259
+ const description = parseStringValue(findObjectEntry(options, "description")?.value);
260
+ const required = parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false;
261
+ if (fieldType === "select") {
262
+ const choices = parseChoicesArray(findObjectEntry(options, "choices")?.value);
263
+ return {
264
+ type: "select",
265
+ required,
266
+ ...(label ? { label } : {}),
267
+ ...(description ? { description } : {}),
268
+ ...(choices ? { options: { choices } } : {}),
269
+ };
270
+ }
271
+ return {
272
+ type: fieldType,
273
+ required,
274
+ ...(label ? { label } : {}),
275
+ ...(description ? { description } : {}),
276
+ };
277
+ }
278
+ function parseFieldOptionsObject(value) {
279
+ const trimmed = value.trim();
280
+ if (!trimmed) {
281
+ return [];
282
+ }
283
+ if (!trimmed.startsWith("{")) {
284
+ return [];
285
+ }
286
+ return parseObjectEntries(unwrapObjectLiteral(trimmed));
287
+ }
288
+ function parseChoicesArray(value) {
289
+ if (!value) {
290
+ return undefined;
291
+ }
292
+ const trimmed = value.trim();
293
+ if (!trimmed.startsWith("[")) {
294
+ return undefined;
295
+ }
296
+ const items = splitTopLevel(unwrapArrayLiteral(trimmed));
297
+ const choices = [];
298
+ for (const item of items) {
299
+ const entries = parseObjectEntries(unwrapObjectLiteral(item));
300
+ const label = parseStringValue(findObjectEntry(entries, "label")?.value);
301
+ const choiceValue = parseStringValue(findObjectEntry(entries, "value")?.value);
302
+ if (label !== undefined && choiceValue !== undefined) {
303
+ choices.push({ label, value: choiceValue });
304
+ }
305
+ }
306
+ return choices;
307
+ }
308
+ function parseObjectEntries(body) {
309
+ const entries = splitTopLevel(body);
310
+ const result = [];
311
+ for (const entry of entries) {
312
+ const separatorIndex = findTopLevelColon(entry);
313
+ if (separatorIndex === -1) {
314
+ continue;
315
+ }
316
+ const rawKey = entry.slice(0, separatorIndex).trim();
317
+ const rawValue = entry.slice(separatorIndex + 1).trim();
318
+ if (!rawKey || !rawValue) {
319
+ continue;
320
+ }
321
+ result.push({
322
+ key: parseObjectKey(rawKey),
323
+ value: rawValue,
324
+ });
325
+ }
326
+ return result;
327
+ }
328
+ function findObjectEntry(entries, key) {
329
+ return entries.find((entry) => entry.key === key);
330
+ }
331
+ function parseObjectKey(rawKey) {
332
+ return parseStringValue(rawKey) ?? rawKey;
333
+ }
334
+ function splitTopLevel(source) {
335
+ const parts = [];
336
+ let current = "";
337
+ let parenDepth = 0;
338
+ let braceDepth = 0;
339
+ let bracketDepth = 0;
340
+ let inString = null;
341
+ let escaped = false;
342
+ for (const char of source) {
343
+ if (escaped) {
344
+ current += char;
345
+ escaped = false;
346
+ continue;
347
+ }
348
+ if (inString) {
349
+ current += char;
350
+ if (char === "\\") {
351
+ escaped = true;
252
352
  }
253
- // Look for description
254
- const descMatch = /description\s*:\s*/.exec(argsBody);
255
- if (descMatch) {
256
- const descStart = descMatch.index + descMatch[0].length;
257
- const descStr = parseStringLiteral(argsBody, skipWhitespace(argsBody, descStart));
258
- if (descStr) {
259
- fieldDef.description = descStr.value;
260
- }
353
+ else if (char === inString) {
354
+ inString = null;
261
355
  }
262
- // Look for choices (select type)
263
- if (fieldType === "select") {
264
- const choices = parseChoicesArray(argsBody);
265
- if (choices.length > 0) {
266
- fieldDef.options = { choices };
267
- }
356
+ continue;
357
+ }
358
+ if (char === '"' || char === "'" || char === "`") {
359
+ inString = char;
360
+ current += char;
361
+ continue;
362
+ }
363
+ if (char === "(")
364
+ parenDepth++;
365
+ if (char === ")")
366
+ parenDepth--;
367
+ if (char === "{")
368
+ braceDepth++;
369
+ if (char === "}")
370
+ braceDepth--;
371
+ if (char === "[")
372
+ bracketDepth++;
373
+ if (char === "]")
374
+ bracketDepth--;
375
+ if (char === "," &&
376
+ parenDepth === 0 &&
377
+ braceDepth === 0 &&
378
+ bracketDepth === 0) {
379
+ if (current.trim()) {
380
+ parts.push(current.trim());
268
381
  }
382
+ current = "";
383
+ continue;
269
384
  }
270
- fields.push(fieldDef);
385
+ current += char;
271
386
  }
272
- return fields;
387
+ if (current.trim()) {
388
+ parts.push(current.trim());
389
+ }
390
+ return parts;
273
391
  }
274
- /**
275
- * Parse a choices array from text like:
276
- * choices: [{ label: "Foo", value: "foo" }, ...]
277
- */
278
- function parseChoicesArray(text) {
279
- const choices = [];
280
- const choicesMatch = /choices\s*:\s*\[/.exec(text);
281
- if (!choicesMatch)
282
- return choices;
283
- const arrayStart = text.indexOf("[", choicesMatch.index);
284
- const arrayBlock = extractBalanced(text, arrayStart, "[", "]");
285
- if (!arrayBlock)
286
- return choices;
287
- // Find each { ... } in the array
288
- const objPattern = /\{/g;
289
- let objMatch;
290
- while ((objMatch = objPattern.exec(arrayBlock.inner)) !== null) {
291
- const objBlock = extractBalanced(arrayBlock.inner, objMatch.index, "{", "}");
292
- if (!objBlock)
392
+ function findTopLevelColon(source) {
393
+ let parenDepth = 0;
394
+ let braceDepth = 0;
395
+ let bracketDepth = 0;
396
+ let inString = null;
397
+ let escaped = false;
398
+ for (let index = 0; index < source.length; index += 1) {
399
+ const char = source[index];
400
+ if (escaped) {
401
+ escaped = false;
402
+ continue;
403
+ }
404
+ if (inString) {
405
+ if (char === "\\") {
406
+ escaped = true;
407
+ }
408
+ else if (char === inString) {
409
+ inString = null;
410
+ }
411
+ continue;
412
+ }
413
+ if (char === '"' || char === "'" || char === "`") {
414
+ inString = char;
293
415
  continue;
294
- const label = extractPropertyString(objBlock.inner, "label");
295
- const value = extractPropertyString(objBlock.inner, "value");
296
- if (label && value) {
297
- choices.push({ label, value });
416
+ }
417
+ if (char === "(")
418
+ parenDepth++;
419
+ if (char === ")")
420
+ parenDepth--;
421
+ if (char === "{")
422
+ braceDepth++;
423
+ if (char === "}")
424
+ braceDepth--;
425
+ if (char === "[")
426
+ bracketDepth++;
427
+ if (char === "]")
428
+ bracketDepth--;
429
+ if (char === ":" &&
430
+ parenDepth === 0 &&
431
+ braceDepth === 0 &&
432
+ bracketDepth === 0) {
433
+ return index;
298
434
  }
299
435
  }
300
- return choices;
436
+ return -1;
437
+ }
438
+ function parseStringValue(value) {
439
+ if (!value) {
440
+ return undefined;
441
+ }
442
+ return parseStringLiteral(value.trim(), 0)?.value;
443
+ }
444
+ function parseBooleanValue(value) {
445
+ if (!value) {
446
+ return undefined;
447
+ }
448
+ const trimmed = value.trim();
449
+ if (trimmed === "true")
450
+ return true;
451
+ if (trimmed === "false")
452
+ return false;
453
+ return undefined;
454
+ }
455
+ function parseNumberValue(value) {
456
+ if (!value) {
457
+ return undefined;
458
+ }
459
+ const parsed = Number(value.trim());
460
+ return Number.isFinite(parsed) ? parsed : undefined;
461
+ }
462
+ function unwrapObjectLiteral(value) {
463
+ const trimmed = value.trim();
464
+ const block = extractBalanced(trimmed, 0, "{", "}");
465
+ return block?.inner ?? "";
466
+ }
467
+ function unwrapArrayLiteral(value) {
468
+ const trimmed = value.trim();
469
+ const block = extractBalanced(trimmed, 0, "[", "]");
470
+ return block?.inner ?? "";
471
+ }
472
+ function stripComments(source) {
473
+ let result = "";
474
+ let index = 0;
475
+ let inString = null;
476
+ let escaped = false;
477
+ while (index < source.length) {
478
+ const char = source[index];
479
+ const next = source[index + 1];
480
+ if (escaped) {
481
+ result += char;
482
+ escaped = false;
483
+ index += 1;
484
+ continue;
485
+ }
486
+ if (inString) {
487
+ if (char === "\\") {
488
+ escaped = true;
489
+ result += char;
490
+ index += 1;
491
+ continue;
492
+ }
493
+ if (char === inString) {
494
+ inString = null;
495
+ }
496
+ result += char;
497
+ index += 1;
498
+ continue;
499
+ }
500
+ if (char === '"' || char === "'" || char === "`") {
501
+ inString = char;
502
+ result += char;
503
+ index += 1;
504
+ continue;
505
+ }
506
+ if (char === "/" && next === "/") {
507
+ while (index < source.length && source[index] !== "\n") {
508
+ result += " ";
509
+ index += 1;
510
+ }
511
+ continue;
512
+ }
513
+ if (char === "/" && next === "*") {
514
+ index += 2;
515
+ while (index < source.length) {
516
+ if (source[index] === "*" && source[index + 1] === "/") {
517
+ index += 2;
518
+ break;
519
+ }
520
+ result += source[index] === "\n" ? "\n" : " ";
521
+ index += 1;
522
+ }
523
+ continue;
524
+ }
525
+ result += char;
526
+ index += 1;
527
+ }
528
+ return result;
529
+ }
530
+ function lineAt(source, index) {
531
+ let line = 1;
532
+ for (let i = 0; i < index && i < source.length; i += 1) {
533
+ if (source[i] === "\n") {
534
+ line += 1;
535
+ }
536
+ }
537
+ return line;
538
+ }
539
+ function columnAt(source, index) {
540
+ let column = 1;
541
+ for (let i = index - 1; i >= 0; i -= 1) {
542
+ if (source[i] === "\n") {
543
+ break;
544
+ }
545
+ column += 1;
546
+ }
547
+ return column;
301
548
  }
302
- // === Utility functions ===
303
549
  function skipWhitespace(source, index) {
304
- while (index < source.length && /\s/.test(source[index])) {
305
- index++;
550
+ let current = index;
551
+ while (current < source.length && /\s/.test(source[current])) {
552
+ current += 1;
306
553
  }
307
- return index;
554
+ return current;
308
555
  }
309
- /**
310
- * Parse a string literal (single-quoted, double-quoted, or backtick) at position i.
311
- */
312
- function parseStringLiteral(source, i) {
313
- if (i >= source.length)
556
+ function parseStringLiteral(source, startIdx) {
557
+ if (startIdx >= source.length) {
314
558
  return null;
315
- const quote = source[i];
316
- if (quote !== '"' && quote !== "'" && quote !== "`")
559
+ }
560
+ const quote = source[startIdx];
561
+ if (quote !== '"' && quote !== "'" && quote !== "`") {
317
562
  return null;
563
+ }
318
564
  let value = "";
319
- let j = i + 1;
565
+ let index = startIdx + 1;
320
566
  let escaped = false;
321
- while (j < source.length) {
322
- const ch = source[j];
567
+ while (index < source.length) {
568
+ const char = source[index];
323
569
  if (escaped) {
324
- switch (ch) {
570
+ switch (char) {
325
571
  case "n":
326
572
  value += "\n";
327
573
  break;
@@ -332,88 +578,70 @@ function parseStringLiteral(source, i) {
332
578
  value += "\\";
333
579
  break;
334
580
  default:
335
- value += ch;
581
+ value += char;
336
582
  break;
337
583
  }
338
584
  escaped = false;
339
- j++;
585
+ index += 1;
340
586
  continue;
341
587
  }
342
- if (ch === "\\") {
588
+ if (char === "\\") {
343
589
  escaped = true;
344
- j++;
590
+ index += 1;
345
591
  continue;
346
592
  }
347
- if (ch === quote) {
348
- return { value, end: j + 1 };
593
+ if (char === quote) {
594
+ return { value, end: index + 1 };
349
595
  }
350
- value += ch;
351
- j++;
596
+ value += char;
597
+ index += 1;
352
598
  }
353
- return null; // unterminated string
599
+ return null;
354
600
  }
355
- /**
356
- * Extract balanced delimiters starting at position i (which must be the opening delimiter).
357
- */
358
- function extractBalanced(source, i, open, close) {
359
- if (source[i] !== open)
601
+ function extractBalanced(source, startIdx, open, close) {
602
+ if (source[startIdx] !== open) {
360
603
  return null;
361
- return extractBalancedFrom(source, i, open, close);
604
+ }
605
+ return extractBalancedFrom(source, startIdx, open, close);
362
606
  }
363
- function extractBalancedFrom(source, i, open, close) {
607
+ function extractBalancedFrom(source, openIdx, open, close) {
364
608
  let depth = 0;
365
609
  let inString = null;
366
610
  let escaped = false;
367
- const start = i + 1;
368
- while (i < source.length) {
369
- const ch = source[i];
611
+ const contentStart = openIdx + 1;
612
+ for (let index = openIdx; index < source.length; index += 1) {
613
+ const char = source[index];
370
614
  if (escaped) {
371
615
  escaped = false;
372
- i++;
373
616
  continue;
374
617
  }
375
618
  if (inString) {
376
- if (ch === "\\") {
619
+ if (char === "\\") {
377
620
  escaped = true;
378
- i++;
379
- continue;
380
621
  }
381
- if (ch === inString) {
622
+ else if (char === inString) {
382
623
  inString = null;
383
624
  }
384
- i++;
385
625
  continue;
386
626
  }
387
- if (ch === '"' || ch === "'" || ch === "`") {
388
- inString = ch;
389
- i++;
627
+ if (char === '"' || char === "'" || char === "`") {
628
+ inString = char;
390
629
  continue;
391
630
  }
392
- if (ch === open) {
393
- depth++;
631
+ if (char === open) {
632
+ depth += 1;
633
+ continue;
394
634
  }
395
- else if (ch === close) {
396
- depth--;
635
+ if (char === close) {
636
+ depth -= 1;
397
637
  if (depth === 0) {
398
- return { inner: source.slice(start, i), end: i + 1 };
638
+ return {
639
+ inner: source.slice(contentStart, index),
640
+ end: index + 1,
641
+ };
399
642
  }
400
643
  }
401
- i++;
402
644
  }
403
645
  return null;
404
646
  }
405
- /**
406
- * Extract a string property value from an object literal body.
407
- * Matches: propName: "value" or propName: 'value'
408
- */
409
- function extractPropertyString(body, propName) {
410
- const pattern = new RegExp(`(?<![\\w])${propName}\\s*:\\s*`);
411
- const match = pattern.exec(body);
412
- if (!match)
413
- return null;
414
- const valueStart = match.index + match[0].length;
415
- const i = skipWhitespace(body, valueStart);
416
- const result = parseStringLiteral(body, i);
417
- return result ? result.value : null;
418
- }
419
647
  //# sourceMappingURL=parse-schema.js.map