constructor(query, executeQuery, opts = {}) {
		this.#createdStack = new Error().stack
			?.split('\n')
			.slice(2)
			.map((line) => line.slice('    at '.length))
			.join('\n');
		const {
			id,
			initialData = undefined,
			knownColumns = undefined,
			initialError = undefined
		} = opts;
		this.opts = opts;
		this.#executeQuery = executeQuery;

		if (typeof query !== 'string' && !(query instanceof QueryBuilder)) {
			console.warn(`Query ${id} has no query text`);
			opts.noResolve = true;
		}

		if (!Query.#constructing) {
			console.warn(
				'Directly using new Query() is not a recommended use-case. Please use Query.create()'
			);
		}
		Query.#constructing = false; // make sure we reset it
		this.#value = this.#buildProxy();
		this.#originalText = query?.toString() ?? "SELECT 'Empty Query' WHERE 0";
		this.#hash = hashQuery(this.#originalText);
		this.#id = id ?? this.#hash;
		this.#opts = opts;

		if (query && typeof query !== 'string') this.#query = query;
		else if (query) {
			const q = new QueryBuilder()
				.from({
					/* 
						Use of nanoid prevent ambiguity when dealing with nested Queries; 
						in theory this could be the querystring has but that's kinda gross 
					*/
					[`inputQuery-${nanoid(2)}`]: taggedSql`(${sterilizeQuery(query)})`
				})
				.select('*');
			this.#query = q;
		} else {
			this.#query = new QueryBuilder();
			this.#error = new Error(`Refusing to create Query: No Query Text provided`);
			return;
		}

		if (initialError) {
			this.#error = initialError;
			return;
		}

		// TODO: Does this make sense?
		if (initialData) {
			this.#debug('initial data', 'Created with initial data', initialData);

			resolveMaybePromise(
				(d) => {
					this.#data = d;
					if (opts.initialDataDirty) {
						this.publish('dataDirty');
						this.#fetchData();
					} else {
						this.#sharedDataPromise.resolve(this);
						this.#fetchLength();
					}
				},
				initialData,
				(e) => {
					this.#error = e;
				}
			);
		} else if (opts.noResolve) {
			this.#sharedDataPromise.start();
			this.#sharedLengthPromise.start();
			this.#sharedColumnsPromise.start();
			return this;
		}

		if (knownColumns) {
			if (!Array.isArray(knownColumns))
				throw new Error(`Expected knownColumns to be an array`, { cause: knownColumns });

			this.#debug('known columns', 'Created with known columns', knownColumns);
			this.#columns = knownColumns;
			this.#sharedColumnsPromise.resolve(this);
		} else {
			resolveMaybePromise(
				() => {
					/* We don't need to do anything with the result */
				},
				this.#fetchColumns(),
				(e, isPromise) => {
					/* Async errors are handled elsewhere */ if (!isPromise) throw e;
				}
			);
		}
		resolveMaybePromise(
			() => {
				/* We don't need to do anything with the result */
			},
			this.#fetchLength(),
			(e, isPromise) => {
				/* Async errors are handled elsewhere */ if (!isPromise) throw e;
			}
		);
		if (opts.autoScore) {
			this.#calculateScore();
		}
	}
@param {QueryBuilder | string} query
@param {import('../types.js').Runner} executeQuery
@param {import("../types.js").QueryOpts<RowType>} opts
@deprecated Use {@link Query.create} instead