diff --git a/package.json b/package.json index 6389c4a9bd..6e220cd960 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "type": "module", "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.1.0", "@apidevtools/swagger-parser": "^10.1.0", "@appwrite.io/pink": "0.0.7-sl10.0", "@appwrite.io/repo": "github:appwrite/appwrite", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c3edb9fec..996a563cf0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@apidevtools/json-schema-ref-parser': + specifier: ^11.1.0 + version: 11.1.0 '@apidevtools/swagger-parser': specifier: ^10.1.0 version: 10.1.0(openapi-types@12.1.3) @@ -122,6 +125,17 @@ packages: '@jridgewell/trace-mapping': 0.3.19 dev: true + /@apidevtools/json-schema-ref-parser@11.1.0: + resolution: {integrity: sha512-g/VW9ZQEFJAOwAyUb8JFf7MLiLy2uEB4rU270rGzDwICxnxMlPy0O11KVePSgS36K1NI29gSlK84n5INGhd4Ag==} + engines: {node: '>= 16'} + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.13 + '@types/lodash.clonedeep': 4.5.7 + js-yaml: 4.1.0 + lodash.clonedeep: 4.5.0 + dev: false + /@apidevtools/json-schema-ref-parser@9.0.6: resolution: {integrity: sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==} dependencies: @@ -1282,6 +1296,10 @@ packages: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true + /@types/json-schema@7.0.13: + resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} + dev: false + /@types/jsonfile@6.1.1: resolution: {integrity: sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==} dependencies: @@ -1293,6 +1311,16 @@ packages: requiresBuild: true dev: true + /@types/lodash.clonedeep@4.5.7: + resolution: {integrity: sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==} + dependencies: + '@types/lodash': 4.14.198 + dev: false + + /@types/lodash@4.14.198: + resolution: {integrity: sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==} + dev: false + /@types/markdown-it@12.2.3: resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} requiresBuild: true @@ -3299,7 +3327,6 @@ packages: hasBin: true dependencies: argparse: 2.0.1 - dev: true /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -3430,6 +3457,10 @@ packages: p-locate: 5.0.0 dev: true + /lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true diff --git a/src/lib/layouts/Docs.svelte b/src/lib/layouts/Docs.svelte index 8e3ec8f393..9004461ece 100644 --- a/src/lib/layouts/Docs.svelte +++ b/src/lib/layouts/Docs.svelte @@ -29,7 +29,7 @@ export let variant: DocsLayoutVariant = 'default'; const variantClasses: Record = { - default: 'aw-grid-side-nav aw-container', + default: 'aw-grid-side-nav', expanded: 'aw-grid-huge-navs', 'two-side-navs': 'aw-grid-two-side-navs' }; diff --git a/src/lib/utils/code.ts b/src/lib/utils/code.ts index 0aebf1597b..db3dc003d0 100644 --- a/src/lib/utils/code.ts +++ b/src/lib/utils/code.ts @@ -97,7 +97,7 @@ type Args = { export const getCodeHtml = (args: Args) => { const { content, language, withLineNumbers } = args; const res = hljs.highlight(content, { language: language ?? 'sh' }).value; - const lines = res.split(/\n/g).slice(0, -1); + const lines = res.split(/\n/g); while (lines.length > 0 && lines[lines.length - 1] === '') { lines.pop(); diff --git a/src/lib/utils/specs.ts b/src/lib/utils/specs.ts index f7308fe640..efd5aed881 100644 --- a/src/lib/utils/specs.ts +++ b/src/lib/utils/specs.ts @@ -1,4 +1,3 @@ -import SwaggerParser from '@apidevtools/swagger-parser'; import { OpenAPIV3 } from 'openapi-types'; import { Platform, type Service } from './references'; @@ -17,13 +16,15 @@ type SDKMethod = { responses: Array<{ code: number; contentType?: string; - model?: { - id: string; - name: string; - }; + models?: SDKMethodModel[]; }>; }; +type SDKMethodModel = { + id: string; + name: string; +}; + type AppwriteOperationObject = OpenAPIV3.OperationObject & { 'x-appwrite': { method: string; @@ -41,7 +42,7 @@ type AppwriteOperationObject = OpenAPIV3.OperationObject & { }; }; -type AppwriteSchemaObject = OpenAPIV3.SchemaObject & { +export type AppwriteSchemaObject = OpenAPIV3.SchemaObject & { 'x-example': string; }; @@ -141,7 +142,9 @@ export function getSchema(id: string, api: OpenAPIV3.Document): OpenAPIV3.Schema throw new Error("Schema doesn't exist"); } -const specs = import.meta.glob('$appwrite/app/config/specs/open-api3*-(client|server).json'); +const specs = import.meta.glob('$appwrite/app/config/specs/open-api3*-(client|server).json', { + as: 'raw' +}); async function getSpec(version: string, platform: string) { const isServer = platform.startsWith('server-'); const target = `/node_modules/@appwrite.io/repo/app/config/specs/open-api3-${version}-${ @@ -151,10 +154,8 @@ async function getSpec(version: string, platform: string) { } export async function getApi(version: string, platform: string): Promise { - const spec = await getSpec(version, platform); - const parser = new SwaggerParser(); - const api = (await parser.bundle(spec as unknown as OpenAPIV3.Document)) as OpenAPIV3.Document; - + const raw = await getSpec(version, platform); + const api = JSON.parse(raw); return api; } @@ -198,28 +199,41 @@ export async function getService( const responses: SDKMethod['responses'] = Object.entries(operation.responses ?? {}).map( (tuple) => { const [code, response] = tuple as [string, OpenAPIV3.ResponseObject]; - const id = ( - response?.content?.['application/json']?.schema as OpenAPIV3.ReferenceObject - )?.$ref - ?.split('/') - .pop(); - const schema = id ? getSchema(id, api) : undefined; + const models: SDKMethodModel[] = []; + const schemas = response?.content?.['application/json']?.schema as OpenAPIV3.SchemaObject; + if (code !== '204') { + if (schemas?.oneOf) { + schemas.oneOf.forEach((ref) => { + const schema = resolveReference(ref as OpenAPIV3.ReferenceObject, api); + models.push({ + id: getIdFromReference(ref as OpenAPIV3.ReferenceObject), + name: schema.description ?? '' + }); + }); + } else { + if (schemas) { + const id = getIdFromReference(schemas as OpenAPIV3.ReferenceObject); + const schema = resolveReference(schemas as OpenAPIV3.ReferenceObject, api); + models.push({ + id, + name: schema?.description ?? '' + }); + } + } + } return { code: Number(code), contentType: response?.content ? Object.keys(response.content)[0] : undefined, - model: id - ? { - id, - name: schema?.description ?? '' - } - : undefined + models }; } ); const path = isAndroid - ? `/node_modules/@appwrite.io/repo/docs/examples/${version}/client-android/${isAndroidJava ? 'java' : 'kotlin'}/${operation['x-appwrite'].demo}` + ? `/node_modules/@appwrite.io/repo/docs/examples/${version}/client-android/${ + isAndroidJava ? 'java' : 'kotlin' + }/${operation['x-appwrite'].demo}` : `/node_modules/@appwrite.io/repo/docs/examples/${version}/${platform}/examples/${operation['x-appwrite'].demo}`; if (!(path in examples)) { continue; @@ -236,3 +250,26 @@ export async function getService( return data; } + +export function getIdFromReference(reference: OpenAPIV3.ReferenceObject) { + const id = reference?.$ref?.split('/')?.pop(); + if (!id) { + throw new Error('Invalid reference'); + } + return id; +} + +export function resolveReference( + reference: OpenAPIV3.ReferenceObject, + api: OpenAPIV3.Document +): AppwriteSchemaObject { + const id = reference.$ref.split('/').pop(); + if (!id) { + throw new Error('Invalid reference'); + } + const schema = api.components?.schemas?.[id] as AppwriteSchemaObject; + if (schema) { + return schema; + } + throw new Error("Schema doesn't exist"); +} diff --git a/src/markdoc/layouts/Article.svelte b/src/markdoc/layouts/Article.svelte index fcebf66c00..3a84dce191 100644 --- a/src/markdoc/layouts/Article.svelte +++ b/src/markdoc/layouts/Article.svelte @@ -21,8 +21,8 @@ export let title: string; export let description: string; - export let difficulty: string; - export let readtime: string; + export let difficulty: string = ''; + export let readtime: string = ''; setContext('headings', writable({})); diff --git a/src/routes/docs/advanced/platform/webhooks/+page.markdoc b/src/routes/docs/advanced/platform/webhooks/+page.markdoc index 8551cbddec..b5c80e23ca 100644 --- a/src/routes/docs/advanced/platform/webhooks/+page.markdoc +++ b/src/routes/docs/advanced/platform/webhooks/+page.markdoc @@ -6,7 +6,7 @@ description: Placeholder SEO. Certainly! Below is the content converted to Markdown format: -```markdown +```md ```php + import Layout from './Layout.svelte'; + + +placeholder diff --git a/src/routes/docs/references/+layout.svelte b/src/routes/docs/references/Layout.svelte similarity index 95% rename from src/routes/docs/references/+layout.svelte rename to src/routes/docs/references/Layout.svelte index 5bbfbe54c9..65bd07f068 100644 --- a/src/routes/docs/references/+layout.svelte +++ b/src/routes/docs/references/Layout.svelte @@ -4,7 +4,8 @@ import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte'; import { preferredPlatform, preferredVersion } from '$lib/utils/references'; - $: expandable = $page.url.pathname.startsWith('/docs/references/'); + export let expandable = false; + $: prefix = `/docs/references/${$preferredVersion ?? $page.params?.version ?? 'cloud'}/${ $preferredPlatform ?? $page.params?.platform ?? 'client-web' }`; diff --git a/src/routes/docs/references/[version]/[platform]/[service]/+layout.svelte b/src/routes/docs/references/[version]/[platform]/[service]/+layout.svelte new file mode 100644 index 0000000000..c72730eac7 --- /dev/null +++ b/src/routes/docs/references/[version]/[platform]/[service]/+layout.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/src/routes/docs/references/[version]/[platform]/[service]/+page.svelte b/src/routes/docs/references/[version]/[platform]/[service]/+page.svelte index ac5545dc85..6475e58ae0 100644 --- a/src/routes/docs/references/[version]/[platform]/[service]/+page.svelte +++ b/src/routes/docs/references/[version]/[platform]/[service]/+page.svelte @@ -181,19 +181,30 @@
    {#each method.responses as response} -
  • -
    -
    -

    - {response.code} -

    - {response.model?.name} -
    -

    - Payload -

    -
    -
  • + {#if response.models} +
  • +
    +
    +

    + {response.code} +

    + application/json +
    + +
    +
  • + {/if} {/each}
diff --git a/src/routes/docs/references/[version]/models/[model]/+layout.svelte b/src/routes/docs/references/[version]/models/[model]/+layout.svelte new file mode 100644 index 0000000000..bd34eeff26 --- /dev/null +++ b/src/routes/docs/references/[version]/models/[model]/+layout.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/src/routes/docs/references/[version]/models/[model]/+page.server.ts b/src/routes/docs/references/[version]/models/[model]/+page.server.ts new file mode 100644 index 0000000000..8638e2ca05 --- /dev/null +++ b/src/routes/docs/references/[version]/models/[model]/+page.server.ts @@ -0,0 +1,88 @@ +import type { EntryGenerator, PageServerLoad } from './$types'; +import { + getApi, + getSchema, + getService, + type AppwriteSchemaObject, + getIdFromReference, + resolveReference +} from '$lib/utils/specs'; +import { Platform, Service, versions } from '$lib/utils/references'; +import { error } from '@sveltejs/kit'; +import type { OpenAPIV3 } from 'openapi-types'; + +// export const entries: EntryGenerator = () => { +// return ['cloud', ...(versions as string[])].flatMap((version) => { +// return platforms.flatMap((platform) => { +// return services.map((service) => { +// return { service, version, platform }; +// }); +// }); +// }); +// }; + +type Model = { + title: string; + properties: Array<{ + name: string; + type: string; + description: string; + example: string | boolean | number | object | Array; + items?: Array; + }>; +}; + +export const load: PageServerLoad = async ({ params }) => { + const version = params.version === 'cloud' ? '1.4.x' : params.version; + const api = await getApi(version, 'server-nodejs'); + const schema = getSchema(params.model, api); + const props = Object.entries(schema.properties ?? {}); + const model: Model = { + title: schema.description as string, + properties: props.map(([name, data]) => { + const property = data as AppwriteSchemaObject; + switch (property.type) { + case 'array': { + const items = []; + const propItems = property.items as AppwriteSchemaObject; + if (Array.isArray(propItems.anyOf)) { + items.push( + ...propItems.anyOf.map((ref) => getIdFromReference(ref as OpenAPIV3.ReferenceObject)) + ); + } else { + // items.push(getIdFromReference(propItems as unknown as OpenAPIV3.ReferenceObject)); + } + return { + name, + type: property.type as string, + description: property.description as string, + example: '', + items: + (property.items as AppwriteSchemaObject)?.anyOf?.map((ref) => { + const item = getIdFromReference(ref as OpenAPIV3.ReferenceObject); + return item; + }) ?? [] + }; + } + + default: + return { + name, + type: property.type as string, + description: property.description as string, + example: property['x-example'] + }; + } + }) + }; + + const example = model.properties.reduce>((carry, property) => { + carry[property.name] = property.example; + + return carry; + }, {}); + return { + model, + example + }; +}; diff --git a/src/routes/docs/references/[version]/models/[model]/+page.svelte b/src/routes/docs/references/[version]/models/[model]/+page.svelte new file mode 100644 index 0000000000..607e1d4471 --- /dev/null +++ b/src/routes/docs/references/[version]/models/[model]/+page.svelte @@ -0,0 +1,38 @@ + + +
+
+ Properties + + + + + + + + + + {#each data.model.properties as property} + + + + + + {/each} + +
+ NAME + + TYPE + + DESCRIPTION +
{property.name}{property.type}{property.description}
+ Example + +
+