UNPKG

superflare

Version:

A full-stack framework for Cloudflare Workers.

1,676 lines (1,647 loc) 47.4 kB
"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 });