@jxsuite/connector 0.6.1 → 0.6.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/LICENSE +1 -1
- package/package.json +4 -4
- package/src/columns.ts +28 -21
- package/src/connectors.ts +19 -8
- package/src/d1.ts +18 -14
- package/src/ddl.ts +8 -14
- package/src/dialects/bun-sqlite.ts +11 -0
- package/src/dialects/d1-http.ts +22 -6
- package/src/provider-utils.ts +28 -14
- package/src/query.ts +40 -23
- package/src/sqlite.ts +8 -6
- package/src/supabase.ts +10 -12
- package/src/table-actions.ts +34 -20
- package/src/table-delete.ts +2 -1
- package/src/table-insert.ts +2 -1
- package/src/table-node.ts +22 -21
- package/src/table-shared.ts +22 -18
- package/src/table-state.ts +33 -16
- package/src/table-update.ts +2 -1
- package/src/types.ts +30 -23
- package/src/validate.ts +25 -26
- package/src/worker.ts +128 -76
package/src/types.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* connection definitions declared in project.json's `data` and `connections` sections.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { JsonObject, JsonValue } from "@jxsuite/schema/types";
|
|
11
|
+
import type { HostEnv } from "./provider-utils.ts";
|
|
12
|
+
|
|
10
13
|
// ─── Field / table schema ─────────────────────────────────────────────────────
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -22,9 +25,9 @@ export interface ColumnFieldSchema {
|
|
|
22
25
|
items?: ColumnFieldSchema;
|
|
23
26
|
properties?: Record<string, ColumnFieldSchema>;
|
|
24
27
|
required?: string[];
|
|
25
|
-
enum?:
|
|
28
|
+
enum?: JsonValue[];
|
|
26
29
|
format?: string;
|
|
27
|
-
default?:
|
|
30
|
+
default?: JsonValue;
|
|
28
31
|
description?: string;
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -57,7 +60,8 @@ export interface ConnectionDef {
|
|
|
57
60
|
file?: string;
|
|
58
61
|
/** Connection name, injected by hosts when passing the def to connector capabilities. */
|
|
59
62
|
$name?: string;
|
|
60
|
-
|
|
63
|
+
/** Provider-specific identifiers; the entry is project.json data, so every value is JSON. */
|
|
64
|
+
[key: string]: JsonValue | undefined;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// ─── Permissions ──────────────────────────────────────────────────────────────
|
|
@@ -79,13 +83,24 @@ export const DEFAULT_PERMISSIONS: Record<PermissionAction, PermissionRule> = {
|
|
|
79
83
|
|
|
80
84
|
// ─── Auth contract (implemented by @jxsuite/auth in Part 4b) ─────────────────
|
|
81
85
|
|
|
86
|
+
/**
|
|
87
|
+
* One field of an auth provider's user or session record: JSON values, plus the `Date` fields
|
|
88
|
+
* (`createdAt`, `expiresAt`, …) a server-side adapter returns; the client copy carries ISO
|
|
89
|
+
* strings.
|
|
90
|
+
*/
|
|
91
|
+
export type AuthRecordValue = JsonValue | Date | undefined;
|
|
92
|
+
|
|
93
|
+
/** A user or session record as the auth provider returns it. */
|
|
94
|
+
export type AuthRecord = Record<string, AuthRecordValue>;
|
|
95
|
+
|
|
82
96
|
/** The session shape auth hooks resolve from a request. */
|
|
83
97
|
export interface SessionInfo {
|
|
84
98
|
userId: string;
|
|
85
99
|
role?: string;
|
|
86
100
|
/** Full user record, when the auth provider exposes one. */
|
|
87
|
-
user?:
|
|
88
|
-
|
|
101
|
+
user?: AuthRecord;
|
|
102
|
+
/** Provider extras (the session record itself, for Better Auth). */
|
|
103
|
+
[key: string]: AuthRecordValue | AuthRecord;
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
export interface AuthorizeInput {
|
|
@@ -102,38 +117,30 @@ export interface AuthorizeDecision {
|
|
|
102
117
|
status?: number;
|
|
103
118
|
error?: string;
|
|
104
119
|
/** Columns forced onto insert/update payloads (e.g. the owner id). */
|
|
105
|
-
setColumns?:
|
|
120
|
+
setColumns?: JsonObject;
|
|
106
121
|
/** Row filter scoping reads/writes to the owner: `WHERE <field> = <value>`. */
|
|
107
|
-
whereOwner?: { field: string; value:
|
|
122
|
+
whereOwner?: { field: string; value: JsonValue } | null;
|
|
108
123
|
}
|
|
109
124
|
|
|
110
125
|
/** The hooks an auth mount publishes on the shared server context. */
|
|
111
126
|
export interface JxAuthHooks {
|
|
112
|
-
getSession: (request: Request, env:
|
|
113
|
-
authorize: (input: AuthorizeInput, env:
|
|
127
|
+
getSession: (request: Request, env: HostEnv) => Promise<SessionInfo | null>;
|
|
128
|
+
authorize: (input: AuthorizeInput, env: HostEnv) => Promise<AuthorizeDecision>;
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
/**
|
|
117
132
|
* One shared mutable context per worker isolate, passed to every mount in `server.order`
|
|
118
133
|
* (specs/extensions.md §11). The auth mount (order 10) sets `auth`; the data mount (order 20)
|
|
119
|
-
* consumes it and fails closed when it is absent.
|
|
134
|
+
* consumes it and fails closed when it is absent. A mount that publishes another service declares
|
|
135
|
+
* its member by augmenting this interface (`declare module "@jxsuite/connector/types"`).
|
|
120
136
|
*/
|
|
121
137
|
export interface JxServerContext {
|
|
122
138
|
auth?: JxAuthHooks;
|
|
123
|
-
[key: string]: unknown;
|
|
124
139
|
}
|
|
125
140
|
|
|
126
141
|
// ─── Deploy / dialect capability results ─────────────────────────────────────
|
|
127
142
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
warnings: string[];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Result shape of the `testConnection` connector capability. */
|
|
136
|
-
export interface TestConnectionResult {
|
|
137
|
-
ok: boolean;
|
|
138
|
-
error?: string;
|
|
139
|
-
}
|
|
143
|
+
/* The `deploySchema` and `testConnection` result shapes are declared once in @jxsuite/schema beside
|
|
144
|
+
the capability names, because the hosts that call these capabilities cannot depend on this
|
|
145
|
+
extension (specs/extensions.md §2). Re-exported so this package's dependents keep one import. */
|
|
146
|
+
export type { DeploySchemaResult, TestConnectionResult } from "@jxsuite/schema/format-registry";
|
package/src/validate.ts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* validate cleanly.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { isBoolean, isJsonObject, isNumber, isString } from "@jxsuite/schema/guards";
|
|
12
13
|
import { parseRefPointer } from "./columns.ts";
|
|
14
|
+
import type { JsonObject, JsonValue } from "@jxsuite/schema/types";
|
|
13
15
|
import type { ColumnFieldSchema, TableDef } from "./types.ts";
|
|
14
16
|
|
|
15
17
|
export interface ValidationIssue {
|
|
@@ -21,7 +23,7 @@ export interface ValidationResult {
|
|
|
21
23
|
valid: boolean;
|
|
22
24
|
errors: ValidationIssue[];
|
|
23
25
|
/** The coerced payload (schema-typed values), keyed by schema field name. */
|
|
24
|
-
value:
|
|
26
|
+
value: JsonObject;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** Columns the data mount owns; clients may never write them. */
|
|
@@ -31,26 +33,25 @@ const READ_ONLY_FIELDS = new Set(["id", "created_at", "updated_at"]);
|
|
|
31
33
|
* Validate (and coerce) a row payload against a table definition.
|
|
32
34
|
*
|
|
33
35
|
* @param {TableDef} table - The table definition from the `data` section
|
|
34
|
-
* @param {
|
|
36
|
+
* @param {JsonValue} payload - Parsed request body
|
|
35
37
|
* @param {{ partial?: boolean }} [options] - `partial` skips required-field checks (PATCH)
|
|
36
38
|
* @returns {ValidationResult}
|
|
37
39
|
*/
|
|
38
40
|
export function validateRow(
|
|
39
41
|
table: TableDef,
|
|
40
|
-
payload:
|
|
42
|
+
payload: JsonValue,
|
|
41
43
|
options: { partial?: boolean } = {},
|
|
42
44
|
): ValidationResult {
|
|
43
45
|
const { partial = false } = options;
|
|
44
46
|
const errors: ValidationIssue[] = [];
|
|
45
|
-
const value:
|
|
47
|
+
const value: JsonObject = {};
|
|
46
48
|
|
|
47
|
-
if (
|
|
49
|
+
if (!isJsonObject(payload)) {
|
|
48
50
|
return { errors: [{ field: "", message: "Body must be a JSON object" }], valid: false, value };
|
|
49
51
|
}
|
|
50
|
-
const body = payload as Record<string, unknown>;
|
|
51
52
|
const fields = table.schema.properties ?? {};
|
|
52
53
|
|
|
53
|
-
for (const [key, raw] of Object.entries(
|
|
54
|
+
for (const [key, raw] of Object.entries(payload)) {
|
|
54
55
|
if (READ_ONLY_FIELDS.has(key)) {
|
|
55
56
|
errors.push({ field: key, message: "Read-only field" });
|
|
56
57
|
continue;
|
|
@@ -68,7 +69,7 @@ export function validateRow(
|
|
|
68
69
|
|
|
69
70
|
if (!partial) {
|
|
70
71
|
for (const required of table.schema.required ?? []) {
|
|
71
|
-
if (
|
|
72
|
+
if (payload[required] === undefined) {
|
|
72
73
|
errors.push({ field: required, message: "Required field is missing" });
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -85,23 +86,23 @@ const INVALID = Symbol("invalid");
|
|
|
85
86
|
*
|
|
86
87
|
* @param {string} field
|
|
87
88
|
* @param {ColumnFieldSchema} schema
|
|
88
|
-
* @param {
|
|
89
|
+
* @param {JsonValue} raw
|
|
89
90
|
* @param {ValidationIssue[]} errors
|
|
90
|
-
* @returns {
|
|
91
|
+
* @returns {JsonValue | typeof INVALID} The coerced value, or the INVALID sentinel
|
|
91
92
|
*/
|
|
92
93
|
function coerceField(
|
|
93
94
|
field: string,
|
|
94
95
|
schema: ColumnFieldSchema,
|
|
95
|
-
raw:
|
|
96
|
+
raw: JsonValue,
|
|
96
97
|
errors: ValidationIssue[],
|
|
97
|
-
):
|
|
98
|
-
if (raw === null
|
|
98
|
+
): JsonValue | typeof INVALID {
|
|
99
|
+
if (raw === null) {
|
|
99
100
|
return null;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
const toOne = parseRefPointer(schema.$ref);
|
|
103
104
|
if (toOne) {
|
|
104
|
-
if (
|
|
105
|
+
if (!isString(raw) || raw === "") {
|
|
105
106
|
errors.push({ field, message: "Reference value must be an entry id string" });
|
|
106
107
|
return INVALID;
|
|
107
108
|
}
|
|
@@ -111,7 +112,7 @@ function coerceField(
|
|
|
111
112
|
const itemsRef = schema.type === "array" ? parseRefPointer(schema.items?.$ref) : null;
|
|
112
113
|
if (itemsRef) {
|
|
113
114
|
const list = parseMaybeJson(raw);
|
|
114
|
-
if (!Array.isArray(list) || list.some((item) =>
|
|
115
|
+
if (!Array.isArray(list) || list.some((item) => !isString(item) || item === "")) {
|
|
115
116
|
errors.push({ field, message: "Reference list must be an array of entry id strings" });
|
|
116
117
|
return INVALID;
|
|
117
118
|
}
|
|
@@ -131,14 +132,14 @@ function coerceField(
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
/** Coerce a scalar payload value to its schema type, or INVALID. */
|
|
134
|
-
function coerceScalar(schema: ColumnFieldSchema, raw:
|
|
135
|
+
function coerceScalar(schema: ColumnFieldSchema, raw: JsonValue): JsonValue | typeof INVALID {
|
|
135
136
|
switch (schema.type) {
|
|
136
137
|
case "number":
|
|
137
138
|
case "integer": {
|
|
138
139
|
let num: number;
|
|
139
|
-
if (
|
|
140
|
+
if (isNumber(raw)) {
|
|
140
141
|
num = raw;
|
|
141
|
-
} else if (
|
|
142
|
+
} else if (isString(raw) && raw.trim() !== "" && !Number.isNaN(Number(raw))) {
|
|
142
143
|
num = Number(raw);
|
|
143
144
|
} else {
|
|
144
145
|
return INVALID;
|
|
@@ -149,7 +150,7 @@ function coerceScalar(schema: ColumnFieldSchema, raw: unknown): unknown {
|
|
|
149
150
|
return num;
|
|
150
151
|
}
|
|
151
152
|
case "boolean": {
|
|
152
|
-
if (
|
|
153
|
+
if (isBoolean(raw)) {
|
|
153
154
|
return raw;
|
|
154
155
|
}
|
|
155
156
|
if (raw === "true" || raw === "on" || raw === "1" || raw === 1) {
|
|
@@ -166,19 +167,17 @@ function coerceScalar(schema: ColumnFieldSchema, raw: unknown): unknown {
|
|
|
166
167
|
}
|
|
167
168
|
case "object": {
|
|
168
169
|
const parsed = parseMaybeJson(raw);
|
|
169
|
-
return parsed
|
|
170
|
-
? parsed
|
|
171
|
-
: INVALID;
|
|
170
|
+
return isJsonObject(parsed) ? parsed : INVALID;
|
|
172
171
|
}
|
|
173
172
|
default: {
|
|
174
|
-
return
|
|
173
|
+
return isString(raw) ? raw : INVALID;
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
/** Parse JSON-looking strings (FormData carries arrays/objects as text); pass everything else. */
|
|
180
|
-
function parseMaybeJson(raw:
|
|
181
|
-
if (
|
|
179
|
+
function parseMaybeJson(raw: JsonValue): JsonValue {
|
|
180
|
+
if (!isString(raw)) {
|
|
182
181
|
return raw;
|
|
183
182
|
}
|
|
184
183
|
const text = raw.trim();
|
|
@@ -186,7 +185,7 @@ function parseMaybeJson(raw: unknown): unknown {
|
|
|
186
185
|
return raw;
|
|
187
186
|
}
|
|
188
187
|
try {
|
|
189
|
-
return JSON.parse(text)
|
|
188
|
+
return JSON.parse(text);
|
|
190
189
|
} catch {
|
|
191
190
|
return raw;
|
|
192
191
|
}
|