@getstrata/core 0.5.100 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -32
- package/README.md +16 -35
- package/dist/core/auth/abilityCatalog.d.ts +2 -2
- package/dist/core/auth/basicAuthGuard.d.ts +9 -0
- package/dist/core/auth/guard.d.ts +6 -0
- package/dist/core/auth/jwt.d.ts +19 -0
- package/dist/core/auth/jwtGuard.d.ts +14 -0
- package/dist/core/auth/tokenAbilityChecker.d.ts +5 -0
- package/dist/core/cache/tags.d.ts +6 -0
- package/dist/core/contracts/authUserDirectory.d.ts +4 -0
- package/dist/core/database/baseRepository.d.ts +5 -1
- package/dist/core/database/dialect.d.ts +18 -0
- package/dist/core/database/factory.d.ts +1 -0
- package/dist/core/database/index.d.ts +11 -3
- package/dist/core/database/model.d.ts +21 -2
- package/dist/core/database/mysqlConnection.d.ts +12 -0
- package/dist/core/database/namedConnections.d.ts +15 -0
- package/dist/core/database/relationQuery.d.ts +22 -3
- package/dist/core/database/relationships.d.ts +22 -2
- package/dist/core/database/repositoryQuery.d.ts +5 -1
- package/dist/core/database/sqliteConnection.d.ts +7 -0
- package/dist/core/http/loginThrottleMiddleware.d.ts +5 -2
- package/dist/core/http/resources.d.ts +2 -2
- package/dist/core/http/response.d.ts +2 -1
- package/dist/core/http/statelessAuth.d.ts +8 -0
- package/dist/core/http/throttleResponse.d.ts +2 -0
- package/dist/core/runtime/frontendMode.d.ts +10 -2
- package/dist/entries/auth/basicAuthGuard.js +137 -0
- package/dist/entries/auth/jwt.js +135 -0
- package/dist/entries/auth/jwtGuard.js +203 -0
- package/dist/entries/auth/sessionGuard.js +3 -21
- package/dist/entries/auth/tokenAbilityChecker.js +24 -0
- package/dist/entries/cache/tags.js +7 -1
- package/dist/entries/database/connectionContext.js +1 -0
- package/dist/entries/database/dialect.js +1 -0
- package/dist/entries/database/factory.js +5 -4
- package/dist/entries/database/model.js +189 -33
- package/dist/entries/database/mysqlConnection.js +35 -0
- package/dist/entries/database/namedConnections.js +1 -0
- package/dist/entries/database/query.js +28 -15
- package/dist/entries/database/relationships.js +43 -6
- package/dist/entries/database/repositoryQuery.js +142 -73
- package/dist/entries/database/schema.js +28 -15
- package/dist/entries/database/sqliteConnection.js +34 -0
- package/dist/entries/facades.js +1 -1
- package/dist/entries/http/contentNegotiation.js +5 -2
- package/dist/entries/http/csrfMiddleware.js +45 -0
- package/dist/entries/http/loginThrottleMiddleware.js +246 -7
- package/dist/entries/http/memoryThrottleMiddleware.js +208 -6
- package/dist/entries/http/requireAbilityMiddleware.js +35 -11
- package/dist/entries/http/requirePasswordConfirmMiddleware.js +5 -2
- package/dist/entries/http/requireVerifiedMiddleware.js +5 -2
- package/dist/entries/http/requireWebAuthMiddleware.js +12 -3
- package/dist/entries/http/resources.js +4 -1
- package/dist/entries/http/response.js +46 -12
- package/dist/entries/http/statelessAuth.js +48 -0
- package/dist/entries/http/throttleMiddleware.js +208 -6
- package/dist/entries/http/webErrorResponse.js +35 -11
- package/dist/entries/http/webFormRequest.js +5 -2
- package/dist/entries/mail/mailer.js +1 -1
- package/dist/entries/openapi/generator.js +48 -5
- package/dist/entries/runtime/frontendMode.js +39 -10
- package/dist/framework/public-api.d.ts +11 -1
- package/dist/index.js +1068 -250
- package/package.json +56 -5
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/database/query.ts
|
|
3
|
+
import { currentSqlDialect } from "@getstrata/core/database/dialect";
|
|
3
4
|
function quoteIdentifier(identifier) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return
|
|
5
|
+
return currentSqlDialect().quoteIdentifier(identifier);
|
|
6
|
+
}
|
|
7
|
+
function returningSuffix(columns) {
|
|
8
|
+
return currentSqlDialect().returningClause(columns);
|
|
8
9
|
}
|
|
9
10
|
function qualifyColumn(tableName, column) {
|
|
10
11
|
return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
|
|
@@ -34,7 +35,7 @@ function isQueryOperator(value) {
|
|
|
34
35
|
}
|
|
35
36
|
function pushParam(values, value) {
|
|
36
37
|
values.push(value);
|
|
37
|
-
return
|
|
38
|
+
return currentSqlDialect().placeholder(values.length);
|
|
38
39
|
}
|
|
39
40
|
function buildInClause(column, values, params) {
|
|
40
41
|
if (values.length === 0) {
|
|
@@ -74,9 +75,12 @@ function buildOperatorClauses(column, operator, params) {
|
|
|
74
75
|
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
75
76
|
}
|
|
76
77
|
if (operator.ilike !== undefined) {
|
|
77
|
-
clauses.push(`${column}
|
|
78
|
+
clauses.push(`${column} ${currentSqlDialect().ilikeOperator()} ${pushParam(params, operator.ilike)}`);
|
|
78
79
|
}
|
|
79
80
|
if (operator.tsMatch !== undefined) {
|
|
81
|
+
if (currentSqlDialect().driver !== "pgsql") {
|
|
82
|
+
throw new Error("Full-text search (tsMatch) is only available on PostgreSQL.");
|
|
83
|
+
}
|
|
80
84
|
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
81
85
|
}
|
|
82
86
|
return clauses;
|
|
@@ -275,7 +279,7 @@ function buildSelectList(table, select, params = []) {
|
|
|
275
279
|
return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
|
|
276
280
|
}
|
|
277
281
|
if (item.kind === "literalText") {
|
|
278
|
-
return `${pushParam(params, item.value)}
|
|
282
|
+
return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
|
|
279
283
|
}
|
|
280
284
|
const column = qualifyColumn(item.table, item.column);
|
|
281
285
|
const placeholder = pushParam(params, item.query);
|
|
@@ -367,7 +371,7 @@ function buildInsertQuery(table, values) {
|
|
|
367
371
|
const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
|
|
368
372
|
const returningColumns = buildReturningColumns(table);
|
|
369
373
|
return {
|
|
370
|
-
text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})
|
|
374
|
+
text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})${returningSuffix(returningColumns)}`,
|
|
371
375
|
params
|
|
372
376
|
};
|
|
373
377
|
}
|
|
@@ -386,7 +390,7 @@ function buildUpdateQuery(table, id, changes) {
|
|
|
386
390
|
appendSoftDeleteScope(table, {}, scopeClauses);
|
|
387
391
|
const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
|
|
388
392
|
return {
|
|
389
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}
|
|
393
|
+
text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
|
|
390
394
|
params
|
|
391
395
|
};
|
|
392
396
|
}
|
|
@@ -399,9 +403,12 @@ function buildSoftDeleteByIdQuery(table, id, deletedAt) {
|
|
|
399
403
|
const scopeClauses = [];
|
|
400
404
|
appendSoftDeleteScope(table, {}, scopeClauses);
|
|
401
405
|
const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
|
|
406
|
+
const params = [];
|
|
407
|
+
const deletedAtPlaceholder = pushParam(params, deletedAt);
|
|
408
|
+
const idPlaceholder = pushParam(params, id);
|
|
402
409
|
return {
|
|
403
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $
|
|
404
|
-
params
|
|
410
|
+
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
|
|
411
|
+
params
|
|
405
412
|
};
|
|
406
413
|
}
|
|
407
414
|
function buildRestoreByIdQuery(table, id) {
|
|
@@ -410,15 +417,21 @@ function buildRestoreByIdQuery(table, id) {
|
|
|
410
417
|
throw new Error(`Table ${table.name} does not support soft deletes.`);
|
|
411
418
|
}
|
|
412
419
|
const returningColumns = buildReturningColumns(table);
|
|
420
|
+
const params = [];
|
|
421
|
+
const deletedAtPlaceholder = pushParam(params, null);
|
|
422
|
+
const idPlaceholder = pushParam(params, id);
|
|
413
423
|
return {
|
|
414
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $
|
|
415
|
-
params
|
|
424
|
+
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder} AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL${returningSuffix(returningColumns)}`,
|
|
425
|
+
params
|
|
416
426
|
};
|
|
417
427
|
}
|
|
418
428
|
function buildDeleteByIdQuery(table, id) {
|
|
429
|
+
const params = [];
|
|
430
|
+
const idPlaceholder = pushParam(params, id);
|
|
431
|
+
const returning = returningSuffix(`${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`);
|
|
419
432
|
return {
|
|
420
|
-
text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = $
|
|
421
|
-
params
|
|
433
|
+
text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${returning}`,
|
|
434
|
+
params
|
|
422
435
|
};
|
|
423
436
|
}
|
|
424
437
|
|
|
@@ -441,6 +454,13 @@ function belongsTo(definition) {
|
|
|
441
454
|
...definition
|
|
442
455
|
};
|
|
443
456
|
}
|
|
457
|
+
function hasManyThrough(definition) {
|
|
458
|
+
return {
|
|
459
|
+
type: "hasManyThrough",
|
|
460
|
+
throughParentKey: "__through_parent_id",
|
|
461
|
+
...definition
|
|
462
|
+
};
|
|
463
|
+
}
|
|
444
464
|
function belongsToMany(definition) {
|
|
445
465
|
return {
|
|
446
466
|
type: "belongsToMany",
|
|
@@ -462,6 +482,19 @@ function relationMatchKey(value) {
|
|
|
462
482
|
}
|
|
463
483
|
return String(value);
|
|
464
484
|
}
|
|
485
|
+
var relationLookupCache = new WeakMap;
|
|
486
|
+
function indexedRelationLookup(map) {
|
|
487
|
+
const cached = relationLookupCache.get(map);
|
|
488
|
+
if (cached) {
|
|
489
|
+
return cached;
|
|
490
|
+
}
|
|
491
|
+
const indexed = new Map;
|
|
492
|
+
for (const [existing, value] of map) {
|
|
493
|
+
indexed.set(relationMatchKey(existing), value);
|
|
494
|
+
}
|
|
495
|
+
relationLookupCache.set(map, indexed);
|
|
496
|
+
return indexed;
|
|
497
|
+
}
|
|
465
498
|
function getByRelationKey(map, key) {
|
|
466
499
|
if (map.has(key)) {
|
|
467
500
|
return map.get(key);
|
|
@@ -470,12 +503,7 @@ function getByRelationKey(map, key) {
|
|
|
470
503
|
if (want === "") {
|
|
471
504
|
return;
|
|
472
505
|
}
|
|
473
|
-
|
|
474
|
-
if (relationMatchKey(existing) === want) {
|
|
475
|
-
return value;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return;
|
|
506
|
+
return indexedRelationLookup(map).get(want);
|
|
479
507
|
}
|
|
480
508
|
function indexHasManyRelation(parents, children, relation) {
|
|
481
509
|
const groups = new Map;
|
|
@@ -614,6 +642,26 @@ function indexMorphOneRelation(parents, children, relation) {
|
|
|
614
642
|
}
|
|
615
643
|
return result;
|
|
616
644
|
}
|
|
645
|
+
function indexHasManyThroughRelation(parents, children, relation) {
|
|
646
|
+
const throughKey = relation.throughParentKey ?? "__through_parent_id";
|
|
647
|
+
const grouped = new Map;
|
|
648
|
+
for (const child of children) {
|
|
649
|
+
const key = relationMatchKey(child[throughKey]);
|
|
650
|
+
if (key === "") {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const existing = grouped.get(key) ?? [];
|
|
654
|
+
const { [throughKey]: _through, ...far } = child;
|
|
655
|
+
existing.push(far);
|
|
656
|
+
grouped.set(key, existing);
|
|
657
|
+
}
|
|
658
|
+
const result = new Map;
|
|
659
|
+
for (const parent of parents) {
|
|
660
|
+
const key = relationMatchKey(parent[relation.localKey]);
|
|
661
|
+
result.set(parent[relation.localKey], grouped.get(key) ?? []);
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
617
665
|
function indexMorphToRelation(children, parentsByType, relation) {
|
|
618
666
|
const result = new Map;
|
|
619
667
|
for (const child of children) {
|
|
@@ -797,6 +845,24 @@ class RepositoryQuery {
|
|
|
797
845
|
});
|
|
798
846
|
return this;
|
|
799
847
|
}
|
|
848
|
+
withHasManyThrough(as, relation, farRepository, options = {}) {
|
|
849
|
+
this.eagerLoads.push({
|
|
850
|
+
kind: "hasManyThrough",
|
|
851
|
+
as,
|
|
852
|
+
relation,
|
|
853
|
+
repository: farRepository,
|
|
854
|
+
options
|
|
855
|
+
});
|
|
856
|
+
return this;
|
|
857
|
+
}
|
|
858
|
+
withTrashed() {
|
|
859
|
+
this.queryOptions = { ...this.queryOptions, withTrashed: true };
|
|
860
|
+
return this;
|
|
861
|
+
}
|
|
862
|
+
onlyTrashed() {
|
|
863
|
+
this.queryOptions = { ...this.queryOptions, onlyTrashed: true };
|
|
864
|
+
return this;
|
|
865
|
+
}
|
|
800
866
|
async get() {
|
|
801
867
|
const rows = await this.repository.findAll(this.buildOptions());
|
|
802
868
|
return await this.attach(rows);
|
|
@@ -850,64 +916,67 @@ class RepositoryQuery {
|
|
|
850
916
|
return this;
|
|
851
917
|
}
|
|
852
918
|
async attach(rows) {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
919
|
+
const result = rows.map((row) => ({ ...row }));
|
|
920
|
+
if (result.length === 0 || this.eagerLoads.length === 0) {
|
|
921
|
+
return result;
|
|
922
|
+
}
|
|
923
|
+
await Promise.all(this.eagerLoads.map((load) => this.hydrateEagerLoad(rows, result, load)));
|
|
924
|
+
return result;
|
|
925
|
+
}
|
|
926
|
+
async hydrateEagerLoad(rows, result, load) {
|
|
927
|
+
if (load.kind === "hasMany") {
|
|
928
|
+
const relation2 = load.relation;
|
|
929
|
+
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
|
|
930
|
+
for (const row of result) {
|
|
931
|
+
row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
|
|
866
932
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
continue;
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (load.kind === "morphMany") {
|
|
936
|
+
const relation2 = load.relation;
|
|
937
|
+
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
|
|
938
|
+
for (const row of result) {
|
|
939
|
+
row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
|
|
875
940
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
continue;
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (load.kind === "morphOne") {
|
|
944
|
+
const relation2 = load.relation;
|
|
945
|
+
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
|
|
946
|
+
for (const row of result) {
|
|
947
|
+
row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]);
|
|
884
948
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
continue;
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
if (load.kind === "hasManyThrough") {
|
|
952
|
+
const relation2 = load.relation;
|
|
953
|
+
const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation2, load.options);
|
|
954
|
+
for (const row of result) {
|
|
955
|
+
row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
|
|
893
956
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
continue;
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (load.kind === "belongsToMany") {
|
|
960
|
+
const relation2 = load.relation;
|
|
961
|
+
const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
|
|
962
|
+
for (const row of result) {
|
|
963
|
+
row[load.as] = getByRelationKey(grouped2, row[relation2.parentKey]) ?? [];
|
|
902
964
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (load.kind === "morphTo") {
|
|
968
|
+
const relation2 = load.relation;
|
|
969
|
+
const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
|
|
970
|
+
for (const row of result) {
|
|
971
|
+
row[load.as] = getByRelationKey(grouped2, row[relation2.morphIdKey]);
|
|
972
|
+
}
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const relation = load.relation;
|
|
976
|
+
const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
|
|
977
|
+
for (const row of result) {
|
|
978
|
+
row[load.as] = getByRelationKey(grouped, row[relation.foreignKey]);
|
|
909
979
|
}
|
|
910
|
-
return result;
|
|
911
980
|
}
|
|
912
981
|
}
|
|
913
982
|
export {
|
|
@@ -267,11 +267,12 @@ class UnsupportedSchemaFeatureError extends Error {
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
// ../../src/core/database/query.ts
|
|
270
|
+
import { currentSqlDialect } from "@getstrata/core/database/dialect";
|
|
270
271
|
function quoteIdentifier(identifier) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return
|
|
272
|
+
return currentSqlDialect().quoteIdentifier(identifier);
|
|
273
|
+
}
|
|
274
|
+
function returningSuffix(columns) {
|
|
275
|
+
return currentSqlDialect().returningClause(columns);
|
|
275
276
|
}
|
|
276
277
|
function qualifyColumn(tableName, column) {
|
|
277
278
|
return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
|
|
@@ -301,7 +302,7 @@ function isQueryOperator(value) {
|
|
|
301
302
|
}
|
|
302
303
|
function pushParam(values, value) {
|
|
303
304
|
values.push(value);
|
|
304
|
-
return
|
|
305
|
+
return currentSqlDialect().placeholder(values.length);
|
|
305
306
|
}
|
|
306
307
|
function buildInClause(column, values, params) {
|
|
307
308
|
if (values.length === 0) {
|
|
@@ -341,9 +342,12 @@ function buildOperatorClauses(column, operator, params) {
|
|
|
341
342
|
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
342
343
|
}
|
|
343
344
|
if (operator.ilike !== undefined) {
|
|
344
|
-
clauses.push(`${column}
|
|
345
|
+
clauses.push(`${column} ${currentSqlDialect().ilikeOperator()} ${pushParam(params, operator.ilike)}`);
|
|
345
346
|
}
|
|
346
347
|
if (operator.tsMatch !== undefined) {
|
|
348
|
+
if (currentSqlDialect().driver !== "pgsql") {
|
|
349
|
+
throw new Error("Full-text search (tsMatch) is only available on PostgreSQL.");
|
|
350
|
+
}
|
|
347
351
|
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
348
352
|
}
|
|
349
353
|
return clauses;
|
|
@@ -542,7 +546,7 @@ function buildSelectList(table, select, params = []) {
|
|
|
542
546
|
return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
|
|
543
547
|
}
|
|
544
548
|
if (item.kind === "literalText") {
|
|
545
|
-
return `${pushParam(params, item.value)}
|
|
549
|
+
return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
|
|
546
550
|
}
|
|
547
551
|
const column = qualifyColumn(item.table, item.column);
|
|
548
552
|
const placeholder = pushParam(params, item.query);
|
|
@@ -634,7 +638,7 @@ function buildInsertQuery(table, values) {
|
|
|
634
638
|
const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
|
|
635
639
|
const returningColumns = buildReturningColumns(table);
|
|
636
640
|
return {
|
|
637
|
-
text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})
|
|
641
|
+
text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})${returningSuffix(returningColumns)}`,
|
|
638
642
|
params
|
|
639
643
|
};
|
|
640
644
|
}
|
|
@@ -653,7 +657,7 @@ function buildUpdateQuery(table, id, changes) {
|
|
|
653
657
|
appendSoftDeleteScope(table, {}, scopeClauses);
|
|
654
658
|
const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
|
|
655
659
|
return {
|
|
656
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}
|
|
660
|
+
text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
|
|
657
661
|
params
|
|
658
662
|
};
|
|
659
663
|
}
|
|
@@ -666,9 +670,12 @@ function buildSoftDeleteByIdQuery(table, id, deletedAt) {
|
|
|
666
670
|
const scopeClauses = [];
|
|
667
671
|
appendSoftDeleteScope(table, {}, scopeClauses);
|
|
668
672
|
const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
|
|
673
|
+
const params = [];
|
|
674
|
+
const deletedAtPlaceholder = pushParam(params, deletedAt);
|
|
675
|
+
const idPlaceholder = pushParam(params, id);
|
|
669
676
|
return {
|
|
670
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $
|
|
671
|
-
params
|
|
677
|
+
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
|
|
678
|
+
params
|
|
672
679
|
};
|
|
673
680
|
}
|
|
674
681
|
function buildRestoreByIdQuery(table, id) {
|
|
@@ -677,15 +684,21 @@ function buildRestoreByIdQuery(table, id) {
|
|
|
677
684
|
throw new Error(`Table ${table.name} does not support soft deletes.`);
|
|
678
685
|
}
|
|
679
686
|
const returningColumns = buildReturningColumns(table);
|
|
687
|
+
const params = [];
|
|
688
|
+
const deletedAtPlaceholder = pushParam(params, null);
|
|
689
|
+
const idPlaceholder = pushParam(params, id);
|
|
680
690
|
return {
|
|
681
|
-
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $
|
|
682
|
-
params
|
|
691
|
+
text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder} AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL${returningSuffix(returningColumns)}`,
|
|
692
|
+
params
|
|
683
693
|
};
|
|
684
694
|
}
|
|
685
695
|
function buildDeleteByIdQuery(table, id) {
|
|
696
|
+
const params = [];
|
|
697
|
+
const idPlaceholder = pushParam(params, id);
|
|
698
|
+
const returning = returningSuffix(`${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`);
|
|
686
699
|
return {
|
|
687
|
-
text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = $
|
|
688
|
-
params
|
|
700
|
+
text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${returning}`,
|
|
701
|
+
params
|
|
689
702
|
};
|
|
690
703
|
}
|
|
691
704
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/core/database/sqliteConnection.ts
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
function isRowReturning(sql) {
|
|
5
|
+
const upper = sql.replace(/\s+/g, " ").trim().toUpperCase();
|
|
6
|
+
if (upper.includes(" RETURNING ")) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
return upper.startsWith("SELECT") || upper.startsWith("WITH") || upper.startsWith("PRAGMA") || upper.startsWith("EXPLAIN");
|
|
10
|
+
}
|
|
11
|
+
function createSqliteConnection(filename) {
|
|
12
|
+
if (!filename.trim()) {
|
|
13
|
+
throw new Error("SQLite path is not configured. Pass a filename or :memory:.");
|
|
14
|
+
}
|
|
15
|
+
const db = new Database(filename, { create: true });
|
|
16
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
17
|
+
return {
|
|
18
|
+
async unsafe(query, params = []) {
|
|
19
|
+
const statement = db.query(query);
|
|
20
|
+
const args = [...params];
|
|
21
|
+
if (isRowReturning(query)) {
|
|
22
|
+
return statement.all(...args);
|
|
23
|
+
}
|
|
24
|
+
statement.run(...args);
|
|
25
|
+
return [];
|
|
26
|
+
},
|
|
27
|
+
close() {
|
|
28
|
+
db.close();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
createSqliteConnection
|
|
34
|
+
};
|
package/dist/entries/facades.js
CHANGED
|
@@ -7,6 +7,10 @@ function requestPrefersJson(request) {
|
|
|
7
7
|
if (request.headers.get("HX-Request") === "true") {
|
|
8
8
|
return false;
|
|
9
9
|
}
|
|
10
|
+
const pathname = new URL(request.url).pathname;
|
|
11
|
+
if (pathname.startsWith("/api/")) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
10
14
|
const accept = request.headers.get("accept")?.toLowerCase() ?? "";
|
|
11
15
|
if (accept.includes("text/html")) {
|
|
12
16
|
return false;
|
|
@@ -18,8 +22,7 @@ function requestPrefersJson(request) {
|
|
|
18
22
|
if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
|
|
19
23
|
return false;
|
|
20
24
|
}
|
|
21
|
-
|
|
22
|
-
return pathname.startsWith("/api/");
|
|
25
|
+
return false;
|
|
23
26
|
}
|
|
24
27
|
export {
|
|
25
28
|
requestPrefersJson
|
|
@@ -195,6 +195,48 @@ function resolveCsrfTokenForRequest(request) {
|
|
|
195
195
|
return resolveCsrfToken(request).token;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// ../../src/core/http/statelessAuth.ts
|
|
199
|
+
function authorizationScheme(request) {
|
|
200
|
+
const header = request.headers.get("authorization")?.trim() ?? "";
|
|
201
|
+
const scheme = header.split(/\s+/, 1)[0];
|
|
202
|
+
return scheme ? scheme.toLowerCase() : "";
|
|
203
|
+
}
|
|
204
|
+
function requestUsesHeaderCredentials(request) {
|
|
205
|
+
const scheme = authorizationScheme(request);
|
|
206
|
+
return scheme === "bearer" || scheme === "basic";
|
|
207
|
+
}
|
|
208
|
+
function readBearerToken(request) {
|
|
209
|
+
const header = request.headers.get("authorization")?.trim() ?? "";
|
|
210
|
+
if (!header.toLowerCase().startsWith("bearer ")) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const token = header.slice("Bearer ".length).trim();
|
|
214
|
+
return token.length > 0 ? token : null;
|
|
215
|
+
}
|
|
216
|
+
function readBasicCredentials(request) {
|
|
217
|
+
const header = request.headers.get("authorization")?.trim() ?? "";
|
|
218
|
+
if (!header.toLowerCase().startsWith("basic ")) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const encoded = header.slice("Basic ".length).trim();
|
|
222
|
+
if (!encoded) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const decoded = Buffer.from(encoded, "base64").toString("utf8");
|
|
227
|
+
const separator = decoded.indexOf(":");
|
|
228
|
+
if (separator < 0) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
username: decoded.slice(0, separator),
|
|
233
|
+
password: decoded.slice(separator + 1)
|
|
234
|
+
};
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
198
240
|
// ../../src/core/http/csrfMiddleware.ts
|
|
199
241
|
var MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
200
242
|
function appendSetCookie(response, cookie) {
|
|
@@ -208,6 +250,9 @@ function appendSetCookie(response, cookie) {
|
|
|
208
250
|
}
|
|
209
251
|
function createCsrfMiddleware() {
|
|
210
252
|
return async (request, next) => {
|
|
253
|
+
if (requestUsesHeaderCredentials(request)) {
|
|
254
|
+
return await next();
|
|
255
|
+
}
|
|
211
256
|
const method = request.method.toUpperCase();
|
|
212
257
|
if (!MUTATING_METHODS.has(method)) {
|
|
213
258
|
const csrf = resolveCsrfToken(request);
|