@yydb/yydb 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1,25 +1,22 @@
1
1
  # `@yydb/yydb`
2
2
 
3
- **All-in-one YYDB for Node.js: CLI, embedded engine, and local `serve`.**
4
-
5
- Open a `.yydb` file with one dependency — `@yydb/yydb` spawns a private loopback server and talks to it over the YY
6
- wire protocol. For a **lightweight client only** (browser, renderer, or an app that already runs `yydb serve`), use
7
- [`@yydb/yydb-client`](../yydb-client).
3
+ Opens a `.yydb` file in Node.js with one dependency: a private loopback `yydb serve` process starts automatically and
4
+ the package talks to it over the YY wire protocol. For a lightweight client only (browser, renderer, or an app that
5
+ already runs `yydb serve`), use [`@yydb/yydb-client`](https://www.npmjs.com/package/@yydb/yydb-client).
8
6
 
9
7
  [![npm](https://img.shields.io/npm/v/@yydb/yydb)](https://www.npmjs.com/package/@yydb/yydb)
10
8
  [![Node.js](https://img.shields.io/node/v/@yydb/yydb)](https://www.npmjs.com/package/@yydb/yydb)
11
- [![Check Rust](https://github.com/yy-database/yydb/actions/workflows/check-rust.yml/badge.svg?branch=dev)](https://github.com/yy-database/yydb/actions/workflows/check-rust.yml)
12
- [![Check TypeScript](https://github.com/yy-database/yydb/actions/workflows/check-typescript.yml/badge.svg?branch=dev)](https://github.com/yy-database/yydb/actions/workflows/check-typescript.yml)
13
9
 
14
- ## Install
10
+ ## Exports
15
11
 
16
- ```bash
17
- npm install @yydb/yydb
18
- ```
12
+ | Import | Role |
13
+ |------------------|------------------------------------------|
14
+ | `@yydb/yydb` | `Database` API and default entry |
15
+ | `@yydb/yydb/cli` | CLI (`version`, `init`, `info`, `serve`) |
16
+
17
+ Node.js 18+. The matching platform `.node` binding installs via optional dependencies.
19
18
 
20
- Node.js 18 or newer is supported. The matching `yydb-napi` binding is installed as an optional platform dependency and
21
- is
22
- started on a private loopback endpoint when `Database.open()` runs.
19
+ Schema truth lives in the `.yydb` file via `ensureSchema` and VOS documents.
23
20
 
24
21
  ## Example
25
22
 
@@ -38,19 +35,16 @@ await db.ensureSchema(
38
35
  );
39
36
 
40
37
  await db.put("theme", "dark");
41
- const value = await db.get("theme");
42
- console.log(value && new TextDecoder().decode(value));
43
-
38
+ console.log(await db.get("theme"));
44
39
  await db.close();
45
40
  ```
46
41
 
47
- The database file is created if it does not exist. The VOS schema and its version are stored with the data, so reopening
48
- the file does not depend on a separate schema registry.
42
+ The file is created when missing. Schema version and VOS document persist with the data.
49
43
 
50
44
  ## API
51
45
 
52
46
  ```ts
53
- const db = await Database.open(path, options ?);
47
+ const db = await Database.open(path, options?);
54
48
 
55
49
  await db.ensureSchema(version, vosDocument);
56
50
  await db.getSchema();
@@ -61,30 +55,21 @@ await db.serverVersion();
61
55
  await db.close();
62
56
  ```
63
57
 
64
- `OpenOptions` supports `cli` (an explicit CLI script path), `port` (a preferred loopback port), and `serveArgs` (extra
65
- arguments forwarded to `yydb serve`). `binary` is a deprecated alias for `cli`.
66
-
67
- ## Use a different package when
68
-
69
- | Need | Package |
70
- |----------------------------------------|-------------------------------------------|
71
- | Node all-in-one (CLI + engine + serve) | **`@yydb/yydb`** (this package) |
72
- | Lightweight wire client only | [`@yydb/yydb-client`](../yydb-client) |
73
- | In-process Rust embed | [`yydb`](https://crates.io/crates/yydb) |
74
- | Rust wire server crate (used by N-API) | [`yydb-server`](../../crates/yydb-server) |
75
- | Rust remote client | [`yydb-client`](../../crates/yydb-client) |
58
+ `OpenOptions`: `cli` (explicit CLI script path), `port` (preferred loopback port), `serveArgs` (extra `yydb serve`
59
+ arguments). `binary` is a deprecated alias for `cli`.
76
60
 
77
- For Electron, the main process can use `@yydb/yydb`; renderer code can use
78
- `@yydb/yydb-client` against the main process endpoint.
61
+ ## Related packages
79
62
 
80
- ## Security
63
+ | Need | Package |
64
+ |--------------------------------|------------------------------------------------------------------------|
65
+ | Node all-in-one (this package) | **`@yydb/yydb`** |
66
+ | Wire client only | [`@yydb/yydb-client`](https://www.npmjs.com/package/@yydb/yydb-client) |
67
+ | In-process Rust embed | [`yydb`](https://crates.io/crates/yydb) |
68
+ | Agent skills (wire protocol) | [`@yydb/yydb-skills`](https://www.npmjs.com/package/@yydb/yydb-skills) |
81
69
 
82
- The managed engine listens on `127.0.0.1` and has no authentication layer. Keep the endpoint private and use the
83
- operating system's file permissions for the `.yydb` file.
70
+ Electron: main process can use `@yydb/yydb`; renderer code can use `@yydb/yydb-client` against the main-process
71
+ endpoint.
84
72
 
85
- ## Links
73
+ The managed engine listens on `127.0.0.1` with no authentication — keep the endpoint private.
86
74
 
87
- - [YYDB overview](https://github.com/yy-database/yydb.rs)
88
- - [User guides](https://github.com/yy-database/yydb.rs/tree/dev/projects/packages/homepage/documentation/zh-hans)
89
- - [API documentation](https://docs.rs/yydb)
90
- - [License](https://github.com/yy-database/yydb.rs/blob/dev/License.md)
75
+ [YYDB overview](https://github.com/yy-database/yydb.rs) · [User guides](https://github.com/yy-database/yydb.rs/tree/dev/projects/packages/homepage/documentation/zh-hans)
package/dist/cli/cli.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { dirname, join } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { createCli } from '@vmz/commander';
4
- import { registerGenerateCommand } from './generate-cmd.js';
5
4
  import { registerInfoCommand } from './info-cmd.js';
6
5
  import { registerInitCommand } from './init-cmd.js';
7
6
  import { registerServeCommand } from './serve-cmd.js';
@@ -13,7 +12,6 @@ function buildYydbCli() {
13
12
  registerInitCommand(cli);
14
13
  registerInfoCommand(cli);
15
14
  registerServeCommand(cli);
16
- registerGenerateCommand(cli);
17
15
  return cli;
18
16
  }
19
17
  export async function runCli(argv) {
package/dist/native.js CHANGED
@@ -1,66 +1,32 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { nativeBindingName, platformKey } from './resolve-bin.js';
6
- function existsFile(filePath) {
7
- try {
8
- fs.accessSync(filePath, fs.constants.F_OK);
9
- return true;
10
- }
11
- catch {
12
- return false;
13
- }
14
- }
15
- function loadFromPath(bindingPath) {
4
+ import { platformKey } from './resolve-bin.js';
5
+ function loadModule(specifier) {
16
6
  const require = createRequire(import.meta.url);
17
- return require(bindingPath);
18
- }
19
- function monorepoBindingPath(key) {
20
- const here = path.dirname(fileURLToPath(import.meta.url));
21
- const repoRoot = path.resolve(here, '../../..');
22
- const profile = process.env.YYDB_NATIVE_PROFILE === 'debug' ? 'debug' : 'release';
23
- const base = path.join(repoRoot, 'target', profile);
24
- const candidates = process.platform === 'win32'
25
- ? [path.join(base, 'yydb_napi.dll'), path.join(base, 'yydb_napi.node')]
26
- : [path.join(base, 'libyydb_napi.so'), path.join(base, 'libyydb_napi.dylib'), path.join(base, 'yydb_napi.node')];
27
- for (const candidate of candidates) {
28
- if (existsFile(candidate)) {
29
- return candidate;
30
- }
31
- }
32
- const staged = path.join(repoRoot, 'projects', 'packages', `yydb-${key}`, nativeBindingName(key));
33
- return existsFile(staged) ? staged : null;
7
+ const loaded = require(specifier);
8
+ return (loaded.default ?? loaded);
34
9
  }
35
10
  /** Load the platform native binding used by the `yydb` CLI. */
36
11
  export function loadNative() {
37
12
  const fromEnv = process.env.YYDB_NATIVE?.trim();
38
13
  if (fromEnv) {
39
- if (!existsFile(fromEnv)) {
14
+ const bindingPath = path.resolve(fromEnv);
15
+ if (!fs.existsSync(bindingPath)) {
40
16
  throw new Error(`YYDB_NATIVE points to missing binding: ${fromEnv}`);
41
17
  }
42
- return loadFromPath(path.resolve(fromEnv));
18
+ return loadModule(bindingPath);
43
19
  }
44
20
  const key = platformKey();
45
- if (key) {
46
- const pkg = `@yydb/yydb-${key}`;
47
- const require = createRequire(import.meta.url);
48
- try {
49
- const pkgJson = require.resolve(`${pkg}/package.json`);
50
- const candidate = path.join(path.dirname(pkgJson), nativeBindingName(key));
51
- if (existsFile(candidate)) {
52
- return loadFromPath(candidate);
53
- }
54
- }
55
- catch {
56
- // optionalDependency not installed for this platform
57
- }
58
- const local = monorepoBindingPath(key);
59
- if (local) {
60
- return loadFromPath(local);
61
- }
21
+ if (!key) {
22
+ throw new Error(`YYDB native binding not available for ${process.platform}-${process.arch}`);
23
+ }
24
+ const pkg = `@yydb/yydb-${key}`;
25
+ try {
26
+ return loadModule(pkg);
27
+ }
28
+ catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ throw new Error(`YYDB native binding not found. Reinstall @yydb/yydb with optional dependencies, set YYDB_NATIVE, or stage ${pkg}. ${message}`);
62
31
  }
63
- throw new Error('YYDB native binding not found. Install the matching optionalDependency ' +
64
- `(@yydb/yydb-${key ?? '<platform>'}), set YYDB_NATIVE, or build ` +
65
- '`cargo build -p yydb-napi --release` in this repo.');
66
32
  }
@@ -2,7 +2,7 @@
2
2
  export type PlatformKey = 'win32-x64' | 'linux-x64' | 'darwin-x64' | 'darwin-arm64';
3
3
  /** Map Node platform/arch to optionalDependency package suffix. */
4
4
  export declare function platformKey(platform?: NodeJS.Platform, arch?: NodeJS.Architecture): PlatformKey | null;
5
- /** Native binding filename inside each `@yydb/yydb-<platform>` package. */
5
+ /** Native binding filename inside each `@yydb/yydb-<platform>` package `lib/`. */
6
6
  export declare function nativeBindingName(key: PlatformKey): string;
7
7
  /**
8
8
  * Resolve the TypeScript `yydb` CLI entry script.
@@ -13,9 +13,18 @@ export function platformKey(platform = process.platform, arch = process.arch) {
13
13
  return 'darwin-arm64';
14
14
  return null;
15
15
  }
16
- /** Native binding filename inside each `@yydb/yydb-<platform>` package. */
16
+ /** Native binding filename inside each `@yydb/yydb-<platform>` package `lib/`. */
17
17
  export function nativeBindingName(key) {
18
- return `yydb.${key}.node`;
18
+ switch (key) {
19
+ case 'win32-x64':
20
+ return 'yydb-win32-x64-msvc.node';
21
+ case 'linux-x64':
22
+ return 'yydb-linux-x64-gnu.node';
23
+ case 'darwin-x64':
24
+ return 'yydb-darwin-x64.node';
25
+ case 'darwin-arm64':
26
+ return 'yydb-darwin-arm64.node';
27
+ }
19
28
  }
20
29
  function existsFile(filePath) {
21
30
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yydb/yydb",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "YYDB all-in-one for Node: embedded engine, CLI, and local serve",
5
5
  "license": "MPL-2.0",
6
6
  "type": "module",
@@ -17,10 +17,6 @@
17
17
  "./cli": {
18
18
  "types": "./dist/cli.d.ts",
19
19
  "import": "./dist/cli.js"
20
- },
21
- "./generator": {
22
- "types": "./dist/generator/index.d.ts",
23
- "import": "./dist/generator/index.js"
24
20
  }
25
21
  },
26
22
  "files": [
@@ -52,7 +48,7 @@
52
48
  },
53
49
  "dependencies": {
54
50
  "@vmz/commander": "^0.2.0",
55
- "@yydb/yydb-client": "0.0.2"
51
+ "@yydb/yydb-client": "0.0.4"
56
52
  },
57
53
  "devDependencies": {
58
54
  "@types/node": "^20.10.0",
@@ -62,9 +58,9 @@
62
58
  "node": ">=18"
63
59
  },
64
60
  "optionalDependencies": {
65
- "@yydb/yydb-win32-x64": "0.0.2",
66
- "@yydb/yydb-linux-x64": "0.0.2",
67
- "@yydb/yydb-darwin-x64": "0.0.2",
68
- "@yydb/yydb-darwin-arm64": "0.0.2"
61
+ "@yydb/yydb-win32-x64": "0.0.4",
62
+ "@yydb/yydb-linux-x64": "0.0.4",
63
+ "@yydb/yydb-darwin-x64": "0.0.4",
64
+ "@yydb/yydb-darwin-arm64": "0.0.4"
69
65
  }
70
66
  }
@@ -1,3 +0,0 @@
1
- import type { Cli, ParsedOptions } from '@vmz/commander';
2
- export declare function registerGenerateCommand(cli: Cli): void;
3
- export declare function cmdGenerate(options: ParsedOptions): number;
@@ -1,25 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { generateTypeScript } from '../generator/index.js';
4
- import { requiredPositional, str } from './options.js';
5
- export function registerGenerateCommand(cli) {
6
- cli.command('generate', 'cli.cmd.generate')
7
- .option('-o, --output <path>', 'cli.opt.output')
8
- .option('--schema-version <n>', 'cli.opt.schema-version')
9
- .action((options) => cmdGenerate(options));
10
- }
11
- export function cmdGenerate(options) {
12
- const input = requiredPositional(options, 'generate');
13
- const output = str(options, 'output');
14
- if (!output) {
15
- throw new Error('generate requires --output <path>');
16
- }
17
- const schemaVersion = Number.parseInt(str(options, 'schema-version') ?? '1', 10);
18
- const source = fs.readFileSync(input, 'utf8');
19
- const ts = generateTypeScript(source, schemaVersion);
20
- const outPath = path.resolve(output);
21
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
22
- fs.writeFileSync(outPath, ts);
23
- console.log(`wrote ${outPath}`);
24
- return 0;
25
- }
@@ -1,3 +0,0 @@
1
- import { type TsSchemaIr } from './ir.js';
2
- /** Generate `schema.generated.ts` source from IR. */
3
- export declare function emitTypeScript(ir: TsSchemaIr): string;
@@ -1,26 +0,0 @@
1
- import { scalarToTs } from './ir.js';
2
- const BANNER = '// @generated by `yydb generate`, do not edit!';
3
- /** Generate `schema.generated.ts` source from IR. */
4
- export function emitTypeScript(ir) {
5
- const lines = [BANNER];
6
- lines.push(`export const APP_SCHEMA_VERSION = ${ir.schemaVersion};`);
7
- lines.push(`export const APP_SCHEMA = ${JSON.stringify(ir.vosSource)};`);
8
- for (const table of ir.tables) {
9
- lines.push(emitInterfaceRow(table));
10
- }
11
- lines.push(emitInterfaceAppDb(ir));
12
- lines.push(`export type AppDbTableName = keyof AppDb["tables"];`);
13
- return `${lines.join('\n')}\n`;
14
- }
15
- function emitInterfaceRow(table) {
16
- const lines = [`export interface ${table.name}Row {`];
17
- for (const field of table.fields) {
18
- lines.push(` ${field.name}: ${scalarToTs(field.ty)};`);
19
- }
20
- lines.push('}');
21
- return lines.join('\n');
22
- }
23
- function emitInterfaceAppDb(ir) {
24
- const tableLines = ir.tables.map((table) => ` ${table.name}: ${table.name}Row;`);
25
- return ['export interface AppDb {', ' tables: {', ...tableLines, ' };', '}'].join('\n');
26
- }
@@ -1,13 +0,0 @@
1
- export declare const APP_SCHEMA_VERSION = 1;
2
- export declare const APP_SCHEMA = "table Setting {\n @@id: uuid,\n key: utf8,\n value: utf8,\n}\n";
3
- export interface SettingRow {
4
- id: string;
5
- key: string;
6
- value: string;
7
- }
8
- export interface AppDb {
9
- tables: {
10
- Setting: SettingRow;
11
- };
12
- }
13
- export type AppDbTableName = keyof AppDb['tables'];
@@ -1,3 +0,0 @@
1
- // @generated by `yydb generate`, do not edit!
2
- export const APP_SCHEMA_VERSION = 1;
3
- export const APP_SCHEMA = 'table Setting {\n @@id: uuid,\n key: utf8,\n value: utf8,\n}\n';
@@ -1,6 +0,0 @@
1
- export type { SchemaField, SchemaTable, ScalarType, TsSchemaIr } from './ir.js';
2
- export { emitTypeScript } from './emit-typescript.js';
3
- export { parseVosSubset, VosParseError } from './parse-vos.js';
4
- /** Generate TypeScript from a VOS document. */
5
- export declare function generateTypeScript(vosSource: string, schemaVersion: number): string;
6
- export { generateTypeScript as generateTypescript };
@@ -1,10 +0,0 @@
1
- import { emitTypeScript } from './emit-typescript.js';
2
- import { parseVosSubset } from './parse-vos.js';
3
- export { emitTypeScript } from './emit-typescript.js';
4
- export { parseVosSubset, VosParseError } from './parse-vos.js';
5
- /** Generate TypeScript from a VOS document. */
6
- export function generateTypeScript(vosSource, schemaVersion) {
7
- const ir = parseVosSubset(vosSource, schemaVersion);
8
- return emitTypeScript(ir);
9
- }
10
- export { generateTypeScript as generateTypescript };
@@ -1,20 +0,0 @@
1
- /** Scalar types supported by the MVP emitter (expand with `vos-parser`). */
2
- export type ScalarType = 'uuid' | 'utf8' | 'bool' | 'i64' | 'f64';
3
- export interface SchemaField {
4
- readonly name: string;
5
- readonly ty: ScalarType;
6
- /** True when this field is declared via `@@id`. */
7
- readonly isId: boolean;
8
- }
9
- export interface SchemaTable {
10
- readonly name: string;
11
- readonly fields: readonly SchemaField[];
12
- }
13
- /** One schema document ready for TypeScript emission. */
14
- export interface TsSchemaIr {
15
- /** Original VOS source (database truth embedded into generated module). */
16
- readonly vosSource: string;
17
- readonly schemaVersion: number;
18
- readonly tables: readonly SchemaTable[];
19
- }
20
- export declare function scalarToTs(ty: ScalarType): string;
@@ -1,12 +0,0 @@
1
- export function scalarToTs(ty) {
2
- switch (ty) {
3
- case 'uuid':
4
- case 'utf8':
5
- return 'string';
6
- case 'bool':
7
- return 'boolean';
8
- case 'i64':
9
- case 'f64':
10
- return 'number';
11
- }
12
- }
@@ -1,6 +0,0 @@
1
- import type { TsSchemaIr } from './ir.js';
2
- export declare class VosParseError extends Error {
3
- constructor(message: string);
4
- }
5
- /** Interim VOS subset parser for `yydb generate` (MVP). */
6
- export declare function parseVosSubset(source: string, schemaVersion: number): TsSchemaIr;
@@ -1,149 +0,0 @@
1
- export class VosParseError extends Error {
2
- constructor(message) {
3
- super(message);
4
- this.name = 'VosParseError';
5
- }
6
- }
7
- /** Interim VOS subset parser for `yydb generate` (MVP). */
8
- export function parseVosSubset(source, schemaVersion) {
9
- if (source.includes('\0')) {
10
- throw new VosParseError('VOS document must not contain NUL bytes');
11
- }
12
- let normalized = source.startsWith('\uFEFF') ? source.slice(1) : source;
13
- normalized = normalized.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14
- const lexer = new Lexer(normalized);
15
- const tables = [];
16
- while (!lexer.eof()) {
17
- lexer.skipWsAndComments();
18
- if (lexer.eof()) {
19
- break;
20
- }
21
- tables.push(parseTable(lexer));
22
- }
23
- if (tables.length === 0) {
24
- throw new VosParseError('expected at least one `table` declaration');
25
- }
26
- return {
27
- vosSource: normalized,
28
- schemaVersion,
29
- tables,
30
- };
31
- }
32
- function parseTable(lexer) {
33
- lexer.expectIdent('table');
34
- const name = lexer.expectAnyIdent();
35
- lexer.expectPunct('{');
36
- const fields = [];
37
- while (true) {
38
- lexer.skipWsAndComments();
39
- if (lexer.eatPunct('}')) {
40
- break;
41
- }
42
- if (lexer.eatPunct('@')) {
43
- lexer.expectPunct('@');
44
- const attr = lexer.expectAnyIdent();
45
- if (attr !== 'id') {
46
- throw new VosParseError(`unsupported table attribute \`@@${attr}\` (MVP supports \`@@id\` only)`);
47
- }
48
- lexer.expectPunct(':');
49
- const ty = parseScalar(lexer);
50
- fields.push({ name: 'id', ty, isId: true });
51
- }
52
- else {
53
- const fieldName = lexer.expectAnyIdent();
54
- lexer.expectPunct(':');
55
- const ty = parseScalar(lexer);
56
- fields.push({ name: fieldName, ty, isId: false });
57
- }
58
- lexer.skipWsAndComments();
59
- lexer.eatPunct(',');
60
- }
61
- if (!fields.some((field) => field.isId)) {
62
- throw new VosParseError(`table \`${name}\` requires \`@@id: <type>\``);
63
- }
64
- return { name, fields };
65
- }
66
- function parseScalar(lexer) {
67
- const name = lexer.expectAnyIdent();
68
- switch (name) {
69
- case 'uuid':
70
- return 'uuid';
71
- case 'utf8':
72
- return 'utf8';
73
- case 'bool':
74
- return 'bool';
75
- case 'i64':
76
- return 'i64';
77
- case 'f64':
78
- return 'f64';
79
- default:
80
- throw new VosParseError(`unsupported scalar type \`${name}\` (MVP: uuid, utf8, bool, i64, f64)`);
81
- }
82
- }
83
- class Lexer {
84
- src;
85
- i = 0;
86
- constructor(src) {
87
- this.src = src;
88
- }
89
- eof() {
90
- return this.i >= this.src.length;
91
- }
92
- peek() {
93
- return this.src[this.i];
94
- }
95
- bump() {
96
- const ch = this.peek();
97
- if (ch === undefined) {
98
- return undefined;
99
- }
100
- this.i += 1;
101
- return ch;
102
- }
103
- skipWsAndComments() {
104
- while (true) {
105
- while (this.peek() !== undefined && /\s/.test(this.peek())) {
106
- this.bump();
107
- }
108
- if (this.src.startsWith('//', this.i)) {
109
- while (this.peek() !== undefined && this.peek() !== '\n') {
110
- this.bump();
111
- }
112
- continue;
113
- }
114
- break;
115
- }
116
- }
117
- eatPunct(expected) {
118
- this.skipWsAndComments();
119
- if (this.peek() === expected) {
120
- this.bump();
121
- return true;
122
- }
123
- return false;
124
- }
125
- expectPunct(expected) {
126
- if (!this.eatPunct(expected)) {
127
- throw new VosParseError(`expected \`${expected}\` at byte ${this.i}`);
128
- }
129
- }
130
- expectIdent(expected) {
131
- const got = this.expectAnyIdent();
132
- if (got !== expected) {
133
- throw new VosParseError(`expected \`${expected}\`, found \`${got}\``);
134
- }
135
- }
136
- expectAnyIdent() {
137
- this.skipWsAndComments();
138
- const start = this.i;
139
- const first = this.peek();
140
- if (first === undefined || !/[A-Za-z_]/.test(first)) {
141
- throw new VosParseError(`expected identifier at byte ${this.i}`);
142
- }
143
- this.bump();
144
- while (this.peek() !== undefined && /[A-Za-z0-9_]/.test(this.peek())) {
145
- this.bump();
146
- }
147
- return this.src.slice(start, this.i);
148
- }
149
- }