New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@embracesql/shared

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@embracesql/shared - npm Package Compare versions

Comparing version 0.0.4 to 0.0.6

src/debounce.ts

4

package.json
{
"name": "@embracesql/shared",
"version": "0.0.4",
"version": "0.0.6",
"description": "EmbraceSQL shared types between browser and node.",

@@ -17,3 +17,3 @@ "type": "module",

},
"gitHead": "343e5761ee3ff99619a2ba34c18139d0ef6240ad"
"gitHead": "924be0edd5fb5b1941d1c9160198ddfda5b93b47"
}

@@ -1,6 +0,34 @@

import { GeneratesTypeScriptParser, GenerationContext } from ".";
import {
GeneratesTypeScript,
GenerationContext,
cleanIdentifierForTypescript,
} from ".";
import { DispatchOperation } from "./index";
import { camelCase, pascalCase } from "change-case";
import * as fs from "fs";
import * as path from "path";
/**
* Common name for return results.
*
* When you are looking for results... look here 🤪.
*/
export const RESULTS = "results";
/**
* Common name for passed in parameters used to filter and search.
*
* Think -- things that go in a WHERE clause.
*/
export const PARAMETERS = "parameters";
/**
* Common name for passed in values to set or read.
*
* Think -- thinks that go in a SELECT <columns> clause
* or in the SET <column>=<value> clause.
*/
export const VALUES = "values";
/**
* Enumeration tags for quick type discrimination via `switch`.

@@ -11,3 +39,3 @@ *

*/
export const enum ASTKind {
export enum ASTKind {
Node,

@@ -22,29 +50,39 @@ Database,

Types,
CreateOperation,
ReadOperation,
UpdateOperation,
DeleteOperation,
Scripts,
ScriptFolder,
Script,
Procedures,
Procedure,
ProcedureArgument,
Type,
Enum,
CompositeType,
AliasType,
Attribute,
DomainType,
ArrayType,
}
/**
* Nameable items, which is going to be nearly everything
* in the database.
*/
export interface IsNamed {
name: string;
typescriptName: string;
interface DatabaseNamed {
/**
* Fully qualified name as expected to exist in the database.
*
* Given that we are connecting to a single database, the names
* are of the form <schema>.<table>
*/
databaseName: string;
}
export function isNamed(node: ASTNode | IsNamed): node is IsNamed {
return (node as IsNamed).name !== undefined;
}
/**
* Some nodes are containers.
* A named type -- it's column like.
*/
export interface IsContainer extends IsNamed {
children: ASTNode[];
export interface NamedType {
name: string;
type: TypeNode;
}
export function isContainer(node: ASTNode | IsContainer): node is IsContainer {
return (node as IsContainer).children !== undefined;
}
/**

@@ -67,2 +105,3 @@ * Use this to visit and generate code.

before?: VisitationHandler<T>;
during?: VisitationHandler<T>;
after?: VisitationHandler<T>;

@@ -72,15 +111,48 @@ }

/**
* And a big old map of visitors for each node type.
* And a big old map kinds to types.
*/
export type ASTKindMap = {
[ASTKind.Node]: ASTNode;
[ASTKind.Database]: DatabaseNode;
[ASTKind.Schema]: SchemaNode;
[ASTKind.Table]: TableNode;
[ASTKind.Tables]: TableNode;
[ASTKind.Column]: ColumnNode;
[ASTKind.Index]: IndexNode;
[ASTKind.IndexColumn]: IndexColumnNode;
[ASTKind.Types]: TypesNode;
[ASTKind.CreateOperation]: CreateOperationNode;
[ASTKind.ReadOperation]: ReadOperationNode;
[ASTKind.UpdateOperation]: UpdateOperationNode;
[ASTKind.DeleteOperation]: DeleteOperationNode;
[ASTKind.Scripts]: ScriptsNode;
[ASTKind.ScriptFolder]: ScriptFolderNode;
[ASTKind.Script]: ScriptNode;
[ASTKind.Procedures]: ProceduresNode;
[ASTKind.Procedure]: ProcedureNode;
[ASTKind.ProcedureArgument]: ProcedureArgumentNode;
[ASTKind.Type]: TypeNode;
[ASTKind.Enum]: EnumTypeNode;
[ASTKind.CompositeType]: CompositeTypeNode;
[ASTKind.Attribute]: AttributeNode;
[ASTKind.AliasType]: AliasTypeNode;
[ASTKind.DomainType]: DomainTypeNode;
[ASTKind.ArrayType]: ArrayTypeNode;
};
/**
* Node type predicate.
*/
export function isNodeType<T extends ASTKind>(
node: ASTNode | undefined,
kind: T,
): node is ASTKindMap[T] {
return node?.kind === kind;
}
/**
* Mapping to set up visitors.
*/
export type VisitorMap = {
[ASTKind.Node]?: Visitor<ASTNode>;
[ASTKind.Database]?: Visitor<DatabaseNode>;
[ASTKind.Schema]?: Visitor<SchemaNode>;
[ASTKind.Table]?: Visitor<TableNode>;
[ASTKind.Tables]?: Visitor<TableNode>;
[ASTKind.Column]?: Visitor<ColumnNode>;
[ASTKind.Index]?: Visitor<IndexNode>;
[ASTKind.IndexColumn]?: Visitor<IndexColumnNode>;
[ASTKind.Type]?: Visitor<TypeNode>;
[ASTKind.Types]?: Visitor<TypeNode>;
[Kind in keyof ASTKindMap]?: Visitor<ASTKindMap[Kind]>;
};

@@ -97,5 +169,25 @@

public kind: ASTKind,
public parent?: NamedASTNode,
) {}
public parent?: ContainerNode,
) {
ASTNode._runningObjectTable.push(this);
parent?.add(this);
}
// track all ast nodes created
static _runningObjectTable: ASTNode[] = [];
static verify() {
// nodes with parents need parents to know this child
for (const node of ASTNode._runningObjectTable) {
if (node.parent) {
console.assert(
node.parent.includes(node),
`${ASTKind[node.kind]} ${(node as NamedASTNode).name} not in ${
node.parent.typescriptNamespacedName
}`,
);
}
}
}
async visit<T extends this>(context: GenerationContext): Promise<string> {

@@ -115,4 +207,5 @@ const generationBuffer = [""];

dispatchName(operation: DispatchOperation = ""): string {
return operation;
lookUpTo<T extends ASTKind>(kind: T): ASTKindMap[T] | undefined {
if (isNodeType(this.parent, kind)) return this.parent;
else return this.parent?.lookUpTo(kind);
}

@@ -125,7 +218,7 @@ }

*/
export abstract class NamedASTNode extends ASTNode implements IsNamed {
export abstract class NamedASTNode extends ASTNode {
constructor(
public name: string,
kind: ASTKind,
parent?: NamedASTNode,
parent?: ContainerNode,
) {

@@ -146,2 +239,14 @@ super(kind, parent);

}
get typescriptPropertyName() {
return camelCase(cleanIdentifierForTypescript(this.name));
}
get typescriptNamespacedPropertyName(): string {
if (this.parent) {
return `${this.parent.typescriptNamespacedName}.${this.typescriptPropertyName}`;
} else {
return `${this.typescriptPropertyName}`;
}
}
}

@@ -152,6 +257,3 @@

*/
export abstract class ContainerNode
extends NamedASTNode
implements IsContainer
{
export abstract class ContainerNode extends NamedASTNode {
children: ASTNode[] = [];

@@ -162,2 +264,10 @@ constructor(name: string, kind: ASTKind, parent?: ContainerNode) {

add(child: ASTNode) {
this.children.push(child);
}
includes(child: ASTNode) {
return this.children.includes(child);
}
async visit<T extends this>(context: GenerationContext): Promise<string> {

@@ -169,2 +279,5 @@ const generationBuffer = [""];

);
generationBuffer.push(
visitor?.during ? await visitor?.during(context, this as T) : "",
);

@@ -190,2 +303,3 @@ // and here is that recursion

private types = new Map<string | number, TypeNode>();
private tables = new Map<string | number, TableNode>();

@@ -196,9 +310,29 @@ constructor(public name: string) {

registerType(id: string | number, type: TypeNode) {
registerType(id: string | number, type: AbstractTypeNode) {
const existing = this.types.get(`${id}`);
if (existing) return existing;
this.types.set(`${id}`, type);
return type;
}
resolveType(id: string | number) {
return this.types.get(`${id}`);
resolveType<T extends AbstractTypeNode>(id: string | number) {
return this.types.get(`${id}`) as T;
}
registerTable(id: string | number, table: TableNode) {
this.tables.set(`${id}`, table);
}
resolveTable(id: string | number) {
return this.tables.get(`${id}`);
}
resolveSchema(name: string) {
const exists = this.children.find(
(c) => (c as unknown as NamedASTNode)?.name === name,
) as SchemaNode;
if (exists) return exists;
const schema = new SchemaNode(this, name);
return schema;
}
}

@@ -218,4 +352,16 @@

super(name, ASTKind.Schema, database);
new TypesNode(this);
new ProceduresNode(this);
}
get types() {
return this.children.find((c) => c.kind === ASTKind.Types) as TypesNode;
}
get procedures() {
return this.children.find(
(c) => c.kind === ASTKind.Procedures,
) as ProceduresNode;
}
async visit(context: GenerationContext): Promise<string> {

@@ -243,3 +389,3 @@ if (context?.skipSchemas?.includes(this.name)) {

export class TypesNode extends ContainerNode {
constructor(schema: SchemaNode) {
constructor(public schema: SchemaNode) {
super("Types", ASTKind.Types, schema);

@@ -250,14 +396,42 @@ }

/**
* Represents a single type inside of postgres.
* Shared base for type nodes.
*
* These are grouped by schema.
* These differ on their enumerated type kind
*/
export class TypeNode extends NamedASTNode {
export class AbstractTypeNode extends ContainerNode {
constructor(
name: string,
kind: ASTKind,
parent: ContainerNode,
public id: string | number,
private parser?: GeneratesTypeScript,
) {
super(name, kind, parent);
}
typescriptTypeParser(context: GenerationContext) {
return this.parser?.typescriptTypeParser(context);
}
typescriptNullOrUndefined(context: GenerationContext) {
console.assert(context);
return `if (from === null || from === undefined) return null;`;
}
typescriptTypeDefinition(context: GenerationContext) {
return this.parser?.typescriptTypeDefinition(context);
}
}
/**
* Represents a single type from a database.
*/
export class TypeNode extends AbstractTypeNode {
constructor(
name: string,
types: TypesNode,
public id: string | number,
public parser: GeneratesTypeScriptParser,
parser: GeneratesTypeScript,
) {
super(name, ASTKind.Type, types);
super(name, ASTKind.Type, types, id, parser);
}

@@ -267,12 +441,72 @@ }

/**
* Represents an array type from a database.
*/
export class ArrayTypeNode extends AbstractTypeNode {
public memberType?: AbstractTypeNode;
constructor(
name: string,
types: TypesNode,
public id: string | number,
) {
super(name, ASTKind.ArrayType, types, id);
}
typescriptTypeDefinition(context: GenerationContext) {
console.assert(context);
return `
Array<${this.memberType?.typescriptNamespacedName ?? "void"}>
`;
}
typescriptTypeParser(context: GenerationContext) {
console.assert(context);
if (this.memberType) {
return `
const rawArray = Array.isArray(from) ? from : JSON.parse(from as string) as unknown[];
return rawArray.map((e:unknown) => ${this.memberType.typescriptName}.parse(e));
`;
} else {
throw new Error(`${this.memberType} could not resolve type of element`);
}
}
typescriptNullOrUndefined(context: GenerationContext) {
console.assert(context);
return `if (from === null || from === undefined) return [];`;
}
}
/**
* Represents a single enum from a database.
*/
export class EnumTypeNode extends AbstractTypeNode {
constructor(
name: string,
public values: string[],
types: TypesNode,
public id: string | number,
parser: GeneratesTypeScript,
) {
super(name, ASTKind.Enum, types, id, parser);
}
override typescriptTypeParser(context: GenerationContext) {
console.assert(context);
return [
` if(Object.values(${this.typescriptNamespacedName}).includes(from as ${this.typescriptNamespacedName})) {`,
` return from as ${this.typescriptNamespacedName};`,
` } else {`,
` return undefined;`,
` }`,
].join("\n");
}
}
/**
* Collects all tables in a schema in a database.
*/
export class TablesNode extends ContainerNode {
constructor(schema: SchemaNode) {
constructor(public schema: SchemaNode) {
super("Tables", ASTKind.Tables, schema);
}
dispatchName(operation: DispatchOperation = "") {
return this.parent?.dispatchName(operation) ?? "";
}
}

@@ -286,19 +520,51 @@

*/
export class TableNode extends ContainerNode {
export class TableNode extends ContainerNode implements DatabaseNamed {
constructor(
tables: TablesNode,
public tables: TablesNode,
public name: string,
public type: CompositeTypeNode,
) {
super(name, ASTKind.Table, tables);
new CreateOperationNode(this);
}
dispatchName(operation: DispatchOperation = "") {
return `${this.parent?.dispatchName()}.${pascalCase(
this.name,
)}${operation}`;
get databaseName() {
return `${this.tables.schema.name}.${this.name}`;
}
get createOperation() {
return this.children.find(
(c) => c.kind === ASTKind.CreateOperation,
) as CreateOperationNode;
}
get primaryKey(): IndexNode | undefined {
return this.children.find((n) => (n as IndexNode).primaryKey) as IndexNode;
}
get columnsInPrimaryKey(): ColumnNode[] {
const primaryKeyNames = this.primaryKey
? this.primaryKey.columns.map((c) => c.name)
: [];
return this.allColumns.filter((a) => primaryKeyNames.includes(a.name));
}
get columnsNotInPrimaryKey(): ColumnNode[] {
const primaryKeyNames = this.primaryKey
? this.primaryKey.columns.map((c) => c.name)
: [];
return this.allColumns.filter((a) => !primaryKeyNames.includes(a.name));
}
get optionalColumns(): ColumnNode[] {
return this.children
.filter<ColumnNode>((n): n is ColumnNode => isNodeType(n, ASTKind.Column))
.filter((c) => c.hasDefault);
}
get allColumns(): ColumnNode[] {
return this.children.filter<ColumnNode>((n): n is ColumnNode =>
isNodeType(n, ASTKind.Column),
);
}
}

@@ -314,2 +580,4 @@

public type: TypeNode,
public hasDefault: boolean,
public allowsNull: boolean,
) {

@@ -337,12 +605,21 @@ super(name, ASTKind.Column, table);

constructor(
table: TableNode,
public table: TableNode,
public name: string,
public unique: boolean,
public primaryKey: boolean,
attributes: NamedType[],
) {
super(name, ASTKind.Index, table);
attributes.forEach((a) => new IndexColumnNode(this, a.name, a.type));
// important that the operations go after the attributes
// so that we can have a well defined `typescriptName`
new ReadOperationNode(this);
new UpdateOperationNode(this);
new DeleteOperationNode(this);
}
dispatchName(operation: DispatchOperation = "") {
return `${this.parent?.dispatchName()}.${camelCase(this.name)}${operation}`;
get typescriptName() {
return `By${pascalCase(
this.columns.map((c) => c.typescriptName).join("_"),
)}`;
}

@@ -360,10 +637,427 @@

*/
export class IndexColumnNode extends ContainerNode {
export class IndexColumnNode extends ContainerNode implements NamedType {
constructor(
table: IndexNode,
public index: IndexNode,
public name: string,
public type: TypeNode,
) {
super(name, ASTKind.IndexColumn, table);
super(name, ASTKind.IndexColumn, index);
}
}
// operations
export abstract class OperationNode extends ContainerNode {
get parametersType() {
return this.children
.filter<CompositeTypeNode>(
(c): c is CompositeTypeNode => c.kind === ASTKind.CompositeType,
)
.find((c) => c.name === PARAMETERS);
}
}
/**
* Function like operations -- scripts and procedures.
*/
export abstract class FunctionOperationNode extends OperationNode {
constructor(
name: string,
kind: ASTKind,
parent: ContainerNode,
public returnsMany: boolean,
) {
// always returnsMany
super(name, kind, parent);
}
/**
* Operations have results, which are each of this type.
*
* An operation can return either a single value or arrary of this type.
*/
get resultsType() {
return this.children
.filter<AbstractTypeNode>((c): c is AbstractTypeNode =>
[ASTKind.CompositeType, ASTKind.AliasType].includes(c.kind),
)
.find((c) => c.name === RESULTS);
}
/**
* The results type might be an alias -- resolve it to the target
* final type.
*/
get resultsResolvedType() {
const typeNode = this.resultsType;
// resolve type alias to a composite
return typeNode?.kind === ASTKind.AliasType
? (typeNode as AliasTypeNode).type
: typeNode;
}
}
/**
* Operation to create a new row in a table. Each table gets one.
*/
export class CreateOperationNode extends OperationNode {
constructor(public table: TableNode) {
super("create", ASTKind.CreateOperation, table);
}
}
/**
* Shared base class for index operations.
*
* This establishes a naming protocol per index.
*/
export abstract class IndexOperationNode extends OperationNode {
constructor(
name: string,
kind: ASTKind,
public index: IndexNode,
) {
super(name, kind, index);
}
}
/**
* Operation to read row(s) by index.
*/
export class ReadOperationNode extends IndexOperationNode {
constructor(public index: IndexNode) {
super("read", ASTKind.ReadOperation, index);
}
}
/**
* Operation to delete row(s) by index.
*/
export class DeleteOperationNode extends IndexOperationNode {
constructor(public index: IndexNode) {
super("delete", ASTKind.DeleteOperation, index);
}
}
/**
* Update row(s) by index.
*/
export class UpdateOperationNode extends IndexOperationNode {
constructor(public index: IndexNode) {
super("update", ASTKind.UpdateOperation, index);
}
}
/**
* Collects scripts into a hierarchy sourced from a
* folder tree on disk.
*/
export class ScriptsNode extends ContainerNode {
static SCRIPTS = "Scripts";
/**
* Loading up the scripts node by file system traversal.
*
* Once done, all scripts will be visited and loaded into the AST.
*/
static async loadAST(context: GenerationContext) {
if (context.sqlScriptsFrom) {
const rootPath = path.parse(path.join(context.sqlScriptsFrom));
const scriptsNode = new ScriptsNode(context.database, rootPath);
await ScriptFolderNode.loadAST(context, rootPath, scriptsNode);
return scriptsNode;
} else {
return undefined;
}
}
constructor(
public database: DatabaseNode,
public path: path.ParsedPath,
) {
super("Scripts", ASTKind.Scripts, database);
}
get typescriptNamespacedName() {
// not returning the database above, scripts serves as a backstop
return this.typescriptName;
}
}
/**
* A single folder of scripts on disk.
*/
export class ScriptFolderNode extends ContainerNode {
/**
* Asynchronous factory builds from a folder path on disk.
*/
static async loadAST(
context: GenerationContext,
searchPath: path.ParsedPath,
addToNode: ContainerNode,
) {
// reading the whole directory
const inPath = await fs.promises.readdir(
path.join(searchPath.dir, searchPath.base),
{
withFileTypes: true,
},
);
for (const entry of inPath) {
if (entry.isDirectory()) {
const folder = new ScriptFolderNode(
path.parse(path.join(entry.path, entry.name)),
addToNode,
);
await ScriptFolderNode.loadAST(context, folder.path, folder);
} else if (entry.name.endsWith(".sql")) {
await ScriptNode.loadAST(
context,
path.parse(path.join(entry.path, entry.name)),
addToNode,
);
}
}
}
constructor(
public path: path.ParsedPath,
parent: ContainerNode,
) {
super(path.name, ASTKind.ScriptFolder, parent);
}
}
/**
* A single script that is source from a .sql file on disk.
*/
export class ScriptNode extends FunctionOperationNode {
/**
* Asynchronous factory builds from a sql file on disk.
*/
static async loadAST(
context: GenerationContext,
scriptPath: path.ParsedPath,
addToNode: ContainerNode,
) {
console.assert(context);
new ScriptNode(
scriptPath,
await fs.promises.readFile(path.join(scriptPath.dir, scriptPath.base), {
encoding: "utf8",
}),
addToNode,
);
}
constructor(
public path: path.ParsedPath,
public script: string,
parent: ContainerNode,
) {
// always returnsMany
super(path.name, ASTKind.Script, parent, true);
}
}
/**
* Collects all procedures in a schema in a database.
*/
export class ProceduresNode extends ContainerNode {
constructor(public schema: SchemaNode) {
super("Procedures", ASTKind.Procedures, schema);
}
}
/**
* A single stored procedure or function.
*/
export class ProcedureNode
extends FunctionOperationNode
implements DatabaseNamed
{
constructor(
name: string,
public procedures: ProceduresNode,
public id: string | number,
public nameInDatabase: string,
returnsMany: boolean,
public isPseudoType: boolean,
) {
super(name, ASTKind.Procedure, procedures, returnsMany);
}
get databaseName() {
return `${this.procedures.schema.name}.${this.nameInDatabase}`;
}
}
/**
* A single argument to a procedure. This is a 'named type'
*/
export class ProcedureArgumentNode extends NamedASTNode {
constructor(
name: string,
public procedure: ProcedureNode,
public type: TypeNode,
public hasDefault: boolean,
) {
super(name, ASTKind.ProcedureArgument, procedure);
}
}
/**
* A composite type is built of named attributes, each with their own type.
*/
export class CompositeTypeNode extends AbstractTypeNode {
constructor(name: string, parent: ContainerNode, id: string | number) {
super(name, ASTKind.CompositeType, parent, id);
}
get attributes() {
return this.children.filter<AttributeNode>(
(c): c is AttributeNode => c.kind === ASTKind.Attribute,
);
}
override typescriptTypeDefinition(
context: GenerationContext,
): string | undefined {
console.assert(context);
const recordAttributes = this.children
.filter<AttributeNode>(
(c): c is AttributeNode => c.kind === ASTKind.Attribute,
)
.map((a) => {
if (a.type.kind === ASTKind.ArrayType) {
return `${a.typescriptPropertyName}: ${a.type.typescriptNamespacedName};`;
}
if (a.nullable) {
return `${a.typescriptPropertyName}: Nullable<${a.type.typescriptNamespacedName}>;`;
}
return `${a.typescriptPropertyName}: ${a.type.typescriptNamespacedName};`;
});
return ` { ${recordAttributes.join("\n")} } `;
}
override typescriptTypeParser(context: GenerationContext) {
console.assert(context);
// parsing on the client side needs to turn 'loose' json types
// into our database mapped types -- for example -- dates
// parsing on the server side -- the database driver is expected to have
// already done the parsing, so this is a no-op
return [
`if (${this.typescriptNamespacedName}.is(from)) {`,
` return {`,
this.children
.filter<AttributeNode>(
(c): c is AttributeNode => c.kind === ASTKind.Attribute,
)
.map(
(a) =>
`${a.typescriptPropertyName}: ${a.type.typescriptNamespacedName}.parse(from.${a.typescriptPropertyName}),`,
)
.join("\n"),
`};`,
`}`,
`throw new Error(JSON.stringify(from))`,
].join("\n");
}
}
/**
* A single named type.
*/
export class AttributeNode extends ContainerNode implements NamedType {
constructor(
public parent: CompositeTypeNode,
public name: string,
public index: number,
public type: TypeNode,
public required: boolean,
public nullable: boolean,
) {
super(name, ASTKind.Attribute, parent);
}
/**
* Generate a synthetic name based on the index
* when no name is provided.
*/
get typescriptPropertyName(): string {
if (this.name) return super.typescriptPropertyName;
else return `argument_${this.index}`;
}
}
/**
* Rename a type, useful in namespaces to allow consistent
* code generation.
*/
export class AliasTypeNode extends AbstractTypeNode {
constructor(
public name: string,
public type: TypeNode,
parent: ContainerNode,
) {
super(name, ASTKind.AliasType, parent, type.id);
}
override typescriptTypeDefinition(
context: GenerationContext,
): string | undefined {
console.assert(context);
if (this.name === RESULTS) {
if (this.type.kind === ASTKind.CompositeType) {
return `NullableMembers<${this.type.typescriptNamespacedName}>`;
}
return `Nullable<${this.type.typescriptNamespacedName}>`;
}
return `${this.type.typescriptNamespacedName}`;
}
override typescriptTypeParser(context: GenerationContext) {
console.assert(context);
// delegate to the actual type
return `return ${this.type.typescriptNamespacedName}.parse(from)`;
}
}
/**
* A domain type is much like an alias, in that it gives a new name
* to an existing type.
*
* The difference is that the database will narrow the range of acceptable
* values for a domain.
*
* For example, imagine a ... useless admittedly ... year type:
*
* ```sql
* CREATE DOMAIN year AS integer
* CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));
* ```
*
*/
export class DomainTypeNode extends AbstractTypeNode {
private _baseType?: AbstractTypeNode;
constructor(name: string, parent: ContainerNode, id: string | number) {
super(name, ASTKind.DomainType, parent, id);
}
set baseType(baseType: AbstractTypeNode | undefined) {
this._baseType = baseType;
}
get baseType() {
return this._baseType;
}
override typescriptTypeDefinition(context: GenerationContext) {
console.assert(context);
// just alias the base type
return `${this.baseType?.typescriptNamespacedName}`;
}
override typescriptTypeParser(context: GenerationContext) {
console.assert(context);
// base type type parser
return `return ${this.baseType?.typescriptNamespacedName}.parse(from);`;
}
}

@@ -6,2 +6,3 @@ import { DatabaseNode, VisitorMap } from "./ast";

export * from "./parsers";
export * from "./debounce";

@@ -38,8 +39,11 @@ /**

export type GenerationContextProps = {
skipSchemas?: string[];
sqlScriptsFrom?: string;
};
/**
* Shared context for the generation sequence.
*/
export type GenerationContext = {
sqlScriptsFrom?: string;
skipSchemas?: string[];
export type GenerationContext = GenerationContextProps & {
database: DatabaseNode;

@@ -77,8 +81,17 @@ handlers?: VisitorMap;

*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const undefinedIsNull = (value: any) => {
export function undefinedIsNull<T>(value: T | undefined) {
if (value === undefined) return null;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return value;
};
}
/**
* Adapter when we get a parsed null from the database but should
* return an undefined for TypeScript
* undefined to null.
*/
export function nullIsUndefined<T>(value: T | null) {
if (value === null) return undefined;
return value;
}
export type JsDate = Date;

@@ -95,4 +108,8 @@ export type Empty = Record<string, never>;

};
export type Nullable<T> = T | null | undefined;
export type Nullable<T> = T | null;
export type NullableMembers<T> = {
[Member in keyof T]: Nullable<T[Member]>;
};
export * from "./uuid";

@@ -105,2 +122,25 @@

export type { GeneratesTypeScriptParser } from "./typescript";
export type { GeneratesTypeScript as GeneratesTypeScriptParser } from "./typescript";
// identifier legalizer?
const notLegalInIdentifiers = /[^\w$]/g;
/**
* Clean identifiers to be only legal characters.
*/
export const cleanIdentifierForTypescript = (identifier: string) => {
return identifier.replace(notLegalInIdentifiers, "_");
};
type CommonKeys<S, T> = {
[K in keyof S & keyof T]: [S[K], T[K]] extends [T[K], S[K]] ? K : never;
}[keyof S & keyof T];
/**
* Make just SOME properties optional.
*/
export type PartiallyOptional<T, U> =
// keys in common -- these will be optional
Partial<Pick<T, CommonKeys<T, U>>> &
// keys not in common, these will remain as is from T
Omit<T, CommonKeys<T, U>>;

@@ -11,3 +11,3 @@ import { delimiter } from ".";

const separators = parsimmon.oneOf("{},");
const arraySeparators = parsimmon.oneOf("{},");

@@ -54,3 +54,3 @@ /**

const quotedString = parsimmon
.alt(neverRequiresEscape, parseEscapedArrayValue, separators)
.alt(neverRequiresEscape, parseEscapedArrayValue, arraySeparators)
.many()

@@ -57,0 +57,0 @@ .wrap(parsimmon.string('"'), parsimmon.string('"'))

@@ -15,3 +15,3 @@ import { interleave } from ".";

*/
const SEPARATORS = `(),`;
const SEPARATORS = `{}(),`;
const separators = parsimmon.oneOf(SEPARATORS);

@@ -18,0 +18,0 @@ const startComposite = parsimmon.string("(");

@@ -1,48 +0,16 @@

import { GenerationContext } from ".";
import { ASTNode, Visitor, isNamed } from "./ast";
import { pascalCase } from "change-case";
import { GenerationContext, NamedASTNode } from ".";
/**
* Give that node a typescript style type name.
*/
export function typescriptTypeName(node: ASTNode) {
if (isNamed(node)) {
return pascalCase(node.name);
} else {
return "";
}
}
/**
* Give that node a typescript style type name.
*/
export function typescriptFullyQualifiedTypeName(node: ASTNode): string {
if (isNamed(node)) {
return node.parent
? `${typescriptFullyQualifiedTypeName(node.parent)}.${pascalCase(
node.name,
)}`
: `${pascalCase(node.name)}`;
} else {
return "";
}
}
/**
* This is a really simple visitor that names the node into a namespace.
*/
export const NamespaceVisitor: Visitor<ASTNode> = {
before: async (_, node) => {
if (isNamed(node)) {
return `export namespace ${typescriptTypeName(node)} {`;
} else {
return "";
}
export const NamespaceVisitor = {
before: async (context: GenerationContext, node: NamedASTNode) => {
console.assert(context);
console.assert(node);
return `export namespace ${node.typescriptName} {`;
},
after: async (_, node) => {
if (isNamed(node)) {
return "}";
} else {
return "";
}
after: async (context: GenerationContext, node: NamedASTNode) => {
console.assert(context);
console.assert(node);
return "}";
},

@@ -59,4 +27,11 @@ };

*/
export interface GeneratesTypeScriptParser {
export interface GeneratesTypeScript {
/**
* Generate code that parses a string into a typed value.
*/
typescriptTypeParser(context: GenerationContext): string;
/**
* Generate code that defines the right hand side of a `type =` statement.
*/
typescriptTypeDefinition(context: GenerationContext): string;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc