Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
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
17 changes: 17 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,21 @@ export type OnDemand = {
isBSONError(value: unknown): value is BSONError;
};
parseToElements: (this: void, bytes: Uint8Array, startOffset?: number) => Iterable<BSONElement>;
parseToStructure: <
TRoot extends Container = {
dest: Record<string, unknown>;
kind: 'object';
}
>(
bytes: Uint8Array,
startOffset?: number,
root?: TRoot,
reviver?: BSONReviver
) => TRoot extends undefined ? Record<string, unknown> : TRoot['dest'];
// Types
BSONElement: BSONElement;
Container: Container;
BSONReviver: BSONReviver;
};

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

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

Object.freeze(onDemand);
Expand Down
12 changes: 8 additions & 4 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,11 @@ 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 Expand Up @@ -80,7 +82,9 @@ function findNull(bytes: Uint8Array, offset: number): number {
* @public
* @experimental
*/
export function parseToElements(bytes: Uint8Array, startOffset = 0): Iterable<BSONElement> {
export function parseToElements(bytes: Uint8Array, pOffset?: number | null): Iterable<BSONElement> {
Comment thread
baileympearson marked this conversation as resolved.
Outdated
const startOffset = pOffset ?? 0;

if (bytes.length < 5) {
throw new BSONOffsetError(
`Input must be at least 5 bytes, got ${bytes.length} bytes`,
Expand Down
143 changes: 143 additions & 0 deletions src/parser/on_demand/parse_to_structure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { type Code } from '../../code';
import { type BSONElement, getSize, parseToElements } from './parse_to_elements';

/** @internal */
const DEFAULT_REVIVER: BSONReviver = (
_bytes: Uint8Array,
_container: Container,
_element: BSONElement
) => null;

/** @internal */
function parseToElementsToArray(bytes: Uint8Array, offset?: number | null): BSONElement[] {
const res = parseToElements(bytes, offset);
return Array.isArray(res) ? res : [...res];
}

/** @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
dest: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
Comment thread
baileympearson marked this conversation as resolved.

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

/**
* @experimental
* @public
*/
export function parseToStructure<
TRoot extends Container = {
dest: Record<string, unknown>;
kind: 'object';
}
>(
bytes: Uint8Array,
startOffset?: number | null,
pRoot?: TRoot | null,
pReviver?: BSONReviver | null
Comment thread
baileympearson marked this conversation as resolved.
): TRoot extends undefined ? Record<string, unknown> : TRoot['dest'] {
const root = pRoot ?? {
kind: 'object',
dest: Object.create(null) as Record<string, unknown>
};

const reviver = pReviver ?? DEFAULT_REVIVER;

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

/** BSONElement offsets: type indicator and value offset */
const enum e {
type = 0,
offset = 3
}

/** BSON Embedded types */
const enum t {
Comment thread
nbbeeken marked this conversation as resolved.
Outdated
object = 3,
array = 4,
javascriptWithScope = 15
}

embedded: while (ctx !== null) {
for (
let it: BSONElement | undefined = ctx.elements[ctx.elementOffset++];
Comment thread
baileympearson marked this conversation as resolved.
Outdated
Comment thread
aditi-khare-mongoDB marked this conversation as resolved.
Outdated
it != null;
it = ctx.elements[ctx.elementOffset++]
) {
const type = it[e.type];
const offset = it[e.offset];

const container = reviver(bytes, ctx.container, it);
const isEmbeddedType =
type === t.object || type === t.array || type === t.javascriptWithScope;

if (container != null && isEmbeddedType) {
const docOffset: number =
type !== t.javascriptWithScope
? offset
: // value offset + codeSize + value int + code int
offset + getSize(bytes, offset + 4) + 4 + 4;

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

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

return root.dest;
}
Loading