convex
Version:
Client for the Convex Cloud
316 lines (315 loc) • 9.61 kB
JavaScript
;
import * as Base64 from "base64-js";
const LITTLE_ENDIAN = true;
const MIN_INT64 = BigInt("-9223372036854775808");
const MAX_INT64 = BigInt("9223372036854775807");
const ZERO = BigInt("0");
const EIGHT = BigInt("8");
const TWOFIFTYSIX = BigInt("256");
export class GenericId {
constructor(tableName, id) {
this.tableName = tableName;
this.id = id;
}
equals(other) {
if (other instanceof GenericId) {
return this.tableName === other.tableName && this.id === other.id;
}
return false;
}
static fromJSON(obj) {
if (typeof obj.$id !== "string") {
throw new Error(
`Object ${JSON.stringify(obj)} isn't a valid Id: $id isn't a string.`
);
}
const parts = obj.$id.split("|");
if (parts.length !== 2) {
throw new Error(
`Object ${JSON.stringify(obj)} isn't a valid Id: Wrong number of parts.`
);
}
return new GenericId(parts[0], parts[1]);
}
toJSON() {
const idString = `${this.tableName}|${this.id}`;
return { $id: idString };
}
toString() {
return this.id;
}
inspect() {
return `Id('${this.tableName}', '${this.id}')`;
}
}
function isSpecial(n) {
return Number.isNaN(n) || !Number.isFinite(n) || Object.is(n, -0);
}
export function slowBigIntToBase64(value) {
if (value < ZERO) {
value -= MIN_INT64 + MIN_INT64;
}
let hex = value.toString(16);
if (hex.length % 2 == 1)
hex = "0" + hex;
const bytes = new Uint8Array(new ArrayBuffer(8));
let i = 0;
for (const hexByte of hex.match(/.{2}/g).reverse()) {
bytes.set([parseInt(hexByte, 16)], i++);
value >>= EIGHT;
}
return Base64.fromByteArray(bytes);
}
export function slowBase64ToBigInt(encoded) {
const integerBytes = Base64.toByteArray(encoded);
if (integerBytes.byteLength !== 8) {
throw new Error(
`Received ${integerBytes.byteLength} bytes, expected 8 for $integer`
);
}
let value = ZERO;
let power = ZERO;
for (const byte of integerBytes) {
value += BigInt(byte) * TWOFIFTYSIX ** power;
power++;
}
if (value > MAX_INT64) {
value += MIN_INT64 + MIN_INT64;
}
return value;
}
export function modernBigIntToBase64(value) {
if (value < MIN_INT64 || MAX_INT64 < value) {
throw new Error(
`BigInt ${value} does not fit into a 64-bit signed integer.`
);
}
const buffer = new ArrayBuffer(8);
new DataView(buffer).setBigInt64(0, value, true);
return Base64.fromByteArray(new Uint8Array(buffer));
}
export function modernBase64ToBigInt(encoded) {
const integerBytes = Base64.toByteArray(encoded);
if (integerBytes.byteLength !== 8) {
throw new Error(
`Received ${integerBytes.byteLength} bytes, expected 8 for $integer`
);
}
const intBytesView = new DataView(integerBytes.buffer);
return intBytesView.getBigInt64(0, true);
}
export const bigIntToBase64 = DataView.prototype.setBigInt64 ? modernBigIntToBase64 : slowBigIntToBase64;
export const base64ToBigInt = DataView.prototype.getBigInt64 ? modernBase64ToBigInt : slowBase64ToBigInt;
const MAX_IDENTIFIER_LEN = 64;
const ALL_UNDERSCORES = /^_+$/;
const IDENTIFIER_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]{0,63}$/;
function validateObjectField(k) {
if (k.length === 0) {
throw new Error("Empty field names are disallowed.");
}
if (k.length > MAX_IDENTIFIER_LEN) {
throw new Error(
`Field name ${k} exceeds maximum field name length ${MAX_IDENTIFIER_LEN}.`
);
}
if (k.startsWith("$")) {
throw new Error(`Field name ${k} starts with a '$', which is reserved.`);
}
if (ALL_UNDERSCORES.test(k)) {
throw new Error(`Field name ${k} can't exclusively be underscores.`);
}
if (!IDENTIFIER_REGEX.test(k)) {
throw new Error(
`Field name ${k} must only contain alphanumeric characters or underscores and can't start with a number.`
);
}
}
function jsonToConvexInternal(value) {
if (value === null) {
return value;
}
if (typeof value === "boolean") {
return value;
}
if (typeof value === "number") {
return value;
}
if (typeof value === "string") {
return value;
}
if (value instanceof Array) {
return value.map(jsonToConvexInternal);
}
if (typeof value !== "object") {
throw new Error(`Unexpected type of ${value}`);
}
const entries = Object.entries(value);
if (entries.length === 1) {
const key = entries[0][0];
if (key === "$id" || key === "$weakRef" || key === "$strongRef") {
return GenericId.fromJSON(value);
}
if (key === "$bytes") {
if (typeof value.$bytes !== "string") {
throw new Error(`Malformed $bytes field on ${value}`);
}
return Base64.toByteArray(value.$bytes).buffer;
}
if (key === "$integer") {
if (typeof value.$integer !== "string") {
throw new Error(`Malformed $integer field on ${value}`);
}
return base64ToBigInt(value.$integer);
}
if (key === "$float") {
if (typeof value.$float !== "string") {
throw new Error(`Malformed $float field on ${value}`);
}
const floatBytes = Base64.toByteArray(value.$float);
if (floatBytes.byteLength !== 8) {
throw new Error(
`Received ${floatBytes.byteLength} bytes, expected 8 for $float`
);
}
const floatBytesView = new DataView(floatBytes.buffer);
const float = floatBytesView.getFloat64(0, LITTLE_ENDIAN);
if (!isSpecial(float)) {
throw new Error(`Float ${float} should be encoded as a number`);
}
return float;
}
if (key === "$set") {
if (!(value.$set instanceof Array)) {
throw new Error(`Malformed $set field on ${value}`);
}
return new Set(value.$set.map(jsonToConvexInternal));
}
if (key === "$map") {
if (!(value.$map instanceof Array)) {
throw new Error(`Malformed $map field on ${value}`);
}
const map = /* @__PURE__ */ new Map();
for (const pair of value.$map) {
if (!(pair instanceof Array) || pair.length !== 2) {
throw new Error(`Malformed pair in $map ${value}`);
}
const k = jsonToConvexInternal(pair[0]);
const v = jsonToConvexInternal(pair[1]);
map.set(k, v);
}
return map;
}
}
const out = {};
for (const [k, v] of Object.entries(value)) {
validateObjectField(k);
out[k] = jsonToConvexInternal(v);
}
return out;
}
export function jsonToConvex(value) {
return jsonToConvexInternal(value);
}
function stringifyValueForError(value) {
return JSON.stringify(value, (_key, value2) => {
if (value2 === void 0) {
return "undefined";
}
return value2;
});
}
function convexToJsonInternal(value, originalValue, context) {
if (value === void 0) {
throw new Error(
`undefined is not a valid Convex value (present at path ${context} in original object ${stringifyValueForError(
originalValue
)}). To learn about Convex's supported types, see https://docs.convex.dev/using/types.`
);
}
if (value === null) {
return value;
}
if (value instanceof GenericId) {
return value.toJSON();
}
if (typeof value === "bigint") {
if (value < MIN_INT64 || MAX_INT64 < value) {
throw new Error(
`BigInt ${value} does not fit into a 64-bit signed integer.`
);
}
return { $integer: bigIntToBase64(value) };
}
if (typeof value === "number") {
if (isSpecial(value)) {
const buffer = new ArrayBuffer(8);
new DataView(buffer).setFloat64(0, value, LITTLE_ENDIAN);
return { $float: Base64.fromByteArray(new Uint8Array(buffer)) };
} else {
return value;
}
}
if (typeof value === "boolean") {
return value;
}
if (typeof value === "string") {
return value;
}
if (value instanceof ArrayBuffer) {
return { $bytes: Base64.fromByteArray(new Uint8Array(value)) };
}
if (value instanceof Array) {
return value.map(
(value2, i) => convexToJsonInternal(value2, originalValue, context + `[${i}]`)
);
}
if (value instanceof Set) {
return {
$set: [...value].map(
(value2, i) => convexToJsonInternal(value2, originalValue, context + `.keys()[${i}]`)
)
};
}
if (value instanceof Map) {
return {
$map: [...value].map(([k, v], i) => {
const jsonKey = convexToJsonInternal(
k,
originalValue,
context + `.keys()[${i}]`
);
const jsonValue = convexToJsonInternal(
v,
originalValue,
context + `.values()[${i}]`
);
return [jsonKey, jsonValue];
})
};
}
if (typeof value !== "object") {
throw new Error(
`${value} is not a supported Convex type (present at path ${context} in original object ${stringifyValueForError(
originalValue
)}). To learn about Convex's supported types, see https://docs.convex.dev/using/types.`
);
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== null && prototype !== Object.prototype) {
throw new Error(
`${value} is not a supported Convex type (present at path ${context} in original object ${stringifyValueForError(
originalValue
)}). To learn about Convex's supported types, see https://docs.convex.dev/using/types.`
);
}
const out = {};
for (const [k, v] of Object.entries(value)) {
validateObjectField(k);
out[k] = convexToJsonInternal(v, originalValue, context + `.${k}`);
}
return out;
}
export function convexToJson(value) {
return convexToJsonInternal(value, value, "");
}
//# sourceMappingURL=values.js.map