@andco/sdk-svelte 0.0.3 → 0.0.5

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 CHANGED
@@ -187,7 +187,7 @@
187
187
  same "printed page" as the copyright notice for easier
188
188
  identification within third-party archives.
189
189
 
190
- Copyright 2026 Andco
190
+ Copyright 2026 Haulmer
191
191
 
192
192
  Licensed under the Apache License, Version 2.0 (the "License");
193
193
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # `@andco/sdk-svelte`
2
2
 
3
+ > [!WARNING]
4
+ > **Pre-1.0 software.** Until version 1.0.0 is released, any new version may include breaking
5
+ > changes, including minor and patch releases. Expect any API incompatibility and data resets
6
+ > in the Andco environments without prior notice. Pin an exact version and review the release
7
+ > notes before upgrading.
8
+
3
9
  Svelte 5 bindings for `@andco/sdk`, implemented with runes, callback props,
4
10
  and Svelte context.
5
11
 
@@ -22,10 +28,11 @@ that wants to hand the provider a session resolved ahead of time via `initialSes
22
28
  ```svelte
23
29
  <script lang="ts">
24
30
  import { AndcoProvider } from "@andco/sdk-svelte";
31
+ import type { AndcoBrowserOptions } from "@andco/sdk/browser";
25
32
 
26
33
  import App from "./App.svelte";
27
34
 
28
- const clientOptions = {
35
+ const clientOptions: AndcoBrowserOptions = {
29
36
  clientId: "client_123",
30
37
  endpointAuth: "https://auth.example.com",
31
38
  endpointApi: "https://api.example.com",
@@ -52,7 +59,7 @@ them.
52
59
  Svelte 5 callback props use lowercase DOM-style names. `AndcoButton` takes its client, endpoints,
53
60
  and theme from the `<AndcoProvider>` context, not from props — the only thing a per-render sign-in
54
61
  needs to pass is `authorization`, the same request-local options `auth.signIn` accepts (`scopes`,
55
- `redirectTo`, `orgId`, `authorizationDetails`, `resource`, …):
62
+ `redirectTo`, `authorizations`, `authorizationDetails`, `resource`, …):
56
63
 
57
64
  ```svelte
58
65
  <script lang="ts">
@@ -105,18 +112,32 @@ expiry, or to observe the unresolved-to-anonymous transition `useAndco`
105
112
  deliberately hides. `useAndcoUser().current` changes only when the person
106
113
  changes — identifier, name, email, or avatar.
107
114
 
108
- ## Progressive scopes
115
+ ## Progressive permissions
116
+
117
+ Scopes are OpenID Connect only; resource permissions are RARs built by the Resource Server's
118
+ authorization builder:
109
119
 
110
120
  ```svelte
111
- {#if session.current && !session.current.scopes.includes("bank_accounts:read")}
112
- <AndcoButton authorization={{ scopes: ["openid", "email", "profile", "bank_accounts:read"] }} />
121
+ <script lang="ts">
122
+ import { Bank } from "@andco/bank-sdk";
123
+ import { AndcoButton, useAndcoSession } from "@andco/sdk-svelte";
124
+
125
+ const session = useAndcoSession();
126
+ const bank = new Bank();
127
+ const accounts = bank.accounts.authorization({ actions: ["read"], subject: { type: "profile" } });
128
+ </script>
129
+
130
+ {#if session.current}
131
+ <AndcoButton authorization={{ authorizations: [accounts] }} />
113
132
  {/if}
114
133
  ```
115
134
 
116
135
  ## Intents
117
136
 
118
- Deposit and withdrawal creation live on the AndCo Bank Resource Server Definition, not on the
119
- core client's domain-neutral `intents`. Bind it to the signed-in identity once, then use it:
137
+ Deposit and transfer creation live on the Andco Bank Resource Server Definition, by domain
138
+ (`bank.deposits`, `bank.transfers`, `bank.accounts`), not on the core client's domain-neutral
139
+ `intents`. Build it from the signed-in identity's credentials — it takes the client id and endpoint
140
+ from them — then use it:
120
141
 
121
142
  ```svelte
122
143
  <script lang="ts">
@@ -124,8 +145,7 @@ core client's domain-neutral `intents`. Bind it to the signed-in identity once,
124
145
  import { Bank } from "@andco/bank-sdk";
125
146
 
126
147
  const andco = useAndcoAuthed();
127
- const bankDefinition = new Bank({ clientId: "client_123", resource: "https://api.andco.cl" });
128
- const bank = $derived(andco.current ? bankDefinition.use(andco.current) : null);
148
+ const bank = $derived(andco.current ? new Bank(andco.current) : null);
129
149
  </script>
130
150
  ```
131
151
 
@@ -133,16 +153,17 @@ core client's domain-neutral `intents`. Bind it to the signed-in identity once,
133
153
  <script lang="ts">
134
154
  async function topUp() {
135
155
  if (!bank) return;
136
- const { data: deposit, error } = await bank.intents.createDeposit(
156
+ const { data: deposit, error } = await bank.deposits.create(
137
157
  {
138
158
  expires_in: "30 minutes",
139
- amount: { currency: "CLP", suggested: "40000", editable: false },
140
- note: { suggested: "Wallet top-up", editable: true },
159
+ amount: { currency: "CLP", value: "40000", editable: false },
160
+ note: { value: "Wallet top-up", editable: true },
141
161
  metadata: { merchant_transaction_id: "deposit_8472" },
142
162
  },
143
163
  { idempotencyKey: "deposit:8472" },
144
164
  );
145
165
  if (error) throw error;
166
+ andco.current?.intents.present(deposit, { presentation: "popup" });
146
167
  }
147
168
  </script>
148
169
 
@@ -151,19 +172,21 @@ core client's domain-neutral `intents`. Bind it to the signed-in identity once,
151
172
 
152
173
  `AndcoIntentButton` renders the same hosted iframe as `AndcoButton`, with its `flow` fixed to
153
174
  `"intent"` — the Hosted Intent Presentation is the canonical way a person confirms an Intent, and
154
- its DOM and code stay Andco's, never the Project's. It takes the same props as `AndcoButton`
155
- (`authorization`, `busy`, `disabled`, `locale`, `release`, `themeMode`, and the `onready` /
156
- `onactivate` / `oncomplete` / `ondismiss` / `onerror` callbacks); there is currently no prop to
157
- target a specific, already-created Intent, and `oncomplete` takes no arguments — read the Intent's
158
- state with `bank.intents.get(intentId)` after it fires, or through `bank.intents.getEvents(intentId)`
159
- or the domain-specific listeners (`onIntentDepositEvent`, `onIntentWithdrawalEvent`), rather than
160
- expecting it on the callback.
175
+ its DOM and code stay Andco's, never the Project's. It takes the props of `AndcoButton` plus a
176
+ required `intentId`: an id, or a function that resolves it *after* the person activates the control,
177
+ so the window is already open when the Intent is created. `oncomplete` receives
178
+ `{ outcome, intent }`; only `intent`, read after the interaction, says what happened to the money.
161
179
 
162
180
  ```svelte
163
- <AndcoIntentButton onerror={(error) => console.error(error.message)} />
181
+ <AndcoIntentButton
182
+ intentId={async () => {
183
+ const response = await fetch("/api/create-transfer", { method: "POST" });
184
+ return (await response.json())["intent_id"];
185
+ }}
186
+ oncomplete={(result) => console.log(result.outcome, result.intent.status)}
187
+ onerror={(error) => console.error(error.message)}
188
+ />
164
189
  ```
165
190
 
166
- See [`apps/example-svelte`](../../apps/example-svelte/README.md) for the complete
191
+ See [`apps/example-svelte`](https://github.com/haulmer/andco-sdk-js/tree/main/apps/example-svelte) for the complete
167
192
  session → Grant → resource flow.
168
-
169
- There is no separate hosted-vs-direct pair of Intent buttons — `AndcoIntentButton` is the only one.
@@ -10,7 +10,10 @@
10
10
 
11
11
  type Options = AndcoButtonControllerOptions;
12
12
  type Props = Omit<HTMLIframeAttributes, "onerror" | "referrerpolicy" | "sandbox" | "src" | "srcdoc"> &
13
- Pick<Options, "authorization" | "busy" | "disabled" | "flow" | "intentId" | "locale" | "release" | "themeMode"> & {
13
+ Pick<
14
+ Options,
15
+ "authorization" | "busy" | "disabled" | "flow" | "intentId" | "locale" | "presentation" | "release" | "themeMode"
16
+ > & {
14
17
  onready?: Options["onReady"];
15
18
  onactivate?: Options["onActivate"];
16
19
  oncomplete?: Options["onComplete"];
@@ -25,6 +28,7 @@
25
28
  flow,
26
29
  intentId,
27
30
  locale,
31
+ presentation,
28
32
  release,
29
33
  themeMode,
30
34
  onready,
@@ -46,14 +50,15 @@
46
50
  $effect(() => {
47
51
  const next = new AndcoButtonController({
48
52
  client,
49
- ...(authorization ? { authorization } : {}),
50
- ...(flow ? { flow } : {}),
51
- ...(intentId ? { intentId } : {}),
52
- ...(release === undefined ? {} : { release }),
53
- ...(themeMode ? { themeMode } : {}),
54
- ...(locale ? { locale } : {}),
55
- ...(disabled === undefined ? {} : { disabled }),
56
- ...(busy === undefined ? {} : { busy }),
53
+ authorization,
54
+ flow,
55
+ intentId,
56
+ release,
57
+ themeMode,
58
+ locale,
59
+ presentation,
60
+ disabled,
61
+ busy,
57
62
  onReady: (event) => onready?.(event),
58
63
  onActivate: (event) => onactivate?.(event),
59
64
  onComplete: (outcome) => oncomplete?.(outcome),
@@ -81,19 +86,20 @@
81
86
  if (!iframe || !controller) return;
82
87
  return controller.connect(iframe);
83
88
  });
89
+
90
+ // The controller publishes the frame contract as data; this component only owns the element.
91
+ const defaultStyle = AndcoButtonController.frameStyleText(AndcoButtonController.IFRAME_DEFAULT_STYLE);
92
+ const frame = $derived(snapshot ? AndcoButtonController.frameProps(snapshot) : { src: undefined, hidden: true });
84
93
  </script>
85
94
 
86
- <!--
87
- A bare iframe is 300x150 with a border, and the hosted control fills whatever box it is given, so
88
- without a default the application sees a bordered grey slab. A `style` on the component wins.
89
- -->
95
+ <!-- A `style` on the component wins over the default set here. -->
90
96
  <iframe
91
- style="border:0;display:block;height:3rem;width:18rem"
97
+ style={defaultStyle}
92
98
  {...attributes}
93
99
  bind:this={iframe}
94
- hidden={!snapshot?.ready}
95
- referrerpolicy="no-referrer"
96
- sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
97
- src={snapshot?.iframeURL}
100
+ hidden={frame.hidden}
101
+ referrerpolicy={AndcoButtonController.IFRAME_REFERRER_POLICY}
102
+ sandbox={AndcoButtonController.IFRAME_SANDBOX}
103
+ src={frame.src}
98
104
  {title}
99
105
  ></iframe>
@@ -2,7 +2,7 @@ import type { AndcoError } from "@andco/sdk";
2
2
  import { type AndcoButtonControllerOptions } from "@andco/sdk/browser";
3
3
  import type { HTMLIframeAttributes } from "svelte/elements";
4
4
  type Options = AndcoButtonControllerOptions;
5
- type Props = Omit<HTMLIframeAttributes, "onerror" | "referrerpolicy" | "sandbox" | "src" | "srcdoc"> & Pick<Options, "authorization" | "busy" | "disabled" | "flow" | "intentId" | "locale" | "release" | "themeMode"> & {
5
+ type Props = Omit<HTMLIframeAttributes, "onerror" | "referrerpolicy" | "sandbox" | "src" | "srcdoc"> & Pick<Options, "authorization" | "busy" | "disabled" | "flow" | "intentId" | "locale" | "presentation" | "release" | "themeMode"> & {
6
6
  onready?: Options["onReady"];
7
7
  onactivate?: Options["onActivate"];
8
8
  oncomplete?: Options["onComplete"];
@@ -1,9 +1,11 @@
1
1
  import { type AndcoClient, type AndcoClientAuthed, type AndcoSession, type AndcoUser } from "@andco/sdk";
2
+ /** Stable provider client and a reactive getter for the current session. */
2
3
  export type AndcoSvelteContext = {
3
4
  /** The instance. One reference for the provider's lifetime — never `with(...)` or `authorized()`. */
4
5
  readonly client: AndcoClient;
5
6
  readonly session: AndcoSession | null | undefined;
6
7
  };
8
+ /** Registers the provider context during component initialization. */
7
9
  export declare function setAndcoContext(context: AndcoSvelteContext): void;
8
10
  /**
9
11
  * The raw context: the stable instance plus the raw session.
@@ -72,6 +74,33 @@ export declare function useAndcoAuthed(): {
72
74
  export declare function useAndcoSession(): {
73
75
  readonly current: AndcoSession | null | undefined;
74
76
  };
77
+ /**
78
+ * Instance Readiness: whether the store has finished its initial load.
79
+ *
80
+ * `false` until the load settles, then `true` for good. Not the same as being signed out — an
81
+ * instance that is not ready has not yet determined whether a session exists at all, which is
82
+ * exactly the `undefined` case {@link useAndcoSession} distinguishes from `null`. This rune is that
83
+ * same distinction read as a boolean, for the common case of gating a render on it rather than
84
+ * checking `undefined` directly.
85
+ *
86
+ * The load itself is already started by the provider's own subscription (`AndCoProvider.svelte`
87
+ * subscribes in its own `$effect`, before this rune ever runs), so reading this starts nothing new.
88
+ *
89
+ * @example
90
+ * ```svelte
91
+ * const ready = useAndcoReady();
92
+ * ```
93
+ * ```svelte
94
+ * {#if !ready.current}
95
+ * <Skeleton />
96
+ * {:else}
97
+ * {@render children()}
98
+ * {/if}
99
+ * ```
100
+ */
101
+ export declare function useAndcoReady(): {
102
+ readonly current: boolean;
103
+ };
75
104
  /**
76
105
  * The authenticated person, or `null`/`undefined` sharing {@link useAndcoSession}'s three states.
77
106
  *
@@ -1,6 +1,7 @@
1
1
  import { sessionIdentityKey, } from "@andco/sdk";
2
2
  import { createContext } from "svelte";
3
3
  const [get, set] = createContext();
4
+ /** Registers the provider context during component initialization. */
4
5
  export function setAndcoContext(context) {
5
6
  set(context);
6
7
  }
@@ -102,6 +103,39 @@ export function useAndcoSession() {
102
103
  },
103
104
  };
104
105
  }
106
+ /**
107
+ * Instance Readiness: whether the store has finished its initial load.
108
+ *
109
+ * `false` until the load settles, then `true` for good. Not the same as being signed out — an
110
+ * instance that is not ready has not yet determined whether a session exists at all, which is
111
+ * exactly the `undefined` case {@link useAndcoSession} distinguishes from `null`. This rune is that
112
+ * same distinction read as a boolean, for the common case of gating a render on it rather than
113
+ * checking `undefined` directly.
114
+ *
115
+ * The load itself is already started by the provider's own subscription (`AndCoProvider.svelte`
116
+ * subscribes in its own `$effect`, before this rune ever runs), so reading this starts nothing new.
117
+ *
118
+ * @example
119
+ * ```svelte
120
+ * const ready = useAndcoReady();
121
+ * ```
122
+ * ```svelte
123
+ * {#if !ready.current}
124
+ * <Skeleton />
125
+ * {:else}
126
+ * {@render children()}
127
+ * {/if}
128
+ * ```
129
+ */
130
+ export function useAndcoReady() {
131
+ const value = andcoContext();
132
+ const ready = $derived(value.session !== undefined);
133
+ return {
134
+ get current() {
135
+ return ready;
136
+ },
137
+ };
138
+ }
105
139
  /**
106
140
  * The authenticated person, or `null`/`undefined` sharing {@link useAndcoSession}'s three states.
107
141
  *
@@ -130,7 +164,10 @@ export function useAndcoUser() {
130
164
  cached.id !== next.id ||
131
165
  cached.name !== next.name ||
132
166
  cached.email !== next.email ||
133
- cached.avatarUrl !== next.avatarUrl) {
167
+ cached.avatarUrl !== next.avatarUrl ||
168
+ cached.emailVerified !== next.emailVerified ||
169
+ cached.phone !== next.phone ||
170
+ cached.phoneVerified !== next.phoneVerified) {
134
171
  cached = next;
135
172
  }
136
173
  return cached;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { default as AndcoButton } from "./AndCoButton.svelte";
2
2
  export { default as AndcoIntentButton } from "./AndCoIntentButton.svelte";
3
3
  export { default as AndcoProvider } from "./AndCoProvider.svelte";
4
- export { type AndcoSvelteContext, setAndcoContext, useAndco, useAndcoAuthed, useAndcoSession, useAndcoUser, } from "./context.svelte.js";
4
+ export { type AndcoSvelteContext, setAndcoContext, useAndco, useAndcoAuthed, useAndcoReady, useAndcoSession, useAndcoUser, } from "./context.svelte.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { default as AndcoButton } from "./AndCoButton.svelte";
2
2
  export { default as AndcoIntentButton } from "./AndCoIntentButton.svelte";
3
3
  export { default as AndcoProvider } from "./AndCoProvider.svelte";
4
- export { setAndcoContext, useAndco, useAndcoAuthed, useAndcoSession, useAndcoUser, } from "./context.svelte.js";
4
+ export { setAndcoContext, useAndco, useAndcoAuthed, useAndcoReady, useAndcoSession, useAndcoUser, } from "./context.svelte.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andco/sdk-svelte",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,7 +23,7 @@
23
23
  "svelte": "^5.0.0"
24
24
  },
25
25
  "dependencies": {
26
- "@andco/sdk": "0.0.3"
26
+ "@andco/sdk": "0.0.5"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@sveltejs/package": "^2.0.0",
@@ -36,6 +36,16 @@
36
36
  },
37
37
  "license": "Apache-2.0",
38
38
  "description": "Svelte bindings for the Andco SDK.",
39
+ "author": "Patricio Lopez Juri <patricio.lopez@haulmer.com>",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/haulmer/andco-sdk-js.git",
43
+ "directory": "packages/sdk-svelte"
44
+ },
45
+ "homepage": "https://github.com/haulmer/andco-sdk-js#readme",
46
+ "bugs": {
47
+ "url": "https://github.com/haulmer/andco-sdk-js/issues"
48
+ },
39
49
  "scripts": {
40
50
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
41
51
  "build": "pnpm run clean && svelte-package",