@chiselstrike/api
Advanced tools
Comparing version
/// <reference lib="dom" /> | ||
declare type column = [string, string]; | ||
declare class Base { | ||
columns: column[]; | ||
limit?: number; | ||
constructor(columns: column[]); | ||
declare enum OpType { | ||
BaseEntity = "BaseEntity", | ||
Take = "Take", | ||
ColumnsSelect = "ColumnsSelect", | ||
RestrictionFilter = "RestrictionFilter", | ||
PredicateFilter = "PredicateFilter" | ||
} | ||
declare class BackingStore extends Base { | ||
name: string; | ||
readonly kind = "BackingStore"; | ||
constructor(columns: column[], name: string); | ||
/** | ||
* Base class for various Operators applicable on `ChiselCursor`. Each operator | ||
* should extend this class and pass on its `type` identifier from the `OpType` | ||
* enum. | ||
*/ | ||
declare abstract class Operator { | ||
readonly type: OpType; | ||
readonly inner: Operator | undefined; | ||
constructor(type: OpType, inner: Operator | undefined); | ||
/** Applies specified Operator `op` on each element of passed iterable | ||
* `iter` creating a new iterable. | ||
*/ | ||
abstract apply(iter: AsyncIterable<Record<string, unknown>>): AsyncIterable<Record<string, unknown>>; | ||
} | ||
declare class Join extends Base { | ||
left: Inner; | ||
right: Inner; | ||
readonly kind = "Join"; | ||
constructor(columns: column[], left: Inner, right: Inner); | ||
} | ||
declare class Filter extends Base { | ||
restrictions: Record<string, unknown>; | ||
inner: Inner; | ||
readonly kind = "Filter"; | ||
constructor(columns: column[], restrictions: Record<string, unknown>, inner: Inner); | ||
} | ||
declare type Inner = BackingStore | Join | Filter; | ||
/** ChiselCursor is a lazy iterator that will be used by ChiselStrike to construct an optimized query. */ | ||
export declare class ChiselCursor<T> { | ||
private type; | ||
private baseConstructor; | ||
private inner; | ||
constructor(type: { | ||
constructor(baseConstructor: { | ||
new (): T; | ||
} | undefined, inner: Inner); | ||
}, inner: Operator); | ||
/** Force ChiselStrike to fetch just the `...columns` that are part of the colums list. */ | ||
select(...columns: (keyof T)[]): ChiselCursor<Pick<T, (keyof T)>>; | ||
/** Restricts this cursor to contain only at most `limit_` elements */ | ||
take(limit_: number): ChiselCursor<T>; | ||
/** Restricts this cursor to contain just the objects that match the `Partial` object `restrictions`. */ | ||
/** Restricts this cursor to contain only at most `count` elements */ | ||
take(count: number): ChiselCursor<T>; | ||
/** | ||
* Restricts this cursor to contain only elements that match the given @predicate. | ||
*/ | ||
filter(predicate: (arg: T) => boolean): ChiselCursor<T>; | ||
/** | ||
* Restricts this cursor to contain just the objects that match the `Partial` | ||
* object `restrictions`. | ||
*/ | ||
filter(restrictions: Partial<T>): ChiselCursor<T>; | ||
/** Joins two ChiselCursors, by matching on the properties of the elements in their cursors. */ | ||
join<U>(right: ChiselCursor<U>): ChiselCursor<T & U>; | ||
/** Executes the function `func` for each element of this cursor. */ | ||
@@ -49,20 +51,19 @@ forEach(func: (arg: T) => void): Promise<void>; | ||
/** ChiselCursor implements asyncIterator, meaning you can use it in any asynchronous context. */ | ||
[Symbol.asyncIterator](): { | ||
next(): Promise<{ | ||
value: T; | ||
done: false; | ||
} | { | ||
done: true; | ||
}>; | ||
return(): { | ||
value: T; | ||
done: false; | ||
} | { | ||
done: true; | ||
}; | ||
}; | ||
[Symbol.asyncIterator](): AsyncGenerator<Awaited<T>, void, unknown>; | ||
/** Performs recursive descent via Operator.inner examining the whole operator | ||
* chain. If PredicateFilter is encountered, a backend query is generated and all consecutive | ||
* operations are applied on the resulting async iterable in TypeScript. In such a | ||
* case, the function returns the resulting AsyncIterable. | ||
* If no PredicateFilter is found, undefined is returned. | ||
*/ | ||
private makeTransformedQueryIter; | ||
private makeQueryIter; | ||
/** Recursively examines operator chain searching for ColumnsSelect operator. | ||
* Returns true if found, false otherwise. | ||
*/ | ||
private containsSelect; | ||
} | ||
export declare function chiselIterator<T>(type: { | ||
new (): T; | ||
}, c?: column[]): ChiselCursor<T>; | ||
}): ChiselCursor<T>; | ||
/** ChiselEntity is a class that ChiselStrike user-defined entities are expected to extend. | ||
@@ -79,3 +80,4 @@ * | ||
* | ||
* @param properties The properties of the created entity. | ||
* @param properties The properties of the created entity. If more than one property | ||
* is passed, the expected order of assignment is the same as Object.assign. | ||
* | ||
@@ -94,5 +96,10 @@ * @example | ||
* | ||
* // Create an entity from different JSON objects: | ||
* const otherUserJson = JSON.parse('{"username": "alice"}, {"email": "alice@example.com"}'); | ||
* const yetAnotherUser = User.build(userJson); | ||
* | ||
* // now optionally save them to the backend | ||
* await user.save(); | ||
* await anotherUser.save(); | ||
* await yetAnotherUser.save(); | ||
* ``` | ||
@@ -103,3 +110,3 @@ * @returns The persisted entity with given properties and the `id` property set. | ||
new (): T; | ||
}, properties: Record<string, unknown>): T; | ||
}, ...properties: Record<string, unknown>[]): T; | ||
/** saves the current object into the backend */ | ||
@@ -123,2 +130,20 @@ save(): Promise<void>; | ||
}, restrictions: Partial<T>): Promise<T | null>; | ||
/** | ||
* Deletes all entities that match the `restrictions` object. | ||
* | ||
* @example | ||
* ```typescript | ||
* export class User extends ChiselEntity { | ||
* username: string, | ||
* email: string, | ||
* } | ||
* const user = User.build({ username: "alice", email: "alice@example.com" }); | ||
* await user.save(); | ||
* | ||
* await User.delete({ email: "alice@example.com"}) | ||
* ``` | ||
*/ | ||
static delete<T extends ChiselEntity>(this: { | ||
new (): T; | ||
}, restrictions: Partial<T>): Promise<void>; | ||
} | ||
@@ -129,6 +154,18 @@ export declare class OAuthUser extends ChiselEntity { | ||
export declare function buildReadableStreamForBody(rid: number): ReadableStream<string>; | ||
/** | ||
* Gets a secret from the environment | ||
* | ||
* To allow a secret to be used, the server has to be run with * --allow-env <YOUR_SECRET> | ||
* | ||
* In development mode, all of your environment variables are accessible | ||
*/ | ||
declare type JSONValue = string | number | boolean | null | { | ||
[x: string]: JSONValue; | ||
} | Array<JSONValue>; | ||
export declare function getSecret(key: string): JSONValue | undefined; | ||
export declare function responseFromJson(body: unknown, status?: number): Response; | ||
export declare function labels(..._val: string[]): <T>(_target: T, _propertyName: string) => void; | ||
export declare function unique(): void; | ||
/** Returns the currently logged-in user or null if no one is logged in. */ | ||
export declare function loggedInUser(): Promise<OAuthUser | null>; | ||
export {}; |
// SPDX-FileCopyrightText: © 2021 ChiselStrike <info@chiselstrike.com> | ||
/// <reference path="lib.deno_core.d.ts" /> | ||
class Base { | ||
columns; | ||
limit; | ||
constructor(columns) { | ||
this.columns = columns; | ||
/// <reference types="./lib.deno_core.d.ts" /> | ||
/// <reference lib="dom" /> | ||
var OpType; | ||
(function (OpType) { | ||
OpType["BaseEntity"] = "BaseEntity"; | ||
OpType["Take"] = "Take"; | ||
OpType["ColumnsSelect"] = "ColumnsSelect"; | ||
OpType["RestrictionFilter"] = "RestrictionFilter"; | ||
OpType["PredicateFilter"] = "PredicateFilter"; | ||
})(OpType || (OpType = {})); | ||
/** | ||
* Base class for various Operators applicable on `ChiselCursor`. Each operator | ||
* should extend this class and pass on its `type` identifier from the `OpType` | ||
* enum. | ||
*/ | ||
class Operator { | ||
type; | ||
inner; | ||
constructor(type, inner) { | ||
this.type = type; | ||
this.inner = inner; | ||
} | ||
} | ||
// This represents a selection of some columns of a table in a DB. | ||
class BackingStore extends Base { | ||
/** | ||
* Specifies Entity whose elements are to be fetched. | ||
*/ | ||
class BaseEntity extends Operator { | ||
name; | ||
// The kind member is use to implement fully covered switch statements. | ||
kind = "BackingStore"; | ||
constructor(columns, name) { | ||
super(columns); | ||
constructor(name) { | ||
super(OpType.BaseEntity, undefined); | ||
this.name = name; | ||
} | ||
apply(_iter) { | ||
throw new Error("can't apply BaseEntity operator on an iterable"); | ||
} | ||
} | ||
// This represents an inner join between two chiselIterators. | ||
// FIXME: Add support for ON. | ||
class Join extends Base { | ||
left; | ||
right; | ||
kind = "Join"; | ||
constructor(columns, left, right) { | ||
super(columns); | ||
this.left = left; | ||
this.right = right; | ||
/** | ||
* Take operator takes first `count` elements from a collection. | ||
* The rest is ignored. | ||
*/ | ||
class Take extends Operator { | ||
count; | ||
constructor(count, inner) { | ||
super(OpType.Take, inner); | ||
this.count = count; | ||
} | ||
apply(iter) { | ||
const count = this.count; | ||
return { | ||
[Symbol.asyncIterator]: async function* () { | ||
if (count == 0) { | ||
return; | ||
} | ||
let i = 0; | ||
for await (const e of iter) { | ||
yield e; | ||
if (++i >= count) { | ||
break; | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
} | ||
class Filter extends Base { | ||
/** | ||
* Forces fetch of just the `columns` (fields) of a given entity. | ||
*/ | ||
class ColumnsSelect extends Operator { | ||
columns; | ||
constructor(columns, inner) { | ||
super(OpType.ColumnsSelect, inner); | ||
this.columns = columns; | ||
} | ||
apply(iter) { | ||
const columns = this.columns; | ||
return { | ||
[Symbol.asyncIterator]: async function* () { | ||
for await (const arg of iter) { | ||
const newObj = {}; | ||
for (const key of columns) { | ||
if (arg[key] !== undefined) { | ||
newObj[key] = arg[key]; | ||
} | ||
} | ||
yield newObj; | ||
} | ||
}, | ||
}; | ||
} | ||
} | ||
/** | ||
* PredicateFilter operator applies @predicate on each element and keeps | ||
* only those for which the @predicate returns true. | ||
*/ | ||
class PredicateFilter extends Operator { | ||
predicate; | ||
constructor(predicate, inner) { | ||
super(OpType.PredicateFilter, inner); | ||
this.predicate = predicate; | ||
} | ||
apply(iter) { | ||
const predicate = this.predicate; | ||
return { | ||
[Symbol.asyncIterator]: async function* () { | ||
for await (const arg of iter) { | ||
if (predicate(arg)) { | ||
yield arg; | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
} | ||
/** | ||
* RestrictionFilter operator applies `restrictions` on each element | ||
* and keeps only those where field value of a field, specified | ||
* by restriction key, equals to restriction value. | ||
*/ | ||
class RestrictionFilter extends Operator { | ||
restrictions; | ||
inner; | ||
kind = "Filter"; | ||
constructor(columns, restrictions, inner) { | ||
super(columns); | ||
constructor(restrictions, inner) { | ||
super(OpType.RestrictionFilter, inner); | ||
this.restrictions = restrictions; | ||
this.inner = inner; | ||
} | ||
apply(iter) { | ||
const restrictions = Object.entries(this.restrictions); | ||
return { | ||
[Symbol.asyncIterator]: async function* () { | ||
for await (const arg of iter) { | ||
verifyMatch: { | ||
for (const [key, value] of restrictions) { | ||
if (arg[key] != value) { | ||
break verifyMatch; | ||
} | ||
} | ||
yield arg; | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
} | ||
/** ChiselCursor is a lazy iterator that will be used by ChiselStrike to construct an optimized query. */ | ||
export class ChiselCursor { | ||
type; | ||
baseConstructor; | ||
inner; | ||
constructor(type, inner) { | ||
this.type = type; | ||
constructor(baseConstructor, inner) { | ||
this.baseConstructor = baseConstructor; | ||
this.inner = inner; | ||
@@ -52,63 +154,16 @@ } | ||
select(...columns) { | ||
const names = columns; | ||
const cs = this.inner.columns.filter((c) => names.includes(c[0])); | ||
switch (this.inner.kind) { | ||
case "BackingStore": { | ||
const b = new BackingStore(cs, this.inner.name); | ||
return new ChiselCursor(undefined, b); | ||
} | ||
case "Join": { | ||
const i = new Join(cs, this.inner.left, this.inner.right); | ||
return new ChiselCursor(undefined, i); | ||
} | ||
case "Filter": { | ||
const i = new Filter(cs, this.inner.restrictions, this.inner.inner); | ||
return new ChiselCursor(undefined, i); | ||
} | ||
} | ||
return new ChiselCursor(this.baseConstructor, new ColumnsSelect(columns, this.inner)); | ||
} | ||
/** Restricts this cursor to contain only at most `limit_` elements */ | ||
take(limit_) { | ||
const limit = (this.inner.limit == null) | ||
? limit_ | ||
: Math.min(limit_, this.inner.limit); | ||
// shallow copy okay because this is an array of strings | ||
const cs = [...this.inner.columns]; | ||
// FIXME: refactor to use the same path as select | ||
switch (this.inner.kind) { | ||
case "BackingStore": { | ||
const i = new BackingStore(cs, this.inner.name); | ||
i.limit = limit; | ||
return new ChiselCursor(this.type, i); | ||
} | ||
case "Join": { | ||
const i = new Join(cs, this.inner.left, this.inner.right); | ||
i.limit = limit; | ||
return new ChiselCursor(this.type, i); | ||
} | ||
case "Filter": { | ||
const i = new Filter(cs, this.inner.restrictions, this.inner.inner); | ||
i.limit = limit; | ||
return new ChiselCursor(this.type, i); | ||
} | ||
} | ||
/** Restricts this cursor to contain only at most `count` elements */ | ||
take(count) { | ||
return new ChiselCursor(this.baseConstructor, new Take(count, this.inner)); | ||
} | ||
/** Restricts this cursor to contain just the objects that match the `Partial` object `restrictions`. */ | ||
filter(restrictions) { | ||
const i = new Filter(this.inner.columns, restrictions, this.inner); | ||
return new ChiselCursor(this.type, i); | ||
} | ||
/** Joins two ChiselCursors, by matching on the properties of the elements in their cursors. */ | ||
join(right) { | ||
const s = new Set(); | ||
const columns = []; | ||
for (const c of this.inner.columns.concat(right.inner.columns)) { | ||
if (s.has(c[0])) { | ||
continue; | ||
} | ||
s.add(c[0]); | ||
columns.push(c); | ||
// Common implementation for filter overloads. | ||
filter(arg1) { | ||
if (typeof arg1 == "function") { | ||
return new ChiselCursor(this.baseConstructor, new PredicateFilter(arg1, this.inner)); | ||
} | ||
const i = new Join(columns, this.inner, right.inner); | ||
return new ChiselCursor(undefined, i); | ||
else { | ||
return new ChiselCursor(this.baseConstructor, new RestrictionFilter(arg1, this.inner)); | ||
} | ||
} | ||
@@ -126,3 +181,3 @@ /** Executes the function `func` for each element of this cursor. */ | ||
async toArray() { | ||
const arr = new Array(); | ||
const arr = []; | ||
for await (const t of this) { | ||
@@ -134,35 +189,80 @@ arr.push(t); | ||
/** ChiselCursor implements asyncIterator, meaning you can use it in any asynchronous context. */ | ||
[Symbol.asyncIterator]() { | ||
const rid = Deno.core.opSync("chisel_relational_query_create", this.inner); | ||
const ctor = this.type; | ||
async *[Symbol.asyncIterator]() { | ||
let iter = this.makeTransformedQueryIter(this.inner); | ||
if (iter === undefined) { | ||
iter = this.makeQueryIter(this.inner); | ||
} | ||
for await (const it of iter) { | ||
yield it; | ||
} | ||
} | ||
/** Performs recursive descent via Operator.inner examining the whole operator | ||
* chain. If PredicateFilter is encountered, a backend query is generated and all consecutive | ||
* operations are applied on the resulting async iterable in TypeScript. In such a | ||
* case, the function returns the resulting AsyncIterable. | ||
* If no PredicateFilter is found, undefined is returned. | ||
*/ | ||
makeTransformedQueryIter(op) { | ||
if (op.type == OpType.BaseEntity) { | ||
return undefined; | ||
} | ||
else if (op.inner === undefined) { | ||
throw new Error("internal error: expected inner operator, got undefined"); | ||
} | ||
let iter = this.makeTransformedQueryIter(op.inner); | ||
if (iter !== undefined) { | ||
return op.apply(iter); | ||
} | ||
else if (op.type == OpType.PredicateFilter) { | ||
iter = this.makeQueryIter(op.inner); | ||
return op.apply(iter); | ||
} | ||
else { | ||
return undefined; | ||
} | ||
} | ||
makeQueryIter(op) { | ||
const ctor = this.containsSelect(op) ? undefined : this.baseConstructor; | ||
return { | ||
async next() { | ||
const properties = await Deno.core.opAsync("chisel_relational_query_next", rid); | ||
if (properties) { | ||
if (ctor) { | ||
const result = new ctor(); | ||
Object.assign(result, properties); | ||
return { value: result, done: false }; | ||
[Symbol.asyncIterator]: async function* () { | ||
const rid = Deno.core.opSync("chisel_relational_query_create", op); | ||
try { | ||
while (true) { | ||
const properties = await Deno.core.opAsync("chisel_relational_query_next", rid); | ||
if (properties == undefined) { | ||
break; | ||
} | ||
if (ctor !== undefined) { | ||
const result = new ctor(); | ||
Object.assign(result, properties); | ||
yield result; | ||
} | ||
else { | ||
yield properties; | ||
} | ||
} | ||
else { | ||
return { value: properties, done: false }; | ||
} | ||
} | ||
else { | ||
finally { | ||
Deno.core.opSync("op_close", rid); | ||
return { done: true }; | ||
} | ||
}, | ||
return() { | ||
Deno.core.opSync("op_close", rid); | ||
return { done: true }; | ||
}, | ||
}; | ||
} | ||
/** Recursively examines operator chain searching for ColumnsSelect operator. | ||
* Returns true if found, false otherwise. | ||
*/ | ||
containsSelect(op) { | ||
if (op.type == OpType.ColumnsSelect) { | ||
return true; | ||
} | ||
else if (op.inner === undefined) { | ||
return false; | ||
} | ||
else { | ||
return this.containsSelect(op.inner); | ||
} | ||
} | ||
} | ||
export function chiselIterator(type, c) { | ||
const columns = (c != undefined) | ||
? c | ||
: Deno.core.opSync("chisel_introspect", { "name": type.name }); | ||
const b = new BackingStore(columns, type.name); | ||
export function chiselIterator(type) { | ||
const b = new BaseEntity(type.name); | ||
return new ChiselCursor(type, b); | ||
@@ -181,3 +281,4 @@ } | ||
* | ||
* @param properties The properties of the created entity. | ||
* @param properties The properties of the created entity. If more than one property | ||
* is passed, the expected order of assignment is the same as Object.assign. | ||
* | ||
@@ -196,11 +297,16 @@ * @example | ||
* | ||
* // Create an entity from different JSON objects: | ||
* const otherUserJson = JSON.parse('{"username": "alice"}, {"email": "alice@example.com"}'); | ||
* const yetAnotherUser = User.build(userJson); | ||
* | ||
* // now optionally save them to the backend | ||
* await user.save(); | ||
* await anotherUser.save(); | ||
* await yetAnotherUser.save(); | ||
* ``` | ||
* @returns The persisted entity with given properties and the `id` property set. | ||
*/ | ||
static build(properties) { | ||
static build(...properties) { | ||
const result = new this(); | ||
Object.assign(result, properties); | ||
Object.assign(result, ...properties); | ||
return result; | ||
@@ -251,2 +357,23 @@ } | ||
} | ||
/** | ||
* Deletes all entities that match the `restrictions` object. | ||
* | ||
* @example | ||
* ```typescript | ||
* export class User extends ChiselEntity { | ||
* username: string, | ||
* email: string, | ||
* } | ||
* const user = User.build({ username: "alice", email: "alice@example.com" }); | ||
* await user.save(); | ||
* | ||
* await User.delete({ email: "alice@example.com"}) | ||
* ``` | ||
*/ | ||
static async delete(restrictions) { | ||
await Deno.core.opAsync("chisel_entity_delete", { | ||
type_name: this.name, | ||
restrictions: restrictions, | ||
}); | ||
} | ||
} | ||
@@ -273,4 +400,16 @@ export class OAuthUser extends ChiselEntity { | ||
} | ||
export function getSecret(key) { | ||
const secret = Deno.core.opSync("chisel_get_secret", key); | ||
if (secret === undefined || secret === null) { | ||
return undefined; | ||
} | ||
return secret; | ||
} | ||
export function responseFromJson(body, status = 200) { | ||
return new Response(JSON.stringify(body), { | ||
// https://fetch.spec.whatwg.org/#null-body-status | ||
const isNullBody = (status) => { | ||
return status == 101 || status == 204 || status == 205 || status == 304; | ||
}; | ||
const json = isNullBody(status) ? null : JSON.stringify(body); | ||
return new Response(json, { | ||
status: status, | ||
@@ -287,2 +426,5 @@ headers: [ | ||
} | ||
export function unique() { | ||
// chisel-decorator, no content | ||
} | ||
/** Returns the currently logged-in user or null if no one is logged in. */ | ||
@@ -289,0 +431,0 @@ export async function loggedInUser() { |
@@ -1,2 +0,2 @@ | ||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. | ||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. | ||
@@ -3,0 +3,0 @@ // deno-lint-ignore-file no-explicit-any |
{ | ||
"name": "@chiselstrike/api", | ||
"version": "0.6.5", | ||
"main": "lib/chisel.js", | ||
"types": "lib/chisel.d.ts", | ||
"author": "Pekka Enberg <penberg@chiselstrike.com>", | ||
"license": "Proprietary", | ||
"prepare": "npm run build", | ||
"scripts": { | ||
"name": "@chiselstrike/api", | ||
"version": "0.7.0", | ||
"main": "lib/chisel.js", | ||
"types": "lib/chisel.d.ts", | ||
"author": "Pekka Enberg <penberg@chiselstrike.com>", | ||
"license": "Proprietary", | ||
"prepare": "npm run build", | ||
"build": "rimraf ./lib && tsc --noResolve && cp -La ../../api/src/lib.deno_core.d.ts lib/" | ||
}, | ||
"dependencies": { | ||
"typescript": "^4.5.4" | ||
}, | ||
"devDependencies": { | ||
"rimraf": "^3.0.2" | ||
}, | ||
"files": [ | ||
"/lib" | ||
] | ||
"scripts": { | ||
"prepare": "npm run build", | ||
"build": "rimraf ./lib && tsc --noResolve && cp -La ../../api/src/lib.deno_core.d.ts lib/" | ||
}, | ||
"dependencies": { | ||
"typescript": "^4.5.4" | ||
}, | ||
"devDependencies": { | ||
"rimraf": "^3.0.2" | ||
}, | ||
"files": [ | ||
"/lib" | ||
] | ||
} |
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
26680
30.25%712
33.58%