@mekari-officeless/sdk 0.1.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 ADDED
@@ -0,0 +1,194 @@
1
+ # @officeless/sdk
2
+
3
+ Official JavaScript SDK for frontend apps (React, Vue, vanilla JS) to interact with Officeless as a serverless backend via `api_v2` workflow triggers.
4
+
5
+ Zero dependencies. ESM-native. Works in any modern browser or Node.js 18+.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @officeless/sdk
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Initialization
18
+
19
+ There are two ways to initialize the client.
20
+
21
+ ### Mode A: URL Map (recommended after AI porting)
22
+
23
+ When you have an `officeless.config.json` in your project root (auto-generated by the AI Porting Agent), import and pass it directly:
24
+
25
+ ```js
26
+ import { OfficelessClient } from '@officeless/sdk'
27
+ import config from './officeless.config.json' assert { type: 'json' }
28
+
29
+ const client = OfficelessClient.fromConfig(config)
30
+ ```
31
+
32
+ The config file provides explicit webhook URLs for every workflow, so no guessing is needed.
33
+
34
+ ### Mode B: Base URL + convention
35
+
36
+ If you don't have a config file, initialize with just a base URL. Workflow names are mapped to `{baseUrl}/wh/{workflowName}` automatically.
37
+
38
+ ```js
39
+ import { OfficelessClient } from '@officeless/sdk'
40
+
41
+ const client = new OfficelessClient({
42
+ baseUrl: 'https://api.officeless.io',
43
+ apiKey: 'olk_live_xxxx', // optional
44
+ projectId: '2BfWnyXYk5mz', // optional
45
+ })
46
+ ```
47
+
48
+ Constructor options:
49
+
50
+ | Option | Type | Required | Description |
51
+ |--------|------|----------|-------------|
52
+ | `baseUrl` | `string` | yes | Officeless API base URL |
53
+ | `apiKey` | `string` | no | Sent as `X-Officeless-Key` header |
54
+ | `projectId` | `string` | no | Your Officeless project ID |
55
+ | `workflows` | `object` | no | Explicit map of workflow name → `{ url, method }` |
56
+
57
+ ---
58
+
59
+ ## `OfficelessClient.fromConfig(config)`
60
+
61
+ Loads client settings from a plain object or a parsed `officeless.config.json`.
62
+
63
+ ```js
64
+ const client = OfficelessClient.fromConfig({
65
+ officeless: {
66
+ project_id: '2BfWnyXYk5mz',
67
+ base_url: 'https://api.officeless.io',
68
+ api_key: 'olk_live_xxxx',
69
+ workflows: {
70
+ 'employee-list': { url: '/app/wh/abc123', method: 'post' },
71
+ 'employee-create': { url: '/app/wh/def456', method: 'post' },
72
+ },
73
+ },
74
+ })
75
+ ```
76
+
77
+ Both snake_case (`base_url`, `api_key`, `project_id`) and camelCase (`baseUrl`, `apiKey`, `projectId`) keys are accepted.
78
+
79
+ ---
80
+
81
+ ## Triggering Workflows
82
+
83
+ Use `client.workflow(name)` to get a `WorkflowRef`, then call `.trigger(payload)`.
84
+
85
+ ```js
86
+ const result = await client.workflow('send-welcome-email').trigger({
87
+ userId: 42,
88
+ email: 'user@example.com',
89
+ })
90
+
91
+ console.log(result) // data returned by the workflow
92
+ ```
93
+
94
+ The workflow name must match the key in `officeless.config.json` or the path segment in `{baseUrl}/wh/{name}`.
95
+
96
+ ---
97
+
98
+ ## Table CRUD
99
+
100
+ Use `client.table(name)` to get a `TableRef` for a named table. Each method calls a corresponding workflow: `{tableName}-list`, `{tableName}-create`, `{tableName}-update`, `{tableName}-delete`.
101
+
102
+ ### `list(filters?)`
103
+
104
+ ```js
105
+ const employees = await client.table('employee').list({ department: 'engineering' })
106
+ // calls workflow: employee-list with payload: { filters: { department: 'engineering' } }
107
+ ```
108
+
109
+ ### `create(data)`
110
+
111
+ ```js
112
+ const newEmployee = await client.table('employee').create({
113
+ name: 'Jane Doe',
114
+ role: 'Engineer',
115
+ })
116
+ // calls workflow: employee-create with payload: { data: { name: 'Jane Doe', role: 'Engineer' } }
117
+ ```
118
+
119
+ ### `update(id, data)`
120
+
121
+ ```js
122
+ const updated = await client.table('employee').update('emp_001', { role: 'Senior Engineer' })
123
+ // calls workflow: employee-update with payload: { id: 'emp_001', data: { role: 'Senior Engineer' } }
124
+ ```
125
+
126
+ ### `delete(id)`
127
+
128
+ ```js
129
+ await client.table('employee').delete('emp_001')
130
+ // calls workflow: employee-delete with payload: { id: 'emp_001' }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Error Handling
136
+
137
+ All errors thrown by the SDK are instances of `OfficelessError`.
138
+
139
+ ```js
140
+ import { OfficelessClient, OfficelessError } from '@officeless/sdk'
141
+
142
+ try {
143
+ const data = await client.table('employee').list()
144
+ } catch (err) {
145
+ if (err instanceof OfficelessError) {
146
+ console.error(err.message) // human-readable message
147
+ console.error(err.statusCode) // HTTP status code (e.g. 404, 500)
148
+ console.error(err.response) // raw parsed response body, if available
149
+ } else {
150
+ throw err // re-throw unexpected errors
151
+ }
152
+ }
153
+ ```
154
+
155
+ `OfficelessError` extends the native `Error` class, so standard `instanceof` checks and stack traces work as expected.
156
+
157
+ ---
158
+
159
+ ## `officeless.config.json` Format
160
+
161
+ This file is auto-generated by the AI Porting Agent and placed in the frontend project root. It maps each workflow to its webhook URL.
162
+
163
+ ```json
164
+ {
165
+ "officeless": {
166
+ "project_id": "2BfWnyXYk5mz",
167
+ "base_url": "https://api.officeless.io",
168
+ "api_key": "olk_live_xxxx",
169
+ "workflows": {
170
+ "employee-list": { "url": "/app/wh/abc123", "method": "post" },
171
+ "employee-create": { "url": "/app/wh/def456", "method": "post" },
172
+ "employee-update": { "url": "/app/wh/ghi789", "method": "post" },
173
+ "employee-delete": { "url": "/app/wh/jkl012", "method": "post" }
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ Fields:
180
+
181
+ | Field | Description |
182
+ |-------|-------------|
183
+ | `project_id` | Unique Officeless project identifier |
184
+ | `base_url` | Base API URL for your Officeless instance |
185
+ | `api_key` | Secret key for authenticating requests |
186
+ | `workflows` | Map of workflow name → `{ url, method }`. `url` may be relative (resolved against `base_url`) or absolute. |
187
+
188
+ Commit this file to your repository, but treat `api_key` as a secret — consider loading it from an environment variable in production and excluding the file from version control, or use a build-time substitution step.
189
+
190
+ ---
191
+
192
+ ## License
193
+
194
+ MIT
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@mekari-officeless/sdk",
3
+ "private": false,
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "version": "0.1.0",
8
+ "description": "Official Officeless SDK for JS frontends",
9
+ "type": "module",
10
+ "main": "src/index.js",
11
+ "exports": {
12
+ ".": "./src/index.js"
13
+ },
14
+ "scripts": {
15
+ "build": "echo 'build placeholder'"
16
+ },
17
+ "keywords": ["officeless", "sdk", "nocode"],
18
+ "license": "MIT"
19
+ }
package/src/client.js ADDED
@@ -0,0 +1,84 @@
1
+ import { WorkflowRef } from './workflow.js'
2
+ import { TableRef } from './table.js'
3
+ import { OfficelessError } from './errors.js'
4
+
5
+ export class OfficelessClient {
6
+ /**
7
+ * @param {object} config
8
+ * @param {string} config.baseUrl - Officeless API base URL
9
+ * @param {string} [config.apiKey] - optional API key (sent as X-Officeless-Key header)
10
+ * @param {string} [config.projectId]
11
+ * @param {object} [config.workflows] - map of workflowName → { url, method }
12
+ * e.g. { 'employee-list': { url: '/app/wh/abc123', method: 'post' } }
13
+ * If provided, used directly. If not, uses baseUrl + /wh/ + name convention.
14
+ */
15
+ constructor(config = {}) {
16
+ if (!config.baseUrl) throw new OfficelessError('baseUrl is required')
17
+ this._baseUrl = config.baseUrl.replace(/\/$/, '')
18
+ this._apiKey = config.apiKey || null
19
+ this._projectId = config.projectId || null
20
+ this._workflowMap = config.workflows || {}
21
+ }
22
+
23
+ /**
24
+ * Load config from a plain object (e.g. imported officeless.config.json)
25
+ */
26
+ static fromConfig(config) {
27
+ const c = config?.officeless || config
28
+ return new OfficelessClient({
29
+ baseUrl: c.base_url || c.baseUrl,
30
+ apiKey: c.api_key || c.apiKey,
31
+ projectId: c.project_id || c.projectId,
32
+ workflows: c.workflows || {},
33
+ })
34
+ }
35
+
36
+ /** Returns a WorkflowRef for the named workflow */
37
+ workflow(name) {
38
+ return new WorkflowRef(this, name)
39
+ }
40
+
41
+ /** Returns a TableRef for the named table */
42
+ table(name) {
43
+ return new TableRef(this, name)
44
+ }
45
+
46
+ /** Internal: resolve workflow URL and POST to it */
47
+ async _callWorkflow(name, payload = {}) {
48
+ const entry = this._workflowMap[name]
49
+ let url
50
+ if (entry && entry.url) {
51
+ // URL map mode: url might be relative or absolute
52
+ url = entry.url.startsWith('http') ? entry.url : this._baseUrl + entry.url
53
+ } else {
54
+ // Convention mode: baseUrl/wh/workflow-name
55
+ url = `${this._baseUrl}/wh/${name}`
56
+ }
57
+
58
+ const headers = { 'Content-Type': 'application/json' }
59
+ if (this._apiKey) headers['X-Officeless-Key'] = this._apiKey
60
+
61
+ const res = await fetch(url, {
62
+ method: 'POST',
63
+ headers,
64
+ body: JSON.stringify(payload),
65
+ })
66
+
67
+ let body
68
+ try {
69
+ body = await res.json()
70
+ } catch {
71
+ throw new OfficelessError(`Failed to parse response from ${name}`, res.status)
72
+ }
73
+
74
+ if (!res.ok || body?.error === true) {
75
+ throw new OfficelessError(
76
+ body?.message || `Request to ${name} failed`,
77
+ res.status,
78
+ body
79
+ )
80
+ }
81
+
82
+ return body?.data !== undefined ? body.data : body
83
+ }
84
+ }
package/src/errors.js ADDED
@@ -0,0 +1,8 @@
1
+ export class OfficelessError extends Error {
2
+ constructor(message, statusCode, response) {
3
+ super(message)
4
+ this.name = 'OfficelessError'
5
+ this.statusCode = statusCode
6
+ this.response = response
7
+ }
8
+ }
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { OfficelessClient } from './client.js'
2
+ export { OfficelessError } from './errors.js'
package/src/table.js ADDED
@@ -0,0 +1,22 @@
1
+ export class TableRef {
2
+ constructor(client, tableName) {
3
+ this._client = client
4
+ this._tableName = tableName
5
+ }
6
+
7
+ async list(filters = {}) {
8
+ return this._client._callWorkflow(`${this._tableName}-list`, { filters })
9
+ }
10
+
11
+ async create(data) {
12
+ return this._client._callWorkflow(`${this._tableName}-create`, { data })
13
+ }
14
+
15
+ async update(id, data) {
16
+ return this._client._callWorkflow(`${this._tableName}-update`, { id, data })
17
+ }
18
+
19
+ async delete(id) {
20
+ return this._client._callWorkflow(`${this._tableName}-delete`, { id })
21
+ }
22
+ }
@@ -0,0 +1,10 @@
1
+ export class WorkflowRef {
2
+ constructor(client, name) {
3
+ this._client = client
4
+ this._name = name
5
+ }
6
+
7
+ async trigger(payload = {}) {
8
+ return this._client._callWorkflow(this._name, payload)
9
+ }
10
+ }