@getstrata/core 0.5.91 → 0.5.98
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 +18 -0
- package/dist/core/contracts/container.d.ts +2 -0
- package/dist/core/database/errors.d.ts +1 -1
- package/dist/core/database/factory.d.ts +31 -4
- package/dist/core/database/index.d.ts +3 -1
- package/dist/core/database/inflection.d.ts +4 -0
- package/dist/core/database/model.d.ts +50 -1
- package/dist/core/database/relationQuery.d.ts +149 -0
- package/dist/core/database/repositoryQuery.d.ts +5 -0
- package/dist/core/database/whereBuilder.d.ts +9 -1
- package/dist/core/errors/http.d.ts +8 -1
- package/dist/core/events/eventBus.d.ts +2 -0
- package/dist/core/http/resources.d.ts +20 -1
- package/dist/entries/contracts/container.js +6 -0
- package/dist/entries/database/errors.js +4 -3
- package/dist/entries/database/factory.js +116 -5
- package/dist/entries/database/model.js +760 -7
- package/dist/entries/database/query.js +9 -0
- package/dist/entries/database/repositoryQuery.js +26 -0
- package/dist/entries/database/schema.js +9 -0
- package/dist/entries/http/resources.js +62 -1
- package/dist/entries/http/response.js +10 -9
- package/dist/entries/http/webErrorResponse.js +8 -7
- package/dist/framework/public-api.d.ts +6 -4
- package/dist/index.js +1020 -15
- package/package.json +1 -1
|
@@ -112,7 +112,16 @@ function buildWhereClause(tableName, where = {}) {
|
|
|
112
112
|
params
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
|
+
function remapExistsSql(sql, existsParams, params) {
|
|
116
|
+
const offset = params.length;
|
|
117
|
+
params.push(...existsParams);
|
|
118
|
+
return sql.replace(/\$(\d+)/g, (_match, index) => `$${offset + Number(index)}`);
|
|
119
|
+
}
|
|
115
120
|
function buildWhereNodeClause(tableName, node, params) {
|
|
121
|
+
if ("exists" in node) {
|
|
122
|
+
const body = remapExistsSql(node.exists.sql, node.exists.params, params);
|
|
123
|
+
return `${node.exists.not ? "NOT " : ""}EXISTS (${body})`;
|
|
124
|
+
}
|
|
116
125
|
if ("where" in node) {
|
|
117
126
|
return appendWhereParts(tableName, node.where, params);
|
|
118
127
|
}
|
|
@@ -112,7 +112,16 @@ function buildWhereClause(tableName, where = {}) {
|
|
|
112
112
|
params
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
|
+
function remapExistsSql(sql, existsParams, params) {
|
|
116
|
+
const offset = params.length;
|
|
117
|
+
params.push(...existsParams);
|
|
118
|
+
return sql.replace(/\$(\d+)/g, (_match, index) => `$${offset + Number(index)}`);
|
|
119
|
+
}
|
|
115
120
|
function buildWhereNodeClause(tableName, node, params) {
|
|
121
|
+
if ("exists" in node) {
|
|
122
|
+
const body = remapExistsSql(node.exists.sql, node.exists.params, params);
|
|
123
|
+
return `${node.exists.not ? "NOT " : ""}EXISTS (${body})`;
|
|
124
|
+
}
|
|
116
125
|
if ("where" in node) {
|
|
117
126
|
return appendWhereParts(tableName, node.where, params);
|
|
118
127
|
}
|
|
@@ -484,6 +493,23 @@ class RepositoryQuery {
|
|
|
484
493
|
this.queryOptions = { ...this.queryOptions, limit };
|
|
485
494
|
return this;
|
|
486
495
|
}
|
|
496
|
+
whereNull(column) {
|
|
497
|
+
return this.where({ [column]: null });
|
|
498
|
+
}
|
|
499
|
+
whereNotNull(column) {
|
|
500
|
+
return this.where({ [column]: { isNull: false } });
|
|
501
|
+
}
|
|
502
|
+
whereIn(column, values) {
|
|
503
|
+
return this.where({ [column]: values });
|
|
504
|
+
}
|
|
505
|
+
whereExists(sql, params = []) {
|
|
506
|
+
this.whereNodes.push({ kind: "and", exists: { sql, params } });
|
|
507
|
+
return this;
|
|
508
|
+
}
|
|
509
|
+
whereNotExists(sql, params = []) {
|
|
510
|
+
this.whereNodes.push({ kind: "and", exists: { sql, params, not: true } });
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
487
513
|
offset(offset) {
|
|
488
514
|
this.queryOptions = { ...this.queryOptions, offset };
|
|
489
515
|
return this;
|
|
@@ -379,7 +379,16 @@ function buildWhereClause(tableName, where = {}) {
|
|
|
379
379
|
params
|
|
380
380
|
};
|
|
381
381
|
}
|
|
382
|
+
function remapExistsSql(sql, existsParams, params) {
|
|
383
|
+
const offset = params.length;
|
|
384
|
+
params.push(...existsParams);
|
|
385
|
+
return sql.replace(/\$(\d+)/g, (_match, index) => `$${offset + Number(index)}`);
|
|
386
|
+
}
|
|
382
387
|
function buildWhereNodeClause(tableName, node, params) {
|
|
388
|
+
if ("exists" in node) {
|
|
389
|
+
const body = remapExistsSql(node.exists.sql, node.exists.params, params);
|
|
390
|
+
return `${node.exists.not ? "NOT " : ""}EXISTS (${body})`;
|
|
391
|
+
}
|
|
383
392
|
if ("where" in node) {
|
|
384
393
|
return appendWhereParts(tableName, node.where, params);
|
|
385
394
|
}
|
|
@@ -3,6 +3,64 @@
|
|
|
3
3
|
function serializeDate(value) {
|
|
4
4
|
return value instanceof Date ? value.toISOString() : value;
|
|
5
5
|
}
|
|
6
|
+
function whenLoaded(model, relation, transform) {
|
|
7
|
+
const value = model.loaded(relation);
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
return transform ? transform(value) : value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class JsonResource {
|
|
15
|
+
resource;
|
|
16
|
+
static wrap = "data";
|
|
17
|
+
extra = {};
|
|
18
|
+
constructor(resource) {
|
|
19
|
+
this.resource = resource;
|
|
20
|
+
}
|
|
21
|
+
static make(resource) {
|
|
22
|
+
return new JsonResource(resource);
|
|
23
|
+
}
|
|
24
|
+
static collection(items) {
|
|
25
|
+
return new ResourceCollection(items);
|
|
26
|
+
}
|
|
27
|
+
additional(data) {
|
|
28
|
+
this.extra = { ...this.extra, ...data };
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
when(condition, value) {
|
|
32
|
+
return condition ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
whenLoaded(relation, transform) {
|
|
35
|
+
const model = this.resource;
|
|
36
|
+
if (typeof model.loaded !== "function") {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
return whenLoaded(model, relation, transform);
|
|
40
|
+
}
|
|
41
|
+
toArray() {
|
|
42
|
+
if (this.resource && typeof this.resource === "object" && "toArray" in this.resource) {
|
|
43
|
+
return this.resource.toArray();
|
|
44
|
+
}
|
|
45
|
+
return { ...this.resource };
|
|
46
|
+
}
|
|
47
|
+
toResponse() {
|
|
48
|
+
const wrap = this.constructor.wrap;
|
|
49
|
+
const payload = this.toArray();
|
|
50
|
+
if (wrap === null) {
|
|
51
|
+
return { ...payload, ...this.extra };
|
|
52
|
+
}
|
|
53
|
+
return { [wrap]: payload, ...this.extra };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class ResourceCollection extends JsonResource {
|
|
58
|
+
toArray() {
|
|
59
|
+
return {
|
|
60
|
+
data: this.resource.map((item) => item instanceof JsonResource ? item.toArray() : { ...item })
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
6
64
|
function toResourceCollection(items, transformer) {
|
|
7
65
|
return items.map(transformer);
|
|
8
66
|
}
|
|
@@ -13,7 +71,10 @@ function toPaginatedResourceCollection(items, meta, transformer) {
|
|
|
13
71
|
};
|
|
14
72
|
}
|
|
15
73
|
export {
|
|
74
|
+
JsonResource,
|
|
75
|
+
ResourceCollection,
|
|
16
76
|
serializeDate,
|
|
17
77
|
toPaginatedResourceCollection,
|
|
18
|
-
toResourceCollection
|
|
78
|
+
toResourceCollection,
|
|
79
|
+
whenLoaded
|
|
19
80
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/http/response.ts
|
|
3
|
-
import {
|
|
3
|
+
import { toHttpError as toHttpError3 } from "@getstrata/core/errors/http";
|
|
4
4
|
|
|
5
5
|
// ../../src/core/database/errors.ts
|
|
6
6
|
import {
|
|
7
7
|
BadRequestError,
|
|
8
8
|
ConflictError,
|
|
9
|
-
|
|
9
|
+
toHttpError,
|
|
10
10
|
UnprocessableEntityError
|
|
11
11
|
} from "@getstrata/core/errors/http";
|
|
12
12
|
function isPostgresError(error) {
|
|
@@ -25,8 +25,9 @@ function getPostgresSqlState(error) {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
function mapDatabaseError(error) {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const httpError = toHttpError(error);
|
|
29
|
+
if (httpError) {
|
|
30
|
+
return httpError;
|
|
30
31
|
}
|
|
31
32
|
if (!isPostgresError(error)) {
|
|
32
33
|
const message = error instanceof Error ? error.message : "Database operation failed.";
|
|
@@ -66,7 +67,7 @@ async function withDatabaseErrorHandling(operation) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
// ../../src/core/http/webErrorResponse.ts
|
|
69
|
-
import {
|
|
70
|
+
import { toHttpError as toHttpError2, ValidationError } from "@getstrata/core/errors/http";
|
|
70
71
|
|
|
71
72
|
// ../../src/core/runtime/frontendMode.ts
|
|
72
73
|
function readFrontendMode() {
|
|
@@ -301,11 +302,11 @@ async function webErrorResponse(error, request) {
|
|
|
301
302
|
if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
|
|
302
303
|
return null;
|
|
303
304
|
}
|
|
304
|
-
const mappedError = error
|
|
305
|
-
if (mappedError
|
|
305
|
+
const mappedError = toHttpError2(error) ?? mapDatabaseError(error);
|
|
306
|
+
if (mappedError.status === 401) {
|
|
306
307
|
return Response.redirect(loginRedirectLocation(request), 302);
|
|
307
308
|
}
|
|
308
|
-
const errors = mappedError instanceof ValidationError ? normalizeFieldErrors(mappedError.details) : undefined;
|
|
309
|
+
const errors = mappedError instanceof ValidationError || mappedError.name === "ValidationError" ? normalizeFieldErrors(mappedError.details) : undefined;
|
|
309
310
|
return htmlErrorResponse({
|
|
310
311
|
status: mappedError.status,
|
|
311
312
|
title: errorPageTitle(mappedError.status, mappedError.message),
|
|
@@ -329,7 +330,7 @@ function noContentResponse() {
|
|
|
329
330
|
return new Response(null, { status: 204 });
|
|
330
331
|
}
|
|
331
332
|
function errorResponse(error) {
|
|
332
|
-
const mappedError = error
|
|
333
|
+
const mappedError = toHttpError3(error) ?? mapDatabaseError(error);
|
|
333
334
|
return Response.json({
|
|
334
335
|
error: mappedError.message,
|
|
335
336
|
...mappedError.details === undefined ? {} : { details: mappedError.details }
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/http/webErrorResponse.ts
|
|
3
|
-
import {
|
|
3
|
+
import { toHttpError as toHttpError2, ValidationError } from "@getstrata/core/errors/http";
|
|
4
4
|
|
|
5
5
|
// ../../src/core/database/errors.ts
|
|
6
6
|
import {
|
|
7
7
|
BadRequestError,
|
|
8
8
|
ConflictError,
|
|
9
|
-
|
|
9
|
+
toHttpError,
|
|
10
10
|
UnprocessableEntityError
|
|
11
11
|
} from "@getstrata/core/errors/http";
|
|
12
12
|
function isPostgresError(error) {
|
|
@@ -25,8 +25,9 @@ function getPostgresSqlState(error) {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
function mapDatabaseError(error) {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const httpError = toHttpError(error);
|
|
29
|
+
if (httpError) {
|
|
30
|
+
return httpError;
|
|
30
31
|
}
|
|
31
32
|
if (!isPostgresError(error)) {
|
|
32
33
|
const message = error instanceof Error ? error.message : "Database operation failed.";
|
|
@@ -298,11 +299,11 @@ async function webErrorResponse(error, request) {
|
|
|
298
299
|
if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
|
|
299
300
|
return null;
|
|
300
301
|
}
|
|
301
|
-
const mappedError = error
|
|
302
|
-
if (mappedError
|
|
302
|
+
const mappedError = toHttpError2(error) ?? mapDatabaseError(error);
|
|
303
|
+
if (mappedError.status === 401) {
|
|
303
304
|
return Response.redirect(loginRedirectLocation(request), 302);
|
|
304
305
|
}
|
|
305
|
-
const errors = mappedError instanceof ValidationError ? normalizeFieldErrors(mappedError.details) : undefined;
|
|
306
|
+
const errors = mappedError instanceof ValidationError || mappedError.name === "ValidationError" ? normalizeFieldErrors(mappedError.details) : undefined;
|
|
306
307
|
return htmlErrorResponse({
|
|
307
308
|
status: mappedError.status,
|
|
308
309
|
title: errorPageTitle(mappedError.status, mappedError.message),
|
|
@@ -10,7 +10,7 @@ export type { AuthUser } from "../core/auth/authContext.ts";
|
|
|
10
10
|
export { authContext, currentAuthUser, runWithAuthUser } from "../core/auth/authContext.ts";
|
|
11
11
|
export type { AuthGuard } from "../core/auth/guard.ts";
|
|
12
12
|
export { ApiTokenGuard, AuthManager, CompositeGuard, DatabaseTokenGuard, GuestGuard, } from "../core/auth/guard.ts";
|
|
13
|
-
export { configureMembershipLookup, currentOrganizationIds, currentOrgRole, hasMinimumOrgRole, hasOrgMembership, } from "../core/auth/membershipContext.ts";
|
|
13
|
+
export { configureMembershipLookup, currentOrganizationIds, currentOrgRole, hasMinimumOrgRole, hasOrgMembership, resolveMembershipLookup, runWithMembershipContext, } from "../core/auth/membershipContext.ts";
|
|
14
14
|
export { createMembershipMiddleware } from "../core/auth/membershipMiddleware.ts";
|
|
15
15
|
export { appendOrganizationScope, appendProjectScope, assertOrganizationReadable, assertResourceInCurrentTenant, emptyPaginateResult, resolveOrganizationScope, scopedOrganizationIds, } from "../core/auth/membershipScope.ts";
|
|
16
16
|
export { default as MembershipService, resolveMembershipService, } from "../core/auth/membershipService.ts";
|
|
@@ -29,11 +29,13 @@ export { bindBunSql, createBunSqlPool } from "../core/database/bunSql.ts";
|
|
|
29
29
|
export { createDatabaseConnection } from "../core/database/connection.ts";
|
|
30
30
|
export { getActiveDatabaseConnection, runWithDatabaseConnection, } from "../core/database/connectionContext.ts";
|
|
31
31
|
export { getDefaultDatabasePool, getDefaultDatabaseQuery, registerDefaultDatabasePool, } from "../core/database/defaultConnection.ts";
|
|
32
|
+
export { Factory } from "../core/database/factory.ts";
|
|
33
|
+
export { foreignKeyFromTable, pivotTableName, singularize } from "../core/database/inflection.ts";
|
|
32
34
|
export { withMigrationLock } from "../core/database/migrations/advisoryLock.ts";
|
|
33
35
|
export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, } from "../core/database/migrations/runner.ts";
|
|
34
36
|
export type { Migration, MigrationDatabase, MigrationStatus, } from "../core/database/migrations/types.ts";
|
|
35
37
|
export type { CastType, GlobalScopeFn, ModelConstructor } from "../core/database/model.ts";
|
|
36
|
-
export { applyCasts, dehydrateValue, filterMassAssignable, hydrateValue, Model, registerModelRepository, } from "../core/database/model.ts";
|
|
38
|
+
export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasOneRelationQuery, hydrateValue, Model, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelRepository, } from "../core/database/model.ts";
|
|
37
39
|
export { createDatabaseQueryProxy } from "../core/database/queryProxy.ts";
|
|
38
40
|
export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, } from "../core/database/relationships.ts";
|
|
39
41
|
export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, } from "../core/database/relationships.ts";
|
|
@@ -48,7 +50,7 @@ export { runInTransaction } from "../core/database/transaction.ts";
|
|
|
48
50
|
export type { QueryJoin, QueryJoinOn, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, } from "../core/database/types.ts";
|
|
49
51
|
export type { WhereNode } from "../core/database/whereBuilder.ts";
|
|
50
52
|
export { WhereBuilder } from "../core/database/whereBuilder.ts";
|
|
51
|
-
export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotFoundError, PayloadTooLargeError, PreconditionFailedError, UnauthorizedError, UnprocessableEntityError, ValidationError, } from "../core/errors/http.ts";
|
|
53
|
+
export { BadRequestError, ConflictError, ForbiddenError, HttpError, isHttpErrorLike, NotFoundError, PayloadTooLargeError, PreconditionFailedError, toHttpError, UnauthorizedError, UnprocessableEntityError, ValidationError, } from "../core/errors/http.ts";
|
|
52
54
|
export type { EventListener } from "../core/events/eventBus.ts";
|
|
53
55
|
export { EventBus, eventBus } from "../core/events/eventBus.ts";
|
|
54
56
|
export { modelEventName } from "../core/events/index.ts";
|
|
@@ -77,7 +79,7 @@ export { currentRequestMeta, runWithRequestMeta } from "../core/http/requestMeta
|
|
|
77
79
|
export { createRequireAbilityMiddleware } from "../core/http/requireAbilityMiddleware.ts";
|
|
78
80
|
export { createRequireGlobalAdminMiddleware } from "../core/http/requireGlobalAdminMiddleware.ts";
|
|
79
81
|
export { createRequireWebAuthMiddleware } from "../core/http/requireWebAuthMiddleware.ts";
|
|
80
|
-
export { serializeDate, toPaginatedResourceCollection, toResourceCollection, } from "../core/http/resources.ts";
|
|
82
|
+
export { JsonResource, ResourceCollection, serializeDate, toPaginatedResourceCollection, toResourceCollection, whenLoaded, } from "../core/http/resources.ts";
|
|
81
83
|
export { createdResponse, jsonResponse, noContentResponse, withErrorHandling, } from "../core/http/response.ts";
|
|
82
84
|
export type { RouteRequest } from "../core/http/route.ts";
|
|
83
85
|
export { loginRedirectLocation, safeInternalRedirectPath, sanitizeInternalPath, } from "../core/http/safeInternalPath.ts";
|