Comparing version 0.3.0 to 0.4.0-beta.0
{ | ||
"name": "groq-js", | ||
"version": "0.3.0", | ||
"version": "0.4.0-beta.0", | ||
"license": "MIT", | ||
"author": "Sanity.io <hello@sanity.io>", | ||
"main": "dist/index.js", | ||
"typings": "dist/index.d.ts", | ||
"main": "dist/groq-js.umd.js", | ||
"module": "dist/groq-js.esm.js", | ||
@@ -15,10 +14,5 @@ "files": [ | ||
"scripts": { | ||
"start": "tsdx watch", | ||
"build": "tsdx build", | ||
"test:jest": "tsdx test", | ||
"test:tap": "[ -e test/suite.taptest.js ] && tap --no-timeout test/suite.taptest.js", | ||
"test": "npm run-script test:jest && npm run-script test:tap", | ||
"coverage": "tsdx test --coverage", | ||
"lint": "tsdx lint", | ||
"prepare": "tsdx build", | ||
"prepublishOnly": "vite build", | ||
"build": "vite build", | ||
"test": "tap --no-timeout test/*.test.*", | ||
"prettify": "prettier --write src/**/*.ts src/*.ts", | ||
@@ -29,5 +23,7 @@ "prettify-check": "prettier --check src/**/*.ts src/*.ts" | ||
"devDependencies": { | ||
"@types/jest": "^26.0.23", | ||
"@types/tap": "^15.0.5", | ||
"@typescript-eslint/eslint-plugin": "^4.22.1", | ||
"@typescript-eslint/parser": "^4.22.1", | ||
"esbuild": "^0.14.2", | ||
"esbuild-register": "^3.2.0", | ||
"eslint-config-sanity": "^5.1.0", | ||
@@ -38,17 +34,14 @@ "ndjson": "^2.0.0", | ||
"tap": "^15.0.6", | ||
"tsdx": "^0.14.1", | ||
"tslib": "^2.2.0", | ||
"typescript": "^4.2.4" | ||
"typescript": "^4.2.4", | ||
"vite": "^2.7.1", | ||
"vite-dts": "^1.0.3" | ||
}, | ||
"jest": { | ||
"testEnvironment": "node", | ||
"globals": { | ||
"ts-jest": { | ||
"diagnostics": false | ||
} | ||
} | ||
}, | ||
"tap": { | ||
"node-arg": [ | ||
"-r", | ||
"esbuild-register" | ||
], | ||
"check-coverage": false | ||
} | ||
} |
@@ -49,2 +49,7 @@ # GROQ-JS | ||
## Learn GROQ | ||
[![Free egghead GROQ introduction course by John Lindquist](https://user-images.githubusercontent.com/6188161/142889665-fc04ac47-d0fa-492b-897b-4203c97e94ec.png)](https://egghead.io/courses/introduction-to-groq-query-language-6e9c6fc0?utm_source=github&utm_medium=cta&utm_term=GROQ) | ||
## Versioning | ||
@@ -51,0 +56,0 @@ |
@@ -31,3 +31,9 @@ import {ExprNode} from '../nodeTypes' | ||
const DUMMY_SCOPE = new Scope({}, NULL_VALUE, NULL_VALUE, null) | ||
const DUMMY_SCOPE = new Scope( | ||
{}, | ||
NULL_VALUE, | ||
NULL_VALUE, | ||
{timestamp: new Date(0), identity: 'me', before: null, after: null}, | ||
null | ||
) | ||
@@ -34,0 +40,0 @@ class ConstantEvaluateError extends Error { |
@@ -6,3 +6,5 @@ import * as NodeTypes from '../nodeTypes' | ||
import { | ||
DateTime, | ||
FALSE_VALUE, | ||
fromDateTime, | ||
fromJS, | ||
@@ -63,2 +65,10 @@ fromNumber, | ||
Context({key}, scope) { | ||
if (key === 'before' || key === 'after') { | ||
const value = scope.context[key] | ||
return value || NULL_VALUE | ||
} | ||
throw new Error(`unknown context key: ${key}`) | ||
}, | ||
Parent({n}, scope) { | ||
@@ -317,2 +327,6 @@ let current = scope | ||
Tuple() { | ||
throw new Error('tuples can not be evaluated') | ||
}, | ||
async Or({left, right}, scope, execute) { | ||
@@ -450,4 +464,16 @@ const leftValue = await execute(left, scope) | ||
const scope = new Scope(params, dataset, root, null) | ||
const scope = new Scope( | ||
params, | ||
dataset, | ||
root, | ||
{ | ||
timestamp: options.timestamp || new Date(), | ||
identity: options.identity === undefined ? 'me' : options.identity, | ||
sanity: options.sanity, | ||
after: options.after ? fromJS(options.after) : null, | ||
before: options.before ? fromJS(options.before) : null, | ||
}, | ||
null | ||
) | ||
return evaluate(tree, scope) | ||
} |
@@ -61,4 +61,5 @@ import type {ExprNode} from '../nodeTypes' | ||
type GroqFunctionArg = ExprNode | ||
type WithArity<T> = T & { | ||
type WithOptions<T> = T & { | ||
arity?: GroqFunctionArity | ||
mode?: 'normal' | 'delta' | ||
} | ||
@@ -74,3 +75,3 @@ | ||
export type FunctionSet = Record<string, WithArity<GroqFunction> | undefined> | ||
export type FunctionSet = Record<string, WithOptions<GroqFunction> | undefined> | ||
@@ -125,4 +126,4 @@ export type NamespaceSet = Record<string, FunctionSet | undefined> | ||
// eslint-disable-next-line require-await | ||
global.identity = async function identity(args) { | ||
return fromString('me') | ||
global.identity = async function identity(args, scope) { | ||
return fromString(scope.context.identity) | ||
} | ||
@@ -230,3 +231,3 @@ global.identity.arity = 0 | ||
global.now = async function now(args, scope) { | ||
return fromString(scope.timestamp) | ||
return fromString(scope.context.timestamp.toISOString()) | ||
} | ||
@@ -284,2 +285,20 @@ global.now.arity = 0 | ||
const sanity: FunctionSet = {} | ||
// eslint-disable-next-line require-await | ||
sanity.projectId = async function (args, scope) { | ||
if (scope.context.sanity) { | ||
return fromString(scope.context.sanity.projectId) | ||
} | ||
return NULL_VALUE | ||
} | ||
// eslint-disable-next-line require-await | ||
sanity.dataset = async function (args, scope) { | ||
if (scope.context.sanity) { | ||
return fromString(scope.context.sanity.dataset) | ||
} | ||
return NULL_VALUE | ||
} | ||
export type GroqPipeFunction = ( | ||
@@ -292,3 +311,3 @@ base: Value, | ||
export const pipeFunctions: {[key: string]: WithArity<GroqPipeFunction>} = {} | ||
export const pipeFunctions: {[key: string]: WithOptions<GroqPipeFunction>} = {} | ||
@@ -388,2 +407,45 @@ pipeFunctions.order = async function order(base, args, scope, execute) { | ||
const delta: FunctionSet = {} | ||
delta.operation = async function (args, scope) { | ||
const hasBefore = scope.context.before !== null | ||
const hasAfter = scope.context.after !== null | ||
if (hasBefore && hasAfter) { | ||
return fromString('update') | ||
} | ||
if (hasAfter) { | ||
return fromString('create') | ||
} | ||
if (hasBefore) { | ||
return fromString('delete') | ||
} | ||
return NULL_VALUE | ||
} | ||
delta.changedAny = () => { | ||
throw new Error('not implemented') | ||
} | ||
delta.changedAny.arity = 1 | ||
delta.changedAny.mode = 'delta' | ||
delta.changedOnly = () => { | ||
throw new Error('not implemented') | ||
} | ||
delta.changedOnly.arity = 1 | ||
delta.changedOnly.mode = 'delta' | ||
const diff: FunctionSet = {} | ||
diff.changedAny = () => { | ||
throw new Error('not implemented') | ||
} | ||
diff.changedAny.arity = 3 | ||
diff.changedOnly = () => { | ||
throw new Error('not implemented') | ||
} | ||
diff.changedOnly.arity = 3 | ||
export const namespaces: NamespaceSet = { | ||
@@ -393,2 +455,5 @@ global, | ||
pt, | ||
delta, | ||
diff, | ||
sanity, | ||
} |
import {Value} from '../values' | ||
import {Context} from './types' | ||
@@ -8,12 +9,18 @@ export class Scope { | ||
public parent: Scope | null | ||
public timestamp: string | ||
public context: Context | ||
public isHidden = false | ||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
constructor(params: Record<string, unknown>, source: Value, value: Value, parent: Scope | null) { | ||
constructor( | ||
params: Record<string, unknown>, | ||
source: Value, | ||
value: Value, | ||
context: Context, | ||
parent: Scope | null | ||
) { | ||
this.params = params | ||
this.source = source | ||
this.value = value | ||
this.context = context | ||
this.parent = parent | ||
this.timestamp = parent ? parent.timestamp : new Date().toISOString() | ||
} | ||
@@ -23,5 +30,5 @@ | ||
if (this.isHidden) { | ||
return new Scope(this.params, this.source, value, this.parent) | ||
return new Scope(this.params, this.source, value, this.context, this.parent) | ||
} | ||
return new Scope(this.params, this.source, value, this) | ||
return new Scope(this.params, this.source, value, this.context, this) | ||
} | ||
@@ -28,0 +35,0 @@ |
@@ -16,2 +16,31 @@ import * as NodeTypes from '../nodeTypes' | ||
params?: Record<string, unknown> | ||
// The timestamp returned from now() | ||
timestamp?: Date | ||
// Value used for identity() | ||
identity?: string | ||
// The value returned from before() in Delta-mode | ||
before?: any | ||
// The value returned from after() in Delta-mode | ||
after?: any | ||
// Settings used for the `sanity`-functions | ||
sanity?: { | ||
projectId: string | ||
dataset: string | ||
} | ||
} | ||
export interface Context { | ||
timestamp: Date | ||
identity: string | ||
before: Value | null | ||
after: Value | null | ||
sanity?: { | ||
projectId: string | ||
dataset: string | ||
} | ||
} |
@@ -19,2 +19,3 @@ import {GroqFunction, GroqPipeFunction} from './evaluator/functions' | ||
| AscNode | ||
| ContextNode | ||
| DerefNode | ||
@@ -42,2 +43,3 @@ | DescNode | ||
| ThisNode | ||
| TupleNode | ||
| ValueNode | ||
@@ -93,2 +95,7 @@ | ||
export interface ContextNode extends BaseNode { | ||
type: 'Context' | ||
key: string | ||
} | ||
export interface DerefNode extends BaseNode { | ||
@@ -212,2 +219,7 @@ type: 'Deref' | ||
export interface TupleNode extends BaseNode { | ||
type: 'Tuple' | ||
members: Array<ExprNode> | ||
} | ||
export interface ValueNode<P = any> { | ||
@@ -214,0 +226,0 @@ type: 'Value' |
@@ -311,2 +311,14 @@ /* eslint-disable camelcase */ | ||
tuple(p) { | ||
const members: NodeTypes.ExprNode[] = [] | ||
while (p.getMark().name !== 'tuple_end') { | ||
members.push(p.process(EXPR_BUILDER)) | ||
} | ||
p.shift() | ||
return { | ||
type: 'Tuple', | ||
members, | ||
} | ||
}, | ||
func_call(p) { | ||
@@ -353,2 +365,11 @@ let namespace = 'global' | ||
if (namespace === 'global' && (name === 'before' || name === 'after')) { | ||
if (p.parseOptions.mode === 'delta') { | ||
return { | ||
type: 'Context', | ||
key: name, | ||
} | ||
} | ||
} | ||
if (namespace === 'global' && name === 'boost' && !p.allowBoost) | ||
@@ -370,2 +391,6 @@ throw new GroqQueryError('unexpected boost') | ||
if (func.mode !== undefined && func.mode !== p.parseOptions.mode) { | ||
throw new GroqQueryError(`Undefined function: ${name}`) | ||
} | ||
return { | ||
@@ -372,0 +397,0 @@ type: 'FuncCall', |
@@ -8,5 +8,3 @@ export declare function recognize(input: string): boolean | ||
export declare function parse( | ||
input: string | ||
): | ||
export declare function parse(input: string): | ||
| { | ||
@@ -13,0 +11,0 @@ type: 'success' |
export interface ParseOptions { | ||
params?: Record<string, unknown> | ||
mode?: 'normal' | 'delta' | ||
} |
export * from './utils' | ||
export * from './types' | ||
export * from './StreamValue' | ||
export * from './StaticValue' | ||
export * from './DateTime' | ||
export * from './Path' |
@@ -1,2 +0,2 @@ | ||
import {Value} from './types' | ||
import type {Value} from './types' | ||
@@ -3,0 +3,0 @@ export class StreamValue { |
@@ -1,5 +0,4 @@ | ||
import {DateTime} from './DateTime' | ||
import {Path} from './Path' | ||
import {StaticValue} from './StaticValue' | ||
import {StreamValue} from './StreamValue' | ||
import type {Path} from './Path' | ||
import type {StaticValue, DateTime} from './utils' | ||
import type {StreamValue} from './StreamValue' | ||
@@ -6,0 +5,0 @@ /** |
@@ -1,7 +0,36 @@ | ||
import {DateTime} from './DateTime' | ||
import {parseRFC3339, formatRFC3339} from './dateHelpers' | ||
import {Path} from './Path' | ||
import {StaticValue} from './StaticValue' | ||
import {StreamValue} from './StreamValue' | ||
import {BooleanValue, GroqType, NullValue, Value} from './types' | ||
export class StaticValue<P, T extends GroqType> { | ||
data: P | ||
type: T | ||
constructor(data: P, type: T) { | ||
this.data = data | ||
this.type = type | ||
} | ||
isArray(): boolean { | ||
return this.type === 'array' | ||
} | ||
// eslint-disable-next-line require-await | ||
async get(): Promise<any> { | ||
return this.data | ||
} | ||
[Symbol.asyncIterator](): Generator<Value, void, unknown> { | ||
if (Array.isArray(this.data)) { | ||
return (function* (data) { | ||
for (const element of data) { | ||
yield fromJS(element) | ||
} | ||
})(this.data) | ||
} | ||
throw new Error(`Cannot iterate over: ${this.type}`) | ||
} | ||
} | ||
export const NULL_VALUE: NullValue = new StaticValue(null, 'null') | ||
@@ -11,2 +40,44 @@ export const TRUE_VALUE: BooleanValue = new StaticValue(true, 'boolean') | ||
export class DateTime { | ||
date: Date | ||
constructor(date: Date) { | ||
this.date = date | ||
} | ||
static parseToValue(str: string): Value { | ||
const date = parseRFC3339(str) | ||
if (date) { | ||
return new StaticValue(new DateTime(date), 'datetime') | ||
} | ||
return NULL_VALUE | ||
} | ||
equals(other: DateTime): boolean { | ||
return this.date.getTime() == other.date.getTime() | ||
} | ||
add(secs: number): DateTime { | ||
const copy = new Date(this.date.getTime()) | ||
copy.setTime(copy.getTime() + secs * 1000) | ||
return new DateTime(copy) | ||
} | ||
difference(other: DateTime): number { | ||
return (this.date.getTime() - other.date.getTime()) / 1000 | ||
} | ||
compareTo(other: DateTime): number { | ||
return this.date.getTime() - other.date.getTime() | ||
} | ||
toString(): string { | ||
return formatRFC3339(this.date) | ||
} | ||
toJSON(): string { | ||
return this.toString() | ||
} | ||
} | ||
export function fromNumber(num: number): Value { | ||
@@ -13,0 +84,0 @@ if (Number.isFinite(num)) { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
90
1
298200
14
35
6340