@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 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
- await sdk.databases.appData.transaction(async (tx) => {
146
- const idea = { id: crypto.randomUUID(), title: input.title };
147
- await tx.insert(schema.ideas).values(idea);
148
- return idea;
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
- Normal Drizzle selects, writes, batches, and interactive transactions execute
154
- against the host-owned database. The host revalidates the installed extension,
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, batches, and
271
- transactions against SQLite resources declared by this extension. No path,
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>) => Promise<ExtensionActionOutput<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) => await callHostMethod("extension.call", {
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]: SqliteRemoteDatabase<TSchemas[TDatabase]>;
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 function createExtensionBackendDatabase<TSchema extends Record<string, unknown> = Record<string, never>>(transport: ExtensionBackendDatabaseTransport): SqliteRemoteDatabase<TSchema>;
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 function createExtensionBackendDatabase(transport) {
3
- return drizzle(transport.execute, transport.batch);
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.261014-alpha.0",
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.261014-alpha.0",
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
  },