This would be a new project entirely independent but compatible with Wildcard.
This is ambitious and I'm not sure if/when I'll finish this.
In case you ask yourself why this is necessary in the context of Wildcard: it's basically for large scale applications that need to decouple the development of several frontends from the backend development. For most users, Wildcard alone is enough.
A preview:
// Browser-side
// Note how we only depend on Wildcard here.
import { server } from '@wildcard-api/client';
// The query syntax is JSON-like.
const posts = await server.query({
_table: 'posts',
authorId: {
_table: 'users',
id: '*',
username: 'brillout',
},
title: '*',
content: '*',
});
assert(posts[0].title==='Introducing a new query language');
assert(posts[0].content.startsWith(
'When GraphQL was announced I was super excited but after the honeymoon'
));
// Similarly for upserting data
await server.query({
_table: 'posts',
authorId: 'brillout',
title: 'NQL + Wildcard = <3',
content: 'WIP',
});
// There is a field with '*' => data retrieval
// No field with '*' => data mutation
Resolvers + Wildcard integration
const { server } = require('@wildcard-api/server');
const { resolveQuery, addResolver } = require('nql');
server.query = async function(query) {
const { data } = await resolveQuery(query);
return data;
};
// One resolver per table for data retrieval
addResolver('posts', async (selectFields, filterFields) => {
// A trick is that `filterFields[fieldName]` is always an array.
// This solves the N+1 problem!
assert(!filterFields.id || filterFields.id.constructor === Array);
// (No SQL injection, everything is sanitized.)
const res = await db.sql([
`SELECT ${selectFields.join(', ')} FROM posts`,
// Do this for every `key in filterFields`.
`WHERE id IN (`${filterFields.id.map(id => `'${id}'`).join(', ')})`,
].join(' '));
return res;
});
// One resolver per table is enough for any graph-like query.
// With no N+1 problem.
With "object-level permissions":
const { server } = require('@wildcard-api/server');
const { resolveQuery, addPermissions } = require('nql');
server.query = async function(query) {
// We need the context for permissions
const context = this;
const { data, permissionDenied } = await resolveQuery(query, context);
if (permissionDenied) {
return { permissionDenied };
} else {
return { data };
}
};
// We define permissions programmatically.
// This is simple and powerful.
addPermissions('read', ({object, context}) => {
const { isAdmin } = context.user;
// Admins can read everything
if (isAdmin) {
return true;
}
// Anyone can read everything from the `posts` table
if (object._table==='posts') {
return true;
}
// Fine grained permission: anyone can read everything from
// the `users` table except for the `password` field.
if (object._table === 'users') {
if (object.password) {
return false;
} else {
return true;
}
}
// Anything else is forbidden
return false;
});
addPermissions('write', ({object, context}) => {
// Same than above but for mutation
});
This would be a new project entirely independent but compatible with Wildcard.
This is ambitious and I'm not sure if/when I'll finish this.
In case you ask yourself why this is necessary in the context of Wildcard: it's basically for large scale applications that need to decouple the development of several frontends from the backend development. For most users, Wildcard alone is enough.
A preview:
Resolvers + Wildcard integration
With "object-level permissions":