@mycreat-labs/integrations-sdk 0.0.0-stage → 0.1.1

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +54 -2
  3. package/dist/adapters/bullmq.cjs +110 -0
  4. package/dist/adapters/bullmq.cjs.map +1 -0
  5. package/dist/adapters/bullmq.d.cts +11 -0
  6. package/dist/adapters/bullmq.d.ts +11 -0
  7. package/dist/adapters/bullmq.js +85 -0
  8. package/dist/adapters/bullmq.js.map +1 -0
  9. package/dist/adapters/postgres.cjs +396 -0
  10. package/dist/adapters/postgres.cjs.map +1 -0
  11. package/dist/adapters/postgres.d.cts +51 -0
  12. package/dist/adapters/postgres.d.ts +51 -0
  13. package/dist/adapters/postgres.js +368 -0
  14. package/dist/adapters/postgres.js.map +1 -0
  15. package/dist/adapters/prisma.cjs +414 -0
  16. package/dist/adapters/prisma.cjs.map +1 -0
  17. package/dist/adapters/prisma.d.cts +15 -0
  18. package/dist/adapters/prisma.d.ts +15 -0
  19. package/dist/adapters/prisma.js +386 -0
  20. package/dist/adapters/prisma.js.map +1 -0
  21. package/dist/adapters/redis.cjs +125 -0
  22. package/dist/adapters/redis.cjs.map +1 -0
  23. package/dist/adapters/redis.d.cts +20 -0
  24. package/dist/adapters/redis.d.ts +20 -0
  25. package/dist/adapters/redis.js +98 -0
  26. package/dist/adapters/redis.js.map +1 -0
  27. package/dist/errors-3hDEtzuZ.d.cts +11 -0
  28. package/dist/errors-3hDEtzuZ.d.ts +11 -0
  29. package/dist/index.cjs +1328 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +128 -0
  32. package/dist/index.d.ts +128 -0
  33. package/dist/index.js +1297 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/providers/index.cjs +1287 -0
  36. package/dist/providers/index.cjs.map +1 -0
  37. package/dist/providers/index.d.cts +144 -0
  38. package/dist/providers/index.d.ts +144 -0
  39. package/dist/providers/index.js +1252 -0
  40. package/dist/providers/index.js.map +1 -0
  41. package/dist/testing.cjs +275 -0
  42. package/dist/testing.cjs.map +1 -0
  43. package/dist/testing.d.cts +20 -0
  44. package/dist/testing.d.ts +20 -0
  45. package/dist/testing.js +248 -0
  46. package/dist/testing.js.map +1 -0
  47. package/dist/types-Y4-DoJ1A.d.cts +260 -0
  48. package/dist/types-Y4-DoJ1A.d.ts +260 -0
  49. package/docs/migration.md +23 -0
  50. package/docs/releases/0.1.0.md +46 -0
  51. package/docs/security.md +26 -0
  52. package/docs/verification.md +22 -0
  53. package/examples/embedded-host.ts +62 -0
  54. package/migrations/0001_initial.sql +40 -0
  55. package/package.json +91 -4
  56. package/schema.prisma +80 -0
@@ -0,0 +1,260 @@
1
+ type Json = null | boolean | number | string | Json[] | {
2
+ [key: string]: Json;
3
+ };
4
+ type Purpose = "identity" | "connect";
5
+ type Scope = {
6
+ namespace: string;
7
+ ownerId: string;
8
+ };
9
+ type VerifiedIdentity = {
10
+ provider: string;
11
+ issuer: string;
12
+ subject: string;
13
+ identityNamespace: string;
14
+ email?: string;
15
+ emailVerified: boolean;
16
+ name?: string;
17
+ username?: string;
18
+ };
19
+ type CredentialMaterial = {
20
+ accessToken: string;
21
+ refreshToken?: string;
22
+ expiresAt?: number;
23
+ scopes?: string[];
24
+ extra?: {
25
+ [key: string]: Json;
26
+ };
27
+ };
28
+ type RemoteAccount = {
29
+ id: string;
30
+ label: string;
31
+ metadata?: {
32
+ [key: string]: Json;
33
+ };
34
+ };
35
+ type Resource = {
36
+ id: string;
37
+ type: string;
38
+ name: string;
39
+ url?: string;
40
+ metadata?: {
41
+ [key: string]: Json;
42
+ };
43
+ };
44
+ type ResourcePage = {
45
+ items: Resource[];
46
+ nextCursor?: string;
47
+ };
48
+ type ProviderContext = {
49
+ credentials: CredentialMaterial;
50
+ signal?: AbortSignal;
51
+ fetch: typeof globalThis.fetch;
52
+ };
53
+ type ActionContext = ProviderContext & {
54
+ operationId: string;
55
+ checkpoint: Json;
56
+ saveCheckpoint(value: Json): Promise<void>;
57
+ };
58
+ type ActionDefinition = {
59
+ validate(input: unknown): Json;
60
+ execute(input: Json, context: ActionContext): Promise<Json>;
61
+ reconcile?(input: Json, context: ActionContext): Promise<{
62
+ resolved: true;
63
+ result: Json;
64
+ } | {
65
+ resolved: false;
66
+ }>;
67
+ };
68
+ type AuthorizationInput = {
69
+ state: string;
70
+ verifier: string;
71
+ challenge: string;
72
+ nonce: string;
73
+ purpose: Purpose;
74
+ };
75
+ type AuthorizationResult = {
76
+ identity?: VerifiedIdentity;
77
+ account?: RemoteAccount;
78
+ credentials?: CredentialMaterial;
79
+ };
80
+ interface IntegrationProvider {
81
+ key: string;
82
+ registrationId: string;
83
+ purposes: readonly Purpose[];
84
+ authorization?: {
85
+ url(input: AuthorizationInput): string;
86
+ exchange(input: {
87
+ code: string;
88
+ verifier: string;
89
+ nonce: string;
90
+ purpose: Purpose;
91
+ signal?: AbortSignal;
92
+ }, fetch: typeof globalThis.fetch): Promise<AuthorizationResult>;
93
+ };
94
+ verifyNative?(input: {
95
+ idToken: string;
96
+ nonce: string;
97
+ authorizationCode?: string;
98
+ }, fetch: typeof globalThis.fetch): Promise<VerifiedIdentity>;
99
+ resources?(input: {
100
+ cursor?: string;
101
+ query?: string;
102
+ parentId?: string;
103
+ }, context: ProviderContext): Promise<ResourcePage>;
104
+ refresh?(credentials: CredentialMaterial, context: {
105
+ fetch: typeof globalThis.fetch;
106
+ signal?: AbortSignal;
107
+ }): Promise<CredentialMaterial>;
108
+ revoke?(credentials: CredentialMaterial, context: {
109
+ fetch: typeof globalThis.fetch;
110
+ signal?: AbortSignal;
111
+ }): Promise<void>;
112
+ actions?: Readonly<Record<string, ActionDefinition>>;
113
+ }
114
+ type SealedValue = {
115
+ keyId: string;
116
+ value: string;
117
+ };
118
+ interface TokenVault {
119
+ seal(value: Json, context: string): Promise<SealedValue>;
120
+ open(value: SealedValue, context: string): Promise<Json>;
121
+ }
122
+ type Flow = {
123
+ id: string;
124
+ namespace: string;
125
+ ownerId: string;
126
+ provider: string;
127
+ registrationId: string;
128
+ purpose: Purpose;
129
+ bindingHash: string;
130
+ expiresAt: number;
131
+ secret: SealedValue;
132
+ authorizationEpoch?: number;
133
+ connectionId?: string;
134
+ generation?: number;
135
+ };
136
+ type Grant = {
137
+ id: string;
138
+ namespace: string;
139
+ ownerId: string;
140
+ provider: string;
141
+ registrationId: string;
142
+ account: RemoteAccount;
143
+ version: number;
144
+ status: "active" | "reauth_required" | "revoked";
145
+ credentials: SealedValue;
146
+ expiresAt?: number;
147
+ refreshLease?: string;
148
+ refreshUntil?: number;
149
+ authorizationLease?: string;
150
+ authorizationUntil?: number;
151
+ };
152
+ type Connection = {
153
+ id: string;
154
+ namespace: string;
155
+ ownerId: string;
156
+ provider: string;
157
+ registrationId: string;
158
+ grantId: string;
159
+ account: RemoteAccount;
160
+ generation: number;
161
+ status: "active" | "reauth_required" | "revoked";
162
+ createdAt: number;
163
+ updatedAt: number;
164
+ };
165
+ type OperationStatus = "pending" | "running" | "succeeded" | "failed" | "blocked" | "outcome_unknown" | "cancelled";
166
+ type Operation = {
167
+ id: string;
168
+ namespace: string;
169
+ ownerId: string;
170
+ connectionId: string;
171
+ generation: number;
172
+ action: string;
173
+ idempotencyKey: string;
174
+ fingerprint: string;
175
+ input: SealedValue;
176
+ hostContext?: Json;
177
+ status: OperationStatus;
178
+ version: number;
179
+ attempts: number;
180
+ authRecoveries?: number;
181
+ availableAt: number;
182
+ checkpoint: Json;
183
+ dispatching: boolean;
184
+ leaseToken?: string;
185
+ leaseUntil?: number;
186
+ result?: Json;
187
+ errorCode?: string;
188
+ createdAt: number;
189
+ updatedAt: number;
190
+ };
191
+ type IntegrationEvent = {
192
+ id: string;
193
+ namespace: string;
194
+ ownerId: string;
195
+ operationId: string;
196
+ type: string;
197
+ createdAt: number;
198
+ data: Json;
199
+ };
200
+ interface IntegrationTransaction {
201
+ readonly nativeContext?: unknown;
202
+ lockScope(scope: Scope, allowErased?: boolean): Promise<boolean>;
203
+ authorizationEpoch(scope: Scope, key: string, advance?: boolean): Promise<number>;
204
+ markOwnerErased(scope: Scope): Promise<void>;
205
+ assertAccountOwner(scope: Scope, provider: string, registrationId: string, accountId: string): Promise<void>;
206
+ collectGarbage(namespace: string, now: number, retentionMs: number): Promise<void>;
207
+ insertFlow(flow: Flow): Promise<void>;
208
+ consumeFlow(namespace: string, id: string, bindingHash: string, purpose: Purpose, now: number): Promise<Flow | null>;
209
+ insertGrant(grant: Grant): Promise<void>;
210
+ getGrant(scope: Scope, id: string, lock?: boolean): Promise<Grant | null>;
211
+ saveGrant(grant: Grant, expectedVersion: number): Promise<boolean>;
212
+ insertConnection(connection: Connection): Promise<void>;
213
+ getConnection(scope: Scope, id: string, lock?: boolean): Promise<Connection | null>;
214
+ listConnections(scope: Scope): Promise<Connection[]>;
215
+ saveConnection(connection: Connection, expectedGeneration: number): Promise<boolean>;
216
+ insertOperation(operation: Operation): Promise<Operation>;
217
+ getOperation(scope: Scope, id: string, lock?: boolean): Promise<Operation | null>;
218
+ listOperations(scope: Scope, connectionId?: string): Promise<Operation[]>;
219
+ hasPendingRevocation(scope: Scope, connectionId: string): Promise<boolean>;
220
+ saveOperation(operation: Operation, expectedVersion: number, leaseToken?: string): Promise<boolean>;
221
+ claimOperation(namespace: string, id: string, version: number, now: number, leaseToken: string, leaseUntil: number): Promise<Operation | null>;
222
+ dueOperations(namespace: string, now: number, limit: number): Promise<Array<{
223
+ id: string;
224
+ version: number;
225
+ }>>;
226
+ recoverOperations(namespace: string, now: number): Promise<number>;
227
+ insertEvent(event: IntegrationEvent): Promise<void>;
228
+ listEvents(scope: Scope, after?: string): Promise<IntegrationEvent[]>;
229
+ eraseOwner(scope: Scope, preserveRevocations: boolean): Promise<void>;
230
+ }
231
+ interface IntegrationStore {
232
+ transaction<T>(work: (tx: IntegrationTransaction) => Promise<T>): Promise<T>;
233
+ assertCompatible(): Promise<void>;
234
+ }
235
+ type TaskMessage = {
236
+ taskId: string;
237
+ version: number;
238
+ };
239
+ interface QueueTransport {
240
+ publish(messages: TaskMessage[]): Promise<void>;
241
+ start(handler: (message: TaskMessage) => Promise<void>): Promise<void>;
242
+ stop(): Promise<void>;
243
+ }
244
+ type SdkLogger = {
245
+ info?(fields: Record<string, string | number | boolean>, message: string): void;
246
+ warn?(fields: Record<string, string | number | boolean>, message: string): void;
247
+ error?(fields: Record<string, string | number | boolean>, message: string): void;
248
+ };
249
+ interface ApiRateLimiter {
250
+ take(key: string): Promise<{
251
+ allowed: boolean;
252
+ retryAfterMs: number;
253
+ }>;
254
+ }
255
+ type HostHooks = {
256
+ beforeExecute?(context: unknown, operation: Operation, input: Json): Promise<void>;
257
+ onResult?(context: unknown, operation: Operation, event: IntegrationEvent, input: Json): Promise<void>;
258
+ };
259
+
260
+ export type { ApiRateLimiter as A, Connection as C, Flow as F, Grant as G, HostHooks as H, IntegrationStore as I, Json as J, Operation as O, Purpose as P, QueueTransport as Q, ResourcePage as R, Scope as S, TokenVault as T, VerifiedIdentity as V, IntegrationTransaction as a, IntegrationEvent as b, TaskMessage as c, IntegrationProvider as d, SdkLogger as e, ActionContext as f, ActionDefinition as g, AuthorizationInput as h, AuthorizationResult as i, CredentialMaterial as j, OperationStatus as k, ProviderContext as l, RemoteAccount as m, Resource as n, SealedValue as o };
@@ -0,0 +1,23 @@
1
+ # Installation, migration and release ownership
2
+
3
+ ## New product
4
+
5
+ The product owns the generated Prisma schema, migrations and deployment. Copy the SDK fragment and apply its SQL using the product migration runner. Keep SDK table names and constraints unchanged. At startup `assertCompatible` accepts storage schema 1; it never creates or upgrades tables. Supply the existing Prisma client, not a separate pool; bind native transactions for atomic business/operation writes. Optional peer adapters are installed by the product, and only selected provider factories are registered.
6
+
7
+ API and Worker use the same pinned package and schema version within a product. An operation's dispatch version fences stale queue messages. Public contract, operation format and storage compatibility are separate concerns: additive package fixes do not authorize destructive DDL. Review migrations and retained operation compatibility before rolling upgrades. Rollback must preserve grant versions, leases, authorization fences and uncertain writes. Never reset state or downgrade by deleting SDK tables.
8
+
9
+ ## Existing Subtic
10
+
11
+ The initial adoption delegates Notion and Raindrop protocol/HTTP handling to the packed SDK through legacy error adapters. Subtic's API shapes, database IDs, ciphertext/AAD, grants, refresh owner/lease, exports, subscriptions, tasks and checkpoints remain authoritative. There is no second SDK scheduler or refresher for these grants. Notion's configured API version and existing timeout policies remain pinned by its host. Safe-error classification must preserve partial writes and distinguish invalid HTTP-200 receipts from definite no-write errors.
12
+
13
+ Subsequent storage/runtime migration is per provider and per connection: quiesce old execution, preserve an explicit ownership fence, translate durable state with a verified mapping, verify decryptability and in-flight task semantics, then activate the new owner. Unknown writes are reconciled, not re-exported. Shared upstream installations keep reference counting and last-reference revocation. Gmail Watch/history and old callbacks are separate migration work; Gmail, Readwise and Instapaper remain on their current host implementations in this initial release.
14
+
15
+ ## Existing Kiipu
16
+
17
+ The initial host installs the packed SDK and adds new SDK storage alongside its account/content tables. Existing password/Apple login, sessions and Desktop/extension handoffs keep their product ownership. New Google/GitHub web login is enabled only by complete product-local configuration; external identity columns are additive. New Notion/Raindrop connections do not import Subtic credentials.
18
+
19
+ The Kiipu staged migrations include the initial SDK schema plus owner-fence and unique external-account constraints. These were applied only to a dedicated local test database during implementation. Production migration and configuration remain a separate release step. The Worker requires the matching SDK package and product keyring; do not remove keys while encrypted records or durable cleanup still require them.
20
+
21
+ ## Package release
22
+
23
+ Release immutable npm artifacts with semantic versions into an internal registry after separate publication authorization. Keep exact package versions, lockfile integrity and a source-to-artifact record. Local implementation uses a checked vendor tarball until a registry is available. Existing released versions must not be overwritten; replace development test artifacts only before first publication. Products upgrade independently and run their own consumer contracts before deployment.
@@ -0,0 +1,46 @@
1
+ # 0.1.0 release — 2026-10-31
2
+
3
+ Package: `@mycreat-labs/integrations-sdk@0.1.0`.
4
+ Registry: `https://registry.npmjs.org/`. Access: public, explicitly approved by the owner.
5
+ The repository `.npmrc` records this registry without credentials.
6
+ Release source: `6e16f40120dcecfacaaeaf20e5537e93a842556b`, tag `npm-v0.1.0`.
7
+
8
+ ```sh
9
+ npm install --save-exact @mycreat-labs/integrations-sdk@0.1.0
10
+ ```
11
+
12
+ The anonymously downloaded npm archive matches both products' pinned vendor
13
+ archives byte for byte:
14
+
15
+ SHA-256: `94b4bff090d8322c7c610c4fbfe5a702a2e093d096bcdfba8b53825c27f3fc10`.
16
+ The npm integrity and source inventory are recorded in `release-manifest.json`.
17
+ Published archives must not be replaced in place.
18
+
19
+ ## Verification
20
+
21
+ - SDK: 77 unit tests, 6 isolated PostgreSQL tests and 2 packed ESM/CJS tests passed.
22
+ - A separate host installed the anonymously downloaded distribution and passed
23
+ strict TypeScript, real Prisma transaction rollback, authorization, durable
24
+ execution and transactional result projection checks.
25
+ - Kiipu: 792 current-source unit tests, application typechecks and production builds passed.
26
+ - Subtic: 69 relevant provider/runtime/probe unit tests passed; connector typechecks,
27
+ changed-file lint and production builds passed. Nine database tests in the focused
28
+ unit command were deliberately skipped; earlier isolated database acceptance is
29
+ documented in the consumer repository.
30
+
31
+ ## Production consumers
32
+
33
+ | Product | Deployed code | Result |
34
+ | --- | --- | --- |
35
+ | Kiipu | `f6cee97fdab537e6bfbaef0d3d29d1e9eefd43b5` | API, Worker and Web healthy; five new migrations applied |
36
+ | Subtic | `9380a609b77e38cb93b627b6a581578626ff29ea` | API, Web, primary/media/search workers and all five connector workers healthy |
37
+
38
+ Production backups and the products' deployment/rollback scripts were used.
39
+ Public login pages and their static assets passed HTTP checks. Kiipu remained
40
+ ready through the shared gateway after Subtic's deployment.
41
+ The five database/Redis containers retained their IDs and start times.
42
+
43
+ Kiipu's independent OAuth registrations and integration encryption configuration
44
+ are not yet present in production. Its third-party capabilities remain disabled
45
+ until the product supplies these settings. Real-provider consent, remote writes,
46
+ revocation and native-device acceptance were not performed in this release.
@@ -0,0 +1,26 @@
1
+ # Security model and provider behavior
2
+
3
+ The SDK executes with host privileges. It does not sandbox untrusted plugins. Provider implementations and infrastructure adapters are reviewed code; owners, connection IDs and operations must always originate from authenticated product routes. Cross-product reuse is code reuse, not shared users, cookies, OAuth clients or secrets.
4
+
5
+ Secrets are AES-256-GCM encrypted using a versioned key ID and AAD bound to namespace, owner, registration and grant/operation purpose. Wrong-context ciphertext is rejected. Preserve old keys for read compatibility during rotation; never silently fall back to another context or key. API responses should project connection/operation status rather than exposing internal sealed payloads or database records.
6
+
7
+ Authorization uses one-time state, browser binding, PKCE/nonce where the provider supports them, fixed callback configuration, issuer/audience validation and native server challenges. Google and Apple JWT verification uses JOSE and bounded JWKS caching. GitHub requires verified primary email and discards its login access token after resolving identity. Login and connection purposes never interchange.
8
+
9
+ Refresh owns a persistent versioned lease. An unknown rotating-token refresh is fenced for reauthorization. A definite HTTP-429 or local admission rejection clears only its lease and remains retryable. Notion does not guarantee an expires_in field; a confirmed no-write 401 can force one coordinated refresh, with a persistent per-operation recovery budget. An unknown write is never retried merely because authentication was refreshed.
10
+
11
+ An authorization callback reserves existing same-provider grants across exchange, preventing a rotating refresh and reauthorization from committing conflicting upstream credentials. A busy refresh rejects exchange before sending the authorization code. An abandoned exchange reservation is treated conservatively as requiring fresh authorization.
12
+
13
+ Notion authorization identity includes workspace, bot and authorizing user. From the [2026-06-08 change](https://developers.notion.com/page/changelog#june-8-2026), newly created Public Connections issue a new token pair per OAuth authorization, while existing connections retain previous token behavior. The SDK stores the returned pair and does not assume every authorization is either shared or independent. Pagination cursors are bounded opaque strings, not parsed UUIDs.
14
+
15
+ Deletion persists a tombstone before cleanup, and authorization epochs prevent old in-flight callbacks from reviving connections. Workers check lease, owner/connection state and generation around external execution. Cancellation cannot retract a request already sent to the upstream service; results remain fenced locally and durable cleanup is retained as necessary.
16
+
17
+ Provider documentation reviewed during implementation:
18
+
19
+ - [Google OpenID Connect](https://developers.google.com/identity/openid-connect/openid-connect)
20
+ - [Apple verification](https://developer.apple.com/documentation/signinwithapple/verifying-a-user) and [token exchange](https://developer.apple.com/documentation/signinwithapplerestapi/generate-and-validate-tokens)
21
+ - [GitHub OAuth](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps) and [email API](https://docs.github.com/en/rest/users/emails)
22
+ - [Notion authorization](https://developers.notion.com/guides/get-started/authorization), [refresh](https://developers.notion.com/reference/refresh-a-token), [revocation](https://developers.notion.com/reference/revoke-token)
23
+ - [Raindrop authorization](https://developer.raindrop.io/v1/authentication/token), [collections](https://developer.raindrop.io/v1/collections/methods), [bookmarks](https://developer.raindrop.io/v1/raindrops/single)
24
+ - [OAuth security BCP](https://www.rfc-editor.org/rfc/rfc9700.html) and [native applications](https://www.rfc-editor.org/rfc/rfc8252.html)
25
+
26
+ Provider versions are configurable and product-owned. Changing a default in the SDK does not change a product's deliberately pinned protocol version.
@@ -0,0 +1,22 @@
1
+ # Local verification — 2026-10-31
2
+
3
+ This is implementation acceptance, not a production release or real-provider acceptance.
4
+
5
+ ## SDK
6
+
7
+ - TypeScript strict typecheck passed.
8
+ - 77 unit/protocol/state-machine tests passed on Node.js 24.14.0 and 22.12.0.
9
+ - Six PostgreSQL integration tests passed on a dedicated local `integration_sdk_test` database: atomic callback consumption, host transaction rollback, concurrent idempotency/claim, crash recovery, revocation pagination fences and atomic result projection.
10
+ - Two packed ESM/CommonJS tests passed using the real npm tarball, including provider/core errors across independently bundled entries.
11
+ - An independent product fixture installed the tarball, generated its own Prisma 6.13 client and passed real-Prisma authorization, transaction rollback, durable execution and result projection. Its provider responses are simulated; no real third-party grant was created.
12
+ - The package contains compiled entries, declarations, schema/migration, examples and documentation. Runtime dependencies do not include either product or their source checkouts.
13
+
14
+ Commands: `pnpm check`; `npx --yes --package=node@22.12.0 -- node node_modules/vitest/vitest.mjs run`; `SDK_TEST_DATABASE_URL=... pnpm test:integration`; `SDK_PACKAGE_ARCHIVE=... pnpm exec vitest run test/providers.package.test.ts`; `SDK_TEST_DATABASE_URL=... node scripts/verify-package.mjs`.
15
+
16
+ ## Product consumers
17
+
18
+ Subtic retains its existing grant, refresh, task and subscription state; only the Notion/Raindrop provider protocol path consumes the SDK in this initial adoption. Its initial verification passed 51 provider/API unit contracts and 27 isolated-database tests, connector typechecks/lint and API/Worker builds. See its `docs/account-integrations/sdk-adoption.md` for the final consumer artifact record.
19
+
20
+ Kiipu adds the new embedded runtime, separate product-local configuration, provider login adapters and Notion/Raindrop UI/API/Worker paths. Its initial verification passed the current-source unit suite, application typechecks/builds, isolated Prisma/Redis host contracts and fixture-backed browser interactions. See its `docs/integrations-sdk.md` for the final consumer commands and counts.
21
+
22
+ All database changes made during acceptance were restricted to isolated local test databases; no existing development or production database was migrated. No Git commit/push, npm publication or deployment was performed. Real OAuth console configuration, consent, remote writes/revocation behavior and native-device acceptance remain release work.
@@ -0,0 +1,62 @@
1
+ import {
2
+ createIntegrationSdk,
3
+ createTokenVault,
4
+ createRuntime,
5
+ type HostHooks,
6
+ } from "@mycreat-labs/integrations-sdk";
7
+ import {
8
+ createNotionProvider,
9
+ createRaindropProvider,
10
+ } from "@mycreat-labs/integrations-sdk/providers";
11
+ import {
12
+ createPrismaStore,
13
+ bindPrismaTransaction,
14
+ type PrismaClientLike,
15
+ type PrismaTransactionLike,
16
+ } from "@mycreat-labs/integrations-sdk/prisma";
17
+ import { createBullMqTransport } from "@mycreat-labs/integrations-sdk/bullmq";
18
+ import { createRedisRateLimiter } from "@mycreat-labs/integrations-sdk/redis";
19
+ import type Redis from "ioredis";
20
+
21
+ // The application supplies its own configuration, infrastructure and business checks.
22
+ export function embeddedHost(
23
+ database: PrismaClientLike,
24
+ redis: Redis,
25
+ config: {
26
+ namespace: string;
27
+ keyId: string;
28
+ keys: Record<string, string>;
29
+ notion: Parameters<typeof createNotionProvider>[0];
30
+ raindrop: Parameters<typeof createRaindropProvider>[0];
31
+ },
32
+ hooks: HostHooks,
33
+ ) {
34
+ const sdk = createIntegrationSdk({
35
+ namespace: config.namespace,
36
+ store: createPrismaStore(database),
37
+ vault: createTokenVault({ activeKeyId: config.keyId, keys: config.keys }),
38
+ providers: [
39
+ createNotionProvider(config.notion),
40
+ createRaindropProvider(config.raindrop),
41
+ ],
42
+ hooks,
43
+ rateLimiter: createRedisRateLimiter(redis, {
44
+ prefix: config.namespace,
45
+ limit: 3,
46
+ windowMs: 1000,
47
+ }),
48
+ });
49
+ const runtime = createRuntime({
50
+ sdk,
51
+ queue: createBullMqTransport({
52
+ connection: redis,
53
+ prefix: config.namespace,
54
+ }),
55
+ });
56
+ return {
57
+ sdk,
58
+ runtime,
59
+ transaction: (tx: PrismaTransactionLike) =>
60
+ sdk.withStore(bindPrismaTransaction(tx)),
61
+ };
62
+ }
@@ -0,0 +1,40 @@
1
+ CREATE TABLE integration_sdk_meta (id INTEGER PRIMARY KEY, version INTEGER NOT NULL);
2
+ INSERT INTO integration_sdk_meta (id,version) VALUES (1,1);
3
+ CREATE TABLE integration_sdk_owners (
4
+ namespace TEXT NOT NULL, owner_id TEXT NOT NULL, erased BOOLEAN NOT NULL DEFAULT false,
5
+ epochs JSONB NOT NULL DEFAULT '{}',
6
+ CONSTRAINT integration_sdk_owners_pkey PRIMARY KEY (namespace,owner_id)
7
+ );
8
+ CREATE TABLE integration_sdk_flows (
9
+ namespace TEXT NOT NULL, id TEXT NOT NULL, owner_id TEXT NOT NULL, purpose TEXT NOT NULL,
10
+ binding_hash TEXT NOT NULL, expires_at BIGINT NOT NULL, data JSONB NOT NULL,
11
+ CONSTRAINT integration_sdk_flows_pkey PRIMARY KEY (namespace,id)
12
+ );
13
+ CREATE INDEX integration_sdk_flows_expiry_idx ON integration_sdk_flows (expires_at);
14
+ CREATE TABLE integration_sdk_grants (
15
+ namespace TEXT NOT NULL, id TEXT NOT NULL, owner_id TEXT NOT NULL, version INTEGER NOT NULL, data JSONB NOT NULL,
16
+ CONSTRAINT integration_sdk_grants_pkey PRIMARY KEY (namespace,id)
17
+ );
18
+ CREATE INDEX integration_sdk_grants_owner_idx ON integration_sdk_grants (namespace,owner_id);
19
+ CREATE TABLE integration_sdk_connections (
20
+ namespace TEXT NOT NULL, id TEXT NOT NULL, owner_id TEXT NOT NULL, provider TEXT NOT NULL,
21
+ registration_id TEXT NOT NULL, account_id TEXT NOT NULL, generation INTEGER NOT NULL, data JSONB NOT NULL,
22
+ CONSTRAINT integration_sdk_connections_pkey PRIMARY KEY (namespace,id),
23
+ CONSTRAINT integration_sdk_connections_account_key UNIQUE (namespace,owner_id,provider,registration_id,account_id)
24
+ );
25
+ CREATE UNIQUE INDEX integration_sdk_connections_account_owner_key ON integration_sdk_connections (namespace,provider,registration_id,account_id);
26
+ CREATE TABLE integration_sdk_operations (
27
+ namespace TEXT NOT NULL, id TEXT NOT NULL, owner_id TEXT NOT NULL, connection_id TEXT NOT NULL,
28
+ action TEXT NOT NULL, idempotency_key TEXT NOT NULL, version INTEGER NOT NULL, status TEXT NOT NULL,
29
+ available_at BIGINT NOT NULL, lease_until BIGINT, data JSONB NOT NULL,
30
+ CONSTRAINT integration_sdk_operations_pkey PRIMARY KEY (namespace,id),
31
+ CONSTRAINT integration_sdk_operations_idempotency_key UNIQUE (namespace,owner_id,action,idempotency_key)
32
+ );
33
+ CREATE INDEX integration_sdk_operations_due_idx ON integration_sdk_operations (namespace,status,available_at);
34
+ CREATE INDEX integration_sdk_operations_owner_idx ON integration_sdk_operations (namespace,owner_id);
35
+ CREATE TABLE integration_sdk_events (
36
+ namespace TEXT NOT NULL, id TEXT NOT NULL, owner_id TEXT NOT NULL, operation_id TEXT NOT NULL,
37
+ created_at BIGINT NOT NULL, data JSONB NOT NULL,
38
+ CONSTRAINT integration_sdk_events_pkey PRIMARY KEY (namespace,id)
39
+ );
40
+ CREATE INDEX integration_sdk_events_owner_idx ON integration_sdk_events (namespace,owner_id,created_at,id);
package/package.json CHANGED
@@ -1,6 +1,93 @@
1
1
  {
2
2
  "name": "@mycreat-labs/integrations-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Product-embedded third-party identity and integration runtime",
5
+ "license": "UNLICENSED",
6
+ "publishConfig": {
7
+ "access": "public",
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "type": "module",
11
+ "files": [
12
+ "dist",
13
+ "migrations",
14
+ "schema.prisma",
15
+ "README.md",
16
+ "CHANGELOG.md",
17
+ "docs",
18
+ "examples"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22.12.0"
22
+ },
23
+ "packageManager": "pnpm@10.14.0",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "require": "./dist/index.cjs"
29
+ },
30
+ "./providers": {
31
+ "types": "./dist/providers/index.d.ts",
32
+ "import": "./dist/providers/index.js",
33
+ "require": "./dist/providers/index.cjs"
34
+ },
35
+ "./postgres": {
36
+ "types": "./dist/adapters/postgres.d.ts",
37
+ "import": "./dist/adapters/postgres.js",
38
+ "require": "./dist/adapters/postgres.cjs"
39
+ },
40
+ "./prisma": {
41
+ "types": "./dist/adapters/prisma.d.ts",
42
+ "import": "./dist/adapters/prisma.js",
43
+ "require": "./dist/adapters/prisma.cjs"
44
+ },
45
+ "./redis": {
46
+ "types": "./dist/adapters/redis.d.ts",
47
+ "import": "./dist/adapters/redis.js",
48
+ "require": "./dist/adapters/redis.cjs"
49
+ },
50
+ "./bullmq": {
51
+ "types": "./dist/adapters/bullmq.d.ts",
52
+ "import": "./dist/adapters/bullmq.js",
53
+ "require": "./dist/adapters/bullmq.cjs"
54
+ },
55
+ "./testing": {
56
+ "types": "./dist/testing.d.ts",
57
+ "import": "./dist/testing.js",
58
+ "require": "./dist/testing.cjs"
59
+ }
60
+ },
61
+ "scripts": {
62
+ "build": "tsup",
63
+ "typecheck": "tsc --noEmit",
64
+ "test": "vitest run",
65
+ "test:integration": "vitest run --config vitest.integration.config.ts",
66
+ "check": "pnpm typecheck && pnpm test && pnpm build"
67
+ },
68
+ "dependencies": {
69
+ "jose": "^6.1.0"
70
+ },
71
+ "peerDependencies": {
72
+ "bullmq": ">=5.58.0 <6",
73
+ "ioredis": ">=5.7.0 <6"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "bullmq": {
77
+ "optional": true
78
+ },
79
+ "ioredis": {
80
+ "optional": true
81
+ }
82
+ },
83
+ "devDependencies": {
84
+ "@types/node": "^24.0.0",
85
+ "bullmq": "^5.58.0",
86
+ "ioredis": "^5.7.0",
87
+ "pg": "^8.16.3",
88
+ "@types/pg": "^8.15.5",
89
+ "tsup": "^8.5.0",
90
+ "typescript": "^5.9.2",
91
+ "vitest": "^3.2.4"
92
+ }
93
+ }
package/schema.prisma ADDED
@@ -0,0 +1,80 @@
1
+ // Copy these models into the host schema and apply migrations/0001_initial.sql through
2
+ // the host's migration pipeline. SDK SQL does not import generated Prisma models.
3
+ model IntegrationSdkMeta {
4
+ id Int @id
5
+ version Int
6
+ @@map("integration_sdk_meta")
7
+ }
8
+ model IntegrationSdkOwner {
9
+ namespace String
10
+ ownerId String @map("owner_id")
11
+ erased Boolean @default(false)
12
+ epochs Json @default("{}")
13
+ @@id([namespace,ownerId], map: "integration_sdk_owners_pkey")
14
+ @@map("integration_sdk_owners")
15
+ }
16
+ model IntegrationSdkFlow {
17
+ namespace String
18
+ id String
19
+ ownerId String @map("owner_id")
20
+ purpose String
21
+ bindingHash String @map("binding_hash")
22
+ expiresAt BigInt @map("expires_at")
23
+ data Json
24
+ @@id([namespace,id], map: "integration_sdk_flows_pkey")
25
+ @@index([expiresAt], map: "integration_sdk_flows_expiry_idx")
26
+ @@map("integration_sdk_flows")
27
+ }
28
+ model IntegrationSdkGrant {
29
+ namespace String
30
+ id String
31
+ ownerId String @map("owner_id")
32
+ version Int
33
+ data Json
34
+ @@id([namespace,id], map: "integration_sdk_grants_pkey")
35
+ @@index([namespace,ownerId], map: "integration_sdk_grants_owner_idx")
36
+ @@map("integration_sdk_grants")
37
+ }
38
+ model IntegrationSdkConnection {
39
+ namespace String
40
+ id String
41
+ ownerId String @map("owner_id")
42
+ provider String
43
+ registrationId String @map("registration_id")
44
+ accountId String @map("account_id")
45
+ generation Int
46
+ data Json
47
+ @@id([namespace,id], map: "integration_sdk_connections_pkey")
48
+ @@unique([namespace,ownerId,provider,registrationId,accountId], map: "integration_sdk_connections_account_key")
49
+ @@unique([namespace,provider,registrationId,accountId], map: "integration_sdk_connections_account_owner_key")
50
+ @@map("integration_sdk_connections")
51
+ }
52
+ model IntegrationSdkOperation {
53
+ namespace String
54
+ id String
55
+ ownerId String @map("owner_id")
56
+ connectionId String @map("connection_id")
57
+ action String
58
+ idempotencyKey String @map("idempotency_key")
59
+ version Int
60
+ status String
61
+ availableAt BigInt @map("available_at")
62
+ leaseUntil BigInt? @map("lease_until")
63
+ data Json
64
+ @@id([namespace,id], map: "integration_sdk_operations_pkey")
65
+ @@unique([namespace,ownerId,action,idempotencyKey], map: "integration_sdk_operations_idempotency_key")
66
+ @@index([namespace,status,availableAt], map: "integration_sdk_operations_due_idx")
67
+ @@index([namespace,ownerId], map: "integration_sdk_operations_owner_idx")
68
+ @@map("integration_sdk_operations")
69
+ }
70
+ model IntegrationSdkEvent {
71
+ namespace String
72
+ id String
73
+ ownerId String @map("owner_id")
74
+ operationId String @map("operation_id")
75
+ createdAt BigInt @map("created_at")
76
+ data Json
77
+ @@id([namespace,id], map: "integration_sdk_events_pkey")
78
+ @@index([namespace,ownerId,createdAt,id], map: "integration_sdk_events_owner_idx")
79
+ @@map("integration_sdk_events")
80
+ }