Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-enum-comparison": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{
Expand Down
2 changes: 1 addition & 1 deletion src/bson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export { BSONValue } from './bson_value';
export { BSONError, BSONVersionError, BSONRuntimeError } from './error';
export { BSONType } from './constants';
export { EJSON } from './extended_json';
export { onDemand } from './parser/on_demand/index';
export { onDemand, type OnDemand } from './parser/on_demand/index';

/** @public */
export interface Document {
Expand Down
9 changes: 9 additions & 0 deletions src/parser/on_demand/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type BSONError, BSONOffsetError } from '../../error';
import { type BSONElement, parseToElements } from './parse_to_elements';
import { type BSONReviver, type Container, parseToStructure } from './parse_to_structure';
/**
* @experimental
* @public
Expand All @@ -12,6 +13,13 @@ export type OnDemand = {
isBSONError(value: unknown): value is BSONError;
};
parseToElements: (this: void, bytes: Uint8Array, startOffset?: number) => Iterable<BSONElement>;
parseToStructure: <TRoot = Record<string, unknown>>(
this: void,
bytes: Uint8Array,
offset?: number,
root?: Container,
reviver?: BSONReviver
) => TRoot;
};

/**
Expand All @@ -21,6 +29,7 @@ export type OnDemand = {
const onDemand: OnDemand = Object.create(null);

onDemand.parseToElements = parseToElements;
onDemand.parseToStructure = parseToStructure;
onDemand.BSONOffsetError = BSONOffsetError;

Object.freeze(onDemand);
Expand Down
5 changes: 2 additions & 3 deletions src/parser/on_demand/parse_to_elements.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
import { BSONOffsetError } from '../../error';

/**
Expand Down Expand Up @@ -45,8 +44,8 @@ export type BSONElement = [
length: number
];

/** Parses a int32 little-endian at offset, throws if it is negative */
function getSize(source: Uint8Array, offset: number): number {
/** @internal Parses a int32 little-endian at offset, throws if it is negative */
export function getSize(source: Uint8Array, offset: number): number {
if (source[offset + 3] > 127) {
throw new BSONOffsetError('BSON size cannot be negative', offset);
}
Expand Down
138 changes: 138 additions & 0 deletions src/parser/on_demand/parse_to_structure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { type Code } from '../../code';
import { type BSONElement, getSize, parseToElements as p } from './parse_to_elements';

/** @internal TODO */
const DEFAULT_REVIVER = () => null;

/** @internal */
function parseToElements(...args: Parameters<typeof p>): BSONElement[] {
const res = p(...args);
return Array.isArray(res) ? res : [...res];
}

/**
* @internal
* BSONElement offsets
*/
const enum e {
type = 0,
nameOffset = 1,
nameLength = 2,
offset = 3,
length = 4
}

/**
* @internal
* Embedded bson types
*/
const enum t {
object = 3,
array = 4,
javascriptWithScope = 15
}

/** @internal */
type ParseContext = {
elementOffset: number;
elements: BSONElement[];
container: Container;
previous: ParseContext | null;
};

/**
* @experimental
* @public
* A union of the possible containers for BSON elements.
*
* Depending on kind, a reviver can accurately assign a value to a name on the container.
*/
export type Container =
| {
dest: Record<string, unknown>;
kind: 'object';
}
| {
dest: Map<string, unknown>;
kind: 'map';
}
| {
dest: Array<unknown>;
kind: 'array';
}
| {
dest: Code;
kind: 'code';
}
| {
kind: 'custom';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};

/**
* @experimental
* @public
*/
export type BSONReviver = (
bytes: Uint8Array,
container: Container,
element: BSONElement
) => Container | null;

/**
* @experimental
* @public
*/
export function parseToStructure<TRoot = Record<string, unknown>>(
bytes: Uint8Array,
startOffset?: number,
root?: Container,
reviver?: BSONReviver
): TRoot {
root ??= {
kind: 'object',
dest: Object.create(null)
};

reviver ??= DEFAULT_REVIVER;

let ctx: ParseContext | null = {
elementOffset: 0,
elements: parseToElements(bytes, startOffset),
container: root,
previous: null
};

embedded: while (ctx !== null) {
for (
let it: BSONElement | undefined = ctx.elements[ctx.elementOffset++];
it != null;
it = ctx.elements[ctx.elementOffset++]
) {
const maybeNewContainer = reviver(bytes, ctx.container, it);
const isEmbeddedType =
it[e.type] === t.object || it[e.type] === t.array || it[e.type] === t.javascriptWithScope;
const iterateEmbedded = maybeNewContainer != null && isEmbeddedType;

if (iterateEmbedded) {
const docOffset: number =
it[e.type] !== t.javascriptWithScope
? it[e.offset]
: it[e.offset] + getSize(bytes, it[e.offset] + 4) + 4 + 4; // value offset + codeSize + value int + code int

ctx = {
elementOffset: 0,
elements: parseToElements(bytes, docOffset),
container: maybeNewContainer,
previous: ctx
};

continue embedded;
}
}
ctx = ctx.previous;
}

return root.dest as unknown as TRoot;
}
Loading