feat: add fluxer upstream source and self-hosting documentation

- Clone of github.com/fluxerapp/fluxer (official upstream)
- SELF_HOSTING.md: full VM rebuild procedure, architecture overview,
  service reference, step-by-step setup, troubleshooting, seattle reference
- dev/.env.example: all env vars with secrets redacted and generation instructions
- dev/livekit.yaml: LiveKit config template with placeholder keys
- fluxer-seattle/: existing seattle deployment setup scripts
This commit is contained in:
Vish
2026-03-13 00:55:14 -07:00
parent 5ceda343b8
commit 3b9d759b4b
5859 changed files with 1923440 additions and 0 deletions

View File

@@ -0,0 +1,294 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {PriorityQueue} from '@fluxer/queue/src/engine/PriorityQueue';
import {createJobID, type ReadyItem} from '@fluxer/queue/src/types/JobTypes';
import {beforeEach, describe, expect, it} from 'vitest';
function createReadyItem(
jobId: string,
priority: number,
runAtMs: number = 1000,
createdAtMs: number = 1000,
sequence: number = 0,
): ReadyItem {
return {
jobId: createJobID(jobId),
priority,
runAtMs,
createdAtMs,
sequence,
};
}
describe('PriorityQueue', () => {
let queue: PriorityQueue;
beforeEach(() => {
queue = new PriorityQueue();
});
describe('basic operations', () => {
it('should start empty', () => {
expect(queue.isEmpty).toBe(true);
expect(queue.size).toBe(0);
expect(queue.peek()).toBeUndefined();
expect(queue.pop()).toBeUndefined();
});
it('should push and pop a single item', () => {
const item = createReadyItem('job-1', 5);
queue.push(item);
expect(queue.isEmpty).toBe(false);
expect(queue.size).toBe(1);
expect(queue.peek()).toEqual(item);
const popped = queue.pop();
expect(popped).toEqual(item);
expect(queue.isEmpty).toBe(true);
});
it('should push multiple items and maintain size', () => {
queue.push(createReadyItem('job-1', 1));
queue.push(createReadyItem('job-2', 2));
queue.push(createReadyItem('job-3', 3));
expect(queue.size).toBe(3);
});
it('should clear all items', () => {
queue.push(createReadyItem('job-1', 1));
queue.push(createReadyItem('job-2', 2));
queue.clear();
expect(queue.isEmpty).toBe(true);
expect(queue.size).toBe(0);
});
});
describe('priority ordering', () => {
it('should pop highest priority first', () => {
queue.push(createReadyItem('low', 1));
queue.push(createReadyItem('high', 10));
queue.push(createReadyItem('medium', 5));
expect(queue.pop()?.jobId).toBe('high');
expect(queue.pop()?.jobId).toBe('medium');
expect(queue.pop()?.jobId).toBe('low');
});
it('should order by runAtMs when priorities are equal', () => {
const baseTime = 1000;
queue.push(createReadyItem('later', 5, baseTime + 200));
queue.push(createReadyItem('earlier', 5, baseTime + 100));
queue.push(createReadyItem('earliest', 5, baseTime));
expect(queue.pop()?.jobId).toBe('earliest');
expect(queue.pop()?.jobId).toBe('earlier');
expect(queue.pop()?.jobId).toBe('later');
});
it('should order by createdAtMs when priority and runAtMs are equal', () => {
const baseTime = 1000;
queue.push(createReadyItem('third', 5, baseTime, baseTime + 200));
queue.push(createReadyItem('first', 5, baseTime, baseTime));
queue.push(createReadyItem('second', 5, baseTime, baseTime + 100));
expect(queue.pop()?.jobId).toBe('first');
expect(queue.pop()?.jobId).toBe('second');
expect(queue.pop()?.jobId).toBe('third');
});
it('should order by sequence when all other fields are equal', () => {
const baseTime = 1000;
queue.push(createReadyItem('third', 5, baseTime, baseTime, 3));
queue.push(createReadyItem('first', 5, baseTime, baseTime, 1));
queue.push(createReadyItem('second', 5, baseTime, baseTime, 2));
expect(queue.pop()?.jobId).toBe('first');
expect(queue.pop()?.jobId).toBe('second');
expect(queue.pop()?.jobId).toBe('third');
});
it('should maintain heap property after multiple insertions', () => {
const priorities = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
priorities.forEach((p, i) => {
queue.push(createReadyItem(`job-${i}`, p, 1000, 1000, i));
});
const sorted = [...priorities].sort((a, b) => b - a);
const popped: Array<number> = [];
while (!queue.isEmpty) {
const item = queue.pop();
if (item) {
popped.push(item.priority);
}
}
expect(popped).toEqual(sorted);
});
});
describe('remove operation', () => {
it('should remove an existing item', () => {
queue.push(createReadyItem('job-1', 1));
queue.push(createReadyItem('job-2', 2));
queue.push(createReadyItem('job-3', 3));
const removed = queue.remove(createJobID('job-2'));
expect(removed).toBe(true);
expect(queue.size).toBe(2);
expect(queue.has(createJobID('job-2'))).toBe(false);
});
it('should return false when removing non-existent item', () => {
queue.push(createReadyItem('job-1', 1));
const removed = queue.remove(createJobID('non-existent'));
expect(removed).toBe(false);
expect(queue.size).toBe(1);
});
it('should maintain heap property after removal', () => {
queue.push(createReadyItem('job-1', 1));
queue.push(createReadyItem('job-2', 5));
queue.push(createReadyItem('job-3', 3));
queue.push(createReadyItem('job-4', 7));
queue.push(createReadyItem('job-5', 2));
queue.remove(createJobID('job-2'));
expect(queue.pop()?.jobId).toBe('job-4');
expect(queue.pop()?.jobId).toBe('job-3');
expect(queue.pop()?.jobId).toBe('job-5');
expect(queue.pop()?.jobId).toBe('job-1');
});
it('should handle removing the only item', () => {
queue.push(createReadyItem('job-1', 1));
const removed = queue.remove(createJobID('job-1'));
expect(removed).toBe(true);
expect(queue.isEmpty).toBe(true);
});
it('should handle removing from the front of the queue', () => {
queue.push(createReadyItem('job-1', 10));
queue.push(createReadyItem('job-2', 5));
queue.push(createReadyItem('job-3', 1));
queue.remove(createJobID('job-1'));
expect(queue.peek()?.jobId).toBe('job-2');
});
});
describe('has operation', () => {
it('should return true for existing item', () => {
queue.push(createReadyItem('job-1', 1));
expect(queue.has(createJobID('job-1'))).toBe(true);
});
it('should return false for non-existent item', () => {
queue.push(createReadyItem('job-1', 1));
expect(queue.has(createJobID('job-2'))).toBe(false);
});
it('should return false after item is removed', () => {
queue.push(createReadyItem('job-1', 1));
queue.remove(createJobID('job-1'));
expect(queue.has(createJobID('job-1'))).toBe(false);
});
});
describe('toArray and fromArray', () => {
it('should convert queue to array', () => {
const items = [createReadyItem('job-1', 1), createReadyItem('job-2', 2), createReadyItem('job-3', 3)];
items.forEach((item) => queue.push(item));
const arr = queue.toArray();
expect(arr.length).toBe(3);
items.forEach((item) => {
expect(arr.some((a) => a.jobId === item.jobId)).toBe(true);
});
});
it('should create queue from array', () => {
const items = [createReadyItem('job-1', 1), createReadyItem('job-2', 5), createReadyItem('job-3', 3)];
const newQueue = PriorityQueue.fromArray(items);
expect(newQueue.size).toBe(3);
expect(newQueue.pop()?.jobId).toBe('job-2');
expect(newQueue.pop()?.jobId).toBe('job-3');
expect(newQueue.pop()?.jobId).toBe('job-1');
});
it('should create empty queue from empty array', () => {
const newQueue = PriorityQueue.fromArray([]);
expect(newQueue.isEmpty).toBe(true);
});
});
describe('edge cases', () => {
it('should handle items with same priority correctly', () => {
for (let i = 0; i < 10; i++) {
queue.push(createReadyItem(`job-${i}`, 5, 1000, 1000, i));
}
let lastSequence = -1;
while (!queue.isEmpty) {
const item = queue.pop();
if (item) {
expect(item.sequence).toBeGreaterThan(lastSequence);
lastSequence = item.sequence;
}
}
});
it('should handle large number of items', () => {
for (let i = 0; i < 1000; i++) {
queue.push(createReadyItem(`job-${i}`, Math.floor(Math.random() * 100)));
}
expect(queue.size).toBe(1000);
let lastPriority = Infinity;
while (!queue.isEmpty) {
const item = queue.pop();
if (item) {
expect(item.priority).toBeLessThanOrEqual(lastPriority);
lastPriority = item.priority;
}
}
});
});
});