superflare
Version:
A full-stack framework for Cloudflare Workers.
1,676 lines (1,647 loc) • 47.4 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __accessCheck = (obj, member, msg) => {
if (!member.has(obj))
throw TypeError("Cannot " + msg);
};
var __privateGet = (obj, member, getter) => {
__accessCheck(obj, member, "read from private field");
return getter ? getter.call(obj) : member.get(obj);
};
var __privateAdd = (obj, member, value) => {
if (member.has(obj))
throw TypeError("Cannot add the same private member more than once");
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
};
var __privateSet = (obj, member, value, setter) => {
__accessCheck(obj, member, "write to private field");
setter ? setter.call(obj, value) : member.set(obj, value);
return value;
};
// index.ts
var superflare_exports = {};
__export(superflare_exports, {
Channel: () => Channel,
DatabaseException: () => DatabaseException,
Event: () => Event,
Factory: () => Factory,
Job: () => Job,
Listener: () => Listener,
Model: () => Model,
Schema: () => Schema,
SuperflareAuth: () => SuperflareAuth,
SuperflareSession: () => SuperflareSession,
defineConfig: () => defineConfig,
handleFetch: () => handleFetch,
handleQueue: () => handleQueue,
handleScheduled: () => handleScheduled,
handleWebSockets: () => handleWebSockets,
hash: () => hash,
parseMultipartFormData: () => parseMultipartFormData,
seed: () => seed,
servePublicPathFromStorage: () => servePublicPathFromStorage,
setConfig: () => setConfig,
storage: () => storage
});
module.exports = __toCommonJS(superflare_exports);
// src/string.ts
var import_pluralize = require("pluralize");
function modelToTableName(modelName) {
const parts = modelName.split(/(?=[A-Z])/);
const last = parts.pop();
return parts.map((part) => part.toLowerCase()).concat((0, import_pluralize.plural)(last.toLowerCase())).join("_");
}
function modelToForeignKeyId(modelName) {
return `${(0, import_pluralize.singular)(modelToTableName(modelName))}Id`;
}
function lowercaseFirstLetter(string) {
return string.charAt(0).toLowerCase() + string.slice(1);
}
function sanitizeModuleName(name) {
return name.replace(/\d+$/, "");
}
// src/config.ts
function setConfig(userConfig) {
Config.appKey = userConfig.appKey;
if (userConfig.database) {
Config.database = {
connections: userConfig.database
};
}
if (userConfig.storage) {
Config.storage = {
disks: userConfig.storage
};
}
if (userConfig.queues) {
Config.queues = userConfig.queues;
}
if (userConfig.channels) {
Config.channels = userConfig.channels;
}
if (userConfig.listeners) {
Config.listeners = new Map(Object.entries(userConfig.listeners));
}
return userConfig;
}
function registerModel(model) {
Config.models = Config.models || {};
Config.models[model.name] = model;
}
function getModel(name) {
var _a;
return (_a = Config.models) == null ? void 0 : _a[name];
}
function registerJob(job) {
const jobName = sanitizeModuleName(job.name);
Config.jobs = Config.jobs || {};
Config.jobs[jobName] = job;
}
function getJob(name) {
var _a;
return (_a = Config.jobs) == null ? void 0 : _a[name];
}
function getQueue(name) {
var _a;
return (_a = Config.queues) == null ? void 0 : _a[name];
}
function registerEvent(event) {
const eventName = sanitizeModuleName(event.name);
Config.events = Config.events || {};
Config.events[eventName] = event;
}
function getEvent(name) {
var _a;
return (_a = Config.events) == null ? void 0 : _a[name];
}
function getListenersForEventClass(eventClass) {
const eventName = sanitizeModuleName(eventClass.name);
return Config.listeners.get(eventName) || [];
}
function setEnv(env) {
Config.env = env;
}
function getChannelNames() {
return Object.keys(Config.channels || {});
}
function getChannel(name) {
var _a;
return (_a = Config.channels) == null ? void 0 : _a[name];
}
var Config = class {
};
Config.listeners = /* @__PURE__ */ new Map();
function defineConfig(callback) {
return (ctx) => {
setEnv(ctx.env);
return setConfig(callback(ctx));
};
}
// src/model.ts
var import_pluralize2 = __toESM(require("pluralize"));
// src/query-builder.ts
var import_tiny_invariant = __toESM(require("tiny-invariant"));
var QueryBuilder = class {
constructor(modelInstance) {
this.modelInstance = modelInstance;
this.$selects = [];
this.$bindings = [];
this.$where = [];
this.$orderBy = [];
this.$eagerLoad = [];
this.$limit = null;
this.$single = false;
this.$afterHooks = [];
this.$modelClass = modelInstance.constructor;
this.$from = this.$modelClass.table || modelToTableName(sanitizeModuleName(this.$modelClass.name));
}
select(...fields) {
this.$selects.push(...fields);
return this;
}
connection() {
return this.$modelClass.getConnection();
}
buildQuery() {
return [
`select ${this.$selects.length ? this.$selects.join(",") : "*"}`,
` from ${this.$from}`,
this.$where.length ? " where " + this.$where.join(" and ") : "",
this.$orderBy.length ? " order by " + this.$orderBy.join(", ") : "",
this.$limit ? ` limit ${this.$limit}` : ""
].join("");
}
toSQL() {
return this.buildQuery();
}
dump() {
console.log({
query: this.toSQL(),
bindings: this.$bindings
});
return this;
}
async execute() {
var _a;
const query = this.toSQL();
try {
const dbResults = await this.connection().prepare(query).bind(...this.$bindings).all();
(0, import_tiny_invariant.default)(dbResults.results, `Query failed: ${dbResults.error}`);
let results = dbResults.results.map(
(data) => this.$modelClass.instanceFromDB(data)
);
results = await this.eagerLoadRelations(results);
let result = this.$single ? (_a = results[0]) != null ? _a : null : results;
this.runCallbacks(result);
return result;
} catch (e) {
throw new DatabaseException((e == null ? void 0 : e.cause) || (e == null ? void 0 : e.message));
}
}
runCallbacks(results) {
return this.$afterHooks.map((callback) => callback(results));
}
find(ids) {
if (Array.isArray(ids)) {
return this.whereIn("id", ids);
}
return this.where("id", ids).first();
}
where(field, operator, value) {
if (!value) {
value = operator;
operator = "=";
}
this.$where.push(`${field} ${operator} ?`);
this.$bindings.push(value);
return this;
}
whereIn(field, values) {
this.$where.push(`${field} in (${values.map(() => "?").join(",")})`);
this.$bindings.push(...values);
return this;
}
with(relationName) {
if (Array.isArray(relationName)) {
this.$eagerLoad.push(...relationName);
} else {
this.$eagerLoad.push(relationName);
}
return this;
}
limit(limit) {
this.$limit = limit;
return this;
}
orderBy(field, direction = "asc") {
this.$orderBy.push(`${field} ${direction}`);
return this;
}
get() {
return this.execute();
}
first() {
this.$single = true;
return this.limit(1);
}
async insert(attributes) {
try {
const results = await this.connection().prepare(
`insert into ${this.$from} (${Object.keys(attributes).join(
","
)}) values (${Object.keys(attributes).map((_, i) => `?`).join(",")}) returning *`
).bind(...Object.values(attributes)).first();
return results;
} catch (e) {
throw new DatabaseException((e == null ? void 0 : e.cause) || (e == null ? void 0 : e.message));
}
}
async update(attributes) {
const keysToUpdate = Object.keys(attributes).filter((key) => key !== "id");
try {
const results = await this.connection().prepare(
`update ${this.$from} set ${keysToUpdate.map((key) => `${key} = ?`).join(",")} where id = ?`
).bind(...keysToUpdate.map((key) => attributes[key]), attributes.id).run();
return results.success;
} catch (e) {
throw new DatabaseException((e == null ? void 0 : e.cause) || (e == null ? void 0 : e.message));
}
}
async delete() {
try {
const results = await this.connection().prepare(`delete from ${this.$from} where id = ?`).bind(this.modelInstance.id).run();
return results.success;
} catch (e) {
throw new DatabaseException((e == null ? void 0 : e.cause) || (e == null ? void 0 : e.message));
}
}
async count() {
this.select("count(*) as count");
const query = this.buildQuery();
const results = await this.connection().prepare(query).bind(...this.$bindings).first();
return results.count;
}
afterExecute(callback) {
this.$afterHooks.push(callback);
return this;
}
async eagerLoadRelations(models) {
if (!this.$eagerLoad.length || !models) {
return models;
}
let modelArray = Array.isArray(models) ? models : [models];
for (const relationName of this.$eagerLoad) {
modelArray = await this.eagerLoadRelation(relationName, modelArray);
}
return modelArray;
}
async eagerLoadRelation(relationName, models) {
const relation = this.$modelClass.getRelation(relationName);
if (!relation) {
throw new Error(`Relation ${relationName} does not exist`);
}
relation.addEagerConstraints(models);
return relation.match(
models,
// Don't set typical constraints on the relation.
await relation.getResults(false),
relationName
);
}
then(onfulfilled, onrejected) {
const promise = this.get();
return promise.then(onfulfilled, onrejected);
}
catch(onrejected) {
const promise = this.get();
return promise.catch(onrejected);
}
};
var DatabaseException = class extends Error {
};
// src/relations/relation.ts
var Relation = class {
constructor(query) {
this.query = query;
return new Proxy(this, {
get(target, prop) {
if (prop in target) {
return target[prop];
}
if (prop in target.query) {
return target.query[prop];
}
return void 0;
}
});
}
then(onfulfilled, onrejected) {
const promise = this.getResults(true);
return promise.then(onfulfilled, onrejected);
}
catch(onrejected) {
const promise = this.getResults(true);
return promise.catch(onrejected);
}
};
// src/relations/belongs-to.ts
var BelongsTo = class extends Relation {
constructor(query, child, foreignKey, ownerKey, relationName) {
super(query);
this.query = query;
this.child = child;
this.foreignKey = foreignKey;
this.ownerKey = ownerKey;
this.relationName = relationName;
}
associate(model) {
this.child[this.foreignKey] = model.id;
return this.child;
}
dissociate() {
this.child[this.foreignKey] = null;
return this.child;
}
addEagerConstraints(models) {
this.query.whereIn(
this.ownerKey,
models.map((model) => model[this.foreignKey])
);
}
match(models, results, relationName) {
return models.map((model) => {
model.setRelation(
relationName,
results.find(
(result) => result[this.ownerKey] === model[this.foreignKey]
)
);
return model;
});
}
getResults(withConstraints = true) {
if (withConstraints) {
this.query.where(this.ownerKey, this.child[this.foreignKey]).first();
}
return this.query.afterExecute((results) => {
this.child.setRelation(this.relationName, results);
}).get();
}
};
// src/relations/has-many.ts
var HasMany = class extends Relation {
constructor(query, parent, foreignKey, ownerKey, relationName) {
super(query);
this.query = query;
this.parent = parent;
this.foreignKey = foreignKey;
this.ownerKey = ownerKey;
this.relationName = relationName;
}
save(models) {
models = models instanceof Array ? models : [models];
return Promise.all(
models.map(async (model) => {
model[this.foreignKey] = this.parent[this.ownerKey];
await model.save();
return model;
})
);
}
create(attributeSets) {
attributeSets = attributeSets instanceof Array ? attributeSets : [attributeSets];
return Promise.all(
attributeSets.map(async (attributes) => {
const model = new this.query.modelInstance.constructor(attributes);
model[this.foreignKey] = this.parent[this.ownerKey];
await model.save();
return model;
})
);
}
addEagerConstraints(models) {
this.query.whereIn(
this.foreignKey,
models.map((model) => model[this.ownerKey])
);
}
match(models, results, relationName) {
return models.map((model) => {
model.setRelation(
relationName,
results.filter(
(result) => result[this.foreignKey] === model.id
)
);
return model;
});
}
getResults(withConstraints = true) {
if (withConstraints) {
this.query.where(
this.foreignKey,
this.parent[this.ownerKey]
);
}
return this.query.afterExecute((results) => {
this.parent.setRelation(this.relationName, results);
}).get();
}
};
// src/relations/has-one.ts
var HasOne = class extends Relation {
constructor(query, parent, foreignKey, ownerKey, relationName) {
super(query);
this.query = query;
this.parent = parent;
this.foreignKey = foreignKey;
this.ownerKey = ownerKey;
this.relationName = relationName;
}
async save(model) {
model[this.foreignKey] = this.parent[this.ownerKey];
await model.save();
return model;
}
async create(attributes) {
const model = new this.query.modelInstance.constructor(attributes);
model[this.foreignKey] = this.parent[this.ownerKey];
await model.save();
return model;
}
addEagerConstraints(models) {
this.query.whereIn(
this.foreignKey,
models.map((model) => model[this.ownerKey])
);
}
match(models, results, relationName) {
return models.map((model) => {
model.setRelation(
relationName,
results.find(
(result) => result[this.foreignKey] === model.id
)
);
return model;
});
}
getResults(withConstraints = true) {
if (withConstraints) {
this.query.where(this.foreignKey, this.parent[this.ownerKey]).first();
}
return this.query.afterExecute((results) => {
this.parent.setRelation(this.relationName, results);
}).get();
}
};
// src/model.ts
var Model = class {
constructor(attributes = {}) {
this.attributes = attributes;
this.timestamps = true;
this.relations = {};
this.id = null;
this.attributes = attributes;
Object.keys(attributes).forEach((key) => {
this[key] = attributes[key];
});
return new Proxy(this, {
get(target, prop) {
if (prop in target) {
return target[prop];
}
if (prop in target.relations) {
return target.relations[prop];
}
if (prop in target.attributes) {
return target.attributes[prop];
}
if (typeof prop === "string" && target[`$${prop}`]) {
return target[`$${prop}`]();
}
return void 0;
},
set(target, prop, value) {
target[prop] = value;
target.attributes[prop] = value;
return true;
}
});
}
static instanceFromDB(dbAttributes) {
let attributes = {};
const propertiesWeKnowAreDates = ["createdAt", "updatedAt"];
Object.keys(dbAttributes).forEach((key) => {
if (propertiesWeKnowAreDates.includes(key)) {
attributes[key] = new Date(dbAttributes[key]);
} else {
attributes[key] = dbAttributes[key];
}
});
return new this(attributes);
}
static getConnection() {
var _a, _b, _c, _d;
if (!Config.database) {
throw new Error("No database connection defined");
}
if (!((_b = (_a = Config.database) == null ? void 0 : _a.connections) == null ? void 0 : _b[this.connection])) {
throw new Error(`No database connection defined for ${this.connection}`);
}
return (_d = (_c = Config.database) == null ? void 0 : _c.connections) == null ? void 0 : _d[this.connection];
}
static query() {
return new QueryBuilder(new this());
}
static all() {
return this.query().select("*").get();
}
static first() {
return this.query().first();
}
static find(ids) {
return this.query().find(ids);
}
static orderBy(field, direction = "asc") {
return this.query().orderBy(field, direction);
}
static count() {
return this.query().count();
}
static where(field, operator, value) {
return this.query().where(field, operator, value);
}
static whereIn(field, values) {
return this.query().whereIn(field, values);
}
static with(relationName) {
return this.query().with(relationName);
}
static getRelation(relationName) {
var _a, _b;
return (_b = (_a = new this())[`$${relationName}`]) == null ? void 0 : _b.call(_a);
}
/**
* Create a model with the attributes, and return an instance of the model.
*/
static async create(attributes) {
const model = new this(attributes);
await model.save();
return model;
}
async performInsert() {
const query = new QueryBuilder(this);
if (this.timestamps) {
this.updateTimestamps();
}
const results = await query.insert(this.serializeAttributes());
this.id = results.id;
return true;
}
async performUpdate() {
const query = new QueryBuilder(this);
if (this.timestamps) {
this.updateTimestamps();
}
await query.update(this.serializeAttributes());
return true;
}
async save() {
return this.id ? await this.performUpdate() : await this.performInsert();
}
async update(attributes) {
Object.keys(attributes).forEach((key) => {
this[key] = attributes[key];
});
return this.save();
}
delete() {
const query = new QueryBuilder(this);
return query.delete();
}
serialize() {
return {
...this.serializeAttributes(),
...this.serializeRelations()
};
}
serializeAttributes() {
return Object.keys(this.attributes).reduce((acc, key) => {
let value = this.attributes[key];
switch (typeof value) {
case "object":
value = value instanceof Date ? value.getTime() : JSON.stringify(value);
break;
case "boolean":
value = value ? 1 : 0;
break;
case "undefined":
value = null;
break;
default:
break;
}
acc[key] = value;
return acc;
}, {});
}
serializeRelations() {
return Object.keys(this.relations).reduce((acc, key) => {
const value = this.relations[key];
acc[key] = value instanceof Array ? value.map((model) => model.serialize()) : value.serialize();
return acc;
}, {});
}
toJSON() {
return this.serialize();
}
/**
* Timestamps
*/
updateTimestamps() {
const now = /* @__PURE__ */ new Date();
this.updatedAt = now;
if (!this.createdAt) {
this.createdAt = now;
}
return this;
}
/**
* Relationships
*/
setRelation(relationName, value) {
this.relations[relationName] = value;
}
getRelation(relationName) {
return this.relations[relationName];
}
belongsTo(model, foreignKey, ownerKey) {
const modelName = sanitizeModuleName(model.name);
foreignKey = foreignKey || modelToForeignKeyId(modelName);
ownerKey = ownerKey || "id";
const relationName = lowercaseFirstLetter(modelName);
return new BelongsTo(
model.query(),
this,
foreignKey,
ownerKey,
relationName
);
}
hasOne(model, foreignKey, ownerKey) {
const constructorName = sanitizeModuleName(this.constructor.name);
const modelName = sanitizeModuleName(model.name);
foreignKey = foreignKey || modelToForeignKeyId(constructorName);
ownerKey = ownerKey || "id";
const relationName = lowercaseFirstLetter(modelName);
return new HasOne(model.query(), this, foreignKey, ownerKey, relationName);
}
hasMany(model, foreignKey, ownerKey) {
const constructorName = sanitizeModuleName(this.constructor.name);
const modelName = sanitizeModuleName(model.name);
foreignKey = foreignKey || modelToForeignKeyId(constructorName);
ownerKey = ownerKey || "id";
const relationName = (0, import_pluralize2.default)(lowercaseFirstLetter(modelName));
return new HasMany(model.query(), this, foreignKey, ownerKey, relationName);
}
static register(model) {
registerModel(model);
}
};
Model.connection = "default";
Model.table = "";
// src/session.ts
var SuperflareSession = class {
constructor(session) {
this.session = session;
this.dirty = false;
}
get id() {
return this.session.id;
}
get data() {
return this.session.data;
}
has(key) {
return this.session.has(key);
}
get(key) {
return this.session.get(key);
}
/**
* Get a flashed value from the session, and indicate that the session has been modified.
*/
getFlash(key) {
this.dirty = true;
return this.session.get(key);
}
set(key, value) {
this.dirty = true;
this.session.set(key, value);
}
unset(key) {
this.dirty = true;
this.session.unset(key);
}
/**
* Flash a value to the session. To read the flashed value on a future request, use `getFlash`.
*/
flash(key, value) {
this.dirty = true;
this.session.flash(key, value);
}
isDirty() {
return this.dirty;
}
getSession() {
return this.session;
}
};
// src/seeder.ts
function seed(callback) {
return (database) => {
setConfig({ database: { default: database } });
callback();
};
}
// src/storage.ts
function storage(disk) {
var _a, _b, _c, _d;
if (!((_a = Config.storage) == null ? void 0 : _a.disks)) {
throw new Error(
"No Storage disks configured. Please assign an R2 bucket in your config file."
);
}
const diskToUse = disk ? (_b = Config.storage) == null ? void 0 : _b.disks[disk] : (_d = (_c = Config.storage) == null ? void 0 : _c.disks) == null ? void 0 : _d.default;
if (!diskToUse || !diskToUse.binding) {
throw new Error(`R2 bucket "${disk}" could not be found.`);
}
const diskName = disk || "default";
return new Storage(diskName, diskToUse);
}
var Storage = class {
constructor(diskName, disk) {
this.diskName = diskName;
this.disk = disk;
}
url(key) {
if (this.disk.publicPath) {
return `${this.disk.publicPath}/${key}`;
}
return null;
}
get(key) {
return this.disk.binding.get(key);
}
delete(key) {
return this.disk.binding.delete(key);
}
put(...args) {
return this.disk.binding.put(...args);
}
/**
* Takes an input without a key and generates a random filename for you.
* Optionally pass an `{ extension: string }` option to add an extension to the filename.
*/
putRandom(input, options) {
const hash2 = crypto.randomUUID();
let key = (options == null ? void 0 : options.prefix) ? `${options.prefix}/${hash2}` : hash2;
if (options == null ? void 0 : options.extension) {
key += `.${options.extension}`;
}
return this.disk.binding.put(key, input, options);
}
};
async function servePublicPathFromStorage(path) {
var _a;
const notFoundResponse = new Response("Not found", { status: 404 });
if (!((_a = Config.storage) == null ? void 0 : _a.disks)) {
return notFoundResponse;
}
const matchingDiskName = Object.keys(Config.storage.disks).find(
(diskName) => {
const { publicPath } = Config.storage.disks[diskName];
return publicPath && path.startsWith(publicPath);
}
);
if (!matchingDiskName) {
return notFoundResponse;
}
const key = path.replace(Config.storage.disks[matchingDiskName].publicPath, "").replace(/^\//, "");
const object = await storage(matchingDiskName).get(key);
if (!object) {
return notFoundResponse;
}
return new Response(object.body, {
headers: {
// TODO: Infer content type from file extension
// 'Content-Type': file.type,
"cache-control": "public, max-age=31536000, immutable"
}
});
}
// src/factory.ts
var _definition;
var Factory = class {
constructor(model) {
this.model = model;
__privateAdd(this, _definition, () => ({}));
}
static for(model) {
return new this(model);
}
create(attributes) {
return this.model.create({
...__privateGet(this, _definition).call(this),
...attributes != null ? attributes : {}
});
}
definition(definition) {
__privateSet(this, _definition, definition);
return this;
}
};
_definition = new WeakMap();
// src/fetch.ts
async function handleFetch({
request,
env,
ctx,
config,
session,
getSessionCookie
}, getResponse) {
config({ request, env, ctx });
if (!session.has("sessionId")) {
session.set("sessionId", crypto.randomUUID());
}
const response = await getResponse();
if (session.isDirty()) {
response.headers.set("Set-Cookie", await getSessionCookie());
}
return response;
}
// src/channels.ts
function getConfigForChannelName(channelName) {
const defaultChannel = getChannel("default");
const specificChannelName = channelNameToConfigName(
channelName,
getChannelNames()
);
return specificChannelName ? [getChannel(specificChannelName), specificChannelName] : [defaultChannel, "default"];
}
function getBindingForChannelName(channelName) {
const defaultChannel = getChannel("default");
const [config] = getConfigForChannelName(channelName);
return (config == null ? void 0 : config.binding) || (defaultChannel == null ? void 0 : defaultChannel.binding);
}
function channelNameToConfigName(channelName, channelNames) {
var _a;
const exactMatch = channelNames.find((name) => name === channelName);
if (exactMatch) {
return exactMatch;
}
const channelNamesWithRegexes = channelNames.map((name) => {
return {
name,
regex: new RegExp(name.replace(/\*/g, "[^.]+"))
};
});
const matches = channelNamesWithRegexes.filter((name) => {
return name.regex.exec(channelName);
});
return (_a = matches.sort(
(a, b) => b.name.split(".").length - a.name.split(".").length
)[0]) == null ? void 0 : _a.name;
}
// src/serialize.ts
function serializeArguments(args) {
return args.map((arg) => {
if (arg instanceof Model && arg.id) {
const newValue = arg.toJSON();
newValue.$model = arg.constructor.name;
arg = { ...arg, id: arg.id };
return JSON.stringify(newValue);
} else {
return JSON.stringify(arg);
}
});
}
async function hydrateArguments(args) {
return await Promise.all(
args.map(async (value) => {
const arg = JSON.parse(value);
if (arg instanceof Object && arg.$model && arg.id) {
const modelClass = getModel(arg.$model);
if (!modelClass) {
throw new Error(`Model ${arg.$model} not found.`);
}
return await modelClass.find(arg.id);
} else {
return arg;
}
})
);
}
// src/event.ts
var Event = class {
static async dispatch(...args) {
var _a;
const event = new this(...args);
if (this.shouldQueue) {
const queueName = (_a = this.queue) != null ? _a : "default";
const queue = getQueue(queueName);
if (!queue) {
throw new Error(`Queue ${queueName} not found.`);
}
queue.send({
event: sanitizeModuleName(this.name),
payload: serializeArguments(args)
});
} else {
dispatchEvent(event);
}
}
static register(event) {
registerEvent(event);
}
};
Event.shouldQueue = false;
Event.queue = "default";
function dispatchEvent(event) {
getListenersForEventClass(event.constructor).forEach((listener) => {
const instance = new listener();
instance.handle(event);
});
if (event.broadcastTo) {
const channelName = event.broadcastTo();
if (channelName) {
broadcastEvent(event, channelName);
}
}
}
async function broadcastEvent(event, channelName) {
const binding = getBindingForChannelName(channelName);
if (!binding) {
throw new Error(
`No channel binding found for ${channelName}. Please update your superflare.config.`
);
}
const id = binding.idFromName(channelName);
const channel = binding.get(id);
const data = {
event: sanitizeModuleName(event.constructor.name),
data: event
};
await channel.fetch("https://example.com/", {
method: "POST",
body: JSON.stringify(data)
});
}
// src/job.ts
var Job = class {
constructor() {
/**
* The name of the queue on which to dispatch this job.
*/
this.queue = "default";
}
/**
* The private instance method we use to dispatch a job onto the queue.
*/
async dispatch(...args) {
var _a;
const queueName = (_a = this.queue) != null ? _a : "default";
const queue = getQueue(queueName);
if (!queue) {
throw new Error(`Queue ${queueName} not found.`);
}
await queue.send(this.toQueuePayload(args));
}
/**
* Dispatch the job with the given arguments.
*/
static async dispatch(...args) {
const job = new this(...args);
return job.dispatch(...args);
}
/**
* Convert the constructor arguments to a payload that can be sent to the queue.
*/
toQueuePayload(constructorArgs) {
return {
job: this.constructor.name,
payload: serializeArguments(constructorArgs)
};
}
static register(job) {
registerJob(job);
}
};
// src/queue.ts
async function handleQueue(batch, env, ctx, config) {
config({ env, ctx });
return await Promise.all(
batch.messages.map((message) => handleQueueMessage(message, ctx))
);
}
async function handleQueueMessage(message, ctx) {
const instance = await hydrateInstanceFromQueuePayload(
message.body
);
if (instance instanceof Job) {
await instance.handle();
return;
}
if (instance instanceof Event) {
dispatchEvent(instance);
return;
}
throw new Error(`Could not hydrate instance from queue payload.`);
}
async function hydrateInstanceFromQueuePayload(payload) {
if (payload.event) {
const eventClass = getEvent(payload.event);
const constructorArgs = await hydrateArguments(payload.payload);
const event = new eventClass(...constructorArgs);
return event;
}
if (payload.job) {
const jobClass = getJob(payload.job);
const constructorArgs = await hydrateArguments(payload.payload);
const job = new jobClass(...constructorArgs);
return job;
}
throw new Error(`Job payload does not contain a job or event.`);
}
// src/hash.ts
var import_bcryptjs = __toESM(require("bcryptjs"));
function hash() {
return {
async make(input) {
return await import_bcryptjs.default.hash(input, 10);
},
async check(input, hash2) {
return await import_bcryptjs.default.compare(input, hash2);
}
};
}
// src/auth.ts
var SESSION_KEY = "superflare:auth:id";
var SuperflareAuth = class {
constructor(session) {
this.session = session;
}
async attempt(model, credentials) {
const user = await model.where("email", credentials.email).first();
if (!user)
return false;
const passwordMatches = await hash().check(
credentials.password,
// @ts-expect-error I don't know how to indicate that we expect a password field
user.password
);
if (!passwordMatches)
return false;
this.login(user);
return true;
}
async check(model) {
return !!await this.user(model);
}
id() {
return this.session.get(SESSION_KEY);
}
login(user) {
this.session.set(SESSION_KEY, user.id);
}
async user(model) {
const id = this.id();
if (!id)
return null;
return await model.find(id);
}
logout() {
this.session.unset(SESSION_KEY);
}
};
// src/listener.ts
var Listener = class {
};
// src/websockets.ts
var import_tiny_invariant2 = __toESM(require("tiny-invariant"));
async function handleWebSockets(request, {
auth,
session,
userModel,
channelName: specifiedChannelName
} = {}) {
const url = new URL(request.url);
const channelNameFrompath = url.pathname.split("/").pop();
const channelName = specifiedChannelName || channelNameFrompath;
(0, import_tiny_invariant2.default)(channelName, "Channel name is required");
const [config, configName] = getConfigForChannelName(channelName);
if (config && config.authorize) {
if (!auth) {
throw new Error(
"You must provide `auth` to `handleWebSockets` in order to authorize the request"
);
}
if (!userModel) {
throw new Error(
"You must provide `userModel` to `handleWebSockets` in order to authorize the request"
);
}
const user = await auth.user(userModel);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
const dynamicValues = getValuesFromChannelName(channelName, configName);
const isAuthorized = await config.authorize(user, ...dynamicValues);
if (!isAuthorized) {
return new Response("Unauthorized", { status: 401 });
}
}
const binding = getBindingForChannelName(channelName);
if (!binding) {
throw new Error(
`No channel binding found for "${channelName}". Please update your superflare.config.`
);
}
const userPayload = {
/**
* This is set in the `handleFetch` function.
*/
sessionId: session == null ? void 0 : session.get("sessionId")
};
if (config == null ? void 0 : config.presence) {
if (!auth) {
throw new Error(
"You must provide `auth` to `handleWebSockets` in order to use a Presence channel"
);
}
if (!userModel) {
throw new Error(
"You must provide `userModel` to `handleWebSockets` in order to use a Presence channel"
);
}
const user = await auth.user(userModel);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
const dynamicValues = getValuesFromChannelName(channelName, configName);
userPayload.presenceData = await config.presence(user, ...dynamicValues);
}
const id = binding.idFromName(channelName);
const channel = binding.get(id);
const newUrl = new URL(url);
newUrl.searchParams.set("user", JSON.stringify(userPayload));
return channel.fetch(newUrl.toString(), request);
}
function getValuesFromChannelName(channelName, configName) {
const inputValues = channelName.split(".");
const configValues = configName.split(".");
const dynamicValues = configValues.map((value, index) => {
if (value === "*") {
return inputValues[index];
}
}).filter(Boolean);
return dynamicValues;
}
// src/durable-objects/Channel.ts
var Channel = class {
constructor(controller, env) {
this.sessions = [];
this.storage = controller.storage;
this.env = env;
}
async fetch(request) {
if (request.method === "POST") {
const message = await request.json();
this.broadcast(message);
return new Response("ok");
}
if (request.method !== "GET") {
return new Response("Method not allowed", { status: 405 });
}
if (request.headers.get("Upgrade") !== "websocket") {
return new Response("expected websocket", { status: 400 });
}
const pair = new WebSocketPair();
const url = new URL(request.url);
const user = JSON.parse(url.searchParams.get("user") || "{}");
await this.handleSession(pair[1], user);
return new Response(null, { status: 101, webSocket: pair[0] });
}
async handleSession(webSocket, user) {
webSocket.accept();
const session = {
webSocket,
user,
id: (user == null ? void 0 : user.sessionId) || "anonymous"
};
this.sessions.push(session);
let receivedInitialMessage = false;
webSocket.addEventListener("message", async (message) => {
var _a;
try {
let data = JSON.parse(message.data);
if (!receivedInitialMessage) {
if ((_a = session.user) == null ? void 0 : _a.presenceData) {
this.broadcast({ joined: session.user.presenceData });
}
receivedInitialMessage = true;
return;
}
data = { message: "" + data.message };
let dataStr = JSON.stringify(data);
this.broadcast(dataStr);
} catch (e) {
webSocket.send(JSON.stringify({ error: e.message }));
}
});
let closeOrErrorHandler = () => {
var _a;
session.quit = true;
this.sessions = this.sessions.filter((member) => member !== session);
if ((_a = session.user) == null ? void 0 : _a.presenceData) {
this.broadcast({ quit: session.user.presenceData });
}
};
webSocket.addEventListener("close", closeOrErrorHandler);
webSocket.addEventListener("error", closeOrErrorHandler);
}
broadcast(message) {
if (typeof message !== "string") {
message = JSON.stringify(message);
}
let quitters = [];
this.sessions = this.sessions.filter((session) => {
try {
session.webSocket.send(message);
return true;
} catch (err) {
session.quit = true;
quitters.push(session);
return false;
}
});
quitters.forEach((quitter) => {
var _a;
if ((_a = quitter.user) == null ? void 0 : _a.presenceData) {
this.broadcast({ quit: quitter.user.presenceData });
}
});
}
};
// src/schema.ts
var Schema = class {
constructor(tableName, builder, mode = "create") {
this.tableName = tableName;
this.builder = builder;
this.mode = mode;
}
addCommand(command) {
this.builder.commands.push(command);
}
static create(tableName, callback) {
const builder = new SchemaBuilder(tableName);
callback(builder);
return new Schema(tableName, builder);
}
static update(tableName, callback) {
const builder = new SchemaBuilder(tableName);
callback(builder);
return new Schema(tableName, builder, "update");
}
static rename(oldTableName, newTableName) {
const schema = new Schema(
oldTableName,
new SchemaBuilder(newTableName),
"update"
);
schema.addCommand(`RENAME TO ${newTableName}`);
return schema;
}
static drop(tableName) {
const schema = new Schema(
tableName,
new SchemaBuilder(tableName),
"delete"
);
return schema;
}
toSql() {
if (this.mode === "create") {
return `CREATE TABLE ${this.tableName} (
${this.builder.columns.map((c) => " " + c).join(",\n")}
);`;
} else if (this.mode === "update") {
const lines = [
...this.builder.commands,
...this.builder.columns.map((c) => "ADD COLUMN " + c)
];
return `ALTER TABLE ${this.tableName}
${lines.join(",\n")};`;
} else if (this.mode === "delete") {
return `DROP TABLE ${this.tableName};`;
}
throw new Error(`Unknown schema mode: ${this.mode}`);
}
};
var SchemaBuilder = class {
constructor(tableName) {
this.tableName = tableName;
this.columns = [];
this.commands = [];
}
blob(columnName) {
return this.addColumn(columnName, "BLOB");
}
boolean(columnName) {
return this.addColumn(columnName, "BOOLEAN");
}
date(columnName) {
return this.addColumn(columnName, "DATE");
}
dropColumn(columnName) {
return this.addCommand(`DROP COLUMN ${columnName}`);
}
dateTime(columnName) {
return this.addColumn(columnName, "DATETIME");
}
increments(columnName) {
return this.integer(columnName, true);
}
float(columnName) {
return this.addColumn(columnName, "FLOAT");
}
integer(columnName, primaryKey) {
return this.addColumn(columnName, "INTEGER", {
primaryKey: Boolean(primaryKey)
});
}
renameColumn(from, to) {
return this.addCommand(`RENAME COLUMN ${from} TO ${to}`);
}
string(columnName) {
return this.addColumn(columnName, "TEXT");
}
text(columnName) {
return this.addColumn(columnName, "TEXT");
}
timestamps() {
this.dateTime("createdAt");
this.dateTime("updatedAt");
}
addCommand(command) {
this.commands.push(command);
return this;
}
addColumn(columnName, columnType, options) {
const definition = new ColumnDefinition(columnName, columnType, options);
this.columns.push(definition);
return definition;
}
};
var _nullable, _unique;
var ColumnDefinition = class {
constructor(columnName, columnType, options) {
this.columnName = columnName;
this.columnType = columnType;
this.options = options;
__privateAdd(this, _nullable, false);
__privateAdd(this, _unique, false);
}
toString() {
var _a;
const parts = [this.columnName, this.columnType];
if ((_a = this.options) == null ? void 0 : _a.primaryKey) {
parts.push("PRIMARY KEY");
} else {
if (!__privateGet(this, _nullable)) {
parts.push("NOT NULL");
}
}
if (__privateGet(this, _unique)) {
parts.push("UNIQUE");
}
return parts.join(" ");
}
nullable() {
__privateSet(this, _nullable, true);
return this;
}
unique() {
__privateSet(this, _unique, true);
return this;
}
};
_nullable = new WeakMap();
_unique = new WeakMap();
// src/form-data.ts
var import_multipart_parser = require("@web3-storage/multipart-parser");
async function parseMultipartFormData(request, uploadHandler) {
let contentType = request.headers.get("Content-Type") || "";
const [type, boundary] = contentType.split(/\s*;\s*boundary=/);
if (!request.body || !boundary || type !== "multipart/form-data") {
throw new TypeError("Could not parse content as FormData.");
}
const formData = new FormData();
const parts = (0, import_multipart_parser.streamMultipart)(request.body, boundary);
for await (let part of parts) {
if (part.done)
break;
if (typeof part.filename === "string") {
part.filename = part.filename.split(/[/\\]/).pop();
}
const stream = new ReadableStream({
async pull(controller) {
for await (let chunk of part.data) {
controller.enqueue(chunk);
}
controller.close();
}
});
let value = await uploadHandler({
...part,
stream
});
if (typeof value !== "undefined" && value !== null) {
formData.append(part.name, value);
}
}
return formData;
}
// src/scheduled.ts
async function handleScheduled(event, env, ctx, config, scheduleDefinition) {
config({ env, ctx });
const now = new Date(event.scheduledTime);
const scheduler = new Scheduler();
await scheduleDefinition(scheduler);
scheduler.tasks.forEach((task) => {
if (shouldRunTask(task, now)) {
ctx.waitUntil(task.fn());
}
});
}
var Scheduler = class {
constructor() {
this.tasks = [];
}
run(fn) {
const task = new Task(fn);
this.tasks.push(task);
return task;
}
job(job) {
const task = new Task(async () => await job.handle());
this.tasks.push(task);
return task;
}
};
var where = {
minute: (minute) => (date) => date.getMinutes() === minute,
hour: (hour) => (date) => date.getHours() === hour,
day: (day) => (date) => date.getDay() === day,
date: (monthDate) => (date) => date.getDate() === monthDate,
month: (month) => (date) => date.getMonth() === month
};
var Task = class {
constructor(fn, constraints = []) {
this.fn = fn;
this.constraints = constraints;
}
/**
* Run every minute.
*/
everyMinute() {
return this;
}
/**
* Run hourly at the top.
*/
hourly() {
this.constraints.push(where.minute(0));
return this;
}
/**
* Run daily at midnight UTC.
*/
daily() {
this.constraints.push(where.minute(0));
this.constraints.push(where.hour(0));
return this;
}
/**
* Run daily at a specific time UTC.
*/
dailyAt(time) {
const [hour, minute] = time.split(":");
this.constraints.push(where.minute(parseInt(minute, 10)));
this.constraints.push(where.hour(parseInt(hour, 10)));
return this;
}
/**
* Run weekly on Sunday at midnight UTC.
*/
weekly() {
this.constraints.push(where.day(0));
this.constraints.push(where.hour(0));
this.constraints.push(where.minute(0));
return this;
}
/**
* Run weekly on a specific day of the week at a specific time UTC.
*/
weeklyOn(day, time) {
const [hour, minute] = time.split(":");
this.constraints.push(where.day(parseInt(day, 10)));
this.constraints.push(where.minute(parseInt(minute, 10)));
this.constraints.push(where.hour(parseInt(hour, 10)));
return this;
}
/**
* Run monthly on the first day of the month at midnight UTC.
*/
monthly() {
this.constraints.push(where.date(1));
this.constraints.push(where.hour(0));
this.constraints.push(where.minute(0));
return this;
}
/**
* Run monthly on a specific date of the month at a specific time UTC.
*/
monthlyOn(date, time) {
const [hour, minute] = time.split(":");
this.constraints.push(where.date(parseInt(date, 10)));
this.constraints.push(where.minute(parseInt(minute, 10)));
this.constraints.push(where.hour(parseInt(hour, 10)));
return this;
}
yearly() {
this.constraints.push(where.month(0));
this.constraints.push(where.date(1));
this.constraints.push(where.hour(0));
this.constraints.push(where.minute(0));
return this;
}
};
function shouldRunTask(task, date) {
if (task.constraints) {
return task.constraints.every((constraint) => constraint(date));
}
return true;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Channel,
DatabaseException,
Event,
Factory,
Job,
Listener,
Model,
Schema,
SuperflareAuth,
SuperflareSession,
defineConfig,
handleFetch,
handleQueue,
handleScheduled,
handleWebSockets,
hash,
parseMultipartFormData,
seed,
servePublicPathFromStorage,
setConfig,
storage
});