@gea-ai/extensions-sdk 0.1.261014-alpha.0 → 0.1.261016-alpha.0
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/README.md +41 -10
- package/dist/actions.d.ts +4 -1
- package/dist/actions.js +26 -5
- package/dist/backend.d.ts +14 -3
- package/dist/backend.js +183 -2
- package/dist/request-id.d.ts +27 -0
- package/dist/request-id.js +160 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -141,17 +141,47 @@ import { actions } from "../contract";
|
|
|
141
141
|
type Sdk = ExtensionBackendSdk<never, { appData: typeof schema }>;
|
|
142
142
|
|
|
143
143
|
export const backend = defineBackendActions<typeof actions, Sdk>(actions, {
|
|
144
|
-
createIdea: async ({ input, sdk }) =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
createIdea: async ({ input, sdk }) => {
|
|
145
|
+
if (!sdk.createId || !sdk.requestId) {
|
|
146
|
+
throw new Error("Extension request id is unavailable.");
|
|
147
|
+
}
|
|
148
|
+
const idea = { id: sdk.createId("idea"), title: input.title };
|
|
149
|
+
const eventId = sdk.createId("idea-event");
|
|
150
|
+
await sdk.databases.appData.batch([
|
|
151
|
+
sdk.databases.appData.insert(schema.ideas).values(idea),
|
|
152
|
+
sdk.databases.appData.insert(schema.ideaEvents).values({
|
|
153
|
+
id: eventId,
|
|
154
|
+
ideaId: idea.id,
|
|
155
|
+
kind: "created",
|
|
156
|
+
}),
|
|
157
|
+
]);
|
|
158
|
+
return idea;
|
|
159
|
+
},
|
|
150
160
|
});
|
|
151
161
|
```
|
|
152
162
|
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
`schema.ideaEvents` is the audit table declared next to `schema.ideas`. Derive
|
|
164
|
+
both ids from `sdk.createId` before calling `batch()` so the two inserts are
|
|
165
|
+
one host request and a retry sends the same primary keys. `batch()` runs
|
|
166
|
+
inside a single SQLite transaction: a failure rolls the whole batch back. The
|
|
167
|
+
caller supplies the request id, and a lost response retries with that same id
|
|
168
|
+
after the host process is gone instead of inserting again. The database write
|
|
169
|
+
id follows that caller id and the statement text, so an extra read on retry
|
|
170
|
+
does not run the write again. Full replay records and the host change log
|
|
171
|
+
each stay inside a byte budget for 24 hours. Compacted ids share one
|
|
172
|
+
fixed-size tombstone capped by that same budget, so the record does not grow
|
|
173
|
+
with each write. A compacted id is not executed again while it remains in
|
|
174
|
+
that record. The tombstone rolls forward in 24 hour generations and can keep
|
|
175
|
+
a compacted id refused for up to two generations. When that fixed record is
|
|
176
|
+
saturated, a new id can be refused instead of expanding past the budget or
|
|
177
|
+
running an old write again.
|
|
178
|
+
|
|
179
|
+
Normal Drizzle selects, writes, and batches execute
|
|
180
|
+
against the host-owned database. Interactive `transaction()` is not available.
|
|
181
|
+
A `BEGIN`, `COMMIT`, `ROLLBACK`, `SAVEPOINT`, or `END` statement, including a
|
|
182
|
+
keyword glued to a comment or a leading semicolon, or an old transaction
|
|
183
|
+
session is rejected before any write with an error that says to use `batch()`.
|
|
184
|
+
The host revalidates the installed extension,
|
|
155
185
|
workspace, declared resource key, engine readiness, and caller on every proxy
|
|
156
186
|
operation. The same proxy protocol is used by `gea ext dev`.
|
|
157
187
|
|
|
@@ -267,8 +297,9 @@ intentionally narrow:
|
|
|
267
297
|
- `sdk.chats.list(...)` for listing the authenticated user's ordinary top-level
|
|
268
298
|
chats in the current workspace, plus `sdk.chats.messages.list(...)` for
|
|
269
299
|
reading a selected chat.
|
|
270
|
-
- `sdk.databases.<handle>` for typed Drizzle SQLite Proxy queries
|
|
271
|
-
|
|
300
|
+
- `sdk.databases.<handle>` for typed Drizzle SQLite Proxy queries and batches
|
|
301
|
+
against SQLite resources declared by this extension. Use `batch()` when
|
|
302
|
+
several writes must commit or roll back together. No path,
|
|
272
303
|
provider locator, credential, or arbitrary resource lookup is exposed.
|
|
273
304
|
|
|
274
305
|
Legacy host helpers outside this surface are not part of the third-party SDK
|
package/dist/actions.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type ExtensionActionTransport = (input: {
|
|
|
16
16
|
action: string;
|
|
17
17
|
extensionSlug: string;
|
|
18
18
|
input: unknown;
|
|
19
|
+
requestId?: string;
|
|
19
20
|
}) => Promise<unknown>;
|
|
20
21
|
export declare function defineActions<const TActions extends Record<string, AnyExtensionActionContract>>(actions: TActions): TActions;
|
|
21
22
|
export declare function createExtensionActionTransport(options?: ExtensionHostMessageClientOptions): ExtensionActionTransport;
|
|
@@ -24,7 +25,9 @@ export declare function createExtensionActionClient<const TActions extends Exten
|
|
|
24
25
|
extensionSlug: string;
|
|
25
26
|
transport: ExtensionActionTransport;
|
|
26
27
|
}): {
|
|
27
|
-
call: <const TAction extends keyof TActions & string>(action: TAction, inputValue: ExtensionActionInput<TActions, TAction
|
|
28
|
+
call: <const TAction extends keyof TActions & string>(action: TAction, inputValue: ExtensionActionInput<TActions, TAction>, options?: {
|
|
29
|
+
requestId?: string;
|
|
30
|
+
}) => Promise<ExtensionActionOutput<TActions, TAction>>;
|
|
28
31
|
mutationOptions<const TAction extends keyof TActions & string, TInput extends ExtensionActionInput<TActions, TAction> = ExtensionActionInput<TActions, TAction>, TOutput extends ExtensionActionOutput<TActions, TAction> = ExtensionActionOutput<TActions, TAction>>(action: TAction, options?: Omit<UseMutationOptions<TOutput, Error, TInput, unknown>, "mutationFn" | "mutationKey">): import("@tanstack/react-query").WithRequired<UseMutationOptions<TOutput, Error, TInput, unknown>, "mutationKey">;
|
|
29
32
|
queryOptions<const TAction extends keyof TActions & string, TInput extends ExtensionActionInput<TActions, TAction> = ExtensionActionInput<TActions, TAction>, TOutput extends ExtensionActionOutput<TActions, TAction> = ExtensionActionOutput<TActions, TAction>>(action: TAction, inputValue: TInput, options?: Omit<UseQueryOptions<TOutput, Error, TOutput, QueryKey>, "queryFn" | "queryKey">): ExtensionActionQueryOptions<TOutput>;
|
|
30
33
|
};
|
package/dist/actions.js
CHANGED
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
import { mutationOptions as createTanStackMutationOptions, queryOptions as createTanStackQueryOptions, } from "@tanstack/react-query";
|
|
2
|
-
import { createExtensionHostMethodCaller, } from "./host-message.js";
|
|
2
|
+
import { createExtensionHostMethodCaller, ExtensionHostError, } from "./host-message.js";
|
|
3
|
+
import { createExtensionClientRequestId } from "./request-id.js";
|
|
3
4
|
export function defineActions(actions) {
|
|
4
5
|
return actions;
|
|
5
6
|
}
|
|
6
7
|
export function createExtensionActionTransport(options = {}) {
|
|
7
8
|
const callHostMethod = createExtensionHostMethodCaller(options);
|
|
8
|
-
return async (input) =>
|
|
9
|
-
input
|
|
10
|
-
|
|
9
|
+
return async (input) => {
|
|
10
|
+
const requestId = input.requestId ?? createExtensionClientRequestId();
|
|
11
|
+
const send = () => callHostMethod("extension.call", {
|
|
12
|
+
input: {
|
|
13
|
+
action: input.action,
|
|
14
|
+
extensionSlug: input.extensionSlug,
|
|
15
|
+
input: input.input,
|
|
16
|
+
requestId,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
return await send();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof ExtensionHostError &&
|
|
24
|
+
error.code === "invalid_input") {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
return await send();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
11
30
|
}
|
|
12
31
|
export function createExtensionActionClient(input) {
|
|
13
32
|
const { actions, extensionSlug, transport } = input;
|
|
14
|
-
async function call(action, inputValue) {
|
|
33
|
+
async function call(action, inputValue, options) {
|
|
15
34
|
const actionContract = actions[action];
|
|
16
35
|
const parsedInput = actionContract.inputSchema.parse(inputValue);
|
|
36
|
+
const requestId = options?.requestId ?? createExtensionClientRequestId();
|
|
17
37
|
const result = await transport({
|
|
18
38
|
action,
|
|
19
39
|
extensionSlug,
|
|
20
40
|
input: parsedInput,
|
|
41
|
+
requestId,
|
|
21
42
|
});
|
|
22
43
|
return actionContract.outputSchema.parse(result);
|
|
23
44
|
}
|
package/dist/backend.d.ts
CHANGED
|
@@ -206,20 +206,30 @@ export type ExtensionBackendAgentsClient<TAgentHandleKey extends string = never>
|
|
|
206
206
|
} & Record<TAgentHandleKey, ExtensionBackendAgentHandle>;
|
|
207
207
|
export type ExtensionBackendDatabaseSchemas = Record<string, Record<string, unknown>>;
|
|
208
208
|
export type ExtensionBackendDatabasesClient<TSchemas extends ExtensionBackendDatabaseSchemas = Record<string, Record<string, never>>> = {
|
|
209
|
-
[TDatabase in keyof TSchemas]:
|
|
209
|
+
[TDatabase in keyof TSchemas]: ExtensionSqliteDatabase<TSchemas[TDatabase]>;
|
|
210
210
|
};
|
|
211
211
|
export type ExtensionBackendDatabaseTransport = {
|
|
212
212
|
batch: AsyncBatchRemoteCallback;
|
|
213
213
|
execute: AsyncRemoteCallback;
|
|
214
214
|
};
|
|
215
|
-
export declare
|
|
215
|
+
export declare const extensionDatabaseTransactionErrorMessage = "Extension database transactions are not supported. Use batch().";
|
|
216
|
+
export declare function extensionDatabaseStatementsUseTransaction(statements: ReadonlyArray<{
|
|
217
|
+
sql: string;
|
|
218
|
+
}>): boolean;
|
|
219
|
+
export declare function isExtensionDatabaseTransactionSql(sql: string): boolean;
|
|
220
|
+
export type ExtensionSqliteDatabase<TSchema extends Record<string, unknown> = Record<string, never>> = Omit<SqliteRemoteDatabase<TSchema>, "transaction">;
|
|
221
|
+
export declare function createExtensionBackendDatabase<TSchema extends Record<string, unknown> = Record<string, never>>(transport: ExtensionBackendDatabaseTransport, options?: {
|
|
222
|
+
rejectTransactionSql?: boolean;
|
|
223
|
+
}): ExtensionSqliteDatabase<TSchema>;
|
|
216
224
|
export declare function getExtensionAgentHandleKey(slug: string): string;
|
|
217
225
|
export type ExtensionBackendSdk<TAgentHandleKey extends string = never, TDatabaseSchemas extends ExtensionBackendDatabaseSchemas = Record<string, Record<string, never>>> = {
|
|
218
226
|
agents: ExtensionBackendAgentsClient<TAgentHandleKey>;
|
|
219
227
|
artifacts: ExtensionBackendArtifactsClient;
|
|
220
228
|
chats: ExtensionBackendChatsClient;
|
|
221
229
|
connectors: ExtensionBackendConnectorsClient;
|
|
230
|
+
createId?(name: string): string;
|
|
222
231
|
databases: ExtensionBackendDatabasesClient<TDatabaseSchemas>;
|
|
232
|
+
requestId?: string;
|
|
223
233
|
resources: ExtensionBackendResourcesClient;
|
|
224
234
|
state: {
|
|
225
235
|
records: ExtensionBackendStateRecordsClient;
|
|
@@ -236,10 +246,12 @@ export type ExtensionBackendActionContext = {
|
|
|
236
246
|
workspaceId: string;
|
|
237
247
|
};
|
|
238
248
|
export type ExtensionBackendActionHandlerInput<TInput, TSdk> = {
|
|
249
|
+
clientRequestId?: string;
|
|
239
250
|
context: ExtensionBackendActionContext;
|
|
240
251
|
input: TInput;
|
|
241
252
|
sdk: TSdk;
|
|
242
253
|
};
|
|
254
|
+
export { createExtensionClientRequestId, extensionClientRequestId, extensionDatabaseRequestId, extensionDatabaseStatementsKey, extensionRequestNamespaceHex, nextExtensionDatabaseRequestId, extensionRequestTimestampMs, extensionScopedId, } from "./request-id.js";
|
|
243
255
|
export type ExtensionBackendHandlers<TActions extends ExtensionActionsInput, TSdk> = {
|
|
244
256
|
[TAction in keyof TActions & string]: (input: ExtensionBackendActionHandlerInput<ExtensionActionInput<TActions, TAction>, TSdk>) => ExtensionActionOutput<TActions, TAction> | Promise<ExtensionActionOutput<TActions, TAction>>;
|
|
245
257
|
};
|
|
@@ -278,4 +290,3 @@ export declare function defineDurableObjects<const TActions extends ExtensionAct
|
|
|
278
290
|
durableObjects: TObjects;
|
|
279
291
|
};
|
|
280
292
|
export declare function defineBackendActions<const TActions extends ExtensionActionsInput, TSdk = ExtensionBackendSdk>(actions: TActions, handlers: ExtensionBackendHandlers<TActions, TSdk>): ExtensionBackendDefinition<TActions, TSdk>;
|
|
281
|
-
export {};
|
package/dist/backend.js
CHANGED
|
@@ -1,6 +1,186 @@
|
|
|
1
1
|
import { drizzle, } from "drizzle-orm/sqlite-proxy";
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export const extensionDatabaseTransactionErrorMessage = "Extension database transactions are not supported. Use batch().";
|
|
3
|
+
export function extensionDatabaseStatementsUseTransaction(statements) {
|
|
4
|
+
return statements.some((statement) => isExtensionDatabaseTransactionSql(statement.sql));
|
|
5
|
+
}
|
|
6
|
+
const extensionDatabaseTransactionKeywords = new Set([
|
|
7
|
+
"begin",
|
|
8
|
+
"commit",
|
|
9
|
+
"end",
|
|
10
|
+
"release",
|
|
11
|
+
"rollback",
|
|
12
|
+
"savepoint",
|
|
13
|
+
]);
|
|
14
|
+
function isSqlWhitespace(char) {
|
|
15
|
+
return (char === " " ||
|
|
16
|
+
char === "\t" ||
|
|
17
|
+
char === "\n" ||
|
|
18
|
+
char === "\r" ||
|
|
19
|
+
char === "\f");
|
|
20
|
+
}
|
|
21
|
+
function isSqlWordChar(char) {
|
|
22
|
+
return ((char >= "A" && char <= "Z") ||
|
|
23
|
+
(char >= "a" && char <= "z") ||
|
|
24
|
+
(char >= "0" && char <= "9") ||
|
|
25
|
+
char === "_");
|
|
26
|
+
}
|
|
27
|
+
function skipSqlQuote(source, index, quote) {
|
|
28
|
+
let cursor = index + 1;
|
|
29
|
+
while (cursor < source.length) {
|
|
30
|
+
if (source[cursor] === quote) {
|
|
31
|
+
if (source[cursor + 1] === quote) {
|
|
32
|
+
cursor += 2;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
return cursor + 1;
|
|
36
|
+
}
|
|
37
|
+
cursor += 1;
|
|
38
|
+
}
|
|
39
|
+
return cursor;
|
|
40
|
+
}
|
|
41
|
+
function skipSqlStatementRemainder(source, index) {
|
|
42
|
+
let cursor = index;
|
|
43
|
+
while (cursor < source.length) {
|
|
44
|
+
const char = source[cursor] ?? "";
|
|
45
|
+
if (char === "-" && source[cursor + 1] === "-") {
|
|
46
|
+
const newline = source.indexOf("\n", cursor + 2);
|
|
47
|
+
cursor = newline < 0 ? source.length : newline + 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (char === "/" && source[cursor + 1] === "*") {
|
|
51
|
+
const end = source.indexOf("*/", cursor + 2);
|
|
52
|
+
if (end < 0)
|
|
53
|
+
return source.length;
|
|
54
|
+
cursor = end + 2;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (char === "'" || char === '"' || char === "`" || char === "[") {
|
|
58
|
+
if (char === "[") {
|
|
59
|
+
const end = source.indexOf("]", cursor + 1);
|
|
60
|
+
cursor = end < 0 ? source.length : end + 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
cursor = skipSqlQuote(source, cursor, char);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (char === ";")
|
|
67
|
+
return cursor + 1;
|
|
68
|
+
cursor += 1;
|
|
69
|
+
}
|
|
70
|
+
return cursor;
|
|
71
|
+
}
|
|
72
|
+
export function isExtensionDatabaseTransactionSql(sql) {
|
|
73
|
+
const source = sql;
|
|
74
|
+
let index = 0;
|
|
75
|
+
while (index < source.length) {
|
|
76
|
+
while (index < source.length && isSqlWhitespace(source[index] ?? "")) {
|
|
77
|
+
index += 1;
|
|
78
|
+
}
|
|
79
|
+
if (index >= source.length)
|
|
80
|
+
return false;
|
|
81
|
+
if (source[index] === "-" && source[index + 1] === "-") {
|
|
82
|
+
const newline = source.indexOf("\n", index + 2);
|
|
83
|
+
index = newline < 0 ? source.length : newline + 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (source[index] === "/" && source[index + 1] === "*") {
|
|
87
|
+
const end = source.indexOf("*/", index + 2);
|
|
88
|
+
if (end < 0)
|
|
89
|
+
return false;
|
|
90
|
+
index = end + 2;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (source[index] === ";") {
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const first = source[index] ?? "";
|
|
98
|
+
if (isSqlWordChar(first) && !(first >= "0" && first <= "9")) {
|
|
99
|
+
let end = index + 1;
|
|
100
|
+
while (end < source.length && isSqlWordChar(source[end] ?? ""))
|
|
101
|
+
end += 1;
|
|
102
|
+
const word = source.slice(index, end).toLowerCase();
|
|
103
|
+
if (extensionDatabaseTransactionKeywords.has(word))
|
|
104
|
+
return true;
|
|
105
|
+
index = skipSqlStatementRemainder(source, end);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
index = skipSqlStatementRemainder(source, index);
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const extensionDatabaseExecutingMethods = new Set([
|
|
113
|
+
"all",
|
|
114
|
+
"batch",
|
|
115
|
+
"execute",
|
|
116
|
+
"get",
|
|
117
|
+
"run",
|
|
118
|
+
"values",
|
|
119
|
+
]);
|
|
120
|
+
function rejectedExtensionDatabaseTransaction(error) {
|
|
121
|
+
let current = error;
|
|
122
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
123
|
+
if (!(current instanceof Error))
|
|
124
|
+
return null;
|
|
125
|
+
if (current.message === extensionDatabaseTransactionErrorMessage) {
|
|
126
|
+
return current;
|
|
127
|
+
}
|
|
128
|
+
current = current.cause;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
export function createExtensionBackendDatabase(transport, options) {
|
|
133
|
+
const rejectTransactionSql = options?.rejectTransactionSql !== false;
|
|
134
|
+
const guardedTransport = {
|
|
135
|
+
batch: async (statements) => {
|
|
136
|
+
if (extensionDatabaseStatementsUseTransaction(statements)) {
|
|
137
|
+
throw new Error(extensionDatabaseTransactionErrorMessage);
|
|
138
|
+
}
|
|
139
|
+
return await transport.batch(statements);
|
|
140
|
+
},
|
|
141
|
+
execute: async (sql, params, method) => {
|
|
142
|
+
if (isExtensionDatabaseTransactionSql(sql)) {
|
|
143
|
+
throw new Error(extensionDatabaseTransactionErrorMessage);
|
|
144
|
+
}
|
|
145
|
+
return await transport.execute(sql, params, method);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
const activeTransport = rejectTransactionSql ? guardedTransport : transport;
|
|
149
|
+
const database = drizzle(activeTransport.execute, activeTransport.batch);
|
|
150
|
+
return new Proxy(database, {
|
|
151
|
+
get(target, property, receiver) {
|
|
152
|
+
if (property === "transaction") {
|
|
153
|
+
return async () => {
|
|
154
|
+
throw new Error(extensionDatabaseTransactionErrorMessage);
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const value = Reflect.get(target, property, receiver);
|
|
158
|
+
if (typeof property !== "string" ||
|
|
159
|
+
typeof value !== "function" ||
|
|
160
|
+
!extensionDatabaseExecutingMethods.has(property)) {
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
return (...args) => {
|
|
164
|
+
const result = value.apply(target, args);
|
|
165
|
+
const then = result?.then;
|
|
166
|
+
if (typeof then !== "function")
|
|
167
|
+
return result;
|
|
168
|
+
return new Proxy(result, {
|
|
169
|
+
get(inner, key, receiver) {
|
|
170
|
+
if (key !== "then")
|
|
171
|
+
return Reflect.get(inner, key, receiver);
|
|
172
|
+
return (onFulfilled, onRejected) => then.call(inner, onFulfilled, (error) => {
|
|
173
|
+
const transactionError = rejectedExtensionDatabaseTransaction(error);
|
|
174
|
+
const unwrapped = transactionError ?? error;
|
|
175
|
+
if (onRejected)
|
|
176
|
+
return onRejected(unwrapped);
|
|
177
|
+
throw unwrapped;
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
});
|
|
4
184
|
}
|
|
5
185
|
export function getExtensionAgentHandleKey(slug) {
|
|
6
186
|
return slug
|
|
@@ -9,6 +189,7 @@ export function getExtensionAgentHandleKey(slug) {
|
|
|
9
189
|
.map((segment, index) => index === 0 ? segment : `${segment[0]?.toUpperCase()}${segment.slice(1)}`)
|
|
10
190
|
.join("");
|
|
11
191
|
}
|
|
192
|
+
export { createExtensionClientRequestId, extensionClientRequestId, extensionDatabaseRequestId, extensionDatabaseStatementsKey, extensionRequestNamespaceHex, nextExtensionDatabaseRequestId, extensionRequestTimestampMs, extensionScopedId, } from "./request-id.js";
|
|
12
193
|
export function defineDurableObjects(backend, durableObjects) {
|
|
13
194
|
for (const [className, definition] of Object.entries(durableObjects)) {
|
|
14
195
|
if (!/^[a-z][a-z0-9-]{0,63}$/u.test(className)) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare function extensionUuidV5(name: string): string;
|
|
2
|
+
export declare const extensionRequestNamespaceHex: string;
|
|
3
|
+
export declare function extensionDatabaseStatementsKey(statements: ReadonlyArray<{
|
|
4
|
+
method: string;
|
|
5
|
+
params?: readonly unknown[];
|
|
6
|
+
sql: string;
|
|
7
|
+
}>): string;
|
|
8
|
+
export declare function extensionDatabaseRequestId(input: {
|
|
9
|
+
clientRequestId: string;
|
|
10
|
+
occurrence: number;
|
|
11
|
+
resourceKey: string;
|
|
12
|
+
statementsKey: string;
|
|
13
|
+
}): string;
|
|
14
|
+
export declare function nextExtensionDatabaseRequestId(input: {
|
|
15
|
+
clientRequestId: string;
|
|
16
|
+
occurrences: Map<string, number>;
|
|
17
|
+
resourceKey: string;
|
|
18
|
+
statements: ReadonlyArray<{
|
|
19
|
+
method: string;
|
|
20
|
+
params?: readonly unknown[];
|
|
21
|
+
sql: string;
|
|
22
|
+
}>;
|
|
23
|
+
}): string;
|
|
24
|
+
export declare function extensionScopedId(clientRequestId: string, name: string): string;
|
|
25
|
+
export declare function extensionClientRequestId(seed: string): string;
|
|
26
|
+
export declare function extensionRequestTimestampMs(requestId: string): number;
|
|
27
|
+
export declare function createExtensionClientRequestId(): string;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const EXTENSION_REQUEST_NAMESPACE = "9c6b5b2e-1f4a-5d3c-8e7b-6a5d4c3b2a19";
|
|
2
|
+
function leftRotate(value, shift) {
|
|
3
|
+
return ((value << shift) | (value >>> (32 - shift))) >>> 0;
|
|
4
|
+
}
|
|
5
|
+
function sha1(bytes) {
|
|
6
|
+
const bitLength = bytes.length * 8;
|
|
7
|
+
const paddedLength = ((bytes.length + 9 + 63) >> 6) << 6;
|
|
8
|
+
const padded = new Uint8Array(paddedLength);
|
|
9
|
+
padded.set(bytes);
|
|
10
|
+
padded[bytes.length] = 0x80;
|
|
11
|
+
const view = new DataView(padded.buffer);
|
|
12
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000));
|
|
13
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0);
|
|
14
|
+
let hash0 = 0x67452301;
|
|
15
|
+
let hash1 = 0xefcdab89;
|
|
16
|
+
let hash2 = 0x98badcfe;
|
|
17
|
+
let hash3 = 0x10325476;
|
|
18
|
+
let hash4 = 0xc3d2e1f0;
|
|
19
|
+
const words = new Uint32Array(80);
|
|
20
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
21
|
+
for (let index = 0; index < 16; index += 1) {
|
|
22
|
+
words[index] = view.getUint32(offset + index * 4);
|
|
23
|
+
}
|
|
24
|
+
for (let index = 16; index < 80; index += 1) {
|
|
25
|
+
words[index] = leftRotate((words[index - 3] ^
|
|
26
|
+
words[index - 8] ^
|
|
27
|
+
words[index - 14] ^
|
|
28
|
+
words[index - 16]) >>>
|
|
29
|
+
0, 1);
|
|
30
|
+
}
|
|
31
|
+
let a = hash0;
|
|
32
|
+
let b = hash1;
|
|
33
|
+
let c = hash2;
|
|
34
|
+
let d = hash3;
|
|
35
|
+
let e = hash4;
|
|
36
|
+
for (let index = 0; index < 80; index += 1) {
|
|
37
|
+
let mixed = 0;
|
|
38
|
+
let constant = 0;
|
|
39
|
+
if (index < 20) {
|
|
40
|
+
mixed = (b & c) | ((~b >>> 0) & d);
|
|
41
|
+
constant = 0x5a827999;
|
|
42
|
+
}
|
|
43
|
+
else if (index < 40) {
|
|
44
|
+
mixed = b ^ c ^ d;
|
|
45
|
+
constant = 0x6ed9eba1;
|
|
46
|
+
}
|
|
47
|
+
else if (index < 60) {
|
|
48
|
+
mixed = (b & c) | (b & d) | (c & d);
|
|
49
|
+
constant = 0x8f1bbcdc;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
mixed = b ^ c ^ d;
|
|
53
|
+
constant = 0xca62c1d6;
|
|
54
|
+
}
|
|
55
|
+
const next = (leftRotate(a, 5) + mixed + e + constant + words[index]) >>> 0;
|
|
56
|
+
e = d;
|
|
57
|
+
d = c;
|
|
58
|
+
c = leftRotate(b, 30);
|
|
59
|
+
b = a;
|
|
60
|
+
a = next;
|
|
61
|
+
}
|
|
62
|
+
hash0 = (hash0 + a) >>> 0;
|
|
63
|
+
hash1 = (hash1 + b) >>> 0;
|
|
64
|
+
hash2 = (hash2 + c) >>> 0;
|
|
65
|
+
hash3 = (hash3 + d) >>> 0;
|
|
66
|
+
hash4 = (hash4 + e) >>> 0;
|
|
67
|
+
}
|
|
68
|
+
const digest = new Uint8Array(20);
|
|
69
|
+
const digestView = new DataView(digest.buffer);
|
|
70
|
+
digestView.setUint32(0, hash0);
|
|
71
|
+
digestView.setUint32(4, hash1);
|
|
72
|
+
digestView.setUint32(8, hash2);
|
|
73
|
+
digestView.setUint32(12, hash3);
|
|
74
|
+
digestView.setUint32(16, hash4);
|
|
75
|
+
return digest;
|
|
76
|
+
}
|
|
77
|
+
function bytesToUuid(bytes) {
|
|
78
|
+
const hex = [...bytes]
|
|
79
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
80
|
+
.join("");
|
|
81
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
82
|
+
}
|
|
83
|
+
function uuidToBytes(uuid) {
|
|
84
|
+
const hex = uuid.replaceAll("-", "");
|
|
85
|
+
const bytes = new Uint8Array(16);
|
|
86
|
+
for (let index = 0; index < 16; index += 1) {
|
|
87
|
+
bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
88
|
+
}
|
|
89
|
+
return bytes;
|
|
90
|
+
}
|
|
91
|
+
export function extensionUuidV5(name) {
|
|
92
|
+
const namespace = uuidToBytes(EXTENSION_REQUEST_NAMESPACE);
|
|
93
|
+
const nameBytes = new TextEncoder().encode(name);
|
|
94
|
+
const input = new Uint8Array(namespace.length + nameBytes.length);
|
|
95
|
+
input.set(namespace);
|
|
96
|
+
input.set(nameBytes, namespace.length);
|
|
97
|
+
const digest = sha1(input).slice(0, 16);
|
|
98
|
+
digest[6] = ((digest[6] ?? 0) & 0x0f) | 0x50;
|
|
99
|
+
digest[8] = ((digest[8] ?? 0) & 0x3f) | 0x80;
|
|
100
|
+
return bytesToUuid(digest);
|
|
101
|
+
}
|
|
102
|
+
export const extensionRequestNamespaceHex = EXTENSION_REQUEST_NAMESPACE.replaceAll("-", "");
|
|
103
|
+
export function extensionDatabaseStatementsKey(statements) {
|
|
104
|
+
return statements
|
|
105
|
+
.map((statement) => `${statement.method}\n${statement.sql}\n${JSON.stringify(statement.params ?? [])}`)
|
|
106
|
+
.join("\n;");
|
|
107
|
+
}
|
|
108
|
+
export function extensionDatabaseRequestId(input) {
|
|
109
|
+
return extensionUuidV5(`${input.clientRequestId}\n${input.resourceKey}\n${input.occurrence}\n${input.statementsKey}`);
|
|
110
|
+
}
|
|
111
|
+
export function nextExtensionDatabaseRequestId(input) {
|
|
112
|
+
const statementsKey = extensionDatabaseStatementsKey(input.statements);
|
|
113
|
+
const occurrenceKey = `${input.resourceKey}\n${statementsKey}`;
|
|
114
|
+
const occurrence = input.occurrences.get(occurrenceKey) ?? 0;
|
|
115
|
+
input.occurrences.set(occurrenceKey, occurrence + 1);
|
|
116
|
+
return extensionDatabaseRequestId({
|
|
117
|
+
clientRequestId: input.clientRequestId,
|
|
118
|
+
occurrence,
|
|
119
|
+
resourceKey: input.resourceKey,
|
|
120
|
+
statementsKey,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export function extensionScopedId(clientRequestId, name) {
|
|
124
|
+
return extensionUuidV5(`${clientRequestId}\n${name}`);
|
|
125
|
+
}
|
|
126
|
+
export function extensionClientRequestId(seed) {
|
|
127
|
+
return extensionUuidV5(`client\n${seed}`);
|
|
128
|
+
}
|
|
129
|
+
export function extensionRequestTimestampMs(requestId) {
|
|
130
|
+
const hex = requestId.replaceAll("-", "").toLowerCase();
|
|
131
|
+
if (hex.length === 32 && hex[12] === "7") {
|
|
132
|
+
const timestamp = Number.parseInt(hex.slice(0, 12), 16);
|
|
133
|
+
if (Number.isSafeInteger(timestamp))
|
|
134
|
+
return timestamp;
|
|
135
|
+
}
|
|
136
|
+
let hash = 2166136261;
|
|
137
|
+
for (let index = 0; index < requestId.length; index += 1) {
|
|
138
|
+
hash ^= requestId.charCodeAt(index);
|
|
139
|
+
hash = Math.imul(hash, 16777619);
|
|
140
|
+
}
|
|
141
|
+
return hash >>> 0;
|
|
142
|
+
}
|
|
143
|
+
export function createExtensionClientRequestId() {
|
|
144
|
+
const bytes = new Uint8Array(16);
|
|
145
|
+
const cryptoApi = globalThis.crypto;
|
|
146
|
+
if (!cryptoApi?.getRandomValues) {
|
|
147
|
+
throw new Error("Extension request id source is unavailable.");
|
|
148
|
+
}
|
|
149
|
+
cryptoApi.getRandomValues(bytes);
|
|
150
|
+
const timestamp = Date.now();
|
|
151
|
+
bytes[0] = Math.floor(timestamp / 0x10000000000) & 0xff;
|
|
152
|
+
bytes[1] = Math.floor(timestamp / 0x100000000) & 0xff;
|
|
153
|
+
bytes[2] = Math.floor(timestamp / 0x1000000) & 0xff;
|
|
154
|
+
bytes[3] = Math.floor(timestamp / 0x10000) & 0xff;
|
|
155
|
+
bytes[4] = Math.floor(timestamp / 0x100) & 0xff;
|
|
156
|
+
bytes[5] = timestamp & 0xff;
|
|
157
|
+
bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x70;
|
|
158
|
+
bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
|
|
159
|
+
return bytesToUuid(bytes);
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gea-ai/extensions-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.261016-alpha.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"homepage": "https://musegea.com/developers/extensions",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@gea-ai/contract": "0.1.
|
|
53
|
+
"@gea-ai/contract": "0.1.261016-alpha.0",
|
|
54
54
|
"@tanstack/react-query": "5.102.0",
|
|
55
55
|
"zod": "4.3.6"
|
|
56
56
|
},
|