@supatype/plugin-sdk 0.1.0-alpha.9
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/dist/__tests__/plugin-integration.test.d.ts +2 -0
- package/dist/__tests__/plugin-integration.test.d.ts.map +1 -0
- package/dist/__tests__/plugin-integration.test.js +426 -0
- package/dist/__tests__/plugin-integration.test.js.map +1 -0
- package/dist/define.d.ts +121 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +165 -0
- package/dist/define.js.map +1 -0
- package/dist/docgen.d.ts +14 -0
- package/dist/docgen.d.ts.map +1 -0
- package/dist/docgen.js +135 -0
- package/dist/docgen.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +122 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +282 -0
- package/dist/loader.js.map +1 -0
- package/dist/types.d.ts +341 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +28 -0
- package/src/__tests__/plugin-integration.test.ts +536 -0
- package/src/define.ts +202 -0
- package/src/docgen.ts +172 -0
- package/src/index.ts +58 -0
- package/src/loader.ts +401 -0
- package/src/types.ts +343 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/define.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder functions for defining Supatype plugins.
|
|
3
|
+
*
|
|
4
|
+
* These are the primary API surface for plugin authors:
|
|
5
|
+
* defineFieldType() — custom field types (e.g., phone, currency)
|
|
6
|
+
* defineComposite() — field bundles (e.g., SEO, address)
|
|
7
|
+
* defineProvider() — service providers (e.g., Stripe, PostHog)
|
|
8
|
+
* defineWidget() — admin panel widgets (e.g., color picker)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
PLUGIN_API_VERSION,
|
|
13
|
+
type FieldTypeDefinition,
|
|
14
|
+
type CompositeDefinition,
|
|
15
|
+
type ProviderDefinition,
|
|
16
|
+
type WidgetDefinition,
|
|
17
|
+
type PluginMeta,
|
|
18
|
+
} from "./types.js"
|
|
19
|
+
|
|
20
|
+
// ─── defineFieldType ─────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Define a custom field type plugin.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { defineFieldType } from '@supatype/plugin-sdk'
|
|
28
|
+
*
|
|
29
|
+
* export default defineFieldType({
|
|
30
|
+
* name: 'phone',
|
|
31
|
+
* pgType: 'TEXT',
|
|
32
|
+
* tsType: 'string',
|
|
33
|
+
* validate(value) {
|
|
34
|
+
* if (typeof value !== 'string') return 'Must be a string'
|
|
35
|
+
* if (!/^\+\d{7,15}$/.test(value)) return 'Invalid phone number (E.164 format required)'
|
|
36
|
+
* return null
|
|
37
|
+
* },
|
|
38
|
+
* widgetPath: './src/PhoneWidget.tsx',
|
|
39
|
+
* filterOperators: ['eq', 'neq', 'in', 'like'],
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function defineFieldType<TValue = unknown>(
|
|
44
|
+
definition: FieldTypeDefinition<TValue>,
|
|
45
|
+
): FieldTypeDefinition<TValue> & { __supatype: "field" } {
|
|
46
|
+
return {
|
|
47
|
+
...definition,
|
|
48
|
+
meta: {
|
|
49
|
+
name: definition.name,
|
|
50
|
+
description: `Custom field type: ${definition.name}`,
|
|
51
|
+
types: ["field"],
|
|
52
|
+
pluginApi: PLUGIN_API_VERSION,
|
|
53
|
+
...definition.meta,
|
|
54
|
+
},
|
|
55
|
+
__supatype: "field" as const,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── defineComposite ─────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Define a composite plugin (field bundle).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { defineComposite } from '@supatype/plugin-sdk'
|
|
67
|
+
*
|
|
68
|
+
* export default defineComposite({
|
|
69
|
+
* name: 'seo',
|
|
70
|
+
* label: 'SEO Meta',
|
|
71
|
+
* fields: [
|
|
72
|
+
* { name: 'meta_title', type: 'text', options: { maxLength: 60 } },
|
|
73
|
+
* { name: 'meta_description', type: 'text', options: { maxLength: 160 } },
|
|
74
|
+
* { name: 'og_image', type: 'text' },
|
|
75
|
+
* { name: 'canonical_url', type: 'text' },
|
|
76
|
+
* { name: 'no_index', type: 'boolean', defaultValue: false },
|
|
77
|
+
* ],
|
|
78
|
+
* adminGroup: { collapsible: true, defaultCollapsed: true },
|
|
79
|
+
* })
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function defineComposite(
|
|
83
|
+
definition: CompositeDefinition,
|
|
84
|
+
): CompositeDefinition & { __supatype: "composite" } {
|
|
85
|
+
return {
|
|
86
|
+
...definition,
|
|
87
|
+
meta: {
|
|
88
|
+
name: definition.name,
|
|
89
|
+
description: `Composite: ${definition.label}`,
|
|
90
|
+
types: ["composite"],
|
|
91
|
+
pluginApi: PLUGIN_API_VERSION,
|
|
92
|
+
...definition.meta,
|
|
93
|
+
},
|
|
94
|
+
__supatype: "composite" as const,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── defineProvider ──────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Define a service provider plugin.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { defineProvider, type CommerceProvider } from '@supatype/plugin-sdk'
|
|
106
|
+
*
|
|
107
|
+
* export default defineProvider<StripeConfig>({
|
|
108
|
+
* name: 'stripe',
|
|
109
|
+
* category: 'commerce',
|
|
110
|
+
* label: 'Stripe',
|
|
111
|
+
* configSchema: {
|
|
112
|
+
* secretKey: { type: 'string', label: 'Secret Key', required: true, secret: true },
|
|
113
|
+
* webhookSecret: { type: 'string', label: 'Webhook Secret', required: true, secret: true },
|
|
114
|
+
* },
|
|
115
|
+
* create(config): CommerceProvider {
|
|
116
|
+
* return new StripeCommerceProvider(config)
|
|
117
|
+
* },
|
|
118
|
+
* })
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export function defineProvider<TConfig = Record<string, unknown>>(
|
|
122
|
+
definition: ProviderDefinition<TConfig>,
|
|
123
|
+
): ProviderDefinition<TConfig> & { __supatype: "provider" } {
|
|
124
|
+
return {
|
|
125
|
+
...definition,
|
|
126
|
+
meta: {
|
|
127
|
+
name: definition.name,
|
|
128
|
+
description: `${definition.category} provider: ${definition.label}`,
|
|
129
|
+
types: ["provider"],
|
|
130
|
+
pluginApi: PLUGIN_API_VERSION,
|
|
131
|
+
...definition.meta,
|
|
132
|
+
},
|
|
133
|
+
__supatype: "provider" as const,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── defineWidget ────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Define a standalone widget plugin.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* import { defineWidget } from '@supatype/plugin-sdk'
|
|
145
|
+
*
|
|
146
|
+
* export default defineWidget({
|
|
147
|
+
* name: 'color-picker',
|
|
148
|
+
* label: 'Colour Picker',
|
|
149
|
+
* compatibleTypes: ['text', 'varchar'],
|
|
150
|
+
* componentPath: './src/ColorPickerWidget.tsx',
|
|
151
|
+
* })
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export function defineWidget(
|
|
155
|
+
definition: WidgetDefinition,
|
|
156
|
+
): WidgetDefinition & { __supatype: "widget" } {
|
|
157
|
+
return {
|
|
158
|
+
...definition,
|
|
159
|
+
meta: {
|
|
160
|
+
name: definition.name,
|
|
161
|
+
description: `Widget: ${definition.label}`,
|
|
162
|
+
types: ["widget"],
|
|
163
|
+
pluginApi: PLUGIN_API_VERSION,
|
|
164
|
+
...definition.meta,
|
|
165
|
+
},
|
|
166
|
+
__supatype: "widget" as const,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Plugin definition union ─────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
export type AnyPluginDefinition =
|
|
173
|
+
| (FieldTypeDefinition & { __supatype: "field" })
|
|
174
|
+
| (CompositeDefinition & { __supatype: "composite" })
|
|
175
|
+
| (ProviderDefinition & { __supatype: "provider" })
|
|
176
|
+
| (WidgetDefinition & { __supatype: "widget" })
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if a value is a Supatype plugin definition.
|
|
180
|
+
*/
|
|
181
|
+
export function isPluginDefinition(value: unknown): value is AnyPluginDefinition {
|
|
182
|
+
return typeof value === "object" && value !== null && "__supatype" in value
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Validate plugin API version compatibility.
|
|
187
|
+
*/
|
|
188
|
+
export function checkPluginApiVersion(meta: PluginMeta | undefined): {
|
|
189
|
+
compatible: boolean
|
|
190
|
+
message?: string | undefined
|
|
191
|
+
} {
|
|
192
|
+
if (!meta) return { compatible: true }
|
|
193
|
+
|
|
194
|
+
if (meta.pluginApi !== PLUGIN_API_VERSION) {
|
|
195
|
+
return {
|
|
196
|
+
compatible: false,
|
|
197
|
+
message: `Plugin "${meta.name}" targets plugin API v${meta.pluginApi}, but the current version is v${PLUGIN_API_VERSION}. The plugin may not work correctly.`,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { compatible: true }
|
|
202
|
+
}
|
package/src/docgen.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin documentation generator.
|
|
3
|
+
*
|
|
4
|
+
* Generates Markdown documentation from a plugin's type definitions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
FieldTypeDefinition,
|
|
9
|
+
CompositeDefinition,
|
|
10
|
+
ProviderDefinition,
|
|
11
|
+
WidgetDefinition,
|
|
12
|
+
} from "./types.js"
|
|
13
|
+
import type { AnyPluginDefinition } from "./define.js"
|
|
14
|
+
|
|
15
|
+
function escapeMarkdown(text: string): string {
|
|
16
|
+
return text.replace(/[|\\`*_{}[\]()#+\-.!]/g, "\\$&")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function generateFieldDocs(def: FieldTypeDefinition & { __supatype: "field" }): string {
|
|
20
|
+
const lines: string[] = []
|
|
21
|
+
|
|
22
|
+
lines.push("## Field Type")
|
|
23
|
+
lines.push("")
|
|
24
|
+
lines.push(`| Property | Value |`)
|
|
25
|
+
lines.push(`| --- | --- |`)
|
|
26
|
+
lines.push(`| Name | \`${def.name}\` |`)
|
|
27
|
+
lines.push(`| Postgres Type | \`${def.pgType}\` |`)
|
|
28
|
+
lines.push(`| TypeScript Type | \`${def.tsType}\` |`)
|
|
29
|
+
|
|
30
|
+
if (def.validate !== undefined) {
|
|
31
|
+
lines.push(`| Validate | \`${def.validate.toString().slice(0, 80)}\` |`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (def.filterOperators !== undefined) {
|
|
35
|
+
lines.push(`| Filter Operators | ${def.filterOperators.map(o => `\`${o}\``).join(", ")} |`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (def.widgetPath !== undefined) {
|
|
39
|
+
lines.push(`| Widget Path | \`${def.widgetPath}\` |`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
lines.push("")
|
|
43
|
+
|
|
44
|
+
if (def.constraints !== undefined && def.constraints.length > 0) {
|
|
45
|
+
lines.push("### Constraints")
|
|
46
|
+
lines.push("")
|
|
47
|
+
for (const c of def.constraints) {
|
|
48
|
+
lines.push(`- \`${c}\``)
|
|
49
|
+
}
|
|
50
|
+
lines.push("")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return lines.join("\n")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function generateCompositeDocs(def: CompositeDefinition & { __supatype: "composite" }): string {
|
|
57
|
+
const lines: string[] = []
|
|
58
|
+
|
|
59
|
+
lines.push("## Composite")
|
|
60
|
+
lines.push("")
|
|
61
|
+
lines.push(`**Label:** ${escapeMarkdown(def.label)}`)
|
|
62
|
+
lines.push("")
|
|
63
|
+
lines.push("### Fields")
|
|
64
|
+
lines.push("")
|
|
65
|
+
lines.push(`| Name | Type | Required | Default |`)
|
|
66
|
+
lines.push(`| --- | --- | --- | --- |`)
|
|
67
|
+
|
|
68
|
+
for (const field of def.fields) {
|
|
69
|
+
const req = field.required === true ? "Yes" : "No"
|
|
70
|
+
const defaultVal = field.defaultValue !== undefined ? `\`${String(field.defaultValue)}\`` : "-"
|
|
71
|
+
lines.push(`| ${field.name} | \`${field.type}\` | ${req} | ${defaultVal} |`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lines.push("")
|
|
75
|
+
|
|
76
|
+
if (def.adminGroup !== undefined) {
|
|
77
|
+
lines.push("### Admin Group")
|
|
78
|
+
lines.push("")
|
|
79
|
+
if (def.adminGroup.collapsible !== undefined) {
|
|
80
|
+
lines.push(`- Collapsible: ${def.adminGroup.collapsible}`)
|
|
81
|
+
}
|
|
82
|
+
if (def.adminGroup.defaultCollapsed !== undefined) {
|
|
83
|
+
lines.push(`- Default Collapsed: ${def.adminGroup.defaultCollapsed}`)
|
|
84
|
+
}
|
|
85
|
+
lines.push("")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return lines.join("\n")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function generateProviderDocs(def: ProviderDefinition & { __supatype: "provider" }): string {
|
|
92
|
+
const lines: string[] = []
|
|
93
|
+
|
|
94
|
+
lines.push("## Provider")
|
|
95
|
+
lines.push("")
|
|
96
|
+
lines.push(`**Category:** ${def.category}`)
|
|
97
|
+
lines.push("")
|
|
98
|
+
|
|
99
|
+
if (def.configSchema && Object.keys(def.configSchema).length > 0) {
|
|
100
|
+
lines.push("### Config Schema")
|
|
101
|
+
lines.push("")
|
|
102
|
+
lines.push(`| Field | Type | Label | Required | Secret |`)
|
|
103
|
+
lines.push(`| --- | --- | --- | --- | --- |`)
|
|
104
|
+
|
|
105
|
+
for (const [key, schema] of Object.entries(def.configSchema)) {
|
|
106
|
+
const req = schema.required === true ? "Yes" : "No"
|
|
107
|
+
const secret = schema.secret === true ? "Yes" : "No"
|
|
108
|
+
lines.push(`| ${key} | \`${schema.type}\` | ${escapeMarkdown(schema.label)} | ${req} | ${secret} |`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
lines.push("")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return lines.join("\n")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function generateWidgetDocs(def: WidgetDefinition & { __supatype: "widget" }): string {
|
|
118
|
+
const lines: string[] = []
|
|
119
|
+
|
|
120
|
+
lines.push("## Widget")
|
|
121
|
+
lines.push("")
|
|
122
|
+
lines.push(`| Property | Value |`)
|
|
123
|
+
lines.push(`| --- | --- |`)
|
|
124
|
+
lines.push(`| Name | \`${def.name}\` |`)
|
|
125
|
+
lines.push(`| Label | ${escapeMarkdown(def.label)} |`)
|
|
126
|
+
lines.push(`| Compatible Types | ${def.compatibleTypes.map(t => `\`${t}\``).join(", ")} |`)
|
|
127
|
+
lines.push(`| Component Path | \`${def.componentPath}\` |`)
|
|
128
|
+
lines.push("")
|
|
129
|
+
|
|
130
|
+
return lines.join("\n")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Generate Markdown documentation from a plugin definition.
|
|
135
|
+
*
|
|
136
|
+
* The output includes YAML frontmatter with the plugin name, type, and version,
|
|
137
|
+
* followed by type-specific documentation sections.
|
|
138
|
+
*/
|
|
139
|
+
export function generatePluginDocs(definition: AnyPluginDefinition): string {
|
|
140
|
+
const name = (definition as { name: string }).name
|
|
141
|
+
const type = definition.__supatype
|
|
142
|
+
const version = definition.meta?.pluginApi ?? 1
|
|
143
|
+
|
|
144
|
+
const frontmatter = [
|
|
145
|
+
"---",
|
|
146
|
+
`name: "${name}"`,
|
|
147
|
+
`type: "${type}"`,
|
|
148
|
+
`version: ${version}`,
|
|
149
|
+
"---",
|
|
150
|
+
].join("\n")
|
|
151
|
+
|
|
152
|
+
const header = `# ${name}\n`
|
|
153
|
+
|
|
154
|
+
let body: string
|
|
155
|
+
|
|
156
|
+
switch (definition.__supatype) {
|
|
157
|
+
case "field":
|
|
158
|
+
body = generateFieldDocs(definition)
|
|
159
|
+
break
|
|
160
|
+
case "composite":
|
|
161
|
+
body = generateCompositeDocs(definition)
|
|
162
|
+
break
|
|
163
|
+
case "provider":
|
|
164
|
+
body = generateProviderDocs(definition)
|
|
165
|
+
break
|
|
166
|
+
case "widget":
|
|
167
|
+
body = generateWidgetDocs(definition)
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return `${frontmatter}\n\n${header}\n${body}`
|
|
172
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @supatype/plugin-sdk — Plugin development kit for Supatype extensions
|
|
2
|
+
|
|
3
|
+
// Core types
|
|
4
|
+
export {
|
|
5
|
+
PLUGIN_API_VERSION,
|
|
6
|
+
type PluginType,
|
|
7
|
+
type PluginMeta,
|
|
8
|
+
type FieldTypeDefinition,
|
|
9
|
+
type CompositeDefinition,
|
|
10
|
+
type CompositeFieldDef,
|
|
11
|
+
type ProviderDefinition,
|
|
12
|
+
type ProviderCategory,
|
|
13
|
+
type WidgetDefinition,
|
|
14
|
+
type WidgetProps,
|
|
15
|
+
type CommerceProvider,
|
|
16
|
+
type TrackingProvider,
|
|
17
|
+
type EmailProvider,
|
|
18
|
+
type StorageProvider,
|
|
19
|
+
type AuthProvider,
|
|
20
|
+
type SSLProvider,
|
|
21
|
+
} from "./types.js"
|
|
22
|
+
|
|
23
|
+
// Builder functions
|
|
24
|
+
export {
|
|
25
|
+
defineFieldType,
|
|
26
|
+
defineComposite,
|
|
27
|
+
defineProvider,
|
|
28
|
+
defineWidget,
|
|
29
|
+
isPluginDefinition,
|
|
30
|
+
checkPluginApiVersion,
|
|
31
|
+
type AnyPluginDefinition,
|
|
32
|
+
} from "./define.js"
|
|
33
|
+
|
|
34
|
+
// Documentation generator
|
|
35
|
+
export { generatePluginDocs } from "./docgen.js"
|
|
36
|
+
|
|
37
|
+
// Plugin loading and registry
|
|
38
|
+
export {
|
|
39
|
+
registerPlugin,
|
|
40
|
+
getRegisteredPlugins,
|
|
41
|
+
getPluginsByType,
|
|
42
|
+
getFieldTypePlugin,
|
|
43
|
+
getProviderPlugin,
|
|
44
|
+
clearPluginRegistry,
|
|
45
|
+
isSupatypePlugin,
|
|
46
|
+
sortByLoadOrder,
|
|
47
|
+
detectConflicts,
|
|
48
|
+
validateProviders,
|
|
49
|
+
getPluginFieldTypeMap,
|
|
50
|
+
getPluginFieldPgTypeMap,
|
|
51
|
+
expandPluginComposites,
|
|
52
|
+
type RegisteredPlugin,
|
|
53
|
+
type PluginPackageInfo,
|
|
54
|
+
type PluginConflict,
|
|
55
|
+
type ProviderValidationResult,
|
|
56
|
+
type FieldPgTypeInfo,
|
|
57
|
+
type CompositeExpansion,
|
|
58
|
+
} from "./loader.js"
|