diff --git a/src/Database/Database.imba b/src/Database/Database.imba
index 3047c7f6..b20294a7 100644
--- a/src/Database/Database.imba
+++ b/src/Database/Database.imba
@@ -3,6 +3,7 @@ import { attachPaginate } from 'knex-paginate'
import querystring from 'querystring'
import location from '../Support/Helpers/location'
import isString from '../Support/Helpers/isString'
+import singularize from '../Support/Helpers/singularize'
import Config from './Config'
import knex from 'knex'
@@ -37,13 +38,16 @@ try
for column in columns
object[column] = result[column]
mappedResults.push(object)
- return mappedResults
+ results = mappedResults
if this._hidden && Array.isArray(this._hidden) && this._hidden.length > 0
for result in results
for column in this._hidden
delete result[column]
+ if this._relationships && Array.isArray(this._relationships) && this._relationships.length > 0
+ results = await this._loadRelationships(results)
+
results
knex.QueryBuilder.extend 'autoPaginate', do(pageSize = 20)
@@ -109,6 +113,9 @@ try
for column in this._hidden
delete result[column]
+ if this._relationships && Array.isArray(this._relationships) && this._relationships.length > 0
+ data = await this._loadRelationships(data)
+
const results = {
data,
pagination: {
@@ -190,6 +197,304 @@ try
return this
+ knex.QueryBuilder.extend 'belongsTo', do(relatedTable, queryCallback, foreignKey, localKey)
+ this._relationships = this._relationships || []
+
+ let tableName = relatedTable
+ if typeof relatedTable == 'function' && relatedTable.prototype && relatedTable.prototype.tableName
+ tableName = new relatedTable().tableName
+
+ if typeof queryCallback == 'function'
+ this._relationships.push({
+ type: 'belongsTo',
+ relatedTable: tableName,
+ queryCallback: queryCallback,
+ foreignKey: foreignKey || singularize(tableName) + '_id',
+ localKey: localKey || 'id'
+ })
+ else if typeof queryCallback == 'string'
+ this._relationships.push({
+ type: 'belongsTo',
+ relatedTable: tableName,
+ queryCallback: null,
+ foreignKey: queryCallback,
+ localKey: foreignKey || 'id'
+ })
+ else
+ this._relationships.push({
+ type: 'belongsTo',
+ relatedTable: tableName,
+ queryCallback: null,
+ foreignKey: singularize(tableName) + '_id',
+ localKey: 'id'
+ })
+
+ return this
+
+ knex.QueryBuilder.extend 'hasOne', do(relatedTable, queryCallback, foreignKey, localKey)
+ this._relationships = this._relationships || []
+
+ let tableName = relatedTable
+ if typeof relatedTable == 'function' && relatedTable.prototype && relatedTable.prototype.tableName
+ tableName = new relatedTable().tableName
+
+ if typeof queryCallback == 'function'
+ this._relationships.push({
+ type: 'hasOne',
+ relatedTable: tableName,
+ queryCallback: queryCallback,
+ foreignKey: foreignKey || singularize(this._single.table) + '_id',
+ localKey: localKey || 'id'
+ })
+ else if typeof queryCallback == 'string'
+ this._relationships.push({
+ type: 'hasOne',
+ relatedTable: tableName,
+ queryCallback: null,
+ foreignKey: queryCallback,
+ localKey: foreignKey || 'id'
+ })
+ else
+ this._relationships.push({
+ type: 'hasOne',
+ relatedTable: tableName,
+ queryCallback: null,
+ foreignKey: singularize(this._single.table) + '_id',
+ localKey: 'id'
+ })
+
+ return this
+
+ knex.QueryBuilder.extend 'hasMany', do(relatedTable, queryCallback, foreignKey, localKey)
+ this._relationships = this._relationships || []
+
+ let tableName = relatedTable
+ if typeof relatedTable == 'function' && relatedTable.prototype && relatedTable.prototype.tableName
+ tableName = new relatedTable().tableName
+
+ if typeof queryCallback == 'function'
+ this._relationships.push({
+ type: 'hasMany',
+ relatedTable: tableName,
+ queryCallback: queryCallback,
+ foreignKey: foreignKey || singularize(this._single.table) + '_id',
+ localKey: localKey || 'id'
+ })
+ else if typeof queryCallback == 'string'
+ this._relationships.push({
+ type: 'hasMany',
+ relatedTable: tableName,
+ queryCallback: null,
+ foreignKey: queryCallback,
+ localKey: foreignKey || 'id'
+ })
+ else
+ this._relationships.push({
+ type: 'hasMany',
+ relatedTable: tableName,
+ queryCallback: null,
+ foreignKey: singularize(this._single.table) + '_id',
+ localKey: 'id'
+ })
+
+ return this
+
+ knex.QueryBuilder.extend 'belongsToMany', do(relatedTable, queryCallback, pivotTable, foreignKey, relatedKey, localKey, relatedLocalKey)
+ this._relationships = this._relationships || []
+
+ let tableName = relatedTable
+ if typeof relatedTable == 'function' && relatedTable.prototype && relatedTable.prototype.tableName
+ tableName = new relatedTable().tableName
+
+ if typeof queryCallback == 'function'
+ this._relationships.push({
+ type: 'belongsToMany',
+ relatedTable: tableName,
+ queryCallback: queryCallback,
+ pivotTable: pivotTable || singularize(this._single.table) + '_' + tableName,
+ foreignKey: foreignKey || singularize(this._single.table) + '_id',
+ relatedKey: relatedKey || singularize(tableName) + '_id',
+ localKey: localKey || 'id',
+ relatedLocalKey: relatedLocalKey || 'id'
+ })
+ else if typeof queryCallback == 'string'
+ this._relationships.push({
+ type: 'belongsToMany',
+ relatedTable: tableName,
+ queryCallback: null,
+ pivotTable: queryCallback,
+ foreignKey: foreignKey || singularize(this._single.table) + '_id',
+ relatedKey: relatedKey || singularize(tableName) + '_id',
+ localKey: localKey || 'id',
+ relatedLocalKey: relatedLocalKey || 'id'
+ })
+ else
+ this._relationships.push({
+ type: 'belongsToMany',
+ relatedTable: tableName,
+ queryCallback: null,
+ pivotTable: singularize(this._single.table) + '_' + tableName,
+ foreignKey: singularize(this._single.table) + '_id',
+ relatedKey: singularize(tableName) + '_id',
+ localKey: 'id',
+ relatedLocalKey: 'id'
+ })
+
+ return this
+
+ knex.QueryBuilder.extend '_loadRelationships', do(results)
+ if !results || results.length == 0
+ return results
+
+ for relationship in this._relationships
+ let relationshipName = relationship.relatedTable
+ if relationship.type == 'belongsTo' || relationship.type == 'hasOne'
+ relationshipName = singularize(relationship.relatedTable)
+
+ if relationship.type == 'belongsTo'
+ await this._loadBelongsTo(results, relationship, relationshipName)
+ else if relationship.type == 'hasOne'
+ await this._loadHasOne(results, relationship, relationshipName)
+ else if relationship.type == 'hasMany'
+ await this._loadHasMany(results, relationship, relationshipName)
+ else if relationship.type == 'belongsToMany'
+ await this._loadBelongsToMany(results, relationship, relationshipName)
+
+ results
+
+ knex.QueryBuilder.extend '_loadBelongsTo', do(results, relationship, relationshipName)
+ const hasForeignKey = results.length > 0 && results[0].hasOwnProperty(relationship.foreignKey)
+
+ if !hasForeignKey
+ const ids = results.map(do(result) result.id).filter(do(id) id != null)
+
+ if ids.length == 0
+ for result in results
+ result[relationshipName] = null
+ return
+
+ const foreignKeyResults = await Database(this._single.table)
+ .select('id', relationship.foreignKey)
+ .whereIn('id', ids)
+
+ const foreignKeyMap = {}
+ for row in foreignKeyResults
+ foreignKeyMap[row.id] = row[relationship.foreignKey]
+
+ for result in results
+ result[relationship.foreignKey] = foreignKeyMap[result.id]
+
+ const foreignKeys = results.map(do(result) result[relationship.foreignKey]).filter(do(key) key != null)
+
+ if foreignKeys.length == 0
+ for result in results
+ result[relationshipName] = null
+ return
+
+ let relatedQuery = Database(relationship.relatedTable).whereIn(relationship.localKey, foreignKeys)
+
+ if relationship.queryCallback
+ relatedQuery = relationship.queryCallback(relatedQuery)
+
+ const relatedResults = await relatedQuery
+ const relatedMap = {}
+
+ for related in relatedResults
+ relatedMap[related[relationship.localKey]] = related
+
+ for result in results
+ result[relationshipName] = relatedMap[result[relationship.foreignKey]] || null
+
+ knex.QueryBuilder.extend '_loadHasOne', do(results, relationship, relationshipName)
+ const localKeys = results.map(do(result) result[relationship.localKey]).filter(do(key) key != null)
+
+ if localKeys.length == 0
+ for result in results
+ result[relationshipName] = null
+ return
+
+ let relatedQuery = Database(relationship.relatedTable).whereIn(relationship.foreignKey, localKeys)
+
+ if relationship.queryCallback
+ relatedQuery = relationship.queryCallback(relatedQuery)
+
+ const relatedResults = await relatedQuery
+ const relatedMap = {}
+
+ for related in relatedResults
+ relatedMap[related[relationship.foreignKey]] = related
+
+ for result in results
+ result[relationshipName] = relatedMap[result[relationship.localKey]] || null
+
+ knex.QueryBuilder.extend '_loadHasMany', do(results, relationship, relationshipName)
+ const localKeys = results.map(do(result) result[relationship.localKey]).filter(do(key) key != null)
+
+ if localKeys.length == 0
+ for result in results
+ result[relationshipName] = []
+ return
+
+ let relatedQuery = Database(relationship.relatedTable).whereIn(relationship.foreignKey, localKeys)
+
+ if relationship.queryCallback
+ relatedQuery = relationship.queryCallback(relatedQuery)
+
+ const relatedResults = await relatedQuery
+ const relatedMap = {}
+
+ for related in relatedResults
+ const key = related[relationship.foreignKey]
+ if !relatedMap[key]
+ relatedMap[key] = []
+ relatedMap[key].push(related)
+
+ for result in results
+ result[relationshipName] = relatedMap[result[relationship.localKey]] || []
+
+ knex.QueryBuilder.extend '_loadBelongsToMany', do(results, relationship, relationshipName)
+ const localKeys = results.map(do(result) result[relationship.localKey]).filter(do(key) key != null)
+
+ if localKeys.length == 0
+ for result in results
+ result[relationshipName] = []
+ return
+
+ const pivotQuery = Database(relationship.pivotTable)
+ .whereIn(relationship.foreignKey, localKeys)
+ .select(relationship.foreignKey, relationship.relatedKey)
+
+ const pivotResults = await pivotQuery
+ const pivotMap = {}
+
+ for pivot in pivotResults
+ const key = pivot[relationship.foreignKey]
+ if !pivotMap[key]
+ pivotMap[key] = []
+ pivotMap[key].push(pivot[relationship.relatedKey])
+
+ const allRelatedIds = [...new Set(pivotResults.map(do(pivot) pivot[relationship.relatedKey]))]
+
+ if allRelatedIds.length == 0
+ for result in results
+ result[relationshipName] = []
+ return
+
+ let relatedQuery = Database(relationship.relatedTable).whereIn(relationship.relatedLocalKey, allRelatedIds)
+
+ if relationship.queryCallback
+ relatedQuery = relationship.queryCallback(relatedQuery)
+
+ const relatedResults = await relatedQuery
+ const relatedMap = {}
+
+ for related in relatedResults
+ relatedMap[related[relationship.relatedLocalKey]] = related
+
+ for result in results
+ const relatedIds = pivotMap[result[relationship.localKey]] || []
+ result[relationshipName] = relatedIds.map(do(id) relatedMap[id]).filter(do(item) item != null)
+
knex.QueryBuilder.extend 'softDelete', do this.update({ deleted_at: Database.fn.now! })
knex.QueryBuilder.extend 'restore', do this.update({ deleted_at: null })
diff --git a/src/Database/Repository.imba b/src/Database/Repository.imba
index 3ae7f7b3..c0124ecf 100644
--- a/src/Database/Repository.imba
+++ b/src/Database/Repository.imba
@@ -343,3 +343,23 @@ export default class Repository
const query = self.table!
query.get.apply(query, args)
+
+ static def belongsTo ...args
+ const query = self.query!
+
+ query.belongsTo.apply(query, args)
+
+ static def hasOne ...args
+ const query = self.query!
+
+ query.hasOne.apply(query, args)
+
+ static def hasMany ...args
+ const query = self.query!
+
+ query.hasMany.apply(query, args)
+
+ static def belongsToMany ...args
+ const query = self.query!
+
+ query.belongsToMany.apply(query, args)
diff --git a/src/Http/View/View.imba b/src/Http/View/View.imba
index d72ff7b0..24e7b27f 100644
--- a/src/Http/View/View.imba
+++ b/src/Http/View/View.imba
@@ -7,6 +7,7 @@ import isString from '../../Support/Helpers/isString'
import querystring from 'querystring'
import UndefinedDataPropException from './Exceptions/UndefinedDataPropException'
import Language from '../../Support/Language/Language'
+import viteHelper from '../../Support/Helpers/vite'
export default class View
@@ -36,6 +37,40 @@ export default class View
self
+ def vite file\string|string[]
+ if Array.isArray(file)
+ let tags = []
+
+ for asset in file
+ if !isString(asset)
+ throw TypeError "Expected string."
+
+ const jsTagExtensions = ['js', 'ts']
+ const cssTagExtensions = ['css', 'scss', 'sass', 'less', 'styl', 'stylus']
+ const imgTagExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'avif']
+ const fontTagExtensions = ['woff', 'woff2', 'eot', 'ttf', 'otf']
+
+ const extension = asset.split('.').pop!.toLowerCase!
+
+ if !isString(extension)
+ throw new Error "Could not determine file extension for: {asset}"
+
+ if jsTagExtensions.includes(extension)
+ tags.push("")
+ else if cssTagExtensions.includes(extension)
+ tags.push("")
+ else if imgTagExtensions.includes(extension)
+ tags.push("
")
+ else if fontTagExtensions.includes(extension)
+ tags.push("")
+ else
+ throw new Error "Unsupported file extension: {extension}"
+
+ if tags.length > 0
+ tags.join('\n')
+ else
+ viteHelper(file)
+
def translate key\string, default\any
self.#_language.get(key, default)
diff --git a/src/Support/Helpers/index.imba b/src/Support/Helpers/index.imba
index 00d15530..01a6a3fd 100644
--- a/src/Support/Helpers/index.imba
+++ b/src/Support/Helpers/index.imba
@@ -35,6 +35,7 @@ const { default: toBoolean } = require './toBoolean'
const { default: updateLine } = require './updateLine'
const { default: version } = require './version'
const { default: view } = require './view'
+const { default: vite } = require './vite'
const { default: wildcard } = require './wildcard'
const { default: without } = require './without'
@@ -81,6 +82,7 @@ export {
updateLine
version
view
+ vite
wildcard
without
}
diff --git a/src/Support/Helpers/vite.imba b/src/Support/Helpers/vite.imba
new file mode 100644
index 00000000..258f3cf5
--- /dev/null
+++ b/src/Support/Helpers/vite.imba
@@ -0,0 +1,4 @@
+import Repository from '../../Vite/Repository'
+
+export default def vite file\string
+ Repository.get file
diff --git a/src/Vite/Repository.imba b/src/Vite/Repository.imba
new file mode 100644
index 00000000..ab4904c6
--- /dev/null
+++ b/src/Vite/Repository.imba
@@ -0,0 +1,46 @@
+import { readFileSync, existsSync } from 'fs'
+import { join } from 'path'
+
+export default class Repository
+
+ static manifestCache\ViteManifest|null = null
+
+ static def get file\string
+ const manifest = self.getManifest!
+
+ if !manifest
+ return file
+
+ const normalizedFile = file.startsWith('/') ? file.slice(1) : file
+
+ if manifest[normalizedFile]
+ return '/build/' + manifest[normalizedFile].file
+
+ if normalizedFile == 'css/app.css'
+ for [key, value] in Object.entries(manifest)
+ if key.includes('css/app.css')
+ return '/build/' + value.file
+
+ if normalizedFile == 'js/app.js'
+ for [key, value] in Object.entries(manifest)
+ if key.includes('js/app.ts')
+ return '/build/' + value.file
+
+ file
+
+ static def getManifest\ViteManifest|null
+ if self.manifestCache != null
+ return self.manifestCache
+
+ const location = join(process.cwd!, 'public', 'build', '.vite', 'manifest.json')
+
+ try
+ if existsSync(location)
+ const content = readFileSync(location, 'utf8')
+ self.manifestCache = content ? JSON.parse(content) : null
+ else
+ self.manifestCache = null
+ catch err
+ self.manifestCache = null
+
+ self.manifestCache
diff --git a/types/Database/Database.d.ts b/types/Database/Database.d.ts
index 814541fd..7e0c8a33 100644
--- a/types/Database/Database.d.ts
+++ b/types/Database/Database.d.ts
@@ -1,5 +1,6 @@
import "knex";
import type { Knex } from "knex";
+import type Repository from "../Database/Repository";
declare let Database: Knex;
declare type Database = Knex;
@@ -52,7 +53,18 @@ declare module "knex" {
*/
autoPaginate(perPage?: number): Promise>;
hidden(columns: string[]): Knex.QueryBuilder;
- hasOne(related: string, foreignKey: string, localKey: string): Knex.QueryBuilder;
+ belongsTo(related: string | typeof Repository): Knex.QueryBuilder;
+ belongsTo(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ belongsTo(related: string | typeof Repository, foreignKey: string, localKey: string): Knex.QueryBuilder;
+ hasOne(related: string | typeof Repository): Knex.QueryBuilder;
+ hasOne(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ hasOne(related: string | typeof Repository, foreignKey: string, localKey: string): Knex.QueryBuilder;
+ hasMany(related: string | typeof Repository): Knex.QueryBuilder;
+ hasMany(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ hasMany(related: string | typeof Repository, foreignKey: string, localKey: string): Knex.QueryBuilder;
+ belongsToMany(related: string | typeof Repository): Knex.QueryBuilder;
+ belongsToMany(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ belongsToMany(related: string | typeof Repository, pivotTable: string, foreignKey?: string, relatedKey?: string, localKey?: string, relatedLocalKey?: string): Knex.QueryBuilder;
}
interface TableBuilder {
softDeletes(): Knex.TableBuilder;
diff --git a/types/Database/Repository.d.ts b/types/Database/Repository.d.ts
index c09d9957..759ebbbe 100644
--- a/types/Database/Repository.d.ts
+++ b/types/Database/Repository.d.ts
@@ -526,4 +526,16 @@ export default class Repository {
static onlyTrashed(): Knex.QueryBuilder;
static get(columns?: string[]): Promise;
get(columns?: string[]): Promise;
+ static belongsTo(related: string | typeof Repository): Knex.QueryBuilder;
+ static belongsTo(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ static belongsTo(related: string | typeof Repository, foreignKey: string, localKey: string): Knex.QueryBuilder;
+ static hasOne(related: string | typeof Repository): Knex.QueryBuilder;
+ static hasOne(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ static hasOne(related: string | typeof Repository, foreignKey: string, localKey: string): Knex.QueryBuilder;
+ static hasMany(related: string | typeof Repository): Knex.QueryBuilder;
+ static hasMany(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ static hasMany(related: string | typeof Repository, foreignKey: string, localKey: string): Knex.QueryBuilder;
+ static belongsToMany(related: string | typeof Repository): Knex.QueryBuilder;
+ static belongsToMany(related: string | typeof Repository, queryCallback: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Knex.QueryBuilder;
+ static belongsToMany(related: string | typeof Repository, pivotTable: string, foreignKey?: string, relatedKey?: string, localKey?: string, relatedLocalKey?: string): Knex.QueryBuilder;
}
diff --git a/types/Http/View/View.d.ts b/types/Http/View/View.d.ts
index 359b369b..d0fd2672 100644
--- a/types/Http/View/View.d.ts
+++ b/types/Http/View/View.d.ts
@@ -31,6 +31,14 @@ export default class View {
*/
__(key: string, default$?: string): string;
+ /**
+ * Get asset path(s) from Vite manifest.
+ *
+ * @param {string | string[]} file
+ * @returns {string}
+ */
+ vite(file: string | string[]): string;
+
/**
* Get old input.
*/
diff --git a/types/Support/Helpers/mix.d.ts b/types/Support/Helpers/mix.d.ts
index 293e0865..8c9d5d68 100644
--- a/types/Support/Helpers/mix.d.ts
+++ b/types/Support/Helpers/mix.d.ts
@@ -1,4 +1,7 @@
/**
-@param {string} file
-*/
+ * Get asset path from Laravel Mix manifest.
+ * @param {string} file
+ * @returns {string}
+ * @deprecated
+ */
export default function mix(file: string): string;
diff --git a/types/Support/Helpers/vite.d.ts b/types/Support/Helpers/vite.d.ts
new file mode 100644
index 00000000..7e06aebc
--- /dev/null
+++ b/types/Support/Helpers/vite.d.ts
@@ -0,0 +1,7 @@
+/**
+ * Get asset path(s) from Vite manifest.
+ *
+ * @param {string | string[]} file
+ * @returns {string}
+ */
+export default function vite(file: string | string[]): string;
diff --git a/types/Vite/Repository.d.ts b/types/Vite/Repository.d.ts
new file mode 100644
index 00000000..56040b83
--- /dev/null
+++ b/types/Vite/Repository.d.ts
@@ -0,0 +1,16 @@
+interface ViteManifest {
+ [key: string]: {
+ file: string;
+ src?: string;
+ isEntry?: boolean;
+ imports?: string[];
+ css?: string[];
+ assets?: string[];
+ };
+}
+
+export default class Repository {
+ static manifestCache: ViteManifest | null = null;
+ static file(file: string): string;
+ static getManifest(): ViteManifest | null;
+}