Skip to content

Commit

Permalink
added scoped queries support
Browse files Browse the repository at this point in the history
  • Loading branch information
Bero committed Aug 9, 2024
1 parent 06aa64c commit c79edca
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 42 deletions.
2 changes: 1 addition & 1 deletion lib/exposure/exposure.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export default class Exposure {
}

/**
* Initializez the method to retrieve the data via Meteor.call
* Initializes the method to retrieve the data via Meteor.call
*/
initMethod() {
const collection = this.collection;
Expand Down
17 changes: 0 additions & 17 deletions lib/links/linker.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,23 +320,6 @@ export default class Linker {
}
}
}

// TODO(v3): await fetchAsArray()
// _.each(accessor.fetchAsArray(), (linkedObj) => {
// const { relatedLinker } = this.linkConfig;
// // We do this check, to avoid self-referencing hell when defining virtual links
// // Virtual links if not found "compile-time", we will try again to reprocess them on Meteor.startup
// // if a removal happens before Meteor.startup this may fail
// if (relatedLinker) {
// let link = relatedLinker.createLink(linkedObj);

// if (relatedLinker.isSingle()) {
// link.unset();
// } else {
// link.remove(doc);
// }
// }
// });
});
}

Expand Down
50 changes: 26 additions & 24 deletions lib/namedQuery/testing/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,42 +157,44 @@ describe('Named Query', function () {
});
});

// TODO(v3): scoped queries
it.skip('Should work with reactive scoped queries', function (done) {
it('Should work with reactive scoped queries', function (done) {
const query = postListExposureScoped.clone({ title: 'User Post - 3' });

const handle = query.subscribe();
Tracker.autorun((c) => {
if (handle.ready()) {
c.stop();
const data = query.fetch();
handle.stop();
try {
c.stop();
const data = query.fetch();
handle.stop();

assert.isTrue(data.length > 0);
assert.isTrue(data.length > 0);

// swap this over to an object
// since in 2.5+ an actual Map is used
const postDocs = Posts._collection._docs._map;
const docMap =
postDocs instanceof Map ? Object.fromEntries(postDocs) : postDocs;
// swap this over to an object
// since in 2.5+ an actual Map is used
const postDocs = Posts._collection._docs._map;
const docMap =
postDocs instanceof Map ? Object.fromEntries(postDocs) : postDocs;

const scopeField = `_sub_${handle.subscriptionId}`;
const queryPathField = '_query_path_posts';
data.forEach((post) => {
// no scope field returned from find
assert.isUndefined(post[scopeField]);
assert.isObject(docMap[post._id]);
assert.equal(docMap[post._id][scopeField], 1);
assert.equal(docMap[post._id][queryPathField], 1);
});
const scopeField = `_sub_${handle.subscriptionId}`;
const queryPathField = '_query_path_posts';
data.forEach((post) => {
// no scope field returned from find
assert.isUndefined(post[scopeField]);
assert.isObject(docMap[post._id]);
assert.equal(docMap[post._id][scopeField], 1);
assert.equal(docMap[post._id][queryPathField], 1);
});

done();
done();
} catch (e) {
done(e);
}
}
});
});

// TODO(v3): scoped queries
it.skip('Should work with reactive recursive scoped queries', function (done) {
it('Should work with reactive recursive scoped queries', function (done) {
const query = userListScoped.clone({ name: 'User - 3' });

const handle = query.subscribe();
Expand Down Expand Up @@ -269,7 +271,7 @@ describe('Named Query', function () {
});
});

it.only('Should work with reactive queries containing link with foreignIdentityField', function (done) {
it('Should work with reactive queries containing link with foreignIdentityField', function (done) {
const query = productsList.clone({
filters: {
// only considering products with productId
Expand Down
38 changes: 38 additions & 0 deletions lib/scoping/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { LocalCollection } from 'meteor/minimongo';

const Connection = Meteor.connection.constructor;

const originalSubscribe = Connection.prototype.subscribe;
Connection.prototype.subscribe = function (...args) {
const handle = originalSubscribe.apply(this, args);

handle.scopeQuery = function () {
const query = {};
query[`_sub_${handle.subscriptionId}`] = {
$exists: true,
};
return query;
};

return handle;
};

// Recreate the convenience method.
Meteor.subscribe = Meteor.connection.subscribe.bind(Meteor.connection);

const originalCompileProjection = LocalCollection._compileProjection;
LocalCollection._compileProjection = function (fields) {
const fun = originalCompileProjection(fields);

return function (obj) {
const res = fun(obj);

for (const field of Object.keys(res)) {
if (field.lastIndexOf('_sub_', 0) === 0) {
delete res[field];
}
}

return res;
};
};
73 changes: 73 additions & 0 deletions lib/scoping/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export const extendPublish = (newPublishArguments) => {
// DDP Server constructor.
const Server = Object.getPrototypeOf(Meteor.server).constructor;

const originalPublish = Server.prototype.publish;
Server.prototype.publish = async function (...args) {
// If the first argument is an object, we let the original publish function to traverse it.
if (typeof args[0] === 'object' && args[0] !== null) {
await originalPublish.apply(this, args);
return;
}

const newArgs = await newPublishArguments.apply(this, args);

await originalPublish.apply(this, newArgs);
};

// Because Meteor.publish is a bound function it remembers old
// prototype method so we have to wrap it directly as well.
const originalMeteorPublish = Meteor.publish;
Meteor.publish = function (...args) {
// If the first argument is an object, we let the original publish function to traverse it.
if (typeof args[0] === 'object' && args[0] !== null) {
originalMeteorPublish.apply(this, args);
return;
}

const newArgs = newPublishArguments.apply(this, args);

originalMeteorPublish.apply(this, newArgs);
};
};

// Extend publish part

extendPublish(function (name, func, options) {
const newFunc = function (...args) {
const publish = this;

const scopeFieldName = `_sub_${publish._subscriptionId}`;

let enabled = false;
publish.enableScope = function () {
enabled = true;
};

const originalAdded = publish.added;
publish.added = function (collectionName, id, fields) {
// Add our scoping field.
if (enabled) {
fields = { ...fields };
fields[scopeFieldName] = 1;
}

originalAdded.call(this, collectionName, id, fields);
};

const originalChanged = publish.changed;
publish.changed = function (collectionName, id, fields) {
// We do not allow changes to our scoping field.
if (enabled && scopeFieldName in fields) {
fields = { ...fields };
delete fields[scopeFieldName];
}

originalChanged.call(this, collectionName, id, fields);
};

func.apply(publish, args);
};

return [name, newFunc, options];
});
7 changes: 7 additions & 0 deletions package.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Package.onUse(function (api) {

api.versionsFrom(['3.0-rc.2']);

api.addFiles('lib/scoping/client.js', 'client');
api.addFiles('lib/scoping/server.js', 'server');

var packages = [
'ecmascript',
'underscore',
Expand Down Expand Up @@ -59,6 +62,9 @@ Package.onUse(function (api) {
Package.onTest(function (api) {
api.use('cultofcoders:grapher');

// api.addFiles('lib/scoping/client.js', 'client');
// api.addFiles('lib/scoping/server.js', 'server');

Npm.depends({
...npmPackages,
chai: '4.3.4',
Expand All @@ -71,6 +77,7 @@ Package.onTest(function (api) {
'matb33:[email protected]',
'reywood:[email protected]',
'dburles:[email protected]',
// 'peerlibrary:[email protected]',
// 'herteby:[email protected]',
'mongo',
];
Expand Down

0 comments on commit c79edca

Please sign in to comment.