@bscotch/yy
Version:
Stringify, parse, read, and write GameMaker yy and yyp files.
126 lines • 4.22 kB
JavaScript
import { z } from 'zod';
export const nameField = '%Name';
export function randomString(length = 32) {
let a = '';
for (let i = 0; i < length; i++) {
a += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'[(Math.random() * 60) | 0];
}
return a;
}
export class FixedNumber extends Number {
digits;
constructor(value, digits = 1) {
super(value.valueOf());
this.digits = digits;
}
[Symbol.toPrimitive](hint) {
return hint === 'string' ? this.toString() : this.valueOf();
}
toString() {
return this.toFixed(this.digits);
}
toJSON() {
return this.valueOf();
}
}
/**
* A wrapper that transforms the wrapped value to `undefined` and marks it as optional.
* Useful for fields you want to provide for extra
* information when transforming parents, since the
* `z.input<>` inferred type will show this field,
* but that don't normally exist in the source data.
*/
export function hint(schema) {
return schema.optional().transform(() => undefined);
}
export function fixedNumber(schema = z.number(), digits = 1) {
const coercedToNumber = z.preprocess((arg) => arg instanceof FixedNumber || typeof arg === 'number' ? +arg : arg, schema);
return coercedToNumber.transform((value) => new FixedNumber(value, digits));
}
/**
* Schema for a number or bigint cast to a bigint
*/
export function bigNumber() {
return z
.union([z.number(), z.bigint()])
.transform((value) => (typeof value === 'bigint' ? value : BigInt(value)));
}
/**
* Ensure that an object is initialized to an empty
* object, allowing for default fields to be populated.
*/
export function ensureObject(obj) {
return z.preprocess((arg) => arg || {}, obj);
}
/**
* Ensure that an array is initialized to an array with
* at least one element, allowing for defaults to be
* populated in each element.
*/
export function ensureObjects(obj, minItems = 1) {
return z.preprocess((arg) => {
arg = typeof arg === 'undefined' ? [] : arg;
if (Array.isArray(arg) && arg.length < minItems) {
const newItems = [...Array(Math.max(minItems - arg.length, 0))].map(() => ({}));
arg.push(...newItems);
}
return arg;
}, z.array(obj));
}
/**
* Shorthand for a `ZodObject` instance that doesn't strip
* out any unknown keys, and that logs unexpected keys to
* the console.
*/
export function unstable(shape) {
return z.object(shape).catchall(z.unknown().superRefine((_arg, ctx) => {
// The new format for name/resourcetype keys should be ignore, since those are handled in other ways.
const isNewKey = `${ctx.path.at(-1)}`.match(/^[$%]/);
if (!isNewKey) {
console.log(`WARNING: Unexpected Key "${ctx.path.join('/')}"`);
}
}));
}
export function getYyResourceId(yyType, name) {
return {
name,
path: `${yyType}/${name}/${name}.yy`,
};
}
export function yyResourceIdSchemaGenerator(yyType) {
const pathFromName = (name) => `${yyType}/${name}/${name}.yy`;
return z.preprocess((arg) => {
if (arg === null || !['undefined', 'object'].includes(typeof arg)) {
return arg;
}
const objectId = arg === undefined ? {} : arg;
if (objectId.name && !objectId.path) {
objectId.path = pathFromName(objectId.name);
}
return objectId;
}, z
.object({
/** Object name */
name: z.string(),
/** Object resource path, e.g. "objects/{name}/{name}.yy" */
path: z.string(),
})
.refine((arg) => arg.path === pathFromName(arg.name)));
}
export function yyIsNewFormat(yyData) {
if (!yyData || typeof yyData !== 'object')
return false;
if (nameField in yyData && yyData[nameField] !== undefined)
return true;
if ('resourceType' in yyData && yyData.resourceType === '2.0')
return true;
return false;
}
export function isObjectWithField(obj, field) {
return (obj !== null &&
typeof obj === 'object' &&
field in obj &&
// @ts-expect-error
obj[field] !== undefined);
}
//# sourceMappingURL=utility.js.map