WIP(meteor3): boot progresses past linker + several async fixes

Past the client-bundle underscore linker (pin underscore@1.6.4) and now working
through the server async/await migration on Meteor 3.1 + Mongo 7:

- Removed fibers (imports/startup/server/fibers.js + import) — gone in Meteor 3.
- accounts/oauth.js: upsert -> upsertAsync (top-level await).
- migrations.js: startup async + await Migrations.migrateTo (async in 2.x).
  mongo7 marked at version 18 so historical up() bodies (still sync Mongo) don't
  run — prod data restores already-migrated. Documented.
- subsUnion.js: fully async (fetchAsync/findOneAsync/countAsync/upsertAsync,
  observeAsync with async callbacks).
- facts.js: dropped dead sync findOne.
- email.js: ostrio:mailer 2.5 is a default export (was named import); dropped
  removed static MailTime.Template (built-in default '{{{html}}}').

NEXT wall + remaining chain (multi-session):
1. aldeed:collection2@4.2.0 needs simpl-schema 3.x (app has 1.x) -> 'attachSchema
   is not a function'. Requires upgrading npm simpl-schema to 3.x and migrating
   all collection schemas (SimpleSchema.RegEx.Id etc.) across ~8 files.
2. Rest.js + helpers (countRealFires/firesUnion/whichAreFalsePositives/
   fireFromHash/subscriptionsInsert/upsertFalsePositive) -> *Async.
3. Patch vendored restivus to await async endpoint handlers.
4. methods/publications/comments to async; cron SyncedCron(quave)/Facts imports.
5. alanning:roles@1.2.10 + gadicohen:sitemaps@0.0.17 warn incompatible -> bump.
6. React 16 -> 18 render root.

Run with: NODE_OPTIONS='--dns-result-order=ipv4first --no-network-family-autoselection'
MONGO_URL='mongodb://localhost:27019/fuegos?replicaSet=rs0'
This commit is contained in:
vjrj 2026-07-13 23:46:45 +02:00
parent 8fa10e47c7
commit 25dc1b99c0
9 changed files with 40 additions and 44 deletions

View file

@ -54,3 +54,4 @@ nspangler:autoreconnect
quave:synced-cron
# lmachens:kadira
nimble:restivus@0.8.12
underscore@1.6.4

View file

@ -120,7 +120,7 @@ themeteorchef:bert@1.1.0
tmeasday:check-npm-versions@0.3.2
tracker@1.3.4
typescript@5.6.3
underscore@1.6.2
underscore@1.6.4
url@1.3.5
vjrj:piwik@0.3.1
webapp@2.0.4

View file

@ -4,10 +4,12 @@ import { ServiceConfiguration } from 'meteor/service-configuration';
const OAuthSettings = Meteor.settings.private.OAuth;
if (OAuthSettings) {
Object.keys(OAuthSettings).forEach((service) => {
ServiceConfiguration.configurations.upsert(
// Meteor 3: sync Mongo write-methods are gone on the server; use *Async.
// Top-level await is supported in eager server modules.
for (const service of Object.keys(OAuthSettings)) {
await ServiceConfiguration.configurations.upsertAsync(
{ service },
{ $set: OAuthSettings[service] },
);
});
}
}

View file

@ -1,6 +1,6 @@
import { Meteor } from 'meteor/meteor';
import nodemailer from 'nodemailer';
import { MailTime } from 'meteor/ostrio:mailer';
import MailTime from 'meteor/ostrio:mailer';
import i18n from 'i18next';
import isMaster from './isMaster';
@ -48,7 +48,8 @@ if (isMailServerMaster) {
concatDelimiter: hr + '<h2>{{{subject}}}</h2>', // Start each concatenated email with it's own subject
/* eslint-enable */
// concatThrottling: 30,
template: MailTime.Template // Use default template
// Mail-Time 2.x removed the static MailTime.Template; omitting `template`
// uses the built-in default ('{{{html}}}'). (debt: richer wrapper template)
});
} else {
console.log('I\'m a mail client');

View file

@ -2,8 +2,4 @@
import { Roles } from 'meteor/alanning:roles';
import { Meteor } from 'meteor/meteor';
Facts.setUserIdFilter((userId) => {
const user = Meteor.users.findOne(userId);
// console.log(`User roles: ${user.roles}`);
return Roles.userIsInRole(userId, ['admin']);
});
Facts.setUserIdFilter(userId => Roles.userIsInRole(userId, ['admin']));

View file

@ -1,7 +0,0 @@
// Workaround for: https://github.com/meteor/meteor/issues/9796
// https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
// https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129
import Fiber from 'fibers';
Fiber.poolSize = 1e9;

View file

@ -1,6 +1,5 @@
import './ravenLogger';
import './catchExceptions';
import './fibers';
import './i18n';
import './accounts';
import './api';

View file

@ -14,7 +14,7 @@ import Notifications from '/imports/api/Notifications/Notifications';
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
import { Mongo } from 'meteor/mongo';
Meteor.startup(() => {
Meteor.startup(async () => {
// https://github.com/percolatestudio/meteor-migrations
Migrations.config({
@ -255,7 +255,12 @@ Meteor.startup(() => {
});
// Set createdAt in users & subs
Migrations.migrateTo('latest');
// Meteor 3: migrateTo is async now.
// NOTE: the individual up() bodies below still use SYNC Mongo — they only run
// on a DB whose migration version is behind. Production data is restored from
// rsmain already at the latest version, so they never execute. If migrations
// must ever run on a truly-fresh DB, their up() bodies need async conversion.
await Migrations.migrateTo('latest');
// Migrations.migrateTo('14,rerun');
});

View file

@ -11,7 +11,7 @@ import { isMailServerMaster } from '/imports/startup/server/email';
// sudo apt-get install libcairo2-dev libjpeg-dev libgif-dev
Meteor.startup(() => {
Meteor.startup(async () => {
if (!isMailServerMaster) {
console.log('We only process subsUnion in master');
return;
@ -35,8 +35,8 @@ Meteor.startup(() => {
const noNoisy = sub => sub;
const process = (isPublic) => {
const subscribers = Subscriptions.find().fetch();
const process = async (isPublic) => {
const subscribers = await Subscriptions.find().fetchAsync();
const result = calcUnion(L, subscribers, isPublic ? addNoisy : noNoisy, true);
const union = result[0];
const bounds = result[1];
@ -74,9 +74,9 @@ Meteor.startup(() => {
};
// FIXME, take care of object size:
// https://stackoverflow.com/questions/10827812/what-is-the-length-maximum-for-a-string-data-type-in-mongodb-used-with-ruby
SiteSettings.upsert({ name: `subs-${publicl}-union` }, unionSet, { multi: false });
SiteSettings.upsert({ name: `subs-${publicl}-union-bounds` }, boundsSet, { multi: false });
SiteSettings.upsert({ name: 'subs-union-count' }, sizeSet, { multi: false });
await SiteSettings.upsertAsync({ name: `subs-${publicl}-union` }, unionSet, { multi: false });
await SiteSettings.upsertAsync({ name: `subs-${publicl}-union-bounds` }, boundsSet, { multi: false });
await SiteSettings.upsertAsync({ name: 'subs-union-count' }, sizeSet, { multi: false });
if (debug) console.log(`${Publicl} subscription union calculated`);
} else {
console.log('Subscription union failed!');
@ -84,40 +84,39 @@ Meteor.startup(() => {
};
// At startup, we check if it's necessary to calc subscriptions union again
const currentUnion = SiteSettings.findOne({ name: 'subs-public-union' });
const lastSubs = Subscriptions.findOne({}, { sort: { updatedAt: -1 } });
const countUnionSubs = SiteSettings.findOne({ name: 'subs-union-count' });
const countSubs = Subscriptions.find({}).count();
const currentUnion = await SiteSettings.findOneAsync({ name: 'subs-public-union' });
const lastSubs = await Subscriptions.findOneAsync({}, { sort: { updatedAt: -1 } });
const countUnionSubs = await SiteSettings.findOneAsync({ name: 'subs-union-count' });
const countSubs = await Subscriptions.find({}).countAsync();
if (currentUnion && lastSubs) {
const lastUnionUpdated = currentUnion.updatedAt;
const lastSubsUpdated = lastSubs.updatedAt;
if (lastUnionUpdated > lastSubsUpdated || !countUnionSubs || countSubs !== countUnionSubs.value) {
console.log('Subs union outdated');
process(true);
process(false);
await process(true);
await process(false);
} else {
console.log('Subs union up-to-date');
}
}
Subscriptions.find({ createdAt: { $gt: new Date() } }).observe({
added: function newSubAdded() { // doc) {
const recreate = async () => { await process(true); await process(false); };
await Subscriptions.find({ createdAt: { $gt: new Date() } }).observeAsync({
added: async function newSubAdded() { // doc) {
if (debug) console.log('Subs added so recreate union');
process(true);
process(false);
await recreate();
}
});
Subscriptions.find().observe({
changed: function subsChanged() { // updatedDoc, oldDoc) {
await Subscriptions.find().observeAsync({
changed: async function subsChanged() { // updatedDoc, oldDoc) {
if (debug) console.log('Subs changed so recreate union');
process(true);
process(false);
await recreate();
},
removed: function subsRemoved() { // oldDoc) {
removed: async function subsRemoved() { // oldDoc) {
if (debug) console.log('Subs removed so recreate union');
process(true);
process(false);
await recreate();
}
});
});